diff --git a/.circleci/config.yml b/.circleci/config.yml index 30eec16e5ae0..2f16bebe3b45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,7 @@ # View these jobs in the browser: https://app.circleci.com/pipelines/github/curl/curl -# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/configuration-reference/ +# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/reference/configuration-reference/ version: 2.1 commands: @@ -114,7 +114,7 @@ commands: executors: ubuntu: machine: - image: ubuntu-2204:2025.09.1 + image: ubuntu-2204:2026.05.1 jobs: basic: @@ -157,7 +157,7 @@ jobs: arm: machine: - image: ubuntu-2204:2025.09.1 + image: ubuntu-2204:2026.05.1 resource_class: arm.medium steps: - checkout @@ -168,7 +168,7 @@ jobs: arm-cares: machine: - image: ubuntu-2204:2025.09.1 + image: ubuntu-2204:2026.05.1 resource_class: arm.medium steps: - checkout diff --git a/.clang-tidy.yml b/.clang-tidy.yml index 5f523fb50ba0..3ea2c6a88a73 100644 --- a/.clang-tidy.yml +++ b/.clang-tidy.yml @@ -8,12 +8,13 @@ Checks: - clang-analyzer-* - -clang-analyzer-optin.performance.Padding - - -clang-analyzer-security.ArrayBound # due to false positives with clang-tidy v21.1.0+ + - -clang-analyzer-security.ArrayBound # due to false positives with clang-tidy 21.1.0+ - -clang-analyzer-security.insecureAPI.bzero # for FD_ZERO() (seen on macOS) - -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling - -clang-diagnostic-nullability-extension - bugprone-assert-side-effect - bugprone-assignment-in-if-condition + - bugprone-assignment-in-selection-statement - bugprone-chained-comparison - bugprone-dynamic-static-initializers - bugprone-invalid-enum-default-initialization @@ -41,7 +42,7 @@ Checks: # readability-enum-initial-value # readability-function-cognitive-complexity - readability-inconsistent-declaration-parameter-name - # readability-misleading-indentation # too many false positives and oddball/conditional source + # readability-misleading-indentation # too many false positives in oddball/conditional source - readability-named-parameter # readability-redundant-casting # false positives in types that change from platform to platform, even with IgnoreTypeAliases: true - readability-redundant-control-flow @@ -56,4 +57,4 @@ CheckOptions: misc-header-include-cycle.IgnoredFilesList: 'curl/curl.h' readability-inconsistent-declaration-parameter-name.Strict: true -HeaderFilterRegex: '.*' # Default in v22.1.0+ +HeaderFilterRegex: '.*' # Default in 22.1.0+ diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c2b79901afbd..52011a6f191a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,6 +15,17 @@ body: **SECURITY RELATED?** Submit here: https://hackerone.com/curl + - type: markdown + attributes: + value: " + > [!IMPORTANT] + + > If you cannot understand or explain your work without using + Artificial Intelligence (AI) then do not file here. Do not paste + massive AI generated explanations. We accept the use of AI as long as + it is digestible. Please explain your issues or improvements briefly + and clearly in your own human voice." + - type: textarea id: reproducer attributes: diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml index 1b60a597adf0..d0c3852183ab 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -14,6 +14,17 @@ body: Only file documentation bugs here! Ask questions on the mailing lists https://curl.se/mail/ + - type: markdown + attributes: + value: " + > [!IMPORTANT] + + > If you cannot understand or explain your work without using + Artificial Intelligence (AI) then do not file here. Do not paste + massive AI generated explanations. We accept the use of AI as long as + it is digestible. Please explain your issues or improvements briefly + and clearly in your own human voice." + - type: textarea id: source attributes: diff --git a/.github/labeler.yml b/.github/labeler.yml index 32117e710751..706c2d1d0648 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,6 +19,10 @@ # patterns, they must be joined with commas to a single string surrounded by # braces. For example: '{lib/**,src/**}'. # +# Note that the labelercheck.sh script is sensitive to the specific formatting +# of this file; strictly follow the established template when adding new +# patterns. +# # See https://github.com/actions/labeler/ for documentation on this file. --- @@ -39,8 +43,11 @@ authentication: - any-glob-to-all-files: "{\ CMake/FindGSS.cmake,\ CMake/FindLibgsasl.cmake,\ + docs/cmdline-opts/httpsig*,\ + docs/internals/CREDENTIALS.md,\ docs/libcurl/opts/CURLINFO_HTTPAUTH*,\ docs/libcurl/opts/CURLINFO_PROXYAUTH*,\ + docs/libcurl/opts/CURLOPT_HTTPSIG*,\ docs/libcurl/opts/CURLOPT_KRB*,\ docs/libcurl/opts/CURLOPT_SASL*,\ docs/libcurl/opts/CURLOPT_SERVICE_NAME*,\ @@ -49,10 +56,14 @@ authentication: docs/libcurl/opts/CURLOPT_XOAUTH*,\ lib/*gssapi*,\ lib/*ntlm*,\ + lib/creds.*,\ + lib/curl_ntlm*,\ lib/curl_sasl.*,\ lib/http_aws*,\ lib/http_digest.*,\ + lib/http_httpsig.*,\ lib/http_negotiate.*,\ + lib/http_ntlm.*,\ lib/vauth/**\ }" @@ -98,6 +109,7 @@ cmake: CMake/**,\ docs/INSTALL-CMAKE.md,\ lib/curl_config-cmake.h.in,\ + scripts/cmakeopts.sh,\ tests/cmake/**\ }" @@ -129,6 +141,8 @@ connecting & proxies: docs/libcurl/opts/CURLOPT_SOCKS*,\ docs/libcurl/opts/CURLOPT_TCP*,\ docs/libcurl/opts/CURLOPT_TIMEOUT*,\ + lib/cf-https-connect.*,\ + lib/cf-ip-happy.*,\ lib/cf-*proxy.*,\ lib/cf-socket.*,\ lib/cfilters.*,\ @@ -136,9 +150,11 @@ connecting & proxies: lib/connect.*,\ lib/http_proxy.*,\ lib/if2ip.*,\ - lib/noproxy.*,\ + lib/proxy.*,\ lib/socks.*,\ + lib/vquic/*-proxy.*,\ src/tool_cb_soc.*,\ + tests/http/*proxy*,\ tests/http/*socks*,\ tests/server/socksd.c\ }" @@ -165,6 +181,7 @@ cryptography: docs/CIPHERS.md,\ docs/RUSTLS.md,\ docs/libcurl/opts/CURLOPT_EGDSOCKET*,\ + lib/*ed25519*,\ lib/*sha256*,\ lib/*sha512*,\ lib/curl_hmac.*,\ @@ -195,7 +212,6 @@ documentation: .github/scripts/verify-synopsis.pl,\ **/*.md,\ **/*.txt,\ - **/*.1,\ CHANGES.md,\ docs/**,\ LICENSES/**,\ @@ -211,6 +227,8 @@ documentation: # negative matches - '!**/CMakeLists.txt' - '!**/Makefile.am' + - '!tests/**/data*.txt' + - '!tests/**/requirements*.txt' FTP: - all: @@ -295,11 +313,13 @@ HTTP/3: .github/workflows/http3-linux.yml,\ CMake/FindNGHTTP3.cmake,\ CMake/FindNGTCP2.cmake,\ + docs/cmdline-opts/proxy-http3.md,\ docs/HTTP3.md,\ docs/examples/http3*,\ lib/vquic/**,\ tests/http3-server.pl,\ - tests/nghttpx.conf\ + tests/nghttpx.conf,\ + tests/http/*httpsrr*\ }" IMAP: @@ -371,16 +391,14 @@ name lookup: docs/libcurl/opts/CURLOPT_DOH*,\ docs/libcurl/opts/CURLOPT_RESOLVE*,\ lib/*addrinfo*,\ - lib/asyn*,\ - lib/cf-dns.*,\ lib/curl_gethostname.*,\ - lib/dns*,\ - lib/doh*,\ - lib/host*,\ lib/idn*,\ lib/socketpair*,\ lib/thrdpool.*,\ lib/thrdqueue.*,\ + lib/vdns/*,\ + tests/http/testenv/dnsd.*,\ + tests/http/*httpsrr*,\ tests/http/*resolve.py,\ tests/server/dnsd.c,\ tests/server/resolve.c\ @@ -458,6 +476,7 @@ tests: - all: - changed-files: - any-glob-to-any-file: + - 'docs/tests/**' - 'tests/**' TFTP: @@ -548,9 +567,9 @@ Windows: lib/curlx/fopen.*,\ lib/curlx/multibyte.*,\ lib/curlx/winapi.*,\ + lib/libcurl.def,\ lib/vtls/schannel*,\ m4/curl-schannel.m4,\ projects/Windows/**,\ - src/tool_doswin.c,\ - lib/libcurl.def\ + src/tool_doswin.c\ }" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..34e39a217218 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ + diff --git a/.github/scripts/c-comments b/.github/scripts/c-comments new file mode 100755 index 000000000000..f8c30cf0f974 --- /dev/null +++ b/.github/scripts/c-comments @@ -0,0 +1,191 @@ +#!/usr/bin/env perl +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +# Output all the C comments and double-quoted strings in the given source +# files. All other contents should be blanked out. Output the text at the same +# horizontal position as in the original file. +# +# Ignores strings for the preprocessor. +# +## States +# +# 0 - default, initial state +# 1 - there was a slash +# 2 - quoted string +# 3 - // comment +# 4 - /* comment +# 5 - asterisk found within a /* comment +# 6 - #include line +# 7 - backslash in a string +# 8 - backslash in plain code +# 9 - single quote in plain code +# +## Flags +# +# 1 - include preprocessor line, ignore strings + +sub scanline { + my ($col, $state, $flags, $l) = @_; + my $line; + + if($state == 3) { + # // ended on the prev line, go back to init + $state = 0; + } + + if(($state == 0) && ($l =~ /^ *\# *include/)) { + # preprocessor include line + $flags |= 1; + } + else { + # not preprocessor + $flags &= ~1; + } + + my @c = split(//, $l); + + # state machine this line + for my $c (@c) { + if($state == 1) { + # we had a slash + if($c eq "/") { + # // confirmed, the rest of the line is a comment + $line .= "//"; + $state = 3; + } + elsif($c eq "*") { + # /* confirmed + $state = 4; + $line .= "/*"; + } + else { + # back to normal + $line .= " "; + $state = 0; + } + } + elsif($state == 2) { + # a string + if($c eq "\\") { + $line .= "\\"; + $state = 7; + } + elsif($c eq "\"") { + # end of the string + $line .= "\""; + $state = 0; + } + else { + $line .= $c; + } + } + elsif($state == 3) { + # a // comment + $line .= $c; + } + elsif($state == 4) { + # an ongoing /* comment + if($c eq "*") { + # could a comment close + $state = 5; + } + else { + $line .= $c; + } + } + elsif($state == 5) { + if($c eq "/") { + # a /* */ comment ended here */ + $line .= "*/"; + $state = 0; + } + else { + # the /* comment continues + $line .= "*$c"; + $state = 4; + } + } + elsif($state == 7) { + # the prev was a backslash in a string + $line .= $c; + # switch back to normal string + $state = 2; + } + elsif($state == 8) { + # the prev was a backslash in code + if($c eq "\n") { + $line .= $c; + } + else { + #$line .= " "; + } + # switch back to plain code + $state = 0; + } + elsif($state == 9) { + # the prev was a single quote in code + if($c eq "\n") { + $line .= $c; + # switch back to plain code + $state = 0; + } + elsif($c eq "\\") { + # a backslash followed the quote + $line .= " "; + $state = 8; + } + else { + # switch back to plain code + $state = 0; + } + } + else { + if($c eq "\\") { + # got a backslash + $line .= " "; + $state = 8 + } + elsif($c eq "\'") { + # got a single quote + $line .= " "; + $state = 9 + } + if($c eq "/") { + $state = 1; # got a slash + } + elsif(($c eq "\"") && !($flags & 1)) { + # start of a string, not within a preprocessor line + $line .= "\""; + $state = 2; + } + elsif($c eq "\n") { + $line .= "\n"; + } + else { + $line .= " "; + } + } + } + # strip trailing space + $line =~ s/( +)\n/\n/; + return $state, $flags, $line; +} + +sub strip { + my ($f) = @_; + my $state = 0; + my $flags = 0; + open(F, "<$f") || die "can't open $f"; + while() { + my $l = $_; + ($state, $flags, $line) = scanline(0, $state, $flags, $l); + print "$line"; + } + close(F); +} + +for my $f (@ARGV) { + strip($f); +} diff --git a/.github/scripts/c-strip b/.github/scripts/c-strip new file mode 100755 index 000000000000..6b5cfbf86306 --- /dev/null +++ b/.github/scripts/c-strip @@ -0,0 +1,7 @@ +#!/bin/sh +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +./.github/scripts/c-comments "$1" > temp +mv temp "$1" diff --git a/.github/scripts/cleancmd.pl b/.github/scripts/cleancmd.pl index 988d4562046a..c362484054aa 100755 --- a/.github/scripts/cleancmd.pl +++ b/.github/scripts/cleancmd.pl @@ -15,8 +15,8 @@ my @asyms; -open(S, "<./docs/libcurl/symbols-in-versions") - || die "cannot find symbols-in-versions"; +open(S, "<", './docs/libcurl/symbols-in-versions') + or die "cannot find symbols-in-versions"; while() { if(/^([^ ]*) /) { push @asyms, $1; @@ -29,8 +29,8 @@ '--ftp-ssl-reqd', # old alias ); -open(O, "<./docs/options-in-versions") - || die "cannot find options-in-versions"; +open(O, "<", './docs/options-in-versions') + or die "cannot find options-in-versions"; while() { chomp; if(/^([^ ]+)/) { @@ -49,8 +49,8 @@ } close(O); -open(C, "<./.github/scripts/spellcheck.curl") - || die "cannot find spellcheck.curl"; +open(C, "<", './.github/scripts/spellcheck.curl') + or die "cannot find spellcheck.curl"; while() { if(/^\#/) { next; @@ -75,7 +75,7 @@ sub process { my $sepcount = 0; my $out; my $line = 0; - open(F, "<$f") or die; + open(F, "<", $f) or die; while() { $line++; @@ -115,14 +115,14 @@ sub process { map { $out =~ s/\b$_\b//g; } (@syms); if(!$ignore) { - open(O, ">$f") or die; + open(O, ">", $f) or die; print O $out; close(O); } } my @filemasks = @ARGV; -open(my $git_ls_files, '-|', 'git', 'ls-files', '--', @filemasks) or die "Failed running git ls-files: $!"; +open(my $git_ls_files, '-|', 'git', 'ls-files', '--end-of-options', @filemasks) or die "Failed running git ls-files: $!"; while(my $f = <$git_ls_files>) { chomp $f; process($f); diff --git a/.github/scripts/cmp-config.pl b/.github/scripts/cmp-config.pl index 66cb65756325..a0d33498a75f 100755 --- a/.github/scripts/cmp-config.pl +++ b/.github/scripts/cmp-config.pl @@ -34,8 +34,7 @@ exit; } -# this lists complete lines that will be removed from the output if -# matching +# this lists complete lines that are removed from the output if matching my %remove = ( '#define CURL_EXTERN_SYMBOL' => 1, '#define CURL_OS "Linux"' => 1, @@ -89,6 +88,7 @@ '#define PACKAGE_TARNAME "curl"' => 1, '#define PACKAGE_URL ""' => 1, '#define PACKAGE_VERSION "-"' => 1, + '#define STDC_HEADERS 1' => 1, '#define VERSION "-"' => 1, '#define _FILE_OFFSET_BITS 64' => 1, ); @@ -106,7 +106,7 @@ sub grepit { my ($input, $output) = @_; my @defines; # first get all the #define lines - open(F, "<$input"); + open(F, "<", $input); while() { if($_ =~ /^#def/) { chomp; @@ -115,7 +115,7 @@ sub grepit { } close(F); - open(O, ">$output"); + open(O, ">", $output); # output the sorted list through the filter foreach my $d(sort @defines) { diff --git a/.github/scripts/cmp-pkg-config.sh b/.github/scripts/cmp-pkg-config.sh index 37a316218c83..940b02d78a7b 100755 --- a/.github/scripts/cmp-pkg-config.sh +++ b/.github/scripts/cmp-pkg-config.sh @@ -44,6 +44,6 @@ am=$(mktemp -t autotools.XXX); sort_lists "$1" > "${am}" cm=$(mktemp -t cmake.XXX) ; sort_lists "$2" > "${cm}" diff -u "${am}" "${cm}" res="$?" -rm -r -f "${am}" "${cm}" +rm -rf "${am}" "${cm}" exit "${res}" diff --git a/.github/scripts/codespell.sh b/.github/scripts/codespell.sh index c5ddf90a4763..ae81c20daff0 100755 --- a/.github/scripts/codespell.sh +++ b/.github/scripts/codespell.sh @@ -5,7 +5,7 @@ set -eu -cd "$(dirname "${0}")"/../.. +cd -- "$(dirname "${0}")"/../.. git ls-files -z | xargs -0 -r \ codespell \ @@ -17,6 +17,8 @@ codespell \ --skip 'RELEASE-NOTES' \ --skip 'scripts/wcurl' \ --skip 'tests/unit/unit1625.c' \ + --skip 'tests/unit/unit1627.c' \ + --skip 'tests/perf/urlparser.c' \ --ignore-regex '.*spellchecker:disable-line' \ --ignore-words '.github/scripts/codespell-ignore.words' \ -- diff --git a/.github/scripts/distfiles.sh b/.github/scripts/distfiles.sh index 5eb6a470a8ec..1fb7d7e31034 100755 --- a/.github/scripts/distfiles.sh +++ b/.github/scripts/distfiles.sh @@ -9,7 +9,9 @@ set -eu gitonly=".git* -^.* +^.circleci/* +^.dir-locals.el +^.mailmap ^appveyor.* ^GIT-INFO.md ^README.md @@ -28,6 +30,24 @@ gitonly=".git* ^scripts/release-notes.pl ^scripts/singleuse.pl" +taronly="^Makefile.in$ +/Makefile.in$ +^aclocal.m4$ +^compile$ +^configure$ +^config.guess$ +^config.sub$ +^depcomp$ +^docs/RELEASE-TOOLS.md$ +^docs/libcurl/libcurl-symbols.md$ +^install-sh$ +^lib/curl_config.h.in$ +^ltmain.sh$ +^m4/libtool.m4$ +^m4/lt*.m4$ +^missing$ +^src/tool_hugehelp.c$" + tarfiles="$(mktemp)" gitfiles="$(mktemp)" @@ -48,7 +68,17 @@ echo 'Only in tarball:' echo "${dif}" | grep '^-' || true echo +exitcode=0 + +echo 'Unexpected in tarball:' +if echo "${dif}" | grep '^-' | sed 's|^-||g' \ + | grep -v -E "($(printf '%s' "${taronly}" | tr $'\n' '|' | sed -e 's|\.|\\.|g' -e 's|\*|.+|g'))$"; then + exitcode=1 +fi + echo 'Missing from tarball:' if echo "${dif}" | grep '^+'; then - exit 1 + exitcode=1 fi + +exit "${exitcode}" diff --git a/.github/scripts/labelercheck.sh b/.github/scripts/labelercheck.sh new file mode 100755 index 000000000000..17662b51c44e --- /dev/null +++ b/.github/scripts/labelercheck.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Copyright (C) 2026 Dan Fandrich +# +# SPDX-License-Identifier: curl + +# Look for invalid patterns in labeler config. +# This is highly sensitive to the format of the labeler.yml file. +# It will also fail if patterns are added containing special shell characters +# like "$" or "|". +grep -E '^ {14}[^-].*\\$' .github/labeler.yml | \ + sed -E -e '/^[[:space:]]*#/d' -e 's/,?\\$//' | \ + xargs -I{} /bin/bash -c 'shopt -s globstar dotglob; ls -d {}' >/dev/null diff --git a/.github/scripts/pyspelling.words b/.github/scripts/pyspelling.words index 0c986f14c6c2..ce0f4cbfca3b 100644 --- a/.github/scripts/pyspelling.words +++ b/.github/scripts/pyspelling.words @@ -167,8 +167,10 @@ CWE cyassl Cygwin daniel +datagrams datatracker dbg +decapsulation Debian DEBUGBUILD decrypt @@ -234,6 +236,7 @@ EGD EHLO EINTR else's +encapsulation encodings enctype endianness @@ -365,6 +368,9 @@ httpget HttpGet HTTPS https +httpsig +HTTPSIG +CURLHTTPSIG HTTPSRR hyper's IANA @@ -389,6 +395,7 @@ IMAPS imaps impacket implementers +INI init initializer inlined @@ -424,6 +431,7 @@ KDE keepalive Keil kerberos +keyid Keychain keychain KiB @@ -896,6 +904,7 @@ trustless Tse Tsujikawa TTL +tty tvOS txt typedef @@ -991,6 +1000,7 @@ WinIDN WinLDAP winsock Wireshark +wolfCrypt wolfSSH wolfSSL ws @@ -1010,6 +1020,7 @@ Youtube YYYY YYYYMMDD Zakrzewski +Zeropath Zitzmann zlib zsh diff --git a/.github/scripts/pyspelling.yaml b/.github/scripts/pyspelling.yaml index bb0585ab7ab8..0161bc376c6a 100644 --- a/.github/scripts/pyspelling.yaml +++ b/.github/scripts/pyspelling.yaml @@ -7,8 +7,8 @@ matrix: - name: Markdown expect_match: false - apsell: - mode: en + aspell: + lang: en dictionary: wordlists: - wordlist.txt diff --git a/.github/scripts/randcurl.pl b/.github/scripts/randcurl.pl index f9c24d90db6c..eaf957545676 100755 --- a/.github/scripts/randcurl.pl +++ b/.github/scripts/randcurl.pl @@ -61,7 +61,7 @@ sub storedata { } sub getoptions { - my @all = `$curl --help all`; + my @all = qx($curl --help all); for my $o (@all) { chomp $o; if($o =~ /^ -(.), --([^ ]*) (.*)/) { @@ -80,7 +80,7 @@ sub addarg { "0123456789-"; my $len = getnum(20) + 2; my $o; - for (1 .. $len) { + for(1 .. $len) { $o .= substr($nice, getnum(length($nice)), 1); } return "--$o"; @@ -93,7 +93,7 @@ sub randarg { ",-?#$%!@ "; my $len = getnum(20); my $o = ''; - for (1 .. $len) { + for(1 .. $len) { $o .= substr($nice, getnum(length($nice)), 1); } return "\'$o\'"; @@ -122,7 +122,7 @@ sub runone { $totalargs += $nargs; $totalcmds++; - for (1 .. $nargs) { + for(1 .. $nargs) { my $o = getnum($nopts); my $option = $opt[$o]; my $ar = ""; @@ -142,7 +142,7 @@ sub runone { $a .= " ".addarg(); } - my $cmd="$curl$a $url"; + my $cmd = "$curl$a $url"; my $rc = system("$cmd >curl-output 2>&1 > 8; #my $rc = system("valgrind -q $cmd >/dev/null 2>&1 > 8; @@ -154,7 +154,7 @@ sub runone { print "CMD: $cmd\n"; print "RC: $rc\n"; print "== curl-output == \n"; - open(D, "; print @out; close(D); @@ -166,11 +166,11 @@ sub runconfig { my $a; my $nargs = getnum(80) + 1; - open(C, ">config"); + open(C, ">", 'config'); $totalargs += $nargs; $totalcmds++; - for (1 .. $nargs) { + for(1 .. $nargs) { my $o = getnum($nopts); my $option = $opt[$o]; my $ar = ""; @@ -194,7 +194,7 @@ sub runconfig { print C "$a\n"; close(C); - my $cmd="$curl -K config $url"; + my $cmd = "$curl -K config $url"; my $rc = system("$cmd >curl-output 2>&1 > 8; @@ -204,12 +204,12 @@ sub runconfig { print "CMD: $cmd\n"; print "RC: $rc\n"; print "== config == \n"; - open(D, "; print @all; close(D); print "\n== curl-output == \n"; - open(D, "; print @out; close(D); @@ -218,7 +218,7 @@ sub runconfig { } # run curl command lines using -K -my $end = time() + $seconds/2; +my $end = time() + $seconds / 2; my $c = 0; print "Running command lines\n"; do { @@ -228,7 +228,7 @@ sub runconfig { print "$c command lines\n"; # run curl command lines -$end = time() + $seconds/2; +$end = time() + $seconds / 2; $c = 0; print "Running config lines\n"; do { diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 19993371414f..e51ba9850e17 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -3,7 +3,7 @@ # SPDX-License-Identifier: curl cmakelang==0.6.13 -codespell==2.4.2 -pytype==2024.10.11 +codespell==2.4.3 +pyrefly==1.2.0 reuse==6.2.0 -ruff==0.15.10 +ruff==0.16.5 diff --git a/.github/scripts/shellcheck.sh b/.github/scripts/shellcheck.sh index 59b49131ac4b..3153a2c7a8e2 100755 --- a/.github/scripts/shellcheck.sh +++ b/.github/scripts/shellcheck.sh @@ -5,7 +5,7 @@ set -eu -cd "$(dirname "${0}")"/../.. +cd -- "$(dirname "${0}")"/../.. git grep -z -l -E '^#!(/usr/bin/env bash|/bin/sh|/bin/bash)' | xargs -0 -r \ shellcheck --exclude=1091,2248 \ diff --git a/.github/scripts/trimmarkdownheader.pl b/.github/scripts/trimmarkdownheader.pl index 89b2e7d9fd05..36bc8de46c69 100755 --- a/.github/scripts/trimmarkdownheader.pl +++ b/.github/scripts/trimmarkdownheader.pl @@ -12,7 +12,7 @@ my $f = $ARGV[0] || ''; -open(F, "<$f") or die; +open(F, "<", $f) or die; my @out; my $line = 0; @@ -37,7 +37,7 @@ } close(F); -open(O, ">$f") or die; +open(O, ">", $f) or die; for my $l (@out) { print O $l; } diff --git a/.github/scripts/typos.sh b/.github/scripts/typos.sh index 76735c1d9fc8..39a7faa81eaf 100755 --- a/.github/scripts/typos.sh +++ b/.github/scripts/typos.sh @@ -5,7 +5,7 @@ set -eu -cd "$(dirname "${0}")"/../.. +cd -- "$(dirname "${0}")"/../.. git ls-files | typos \ --isolated \ diff --git a/.github/scripts/typos.toml b/.github/scripts/typos.toml index 8d8511953a81..da75ca84b9a5 100644 --- a/.github/scripts/typos.toml +++ b/.github/scripts/typos.toml @@ -6,7 +6,7 @@ extend-ignore-identifiers-re = [ "^(ba|fo|pn|PN|UE)$", "^(CNA|cpy|ser)$", - "^(ECT0|ECT1|HELO|htpts|PASE)$", + "^(ECT0|ECT1|HELO|htpts|mport|PASE)$", "^[A-Za-z0-9_-]*(EDE|GOST)[A-Z0-9_-]*$", # ciphers "^0x[0-9a-fA-F]+FUL$", # unsigned long hex literals ending with 'F' "^[0-9a-zA-Z+]{64,}$", # possibly base64 @@ -14,6 +14,8 @@ extend-ignore-identifiers-re = [ "^(clen|req_clen|smtp_perform_helo|smtp_state_helo_resp|Tru64|_stati64)$", "(_ccontains|_controllen|O_WRONLY|secur32)", "proxys", # this should be limited to tests/http/*. Short for secure proxy. + "rto", # (TCP) retransmission timeout + "RTO" ] extend-ignore-re = [ @@ -33,4 +35,6 @@ extend-exclude = [ "scripts/wcurl", "tests/data/test*", "tests/unit/unit1625.c", + "tests/unit/unit1627.c", + "tests/perf/urlparser.c", ] diff --git a/.github/scripts/verify-examples.pl b/.github/scripts/verify-examples.pl index 007369b4abbc..8aeda5ef7d03 100755 --- a/.github/scripts/verify-examples.pl +++ b/.github/scripts/verify-examples.pl @@ -37,7 +37,12 @@ } sub testcompile { - my $rc = system('gcc -c test.c -I include -W -Wall -pedantic -Werror ' . + my $cc = defined $ENV{'CC'} ? $ENV{'CC'} : 'gcc'; + my $cflags = ''; + if(defined $ENV{'CFLAGS'}) { + $cflags .= ' ' . $ENV{'CFLAGS'}; + } + my $rc = system($cc . ' -c test.c -I include -W -Wall -pedantic -Werror ' . $cflags . ' ' . '-Wno-unused-parameter -Wno-unused-but-set-variable ' . '-DCURL_ALLOW_OLD_MULTI_SOCKET -DCURL_DISABLE_DEPRECATION') >> 8; return $rc; @@ -54,8 +59,8 @@ sub extract { my $l = 0; my $iline = 0; my $fail = 0; - open(F, "<$f") or die "failed opening input file $f : $!"; - open(O, ">$cfile") or die "failed opening output file $cfile : $!"; + open(F, "<", $f) or die "failed opening input file $f : $!"; + open(O, ">", $cfile) or die "failed opening output file $cfile : $!"; print O "#include \n"; while() { $iline++; @@ -68,7 +73,7 @@ sub extract { print O "/* !checksrc! disable BANNEDFUNC all */\n"; # for fopen() print O "/* !checksrc! disable COPYRIGHT all */\n"; print O "/* !checksrc! disable UNUSEDIGNORE all */\n"; - printf O "#line %d \"$f\"\n", $iline+1; + printf O "#line %d \"$f\"\n", $iline + 1; } } elsif($syn == 2) { diff --git a/.github/scripts/verify-synopsis.pl b/.github/scripts/verify-synopsis.pl index 02918dd65d1e..7ff90c049196 100755 --- a/.github/scripts/verify-synopsis.pl +++ b/.github/scripts/verify-synopsis.pl @@ -45,8 +45,8 @@ sub extract { my $syn = 0; my $l = 0; my $iline = 0; - open(F, "<$f"); - open(O, ">$cfile"); + open(F, "<", $f); + open(O, ">", $cfile); while() { $iline++; if(/^# SYNOPSIS/) { diff --git a/.github/scripts/yamlcheck.sh b/.github/scripts/yamlcheck.sh index 4bdeff45cb96..402aa088fccf 100755 --- a/.github/scripts/yamlcheck.sh +++ b/.github/scripts/yamlcheck.sh @@ -5,7 +5,7 @@ set -eu -cd "$(dirname "${0}")"/../.. +cd -- "$(dirname "${0}")"/../.. git ls-files '*.yaml' '*.yml' -z | xargs -0 -r \ yamllint \ diff --git a/.github/stale.yml b/.github/stale.yml index 69a822e78be8..1c2b57c2cf90 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -15,7 +15,7 @@ staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you + recent activity. It is closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index 52f002fa711c..7eca7305ac86 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -11,6 +11,7 @@ name: 'Docs' push: branches: - master + - 'curl-*' - '*/ci' paths: - '.github/workflows/checkdocs.yml' @@ -21,6 +22,7 @@ name: 'Docs' pull_request: branches: - master + - 'curl-*' paths: - '.github/workflows/checkdocs.yml' - '.github/scripts/**' @@ -41,9 +43,9 @@ jobs: # config file help: https://github.com/amperser/proselint/ proselint: name: 'proselint' - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -92,9 +94,9 @@ jobs: pyspelling: name: 'pyspelling' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -122,14 +124,26 @@ jobs: synopsis-man-examples: name: 'synopsis, man-examples' - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'verify synopsis' run: .github/scripts/verify-synopsis.pl docs/libcurl/curl*.md - - name: 'verify examples' - run: .github/scripts/verify-examples.pl docs/libcurl/curl*.md docs/libcurl/opts/*.md + - name: 'verify examples (gcc)' + run: | + cmake -B bld-gcc -G Ninja -D_CURL_PREFILL=ON -DCURL_USE_LIBPSL=OFF -DCURL_WERROR=ON -D_CURL_SAVE_PICKY_OPTIONS=ON + export CFLAGS; CFLAGS="$(cat bld-gcc/picky-options.txt)" + echo "${CFLAGS}" + .github/scripts/verify-examples.pl docs/libcurl/curl*.md docs/libcurl/opts/*.md + + - name: 'verify examples (clang)' + run: | + export CC=clang + cmake -B bld-clang -G Ninja -D_CURL_PREFILL=ON -DCURL_USE_LIBPSL=OFF -DCURL_WERROR=ON -D_CURL_SAVE_PICKY_OPTIONS=ON + export CFLAGS; CFLAGS="$(cat bld-clang/picky-options.txt)" + echo "${CFLAGS}" + .github/scripts/verify-examples.pl docs/libcurl/curl*.md docs/libcurl/opts/*.md diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 1571983ced9e..2d8bb26a3541 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -10,6 +10,7 @@ name: 'Source' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '.circleci/**' @@ -18,6 +19,7 @@ name: 'Source' pull_request: branches: - master + - 'curl-*' paths-ignore: - '.circleci/**' - 'appveyor.*' @@ -31,13 +33,14 @@ permissions: {} env: DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: checksrc: name: 'checksrc' runs-on: ubuntu-slim steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -46,9 +49,9 @@ jobs: linters: name: 'spellcheck, linters, REUSE' - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -65,6 +68,35 @@ jobs: source ~/venv/bin/activate reuse lint + - name: 'cmakelint' + run: | + source ~/venv/bin/activate + scripts/cmakelint.sh + + - name: 'cmake options in documentation' + run: scripts/cmakeopts.sh + + - name: 'labelercheck' + run: .github/scripts/labelercheck.sh + + - name: 'perlcheck' + run: scripts/perlcheck.sh + + - name: 'ruff' + run: | + source ~/venv/bin/activate + scripts/pythonlint.sh + + - name: 'pyrefly' + run: | + source ~/venv/bin/activate + cd tests/http/ + pyrefly check --python-version=3.8 --preset=basic --check-unannotated-defs --summary + + - name: filter off code from all C and H files + run: | + git ls-files -z '**.[ch]' | xargs -0 -r -n1 ./.github/scripts/c-strip + - name: 'codespell' run: | source ~/venv/bin/activate @@ -74,32 +106,34 @@ jobs: - name: 'typos' timeout-minutes: 2 run: | - HOMEBREW_NO_AUTO_UPDATE=1 /home/linuxbrew/.linuxbrew/bin/brew install typos-cli + /home/linuxbrew/.linuxbrew/bin/brew install typos-cli eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" typos --version .github/scripts/typos.sh - - name: 'cmakelint' - run: | - source ~/venv/bin/activate - scripts/cmakelint.sh + pytype: + name: 'pytype' + runs-on: ubuntu-24.04-arm # pytype is discontinued and requires python 3.8-3.12 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - - name: 'perlcheck' + - name: 'install prereqs' run: | - scripts/perlcheck.sh + python3 -m venv ~/venv + ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary pytype==2024.10.11 \ + -r .github/scripts/requirements.txt \ + -r tests/http/requirements.txt \ + -r tests/requirements.txt - - name: 'pytype' + - name: 'check' run: | source ~/venv/bin/activate find . -name '*.py' -exec pytype -j auto -k -- {} + - - name: 'ruff' - run: | - source ~/venv/bin/activate - scripts/pythonlint.sh - complexity: - name: 'complexity' + name: 'complexity and function sizes' runs-on: ubuntu-slim timeout-minutes: 3 steps: @@ -110,16 +144,18 @@ jobs: sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - pmccabe + sudo apt-get -o Dpkg::Use-Pty=0 install pmccabe - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: 'check scores' + - name: 'check function complexity' run: ./scripts/top-complexity + - name: 'check function lengths' + run: ./scripts/top-length + xmllint: name: 'xmllint' runs-on: ubuntu-slim @@ -131,10 +167,9 @@ jobs: sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libxml2-utils + sudo apt-get -o Dpkg::Use-Pty=0 install libxml2-utils - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -143,17 +178,21 @@ jobs: miscchecks: name: 'misc checks' - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm timeout-minutes: 5 steps: - name: 'install prereqs' timeout-minutes: 2 - run: HOMEBREW_NO_AUTO_UPDATE=1 /home/linuxbrew/.linuxbrew/bin/brew install actionlint shellcheck zizmor + run: /home/linuxbrew/.linuxbrew/bin/brew install actionlint shellcheck zizmor - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - name: 'zizmor/actionlint (prepare)' + # Replace custom bash-like shell designators with the standard one to make linters process them + run: sed -i.bak -E 's/shell\x3a .+ zizmor\x3a ignore.+$/shell\x3a bash/g' .github/workflows/*.yml + - name: 'zizmor GHA' env: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' @@ -161,12 +200,19 @@ jobs: eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" zizmor --persona pedantic .github/workflows/*.yml .github/dependabot.yml + - name: 'zizmor GHA (auditor, warning-only)' + env: + GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + run: | + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + zizmor --persona auditor .github/workflows/*.yml .github/dependabot.yml || true + - name: 'actionlint' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' actionlint --version - actionlint --ignore matrix .github/workflows/*.yml + actionlint --ignore matrix --ignore ubuntu-26.04 .github/workflows/*.yml - name: 'shellcheck CI' run: | diff --git a/.github/workflows/checkurls.yml b/.github/workflows/checkurls.yml index da5a99b861a4..1842b975f42b 100644 --- a/.github/workflows/checkurls.yml +++ b/.github/workflows/checkurls.yml @@ -8,10 +8,12 @@ name: 'URLs' push: branches: - master + - 'curl-*' - '*/ci' pull_request: branches: - master + - 'curl-*' schedule: - cron: '10 5 * * *' @@ -30,7 +32,7 @@ jobs: name: 'linkcheck' runs-on: ubuntu-slim steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8d4f6f9dbf0d..03aa9ba9d6e4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,6 +8,7 @@ name: 'CodeQL' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'CodeQL' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -35,32 +37,33 @@ permissions: {} env: DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: gha_python: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'GHA and Python' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: security-events: write # To create/update security events steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'initialize' - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: actions, python queries: security-extended - name: 'perform analysis' - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 c: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'C' - runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-latest' || 'windows-2022' }} + runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-26.04' || 'windows-2022' }} permissions: security-events: write # To create/update security events strategy: @@ -80,9 +83,10 @@ jobs: sudo apt-get -o Dpkg::Use-Pty=0 install \ libpsl-dev libbrotli-dev libidn2-dev libssh2-1-dev libssh-dev \ libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev libwolfssl-dev - HOMEBREW_NO_AUTO_UPDATE=1 /home/linuxbrew/.linuxbrew/bin/brew install c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi + /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true + /home/linuxbrew/.linuxbrew/bin/brew install c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -92,7 +96,7 @@ jobs: - name: 'initialize' # https://github.com/github/codeql-action/blob/main/init/action.yml - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: cpp build-mode: manual @@ -128,7 +132,7 @@ jobs: cmake -B _bld2 -G Ninja -DCURL_DISABLE_TYPECHECK=ON -DCURL_WERROR=ON \ -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" -DUSE_NGTCP2=ON \ -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON \ - -DCURL_USE_GSASL=ON -DCURL_USE_GSSAPI=ON -DUSE_SSLS_EXPORT=ON + -DCURL_USE_GSASL=ON -DCURL_USE_GSSAPI=ON -DUSE_SSLS_EXPORT=ON -DUSE_PROXY_HTTP3=ON cmake --build _bld2 cmake --build _bld2 --target testdeps cmake --build _bld2 --target curl-examples-build @@ -139,4 +143,4 @@ jobs: - name: 'perform analysis' # https://github.com/github/codeql-action/blob/main/analyze/action.yml - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index e6a417944a28..1195878e0915 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -7,6 +7,7 @@ name: 'configure-vs-cmake' push: branches: - master + - 'curl-*' paths: - '*.ac' - '**/*.m4' @@ -20,6 +21,7 @@ name: 'configure-vs-cmake' pull_request: branches: - master + - 'curl-*' paths: - '*.ac' - '**/*.m4' @@ -37,20 +39,23 @@ concurrency: permissions: {} env: + CURL_CI: github DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: check-linux: name: 'Linux' runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'run configure --with-openssl' run: | autoreconf -fi + echo '::group::configure --help output'; ./configure --help; echo '::endgroup::' export PKG_CONFIG_DEBUG_SPEW=1 mkdir bld-am && cd bld-am && ../configure --enable-static=no --with-openssl --without-libpsl @@ -63,7 +68,7 @@ jobs: - name: 'cmake log' run: cat bld-cm/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true - - name: 'dump generated files' + - name: 'generated libcurl.pc files' run: | for f in libcurl.pc curl-config; do echo "::group::AM ${f}"; grep -v '^#' bld-am/"${f}" || true; echo '::endgroup::' @@ -89,7 +94,7 @@ jobs: # shellcheck disable=SC2181 while [[ $? == 0 ]]; do for i in 1 2 3; do - if brew update && brew install automake libtool; then + if brew install automake libtool; then break 2 else echo "Error: wait to try again: $i" @@ -102,13 +107,14 @@ jobs: - name: 'toolchain versions' run: echo '::group::brew packages installed'; ls -l /opt/homebrew/opt; echo '::endgroup::' - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'run configure --with-openssl' run: | autoreconf -fi + echo '::group::configure --help output'; ./configure --help; echo '::endgroup::' export PKG_CONFIG_DEBUG_SPEW=1 mkdir bld-am && cd bld-am && ../configure --enable-static=no --with-openssl --without-libpsl --disable-ldap --with-brotli --with-zstd --with-apple-sectrust @@ -124,12 +130,15 @@ jobs: - name: 'cmake log' run: cat bld-cm/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true - - name: 'dump generated files' + - name: 'generated libcurl.pc files' run: | for f in libcurl.pc curl-config; do echo "::group::AM ${f}"; grep -v '^#' bld-am/"${f}" || true; echo '::endgroup::' echo "::group::CM ${f}"; grep -v '^#' bld-cm/"${f}" || true; echo '::endgroup::' done + pccritic --version + pccritic --min-score 98 bld-am/libcurl.pc + pccritic --min-score 98 bld-cm/libcurl.pc - name: 'compare generated curl_config.h files' run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h @@ -142,23 +151,26 @@ jobs: check-windows: name: 'Windows' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 env: TRIPLET: 'x86_64-w64-mingw32' steps: - name: 'install packages' timeout-minutes: 1 run: | + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + sudo apt-get -o Dpkg::Use-Pty=0 update sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'run configure --with-schannel' run: | autoreconf -fi + echo '::group::configure --help output'; ./configure --help; echo '::endgroup::' export PKG_CONFIG_DEBUG_SPEW=1 mkdir bld-am && cd bld-am && ../configure --enable-static=no --with-schannel --without-libpsl --host="${TRIPLET}" @@ -175,7 +187,7 @@ jobs: - name: 'cmake log' run: cat bld-cm/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true - - name: 'dump generated files' + - name: 'generated libcurl.pc files' run: | for f in libcurl.pc curl-config; do echo "::group::AM ${f}"; grep -v '^#' bld-am/"${f}" || true; echo '::endgroup::' diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index 6469448b5044..b84241f3e426 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -8,6 +8,7 @@ name: 'curl-for-win' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'curl-for-win' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -41,10 +43,10 @@ env: jobs: linux-glibc-gcc: name: 'Linux gcc glibc (amd64, arm64)' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -70,10 +72,10 @@ jobs: linux-glibc-gcc-minimal: # use gcc to minimize installed packages name: 'Linux gcc glibc minimal (amd64)' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 5 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -97,10 +99,10 @@ jobs: linux-musl-llvm: name: 'Linux llvm MUSL (amd64, riscv64)' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -112,14 +114,16 @@ jobs: export CW_CONFIG='-main-werror-unitybatch-nocertdata-linux-musl-r64-x64' export CW_REVISION="${GITHUB_SHA}" . ./_versions.sh + export CW_CCSUFFIX='-19' + export CW_GCCSUFFIX='-14' sudo podman image trust set --type reject default sudo podman image trust set --type accept docker.io/library - time podman pull "${OCI_IMAGE_DEBIAN}" + time podman pull "${OCI_IMAGE_DEBIAN_STABLE}" podman images --digests time podman run --volume "$(pwd):$(pwd)" --workdir "$(pwd)" \ --env-file <(env | grep -a -E \ '^(CW_|DO_NOT_TRACK|GITHUB_)') \ - "${OCI_IMAGE_DEBIAN}" \ + "${OCI_IMAGE_DEBIAN_STABLE}" \ sh -c ./_ci-linux-debian.sh mac-clang: @@ -129,7 +133,7 @@ jobs: env: CW_JOBS: '4' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -144,10 +148,13 @@ jobs: win-llvm: name: 'Windows llvm (x64)' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 + env: + CW_PKG_NODELETE: '1' + CW_PKG_FLATTEN: '1' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -169,12 +176,18 @@ jobs: "${OCI_IMAGE_DEBIAN}" \ sh -c ./_ci-linux-debian.sh + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: 'curl-windows-snapshot-tool' + retention-days: 5 + path: curl-*-*-*/curl* + win-gcc-zlibold-x64: name: 'Windows gcc zlib-classic (x64)' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false path: 'curl' @@ -183,7 +196,7 @@ jobs: run: | git clone --depth 1 https://github.com/curl/curl-for-win mv curl-for-win/* . - export CW_CONFIG='-main-werror-unitybatch-nocertdata-win-x64-gcc-zlibold-noWINE' + export CW_CONFIG='-main-werror-unitybatch-nocertdata-curltests-win-x64-gcc-zlibold-noWINE' export CW_REVISION="${GITHUB_SHA}" . ./_versions.sh sudo podman image trust set --type reject default diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index bbe1e3864431..f8936b1cbe6a 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -8,10 +8,12 @@ name: 'dist' push: branches: - master + - 'curl-*' - '*/ci' pull_request: branches: - master + - 'curl-*' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} @@ -22,6 +24,7 @@ permissions: {} env: CURL_TEST_MIN: 1500 DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 5 jobs: @@ -30,7 +33,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -49,7 +52,7 @@ jobs: - name: 'maketgz' run: SOURCE_DATE_EPOCH=1711526400 ./scripts/maketgz 99.98.97 - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: 'release-tgz' path: 'curl-99.98.97.tar.gz' @@ -214,7 +217,7 @@ jobs: timeout-minutes: 5 needs: maketgz-and-verify-in-tree steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -230,7 +233,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -266,9 +269,9 @@ jobs: strategy: fail-fast: false matrix: - image: [ubuntu-24.04-arm, macos-latest, windows-2022] + image: [ubuntu-26.04-arm, macos-latest, windows-2022] steps: - - uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0 + - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 if: ${{ contains(matrix.image, 'windows') }} with: msystem: mingw64 @@ -277,7 +280,7 @@ jobs: cache: false path-type: inherit install: >- - mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl + perl mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl - name: 'install prereqs' timeout-minutes: 3 @@ -285,27 +288,29 @@ jobs: if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-win64-x64.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-win64-x64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OLD_CMAKE_SHA256_WIN_INTEL}" && unzip -q pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-win64-x64/bin/cmake.exe > ~/old-cmake-path.txt elif [[ "${MATRIX_IMAGE}" = *'ubuntu'* ]]; then + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + sudo apt-get -o Dpkg::Use-Pty=0 update sudo apt-get -o Dpkg::Use-Pty=0 install libpsl-dev libssl-dev cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-Linux-aarch64.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-Linux-aarch64.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_LINUX_ARM}" && tar -xzf pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-Linux-aarch64/bin/cmake > ~/old-cmake-path.txt else brew install libpsl openssl cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-macos-universal.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-macos-universal.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_MACOS_UNI}" && tar -xzf pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-macos-universal/CMake.app/Contents/bin/cmake > ~/old-cmake-path.txt fi - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -367,3 +372,41 @@ jobs: export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/mingw64' fi ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON + + verify-tarball-downloads: + name: 'Verify tarball downloads' + runs-on: ubuntu-slim + timeout-minutes: 2 + steps: + - name: 'download and import GPG key' + env: + CURL_GPG_ID: 27EDEAF22F3ABCEB50DB9A125CC908FDB71E12C2 + run: | + for keyserver in \ + https://keyserver.ubuntu.com/ \ + https://pgpkeys.eu/ \ + ; do + echo "--- Downloading from ${keyserver}..." + if curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ + "${keyserver}pks/lookup?op=get&options=mr&exact=on&search=0x${CURL_GPG_ID}" \ + | gpg --batch --keyserver-options timeout=15 --display-charset utf-8 --keyid-format 0xlong --import --status-fd 1 2>&1; then + break + fi + done + + - name: 'download and verify tarballs' + run: | + echo "--- Detecting latest curl tarball version..." + curl_version="$(curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused https://curl.se/info.json \ + | jq --raw-output .Version)" + + for suffix in .tar.bz2 .tar.gz .tar.xz .zip; do + echo "--- Downloading ${curl_version} ${suffix}..." + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ + --output pkg.bin "https://curl.se/download/curl-${curl_version}${suffix}" \ + --output pkg.sig "https://curl.se/download/curl-${curl_version}${suffix}.asc" + echo "--- Verifying ${curl_version} ${suffix}..." + gpg --batch --keyserver-options timeout=15 --display-charset utf-8 --keyid-format 0xlong --verify-options show-primary-uid-only \ + --verify pkg.sig pkg.bin 2>&1 + echo '---' + done diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index dc15b9c9db76..7ac58d902b2a 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -8,6 +8,7 @@ name: 'Fuzzer' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -21,6 +22,7 @@ name: 'Fuzzer' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '**/CMakeLists.txt' diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index 91699e18b212..ca2bad2ea053 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -8,6 +8,7 @@ name: 'Linux HTTP/3' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'Linux HTTP/3' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -36,67 +38,43 @@ env: CURL_CI: github CURL_TEST_MIN: 1850 DO_NOT_TRACK: '1' - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.0 - # manually bumped - OPENSSL_PREV_VERSION: 3.6.2 - OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.1 # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 1.71.0 + AWSLC_VERSION: 5.5.0 # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260413.0 + BORINGSSL_VERSION: 0.20260730.0 # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com NETTLE_VERSION: 3.10.2 # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com GNUTLS_VERSION: 3.8.11 + # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com + LIBRESSL_VERSION: 4.3.2 + # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com + OPENSSL_VERSION: 4.0.2 + # manually bumped + OPENSSL_PREV_VERSION: 3.6.2 + OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f + # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com + QUICHE_VERSION: 0.29.3 # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.1 + WOLFSSL_VERSION: 5.9.2 # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com - NGHTTP3_VERSION: 1.15.0 + NGHTTP3_VERSION: 1.18.0 # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 versioning=semver registryUrl=https://github.com - NGTCP2_VERSION: 1.22.1 + NGTCP2_VERSION: 1.25.0 # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com - NGHTTP2_VERSION: 1.69.0 - # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com - QUICHE_VERSION: 0.24.7 + NGHTTP2_VERSION: 1.70.0 + # no tagged releases + H2O_VERSION: 11b0cfa2771e3ccad4a852e72473e4e278ab1de7 # 2026-05-28 + H2O_SHA256: 5ae1bd7b09970d7d49c41fa68193e24da04c2a7ac5581fbe2affc79200b0721f jobs: build-cache: name: 'Build caches' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - name: 'cache openssl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-openssl-http3-no-deprecated - env: - cache-name: cache-openssl-http3-no-deprecated - with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} - - - name: 'cache openssl-prev' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-openssl-prev-http3-no-deprecated - env: - cache-name: cache-openssl-prev-http3-no-deprecated - with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} - - - name: 'cache libressl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-libressl - env: - cache-name: cache-libressl - with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} - - name: 'cache awslc' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-awslc env: cache-name: cache-awslc @@ -105,7 +83,7 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} - name: 'cache boringssl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-boringssl env: cache-name: cache-boringssl @@ -114,7 +92,7 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} - name: 'cache nettle' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nettle env: cache-name: cache-nettle @@ -123,7 +101,7 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }} - name: 'cache gnutls' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-gnutls env: cache-name: cache-gnutls @@ -131,8 +109,35 @@ jobs: path: ~/gnutls/build key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} + - name: 'cache libressl' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-libressl + env: + cache-name: cache-libressl + with: + path: ~/libressl/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + + - name: 'cache openssl' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-openssl-http3-no-deprecated + env: + cache-name: cache-openssl-http3-no-deprecated + with: + path: ~/openssl/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + + - name: 'cache openssl-prev' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-openssl-prev-http3 + env: + cache-name: cache-openssl-prev-http3 + with: + path: ~/openssl-prev/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + - name: 'cache wolfssl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-wolfssl env: cache-name: cache-wolfssl @@ -141,7 +146,7 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} - name: 'cache nghttp3' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nghttp3 env: cache-name: cache-nghttp3 @@ -150,7 +155,7 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }} - name: 'cache ngtcp2' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-ngtcp2 env: cache-name: cache-ngtcp2 @@ -159,17 +164,8 @@ jobs: key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" - - name: 'cache ngtcp2 openssl-prev' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-ngtcp2-openssl-prev - env: - cache-name: cache-ngtcp2-openssl-prev - with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} - - name: 'cache ngtcp2 boringssl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-ngtcp2-boringssl env: cache-name: cache-ngtcp2-boringssl @@ -177,8 +173,17 @@ jobs: path: ~/ngtcp2-boringssl/build key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} + - name: 'cache ngtcp2 openssl-prev' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-ngtcp2-openssl-prev + env: + cache-name: cache-ngtcp2-openssl-prev + with: + path: ~/ngtcp2-openssl-prev/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + - name: 'cache nghttp2' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nghttp2 env: cache-name: cache-nghttp2 @@ -187,26 +192,36 @@ jobs: key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ ${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}" + - name: 'cache h2o' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-h2o + env: + cache-name: cache-h2o + with: + path: ~/h2o/build + key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}" + - id: settings if: >- - ${{ steps.cache-openssl-http3-no-deprecated.outputs.cache-hit != 'true' || - steps.cache-openssl-prev-http3-no-deprecated.outputs.cache-hit != 'true' || - steps.cache-libressl.outputs.cache-hit != 'true' || - steps.cache-awslc.outputs.cache-hit != 'true' || - steps.cache-boringssl.outputs.cache-hit != 'true' || - steps.cache-nettle.outputs.cache-hit != 'true' || - steps.cache-gnutls.outputs.cache-hit != 'true' || - steps.cache-wolfssl.outputs.cache-hit != 'true' || - steps.cache-nghttp3.outputs.cache-hit != 'true' || - steps.cache-ngtcp2.outputs.cache-hit != 'true' || - steps.cache-ngtcp2-openssl-prev.outputs.cache-hit != 'true' || - steps.cache-ngtcp2-boringssl.outputs.cache-hit != 'true' || - steps.cache-nghttp2.outputs.cache-hit != 'true' }} + ${{ !steps.cache-awslc.outputs.cache-hit || + !steps.cache-boringssl.outputs.cache-hit || + !steps.cache-nettle.outputs.cache-hit || + !steps.cache-gnutls.outputs.cache-hit || + !steps.cache-libressl.outputs.cache-hit || + !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit || + !steps.cache-openssl-prev-http3.outputs.cache-hit || + !steps.cache-wolfssl.outputs.cache-hit || + !steps.cache-nghttp3.outputs.cache-hit || + !steps.cache-ngtcp2-boringssl.outputs.cache-hit || + !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit || + !steps.cache-ngtcp2.outputs.cache-hit || + !steps.cache-nghttp2.outputs.cache-hit || + !steps.cache-h2o.outputs.cache-hit }} run: echo 'needs-build=true' >> "$GITHUB_OUTPUT" - name: 'install build prereqs' - if: ${{ steps.settings.outputs.needs-build == 'true' }} + if: ${{ steps.settings.outputs.needs-build }} timeout-minutes: 2 run: | sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print @@ -216,51 +231,16 @@ jobs: libtool autoconf automake pkgconf \ libbrotli-dev libzstd-dev zlib1g-dev \ libev-dev \ + libuv1-dev \ libc-ares-dev \ libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin # for GnuTLS - echo 'CC=gcc-12' >> "$GITHUB_ENV" - echo 'CXX=g++-12' >> "$GITHUB_ENV" - - - name: 'build openssl' - if: ${{ steps.cache-openssl-http3-no-deprecated.outputs.cache-hit != 'true' }} - run: | - cd ~ - git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl - cd openssl - ./config --prefix="$PWD"/build --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated - make - make -j1 install_sw - - - name: 'build openssl-prev' - if: ${{ steps.cache-openssl-prev-http3-no-deprecated.outputs.cache-hit != 'true' }} - run: | - cd ~ - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin - sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OPENSSL_PREV_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin - cd "openssl-${OPENSSL_PREV_VERSION}" - ./config --prefix=/home/runner/openssl-prev/build --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated - make - make -j1 install_sw - - - name: 'build libressl' - if: ${{ steps.cache-libressl.outputs.cache-hit != 'true' }} - run: | - cd ~ - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin - sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl/build - cmake --build . - cmake --install . - name: 'build awslc' - if: ${{ steps.cache-awslc.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-awslc.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc/build @@ -268,7 +248,7 @@ jobs: cmake --install . - name: 'build boringssl' - if: ${{ steps.cache-boringssl.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-boringssl.outputs.cache-hit }} run: | mkdir boringssl-src cd boringssl-src @@ -280,11 +260,11 @@ jobs: cmake --install . - name: 'build nettle' - if: ${{ steps.cache-nettle.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-nettle.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://ftpmirror.gnu.org/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://ftpmirror.gnu.org/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "nettle-${NETTLE_VERSION}" autoreconf -fi @@ -293,7 +273,7 @@ jobs: make install - name: 'build gnutls' - if: ${{ steps.cache-gnutls.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-gnutls.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ @@ -309,8 +289,42 @@ jobs: --disable-guile --disable-doc --disable-tests --disable-tools make install + - name: 'build libressl' + if: ${{ !steps.cache-libressl.outputs.cache-hit }} + run: | + cd ~ + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin + cd "libressl-${LIBRESSL_VERSION}" + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl/build + cmake --build . + cmake --install . + + - name: 'build openssl' + if: ${{ !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit }} + run: | + cd ~ + git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl + cd openssl + ./config --prefix="$PWD"/build --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated + make + make -j1 install_sw + + - name: 'build openssl-prev' + if: ${{ !steps.cache-openssl-prev-http3.outputs.cache-hit }} + run: | + cd ~ + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + --location --proto-redir =https "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin + sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OPENSSL_PREV_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin + cd "openssl-${OPENSSL_PREV_VERSION}" + ./config --prefix=/home/runner/openssl-prev/build --libdir=lib no-makedepend no-apps no-docs no-tests + make + make -j1 install_sw + - name: 'build wolfssl' - if: ${{ steps.cache-wolfssl.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-wolfssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${WOLFSSL_VERSION}-stable" https://github.com/wolfSSL/wolfssl @@ -322,7 +336,7 @@ jobs: make install - name: 'build nghttp3' - if: ${{ steps.cache-nghttp3.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-nghttp3.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP3_VERSION}" https://github.com/ngtcp2/nghttp3 @@ -334,7 +348,7 @@ jobs: make install - name: 'build ngtcp2' - if: ${{ steps.cache-ngtcp2.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-ngtcp2.outputs.cache-hit }} # building twice to get crypto libs for ossl, libressl and awslc installed run: | cd ~ @@ -357,7 +371,7 @@ jobs: make install - name: 'build ngtcp2 openssl-prev' - if: ${{ steps.cache-ngtcp2-openssl-prev.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl-prev @@ -369,7 +383,7 @@ jobs: make install - name: 'build ngtcp2 boringssl' - if: ${{ steps.cache-ngtcp2-boringssl.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-ngtcp2-boringssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-boringssl @@ -382,7 +396,7 @@ jobs: make install - name: 'build nghttp2' - if: ${{ steps.cache-nghttp2.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-nghttp2.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP2_VERSION}" https://github.com/nghttp2/nghttp2 @@ -399,10 +413,22 @@ jobs: --with-libbrotlienc --with-libbrotlidec make install + - name: 'build h2o' + if: ${{ !steps.cache-h2o.outputs.cache-hit }} + run: | + cd ~ + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + --location --proto-redir =https "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz" --output pkg.bin + sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${H2O_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin + cd "h2o-${H2O_VERSION}" + cmake -B . -G Ninja -DWITHOUT_LIBS=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DCMAKE_INSTALL_PREFIX=/home/runner/h2o/build + cmake --build . + cmake --install . + linux: name: ${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.name }} needs: build-cache - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 env: CURL_TRACE_PKG_CONFIG: '1' @@ -412,57 +438,6 @@ jobs: fail-fast: false matrix: build: - - name: 'openssl' - tflags: '--min=1700' - LDFLAGS: -Wl,-rpath,/home/runner/openssl/build/lib - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - configure: >- - --with-openssl=/home/runner/openssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --enable-ssls-export - - - name: 'openssl' - install_steps: skipall - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl/build -DUSE_NGTCP2=ON - -DCURL_DISABLE_LDAP=ON - -DUSE_ECH=ON - -DCMAKE_UNITY_BUILD=ON - - - name: 'openssl-prev' - install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/openssl-prev/build/lib - PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/nghttp2-openssl-prev/build/lib/pkgconfig" - configure: >- - --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export - - - name: 'openssl-prev' - tflags: '--min=1700' - PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" - generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DUSE_NGTCP2=ON - -DCURL_DISABLE_LDAP=ON - - - name: 'libressl' - install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/libressl/build/lib - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - # Intentionally using '--with-ngtcp2=' to test this way of configuration, in addition to bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' in other jobs. - configure: >- - --with-openssl=/home/runner/libressl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ssls-export - --enable-unity - - - name: 'libressl' - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/libressl/build -DUSE_NGTCP2=ON - - name: 'awslc' install_steps: skipall LDFLAGS: -Wl,-rpath,/home/runner/awslc/build/lib @@ -475,7 +450,7 @@ jobs: PKG_CONFIG_PATH: /home/runner/awslc/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig generate: >- -DOPENSSL_ROOT_DIR=/home/runner/awslc/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF - -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON + -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'boringssl' install_steps: skipall @@ -492,7 +467,7 @@ jobs: /home/runner/nghttp2/build/lib/pkgconfig" generate: >- -DOPENSSL_ROOT_DIR=/home/runner/boringssl/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF - -DCMAKE_UNITY_BUILD=ON + -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'gnutls' install_packages: libp11-kit-dev libssh-dev @@ -500,7 +475,7 @@ jobs: LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib -Wl,-rpath,/home/runner/nettle/build/lib64 -Wl,-rpath,/home/runner/ngtcp2/build/lib PKG_CONFIG_PATH: /home/runner/nettle/build/lib64/pkgconfig:/home/runner/gnutls/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig configure: >- - --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export + --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export --enable-httpsig - name: 'gnutls' install_packages: libp11-kit-dev libssh-dev @@ -513,31 +488,65 @@ jobs: /home/runner/nghttp2/build/lib/pkgconfig" generate: >- -DCURL_USE_GNUTLS=ON -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH=ON - -DCMAKE_UNITY_BUILD=ON + -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF - - name: 'wolfssl' - install_packages: libssh2-1-dev + - name: 'libressl' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl/build/lib - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/libressl/build/lib + PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + # Intentionally using '--with-ngtcp2=' to test this way of configuration, in addition to bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' in other jobs. configure: >- - --with-wolfssl=/home/runner/wolfssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --with-libssh2 --enable-ssls-export + --with-openssl=/home/runner/libressl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ssls-export --enable-unity - - name: 'wolfssl' - install_packages: libssh2-1-dev - tflags: '--min=1900' - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + - name: 'libressl' + PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig generate: >- - -DCURL_USE_WOLFSSL=ON -DUSE_NGTCP2=ON - -DUSE_ECH=ON + -DOPENSSL_ROOT_DIR=/home/runner/libressl/build -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF + + - name: 'openssl' + tflags: '--min=1700' + LDFLAGS: -Wl,-rpath,/home/runner/openssl/build/lib + PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + configure: >- + --with-openssl=/home/runner/openssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --enable-ssls-export --enable-proxy-http3 --enable-httpsig + + - name: 'openssl' + install_steps: skipall + PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + generate: >- + -DOPENSSL_ROOT_DIR=/home/runner/openssl/build -DUSE_NGTCP2=ON + -DCURL_DISABLE_LDAP=ON + -DUSE_ECH=ON -DUSE_PROXY_HTTP3=ON + -DCMAKE_UNITY_BUILD=ON + + - name: 'openssl-prev' + install_steps: skipall + LDFLAGS: -Wl,-rpath,/home/runner/openssl-prev/build/lib + PKG_CONFIG_PATH: "\ + /home/runner/openssl-prev/build/lib/pkgconfig:\ + /home/runner/nghttp3/build/lib/pkgconfig:\ + /home/runner/nghttp2-openssl-prev/build/lib/pkgconfig" + configure: >- + --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export + + - name: 'openssl-prev' + tflags: '--min=1700' + PKG_CONFIG_PATH: "\ + /home/runner/openssl-prev/build/lib/pkgconfig:\ + /home/runner/nghttp3/build/lib/pkgconfig:\ + /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig:\ + /home/runner/nghttp2/build/lib/pkgconfig" + generate: >- + -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DUSE_NGTCP2=ON + -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'quiche' install_steps: skipall LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig configure: >- - --with-openssl=/home/runner/quiche/quiche/deps/boringssl/src + --with-openssl=/home/runner/quiche/boringssl --with-quiche=/home/runner/quiche/target/release --with-ca-fallback --enable-unity @@ -545,9 +554,26 @@ jobs: - name: 'quiche' PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig:/home/runner/quiche/target/release generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/quiche/quiche/deps/boringssl/src + -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl -DUSE_QUICHE=ON - -DCURL_CA_FALLBACK=ON + -DCURL_CA_FALLBACK=ON -DCURL_DISABLE_HTTPSIG=OFF + + - name: 'wolfssl' + install_packages: libssh2-1-dev + install_steps: skipall + LDFLAGS: -Wl,-rpath,/home/runner/wolfssl/build/lib + PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + configure: >- + --with-wolfssl=/home/runner/wolfssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --with-libssh2 --enable-ssls-export + --enable-unity + + - name: 'wolfssl' + install_packages: libssh2-1-dev + tflags: '--min=1900' + PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + generate: >- + -DCURL_USE_WOLFSSL=ON -DUSE_NGTCP2=ON + -DUSE_ECH=ON -DCURL_DISABLE_HTTPSIG=OFF steps: - name: 'install prereqs' @@ -567,44 +593,10 @@ jobs: libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libidn2-0-dev libldap-dev libuv1-dev valgrind \ ${INSTALL_PACKAGES} \ ${MATRIX_INSTALL_PACKAGES} - echo 'CC=gcc-12' >> "$GITHUB_ENV" - echo 'CXX=g++-12' >> "$GITHUB_ENV" - - - name: 'cache openssl' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-openssl-http3-no-deprecated - env: - cache-name: cache-openssl-http3-no-deprecated - with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} - fail-on-cache-miss: true - - - name: 'cache openssl-prev' - if: ${{ contains(matrix.build.name, 'openssl-prev') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-openssl-prev-http3-no-deprecated - env: - cache-name: cache-openssl-prev-http3-no-deprecated - with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} - fail-on-cache-miss: true - - - name: 'cache libressl' - if: ${{ contains(matrix.build.name, 'libressl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-libressl - env: - cache-name: cache-libressl - with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} - fail-on-cache-miss: true - name: 'cache awslc' if: ${{ contains(matrix.build.name, 'awslc') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-awslc env: cache-name: cache-awslc @@ -615,7 +607,7 @@ jobs: - name: 'cache boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-boringssl env: cache-name: cache-boringssl @@ -626,7 +618,7 @@ jobs: - name: 'cache nettle' if: ${{ contains(matrix.build.name, 'gnutls') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nettle env: cache-name: cache-nettle @@ -637,7 +629,7 @@ jobs: - name: 'cache gnutls' if: ${{ contains(matrix.build.name, 'gnutls') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-gnutls env: cache-name: cache-gnutls @@ -646,9 +638,41 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} fail-on-cache-miss: true + - name: 'cache libressl' + if: ${{ contains(matrix.build.name, 'libressl') }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-libressl + env: + cache-name: cache-libressl + with: + path: ~/libressl/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + fail-on-cache-miss: true + + - name: 'cache openssl' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-openssl-http3-no-deprecated + env: + cache-name: cache-openssl-http3-no-deprecated + with: + path: ~/openssl/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + fail-on-cache-miss: true + + - name: 'cache openssl-prev' + if: ${{ contains(matrix.build.name, 'openssl-prev') }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-openssl-prev-http3 + env: + cache-name: cache-openssl-prev-http3 + with: + path: ~/openssl-prev/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + fail-on-cache-miss: true + - name: 'cache wolfssl' if: ${{ contains(matrix.build.name, 'wolfssl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-wolfssl env: cache-name: cache-wolfssl @@ -658,7 +682,7 @@ jobs: fail-on-cache-miss: true - name: 'cache nghttp3' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nghttp3 env: cache-name: cache-nghttp3 @@ -668,7 +692,7 @@ jobs: fail-on-cache-miss: true - name: 'cache ngtcp2' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-ngtcp2 env: cache-name: cache-ngtcp2 @@ -678,20 +702,9 @@ jobs: ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" fail-on-cache-miss: true - - name: 'cache ngtcp2 openssl-prev' - if: ${{ contains(matrix.build.name, 'openssl-prev') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - id: cache-ngtcp2-openssl-prev - env: - cache-name: cache-ngtcp2-openssl-prev - with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} - fail-on-cache-miss: true - - name: 'cache ngtcp2 boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-ngtcp2-boringssl env: cache-name: cache-ngtcp2-boringssl @@ -700,8 +713,19 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} fail-on-cache-miss: true + - name: 'cache ngtcp2 openssl-prev' + if: ${{ contains(matrix.build.name, 'openssl-prev') }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-ngtcp2-openssl-prev + env: + cache-name: cache-ngtcp2-openssl-prev + with: + path: ~/ngtcp2-openssl-prev/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + fail-on-cache-miss: true + - name: 'cache nghttp2' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nghttp2 env: cache-name: cache-nghttp2 @@ -710,9 +734,19 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }} fail-on-cache-miss: true + - name: 'cache h2o' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-h2o + env: + cache-name: cache-h2o + with: + path: ~/h2o/build + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + fail-on-cache-miss: true + - name: 'cache quiche' if: ${{ contains(matrix.build.name, 'quiche') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-quiche env: cache-name: cache-quiche @@ -721,26 +755,24 @@ jobs: key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.QUICHE_VERSION }} - name: 'build quiche and boringssl' - if: ${{ contains(matrix.build.name, 'quiche') && steps.cache-quiche.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.name, 'quiche') && !steps.cache-quiche.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "${QUICHE_VERSION}" --recursive https://github.com/cloudflare/quiche cd quiche - #### Work-around https://github.com/curl/curl/issues/7927 ####### - #### See https://github.com/alexcrichton/cmake-rs/issues/131 #### - sed -i -e 's/cmake = "0.1"/cmake = "=0.1.45"/' quiche/Cargo.toml - cargo build -v --package quiche --release --features ffi,pkg-config-meta,qlog --verbose ln -s libquiche.so target/release/libquiche.so.0 - mkdir -v quiche/deps/boringssl/src/lib - find target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' quiche/deps/boringssl/src/lib \; + cd .. + mkdir -p quiche/boringssl/lib + find quiche/target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' quiche/boringssl/lib \; + find quiche/target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; # include dir - # /home/runner/quiche/quiche/deps/boringssl/src/include + # /home/runner/quiche/boringssl/include # lib dir - # /home/runner/quiche/quiche/deps/boringssl/src/lib + # /home/runner/quiche/boringssl/lib - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -759,10 +791,11 @@ jobs: if [ "${MATRIX_BUILD}" = 'cmake' ]; then [[ "${MATRIX_GENERATE}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" cmake -B bld -G Ninja \ - -DCMAKE_C_COMPILER_TARGET="$(uname -m)-pc-linux-gnu" -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ -DCURL_WERROR=ON -DENABLE_DEBUG=ON \ -DCURL_USE_LIBUV=ON -DCURL_ENABLE_NTLM=ON \ -DTEST_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ + -DH2O=/home/runner/h2o/build/bin/h2o \ -DHTTPD_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ ${MATRIX_GENERATE} ${options} else @@ -770,6 +803,7 @@ jobs: mkdir bld && cd bld && ../configure --enable-warnings --enable-werror --enable-debug --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ --with-libuv --enable-ntlm \ + --with-test-h2o=/home/runner/h2o/build/bin/h2o \ --with-test-nghttpx=/home/runner/nghttp2/build/bin/nghttpx \ ${MATRIX_CONFIGURE} fi @@ -783,6 +817,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'test configs' run: grep -H -v '^#' bld/tests/config bld/tests/http/config.ini || true diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index e35f09304582..fa06c826ac5a 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -2,12 +2,11 @@ # # SPDX-License-Identifier: curl -# This workflow will triage pull requests and apply a label based on the +# This workflow triages pull requests and applies a label based on the # paths that are modified in the pull request. # -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler +# To use this workflow, you need to set up a .github/labeler.yml file with +# configuration. For more information, see: https://github.com/actions/labeler name: 'Labeler' @@ -31,6 +30,6 @@ jobs: pull-requests: write # To edit labels on PRs steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/linux-old.yml b/.github/workflows/linux-old.yml index 11644174623b..7fe01f69b436 100644 --- a/.github/workflows/linux-old.yml +++ b/.github/workflows/linux-old.yml @@ -7,7 +7,7 @@ # outdated systems. # # Debian stretch is chosen as it closely matches some of the oldest major -# versions we support (especially cmake); see docs/INTERNALS.md and it +# versions we support (especially cmake); see docs/DEPENDENCIES.md and it # is still supported (as of this writing). # stretch has ELTS support from Freexian until 2027-06-30 # For ELTS info see https://www.freexian.com/lts/extended/docs/how-to-use-extended-lts/ @@ -22,6 +22,7 @@ name: 'Linux Old' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -32,6 +33,7 @@ name: 'Linux Old' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -56,7 +58,7 @@ jobs: cmake-autotools: name: 'autotools & cmake' runs-on: ubuntu-latest - container: 'debian:stretch' + container: debian:stretch-20220622-slim@sha256:c5cd3ffceeb25b683bf5111ea89bf8049a177e00fb237235d48076a19cc80097 steps: - name: 'install prereqs' @@ -90,7 +92,7 @@ jobs: sha256sum "${fn}".tar*.gz | tee /dev/stderr | grep -qwF -- "${CMAKE_SHA256}" && tar -xf "${fn}".tar*.gz && rm -f "${fn}".tar*.gz mv "cmake-${CMAKE_VERSION}-Linux-x86_64" cmake - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -116,10 +118,8 @@ jobs: echo '::group::raw'; cat bld-1/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld-1/lib/curl_config.h | sort || true - # when this job can get a libssh version 0.9.0 or later, this should get - # that enabled again - # when this job can get c-ares 1.16.0 or later, we can enable that - # again + # when this job can get libssh 0.9.0 or greater, this should get that enabled again + # when this job can get c-ares 1.16.0 or greater, this should get that enabled again - name: 'CM configure (out-of-tree, zstd, gssapi)' run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d9e09b518782..a2d610219f56 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -8,6 +8,7 @@ name: 'Linux' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '.circleci/**' @@ -17,6 +18,7 @@ name: 'Linux' pull_request: branches: - master + - 'curl-*' paths-ignore: - '.circleci/**' - 'appveyor.*' @@ -34,29 +36,30 @@ env: CURL_CI: github CURL_TEST_MIN: 1660 DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' + # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com + AWSLC_VERSION: 5.5.0 + # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com + BORINGSSL_VERSION: 0.20260730.0 + # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com + FIL_C_VERSION: 0.684 # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.1 - # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.1 + LIBRESSL_VERSION: 4.3.2 # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com - MBEDTLS_VERSION: 4.0.0 + MBEDTLS_VERSION: 4.2.0 # manually bumped - MBEDTLS_PREV_VERSION: 3.6.5 - MBEDTLS_PREV_SHA256: 4a11f1777bb95bf4ad96721cac945a26e04bf19f57d905f241fe77ebeddf46d8 - # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 1.71.0 - # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260413.0 + MBEDTLS_PREV_VERSION: 3.6.7 + MBEDTLS_PREV_SHA256: a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6 + # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com + NGHTTP2_VERSION: 1.70.0 + # handled in renovate.json + OPENLDAP_VERSION: 2.6.10 # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.0 + OPENSSL_VERSION: 4.0.2 # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com RUSTLS_VERSION: 0.15.3 - # handled in renovate.json - OPENLDAP_VERSION: 2.6.10 - # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com - NGHTTP2_VERSION: 1.69.0 - # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com - FIL_C_VERSION: 0.678 + # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com + WOLFSSL_VERSION: 5.9.2 jobs: linux: @@ -72,10 +75,24 @@ jobs: fail-fast: false matrix: build: + - name: 'awslc' + install_steps: awslc pytest + LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib + configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm --enable-smb + + - name: 'awslc' + install_packages: libidn2-dev + install_steps: awslc clean + generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON + + - name: 'boringssl' + install_steps: boringssl pytest + generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + - name: 'libressl krb5' image: ubuntu-24.04-arm install_packages: libidn2-dev libnghttp2-dev libldap-dev libkrb5-dev - install_steps: libressl-c-arm pytest codeset-test + install_steps: libressl-c-arm pytest codeset-test1 LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib configure: --with-openssl=/home/runner/libressl --with-gssapi --enable-debug @@ -83,14 +100,14 @@ jobs: image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind install_steps: libressl-c-arm - tflags: '--min=870 1 to 950' + tflags: '--subset=0/2' generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5 valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind install_steps: libressl-c-arm - tflags: '--min=900 951 to 9999' + tflags: '--subset=1/2' generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl clang' @@ -101,33 +118,23 @@ jobs: LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib configure: --with-openssl=/home/runner/libressl --enable-debug - - name: 'wolfssl-all' - image: ubuntu-24.04-arm - install_steps: wolfssl-all-arm - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-all/lib - configure: --with-wolfssl=/home/runner/wolfssl-all --enable-ech --enable-debug - - - name: 'wolfssl-opensslextra valgrind 1' - image: ubuntu-24.04-arm - install_packages: valgrind - install_steps: wolfssl-opensslextra-arm - tflags: '--min=815 1 to 1000' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug - - - name: 'wolfssl-opensslextra valgrind 2' - image: ubuntu-24.04-arm - install_packages: valgrind - install_steps: wolfssl-opensslextra-arm - tflags: '--min=835 1001 to 9999' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug + - name: 'libressl Fil-C' + install_steps: filc libressl-filc nghttp2-filc pytest + tflags: '!776' # adds 1-9 minutes to the test run step, and fails consistently + CC: /home/runner/filc/build/bin/filcc + PKG_CONFIG_PATH: /home/runner/nghttp2/lib/pkgconfig + generate: >- + -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_DISABLE_TYPECHECK=ON + -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_LIBPSL=OFF + -DCURL_ZLIB=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF + -DCURL_DISABLE_LDAP=ON -DUSE_LIBIDN2=OFF -DCURL_USE_LIBSSH2=OFF + -DCURL_ENABLE_NTLM=ON - name: 'mbedtls gss valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind install_steps: mbedtls-latest-arm - tflags: '--min=850 1 to 1000' + tflags: '--subset=0/2' LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON -DCURL_DROP_UNUSED=ON @@ -136,7 +143,7 @@ jobs: image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind install_steps: mbedtls-latest-arm - tflags: '--min=900 1001 to 9999' + tflags: '--subset=1/2' LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON @@ -167,34 +174,61 @@ jobs: -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_COMPLETION_FISH=ON -DCURL_COMPLETION_ZSH=ON - - name: 'awslc' - install_steps: awslc pytest - LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib - configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm + - name: 'rustls valgrind 1' + install_packages: libnghttp2-dev libldap-dev valgrind + install_steps: rust rustls + tflags: '--subset=0/2' + generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - - name: 'awslc' - install_packages: libidn2-dev - install_steps: awslc - generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON + - name: 'rustls valgrind 2' + install_packages: libnghttp2-dev libldap-dev valgrind + install_steps: rust rustls + tflags: '--subset=1/2' + generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - - name: 'boringssl' - install_steps: boringssl pytest - generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + - name: 'rustls' + install_packages: libnghttp2-dev libldap-dev + install_steps: rust rustls skiprun pytest + configure: --with-rustls --enable-ech --enable-debug + + - name: 'wolfssl-all' + image: ubuntu-26.04-arm + install_steps: wolfssl-all-arm + LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-all/lib + configure: --with-wolfssl=/home/runner/wolfssl-all --enable-ech --enable-debug + + - name: 'wolfssl-opensslextra valgrind 1' + image: ubuntu-26.04-arm + install_packages: valgrind + install_steps: wolfssl-opensslextra-arm + tflags: '--subset=0/2' + LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig + + - name: 'wolfssl-opensslextra valgrind 2' + image: ubuntu-26.04-arm + install_packages: valgrind + install_steps: wolfssl-opensslextra-arm + tflags: '--subset=1/2' + LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'openssl default' install_steps: pytest configure: --with-openssl --enable-debug --disable-unity - name: 'openssl libssh2 sync-resolver valgrind 1 +analyzer' - image: ubuntu-24.04-arm - install_packages: libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind - tflags: '--min=965 1 to 1000' + image: ubuntu-26.04-arm + install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind + CC: gcc-16 + tflags: '--subset=0/2' generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_GCC_ANALYZER=ON -DCURL_ENABLE_NTLM=ON - name: 'openssl libssh2 sync-resolver valgrind 2' - image: ubuntu-24.04-arm - install_packages: libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind - tflags: '--min=920 1001 to 9999' + image: ubuntu-26.04-arm + install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind + CC: gcc-16 + tflags: '--subset=1/2' generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON - name: 'openssl intel C89' @@ -211,13 +245,13 @@ jobs: - name: 'openssl -O3 libssh valgrind 1' install_packages: libssh-dev valgrind CFLAGS: -O3 - tflags: '--min=950 1 to 1000' + tflags: '--subset=0/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl -O3 libssh valgrind 2' install_packages: libssh-dev valgrind CFLAGS: -O3 - tflags: '--min=900 1001 to 9999' + tflags: '--subset=1/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl clang krb5 openldap static' @@ -237,7 +271,7 @@ jobs: - name: 'openssl !ipv6 !--libcurl !--digest-auth' image: ubuntu-24.04-arm - configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm + configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm --enable-smb - name: 'curl_global_init_mem debug valgrind' image: ubuntu-24.04-arm @@ -246,7 +280,7 @@ jobs: configure: >- --enable-init-mem-debug --with-openssl --disable-debug --enable-unity - --enable-ntlm + --enable-ntlm --enable-smb - name: 'openssl https-only' image: ubuntu-24.04-arm @@ -262,15 +296,15 @@ jobs: - name: 'openssl torture 1' install_packages: libnghttp2-dev libssh2-1-dev libc-ares-dev - tflags: '-t --shallow=25 --min=960 1 to 1000' + tflags: '-t --shallow=25 --subset=0/2' torture: true - generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON + generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl torture 2' install_packages: libnghttp2-dev libssh2-1-dev libc-ares-dev - tflags: '-t --shallow=25 --min=915 1001 to 9999' + tflags: '-t --shallow=25 --subset=1/2' torture: true - generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON + generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl i686' install_packages: gcc-14-i686-linux-gnu libssl-dev:i386 libssh2-1-dev:i386 libidn2-dev:i386 libc-ares-dev:i386 zlib1g-dev:i386 @@ -283,27 +317,17 @@ jobs: --with-openssl --with-libssh2 --with-libidn2 --enable-ares --enable-debug - name: '!ssl !http !smtp !imap' - image: ubuntu-24.04-arm + image: ubuntu-26.04-arm tflags: '--min=500' - configure: --without-ssl --enable-debug --disable-http --disable-smtp --disable-imap --disable-unity - - - name: 'libressl Fil-C' - install_steps: filc libressl-filc nghttp2-filc pytest - tflags: '!776' # adds 1-9 minutes to the test run step, and fails consistently - CC: /home/runner/filc/build/bin/filcc - PKG_CONFIG_PATH: /home/runner/nghttp2/lib/pkgconfig - generate: >- - -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_DISABLE_TYPECHECK=ON - -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_LIBPSL=OFF - -DCURL_ZLIB=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF - -DCURL_DISABLE_LDAP=ON -DUSE_LIBIDN2=OFF -DCURL_USE_LIBSSH2=OFF - -DCURL_ENABLE_NTLM=ON + configure: --without-ssl --enable-debug --disable-http --disable-smtp --disable-imap --disable-unity --enable-httpsig - name: 'clang-tidy' - install_packages: clang-20 clang-tidy-20 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev + image: ubuntu-26.04 + install_packages: clang-22 clang-tidy-22 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev install_steps: skiprun mbedtls-latest-intel rustls wolfssl-opensslextra-intel install_steps_brew: openssl@4 gsasl - CC: clang-20 + CC: clang-22 + CFLAGS: -Wunused-macros LDFLAGS: >- -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib -Wl,-rpath,/home/runner/mbedtls/lib @@ -320,14 +344,16 @@ jobs: -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/home/linuxbrew/.linuxbrew/opt/openssl@4 -DCURL_USE_WOLFSSL=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_MBEDTLS=ON -DCURL_USE_RUSTLS=ON -DCURL_USE_GSASL=ON - -DUSE_ECH=ON -DCURL_USE_GSSAPI=ON -DUSE_SSLS_EXPORT=ON - -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-20 + -DUSE_ECH=ON -DCURL_USE_GSSAPI=ON -DUSE_SSLS_EXPORT=ON -DCURL_DISABLE_HTTPSIG=OFF + -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-22 - name: 'clang-tidy H3 c-ares !examples' - install_packages: clang-20 clang-tidy-20 libidn2-dev libssh-dev libnghttp2-dev + image: ubuntu-26.04 + install_packages: clang-22 clang-tidy-22 libidn2-dev libssh-dev libnghttp2-dev install_steps: skiprun install_steps_brew: libngtcp2 libnghttp3 c-ares - CC: clang-20 + CC: clang-22 + CFLAGS: -Wunused-macros LDFLAGS: >- -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/openssl/lib -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/libngtcp2/lib @@ -340,14 +366,16 @@ jobs: /home/linuxbrew/.linuxbrew/opt/c-ares/lib/pkgconfig" generate: >- -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/home/linuxbrew/.linuxbrew/opt/openssl -DUSE_NGTCP2=ON - -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DUSE_HTTPSRR=ON -DENABLE_ARES=ON + -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DUSE_HTTPSRR=ON -DENABLE_ARES=ON -DUSE_PROXY_HTTP3=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON - -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-20 + -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-22 - name: 'address-sanitizer' - install_packages: clang-20 libssl-dev libssh-dev libidn2-dev libnghttp2-dev libubsan1 libasan8 libtsan2 + image: ubuntu-26.04 + install_packages: clang-22 libssh-dev libidn2-dev libnghttp2-dev libubsan1 libasan8 libtsan2 install_steps: pytest randcurl - CC: clang-20 + install_steps_brew: openssl@4 + CC: clang-22 CFLAGS: >- -fsanitize=address,bounds,leak,signed-integer-overflow,undefined -fno-sanitize-recover=address,bounds,leak,signed-integer-overflow,undefined @@ -357,13 +385,14 @@ jobs: -fsanitize=address,bounds,leak,signed-integer-overflow,undefined -fno-sanitize-recover=address,bounds,leak,signed-integer-overflow,undefined -ldl -lubsan - generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON + generate: -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/home/linuxbrew/.linuxbrew/opt/openssl@4 -DUSE_ECH=ON -DCURL_USE_LIBSSH=ON - name: 'address-sanitizer H3 c-ares' - install_packages: clang-20 libubsan1 libasan8 libtsan2 + image: ubuntu-26.04 + install_packages: clang-22 libubsan1 libasan8 libtsan2 install_steps: pytest - install_steps_brew: openssl@4 libssh2 libngtcp2 libnghttp3 c-ares - CC: clang-20 + install_steps_brew: openssl libssh2 libngtcp2 libnghttp3 c-ares + CC: clang-22 CFLAGS: >- -fsanitize=address,bounds,leak,signed-integer-overflow,undefined -fno-sanitize-recover=address,bounds,leak,signed-integer-overflow,undefined @@ -379,22 +408,24 @@ jobs: /home/linuxbrew/.linuxbrew/opt/libnghttp3/lib/pkgconfig:\ /home/linuxbrew/.linuxbrew/opt/c-ares/lib/pkgconfig" generate: >- - -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/home/linuxbrew/.linuxbrew/opt/openssl@4 -DUSE_ECH=ON -DUSE_NGTCP2=ON - -DUSE_SSLS_EXPORT=ON -DENABLE_ARES=ON + -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/home/linuxbrew/.linuxbrew/opt/openssl -DUSE_NGTCP2=ON + -DUSE_SSLS_EXPORT=ON -DENABLE_ARES=ON -DUSE_PROXY_HTTP3=ON - name: 'thread-sanitizer' - install_packages: clang-20 libtsan2 + image: ubuntu-26.04 + install_packages: clang-22 libtsan2 install_steps: pytest openssl-tsan - CC: clang-20 - CFLAGS: -fsanitize=thread -g + CC: clang-22 + CFLAGS: -fsanitize=thread LDFLAGS: -fsanitize=thread - generate: -DOPENSSL_ROOT_DIR=/home/runner/openssl -DUSE_ECH=ON -DENABLE_DEBUG=ON + generate: -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl -DUSE_ECH=ON - name: 'memory-sanitizer' - install_packages: clang-20 + image: ubuntu-26.04 + install_packages: clang-22 install_steps: randcurl - CC: clang-20 - CFLAGS: -fsanitize=memory -Wformat -Werror=format-security -Werror=array-bounds -g + CC: clang-22 + CFLAGS: -fsanitize=memory -Wformat -Werror=format-security -Werror=array-bounds LDFLAGS: -fsanitize=memory LIBS: -ldl configure: --without-ssl --without-zlib --without-brotli --without-zstd --without-libpsl --without-nghttp2 --enable-debug @@ -402,32 +433,15 @@ jobs: - name: 'event-based' install_packages: libssh-dev - configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm + configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm --enable-smb tflags: '-n --test-event --min=1420' - name: 'duphandle' - image: ubuntu-24.04-arm + image: ubuntu-26.04-arm install_packages: libssh-dev configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl tflags: '-n --test-duphandle' - - name: 'rustls valgrind 1' - install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls - tflags: '--min=820 1 to 1000' - generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - - - name: 'rustls valgrind 2' - install_packages: libnghttp2-dev libldap-dev valgrind - install_steps: rust rustls - tflags: '--min=830 1001 to 9999' - generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - - - name: 'rustls' - install_packages: libnghttp2-dev libldap-dev - install_steps: rust rustls skiprun pytest - configure: --with-rustls --enable-ech --enable-debug - - name: 'IntelC openssl' install_packages: libssl-dev install_steps: intelc @@ -439,20 +453,20 @@ jobs: # https://ftpmirror.infania.net/slackware/slackware64-current/source/n/curl/curl.SlackBuild configure: --enable-debug --without-ssl --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs # Docker Hub image that `container-job` executes in - container: 'andy5995/slackware-build-essential:15.0' + container: andy5995/slackware-build-essential:15.0@sha256:f4f2242999038a2c2deb4e5727187caaae92502a7daf8353068932621e1ec92f - name: 'Alpine MUSL https-rr' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --enable-threaded-resolver - container: 'alpine:3.20' + container: alpine:3.23@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 # 3.23.4 - name: 'Alpine MUSL https-rr c-ares' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --disable-threaded-resolver - container: 'alpine:3.20' + container: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc # 3.20.10 steps: - name: 'install prereqs' if: ${{ matrix.build.container == null && !contains(matrix.build.name, 'i686') }} - timeout-minutes: 2 + timeout-minutes: 3 env: INSTALL_PACKAGES_BREW: '${{ matrix.build.install_steps_brew }}' INSTALL_PACKAGES: >- @@ -470,13 +484,8 @@ jobs: ${INSTALL_PACKAGES} \ ${MATRIX_INSTALL_PACKAGES} if [ -n "${INSTALL_PACKAGES_BREW}" ]; then - HOMEBREW_NO_AUTO_UPDATE=1 /home/linuxbrew/.linuxbrew/bin/brew install ${INSTALL_PACKAGES_BREW} - fi - # Workaround for ubuntu-24.04-arm images having 0777 for /home/runner, - # which breaks the test sshd server used in pytest. - if [[ "$(uname -m)" = *'aarch64'* ]]; then - ls -l /home - chmod 0755 /home/runner + /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true + /home/linuxbrew/.linuxbrew/bin/brew install ${INSTALL_PACKAGES_BREW} fi - name: 'install prereqs (i686)' @@ -498,7 +507,6 @@ jobs: apk add --no-cache build-base autoconf automake libtool perl openssl-dev \ libssh2-dev zlib-dev brotli-dev zstd-dev libidn2-dev openldap-dev \ krb5-dev libpsl-dev c-ares-dev \ - py3-impacket py3-asn1 py3-six py3-pycryptodomex \ perl-time-hires openssh stunnel sudo git openssl - name: 'install Fil-C' @@ -506,14 +514,14 @@ jobs: run: | cd /home/runner curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-x86_64.tar.xz" --output pkg.bin + --location --proto-redir =https "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-x86_64.tar.xz" --output pkg.bin sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin && mv "filc-${FIL_C_VERSION}-linux-x86_64" filc cd filc ./setup.sh - name: 'cache libressl (c-arm)' if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-libressl-c-arm env: cache-name: cache-libressl-c-arm @@ -522,10 +530,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} - name: 'build libressl (c-arm)' - if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') && steps.cache-libressl-c-arm.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') && !steps.cache-libressl-c-arm.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl -DCURL_ENABLE_NTLM=ON @@ -534,7 +542,7 @@ jobs: - name: 'cache libressl (filc)' if: ${{ contains(matrix.build.install_steps, 'libressl-filc') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-libressl-filc env: cache-name: cache-libressl-filc @@ -543,10 +551,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ env.FIL_C_VERSION }} - name: 'build libressl (filc)' - if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && steps.cache-libressl-filc.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && !steps.cache-libressl-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl \ @@ -556,7 +564,7 @@ jobs: - name: 'cache nghttp2 (filc)' if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-nghttp2-filc env: cache-name: cache-nghttp2-filc @@ -565,10 +573,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.FIL_C_VERSION }} - name: 'build nghttp2 (filc)' - if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && steps.cache-nghttp2-filc.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && !steps.cache-nghttp2-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2_VERSION}/nghttp2-${NGHTTP2_VERSION}.tar.xz" --output pkg.bin + --location --proto-redir =https "https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2_VERSION}/nghttp2-${NGHTTP2_VERSION}.tar.xz" --output pkg.bin sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin cd "nghttp2-${NGHTTP2_VERSION}" cmake -B . -G Ninja -DENABLE_LIB_ONLY=ON -DBUILD_TESTING=OFF -DENABLE_DOC=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/nghttp2 \ @@ -579,7 +587,7 @@ jobs: - name: 'cache wolfssl (all-arm)' if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-wolfssl-all-arm env: cache-name: cache-wolfssl-all-arm @@ -588,20 +596,21 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} - name: 'build wolfssl (all-arm)' # does not support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') && steps.cache-wolfssl-all-arm.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') && !steps.cache-wolfssl-all-arm.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-all --enable-tls13 --enable-harden --enable-all \ + ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-all \ + --enable-tls13 --enable-harden --enable-all \ --disable-benchmark --disable-crypttests --disable-examples make install - name: 'cache wolfssl (opensslextra-intel)' # does support `OPENSSL_COEXIST` if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-wolfssl-opensslextra-intel env: cache-name: cache-wolfssl-opensslextra-intel @@ -610,20 +619,21 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} - name: 'build wolfssl (opensslextra-intel)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') && steps.cache-wolfssl-opensslextra-intel.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') && !steps.cache-wolfssl-opensslextra-intel.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra --enable-tls13 --enable-harden --enable-ech --enable-opensslextra \ + ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ + --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ --disable-benchmark --disable-crypttests --disable-examples make install - name: 'cache wolfssl (opensslextra-arm)' # does support `OPENSSL_COEXIST` if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-wolfssl-opensslextra-arm env: cache-name: cache-wolfssl-opensslextra-arm @@ -632,20 +642,21 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} - name: 'build wolfssl (opensslextra-arm)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') && steps.cache-wolfssl-opensslextra-arm.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') && !steps.cache-wolfssl-opensslextra-arm.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra --enable-tls13 --enable-harden --enable-ech --enable-opensslextra \ + ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ + --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ --disable-benchmark --disable-crypttests --disable-examples make install - name: 'cache mbedtls (latest-intel)' if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-mbedtls-latest-intel env: cache-name: cache-mbedtls-latest-intel @@ -654,10 +665,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - name: 'build mbedtls (latest-intel)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') && steps.cache-mbedtls-latest-intel.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') && !steps.cache-mbedtls-latest-intel.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin + --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin cd "mbedtls-${MBEDTLS_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C @@ -669,7 +680,7 @@ jobs: - name: 'cache mbedtls (latest-arm)' if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-mbedtls-latest-arm env: cache-name: cache-mbedtls-latest-arm @@ -678,10 +689,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - name: 'build mbedtls (latest-arm)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') && steps.cache-mbedtls-latest-arm.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') && !steps.cache-mbedtls-latest-arm.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin + --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin cd "mbedtls-${MBEDTLS_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C @@ -693,7 +704,7 @@ jobs: - name: 'cache mbedtls (prev)' if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-mbedtls-prev env: cache-name: cache-mbedtls-prev @@ -702,10 +713,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_PREV_VERSION }} - name: 'build mbedtls (prev)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && steps.cache-mbedtls-prev.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && !steps.cache-mbedtls-prev.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_PREV_VERSION}/mbedtls-${MBEDTLS_PREV_VERSION}.tar.bz2" --output pkg.bin + --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_PREV_VERSION}/mbedtls-${MBEDTLS_PREV_VERSION}.tar.bz2" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${MBEDTLS_PREV_SHA256}" && tar -xjf pkg.bin && rm -f pkg.bin cd "mbedtls-${MBEDTLS_PREV_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C @@ -717,7 +728,7 @@ jobs: - name: 'cache openldap (static)' if: ${{ contains(matrix.build.install_steps, 'openldap-static') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-openldap-static env: cache-name: cache-openldap-static @@ -726,10 +737,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENLDAP_VERSION }} - name: 'build openldap (static)' - if: ${{ contains(matrix.build.install_steps, 'openldap-static') && steps.cache-openldap-static.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache-openldap-static.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION}.tgz" --output pkg.bin + "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION}.tgz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "openldap-${OPENLDAP_VERSION}" autoreconf -fi @@ -738,7 +749,7 @@ jobs: - name: 'cache openssl (thread sanitizer)' if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-openssl-tsan env: cache-name: cache-openssl-tsan @@ -747,7 +758,7 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} - name: 'build openssl (thread sanitizer)' - if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && steps.cache-openssl-tsan.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && !steps.cache-openssl-tsan.outputs.cache-hit }} run: | git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl @@ -757,7 +768,7 @@ jobs: - name: 'cache awslc' if: ${{ contains(matrix.build.install_steps, 'awslc') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-awslc env: cache-name: cache-awslc @@ -766,10 +777,10 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} - name: 'build awslc' - if: ${{ contains(matrix.build.install_steps, 'awslc') && steps.cache-awslc.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'awslc') && !steps.cache-awslc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/awslc -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF @@ -778,7 +789,7 @@ jobs: - name: 'cache boringssl' if: ${{ contains(matrix.build.install_steps, 'boringssl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-boringssl env: cache-name: cache-boringssl @@ -787,7 +798,7 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} - name: 'build boringssl' - if: ${{ contains(matrix.build.install_steps, 'boringssl') && steps.cache-boringssl.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache-boringssl.outputs.cache-hit }} run: | mkdir boringssl-src cd boringssl-src @@ -800,7 +811,7 @@ jobs: - name: 'cache rustls' if: ${{ contains(matrix.build.install_steps, 'rustls') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-rustls env: cache-name: cache-rustls @@ -809,11 +820,11 @@ jobs: key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} - name: 'fetch rustls deb' - if: ${{ contains(matrix.build.install_steps, 'rustls') && steps.cache-rustls.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'rustls') && !steps.cache-rustls.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/librustls_${RUSTLS_VERSION}_amd64.deb.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/librustls_${RUSTLS_VERSION}_amd64.deb.zip" --output pkg.bin sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin - name: 'build rustls' @@ -823,18 +834,19 @@ jobs: - name: 'install Intel compilers' if: ${{ contains(matrix.build.install_steps, 'intelc') }} - timeout-minutes: 2 + timeout-minutes: 4 run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --compressed https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | \ sudo tee /etc/apt/trusted.gpg.d/intel-sw.asc >/dev/null + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt sudo add-apt-repository 'deb https://apt.repos.intel.com/oneapi all main' sudo apt-get -o Dpkg::Use-Pty=0 install intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic source /opt/intel/oneapi/setvars.sh printenv >> "$GITHUB_ENV" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -857,7 +869,7 @@ jobs: if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake -B bld -G Ninja \ -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ - -DCMAKE_C_COMPILER_TARGET="$(uname -m)-pc-linux-gnu" -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \ ${MATRIX_GENERATE} else @@ -876,6 +888,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'test configs' run: grep -H -v '^#' bld/tests/config bld/tests/http/config.ini || true @@ -908,6 +926,9 @@ jobs: run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --install bld --strip + if [[ "${MATRIX_INSTALL_STEPS}" = *'clean'* ]]; then + cmake --build bld --target curl_uninstall + fi else make -C bld V=1 install fi @@ -946,7 +967,7 @@ jobs: TFLAGS+=' --buildinfo' # only test-ci sets this by default, set it manually for test-torture fi [ -f ~/venv/bin/activate ] && source ~/venv/bin/activate - if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test'* ]]; then + if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test1'* ]]; then locale || true export LC_ALL=C export LC_CTYPE=C @@ -954,6 +975,9 @@ jobs: fi if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --build bld --verbose --target "${TEST_TARGET}" + if [[ "${MATRIX_INSTALL_STEPS}" = *'clean'* ]]; then + cmake --build bld --target clean-certs + fi else make -C bld V=1 "${TEST_TARGET}" fi diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4ce54f39d2c5..e44b276a2a98 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -8,6 +8,7 @@ name: 'macOS' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'macOS' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -36,15 +38,17 @@ permissions: {} # or runtime: # # - 10.7 Lion (2011) - GSS (build-time, deprecated MIT Kerberos shim) -# - 10.9 Mavericks (2013) - LDAP (build-time, deprecated), OCSP (runtime) +# - 10.9 Mavericks (2013) - LDAP (build-time, deprecated), memset_s(), OCSP (runtime) +# - 10.10 Yosemite (2014) - SYS_recvmsg_x, SYS_sendmsg_x (build-time) # - 10.11 El Capitan (2015) - connectx() (runtime) # - 10.12 Sierra (2016) - clock_gettime() (build-time, runtime) -# - 10.14 Mojave (2018) - SecTrustEvaluateWithError() (runtime) +# - 10.14 Mojave (2018) - SecTrustEvaluateWithError() (runtime), GSS Framework env: CURL_CI: github CURL_TEST_MIN: 1750 DO_NOT_TRACK: '1' + HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 4 LDFLAGS: -w # suppress 'object file was built for newer macOS version than being linked' warnings @@ -91,7 +95,7 @@ jobs: # shellcheck disable=SC2181 while [[ $? == 0 ]]; do for i in 1 2 3; do - if brew update && brew install automake libtool; then + if brew install automake libtool; then break 2 else echo "Error: wait to try again: $i" @@ -114,7 +118,7 @@ jobs: - name: 'cache libressl' if: ${{ contains(matrix.build.install_steps, 'libressl') }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-libressl env: cache-name: cache-libressl @@ -123,10 +127,10 @@ jobs: key: iOS-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} - name: 'build libressl' - if: ${{ contains(matrix.build.install_steps, 'libressl') && steps.cache-libressl.outputs.cache-hit != 'true' }} + if: ${{ contains(matrix.build.install_steps, 'libressl') && !steps.cache-libressl.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" cmake -B . -G Ninja \ @@ -139,7 +143,7 @@ jobs: cmake --build . cmake --install . --verbose - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -179,6 +183,16 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + if command -v pccritic >/dev/null 2>&1; then + pccritic --version + pccritic --min-score 98 bld/libcurl.pc + fi + - name: 'build' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -234,7 +248,7 @@ jobs: - name: '!ssl libssh2 AppleIDN' compiler: clang - generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_SSL=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF + generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_SSL=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_DISABLE_HTTPSIG=OFF tflags: '--min=1630' - name: 'OpenSSL libssh c-ares' @@ -244,7 +258,7 @@ jobs: - name: 'OpenSSL libssh' compiler: llvm@18 - install: libssh libnghttp3 + install: libssh generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF - name: '!ssl HTTP-only c-ares' @@ -275,13 +289,15 @@ jobs: install_steps: pytest generate: >- -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_NGTCP2=ON -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_USE_LIBSSH2=OFF - -DCMAKE_C_STANDARD=90 -DCURL_ENABLE_NTLM=ON + -DCMAKE_C_STANDARD=90 -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - - name: 'OpenSSL SecTrust' + - name: 'OpenSSL SecTrust krb5' compiler: clang install: libnghttp3 libngtcp2 install_steps: pytest - configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust --enable-ntlm + configure: >- + --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust --enable-ntlm --enable-proxy-http3 --with-gssapi + --enable-apple-fast-udp - name: 'OpenSSL event-based' compiler: clang @@ -293,13 +309,14 @@ jobs: install: openssl@4 libnghttp3 libngtcp2 gsasl generate: >- -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@4 -DUSE_ECH=ON -DCURL_USE_GSASL=ON -DUSE_APPLE_IDN=ON -DUSE_NGTCP2=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON - -DUSE_APPLE_SECTRUST=ON -DCURL_ENABLE_NTLM=ON + -DUSE_APPLE_SECTRUST=ON -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'MultiSSL AppleIDN clang-tidy +examples' image: macos-26 compiler: clang install: llvm gnutls nettle libressl krb5 mbedtls gsasl rustls-ffi libssh fish install_steps: skiprun + CFLAGS: -Wunused-macros chkprefill: _chkprefill generate: >- -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DCURL_DEFAULT_SSL_BACKEND=openssl @@ -316,6 +333,7 @@ jobs: compiler: clang install: llvm libnghttp3 libngtcp2 openldap krb5 install_steps: skipall + CFLAGS: -Wunused-macros generate: >- -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_NGTCP2=ON -DLDAP_INCLUDE_DIR=/opt/homebrew/opt/openldap/include @@ -324,14 +342,14 @@ jobs: -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5 -DCURL_BROTLI=ON -DCURL_ZSTD=ON -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/opt/homebrew/opt/llvm/bin/clang-tidy - -DCURL_ENABLE_NTLM=ON + -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON -DCURL_ENABLE_APPLE_FAST_UDP=ON - - name: 'LibreSSL openldap krb5 c-ares +examples' + - name: 'LibreSSL openldap AppleGSS c-ares +examples' compiler: clang install: libressl krb5 openldap generate: >- - -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DENABLE_ARES=ON -DCURL_USE_GSSAPI=ON - -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5 + -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/libressl -DENABLE_ARES=ON + -DCURL_USE_GSSAPI=ON -DCURL_GSS_FLAVOR=Apple -DLDAP_INCLUDE_DIR=/opt/homebrew/opt/openldap/include -DLDAP_LIBRARY=/opt/homebrew/opt/openldap/lib/libldap.dylib -DLDAP_LBER_LIBRARY=/opt/homebrew/opt/openldap/lib/liblber.dylib @@ -340,21 +358,22 @@ jobs: compiler: clang install: brotli wolfssl zstd install_steps: pytest - generate: -DCURL_USE_WOLFSSL=ON -DCURL_DISABLE_LDAP=ON -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + generate: -DCURL_USE_WOLFSSL=ON -DCURL_DISABLE_LDAP=ON -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'mbedTLS !ldap brotli zstd MultiSSL AppleIDN' compiler: llvm@18 install: brotli mbedtls zstd - install_steps: codeset-test + install_steps: codeset-test1 generate: -DCURL_USE_MBEDTLS=ON -DCURL_DISABLE_LDAP=ON -DCURL_DEFAULT_SSL_BACKEND=mbedtls -DCURL_USE_OPENSSL=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_NTLM=ON - name: 'GnuTLS !ldap krb5 +examples' compiler: clang install: gnutls nettle krb5 + install_steps: codeset-test2 generate: >- -DENABLE_DEBUG=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=OFF -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5 - -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON -DCURL_ENABLE_NTLM=ON + -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON - name: 'aws-lc +analyzer' compiler: gcc-15 @@ -371,24 +390,24 @@ jobs: - name: 'OpenSSL torture 1' compiler: clang - install: openssl@4 libnghttp3 + install: openssl@4 install_steps: torture generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@4 -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - tflags: '-t --shallow=25 --min=480 1 to 500' + tflags: '-t --shallow=25 --subset=0/3' - name: 'OpenSSL torture 2' compiler: clang - install: openssl@4 libnghttp3 + install: openssl@4 install_steps: torture generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@4 -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - tflags: '-t --shallow=25 --min=730 501 to 1250' + tflags: '-t --shallow=25 --subset=1/3' - name: 'OpenSSL torture 3' compiler: clang - install: openssl@4 libnghttp3 + install: openssl@4 install_steps: torture generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@4 -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - tflags: '-t --shallow=25 --min=628 1251 to 9999' + tflags: '-t --shallow=25 --subset=2/3' steps: - name: 'brew unlink openssl' @@ -413,7 +432,7 @@ jobs: # shellcheck disable=SC2181 while [[ $? == 0 ]]; do for i in 1 2 3; do - if brew update && brew install pkgconf libpsl libssh2 ${INSTALL_PACKAGES} ${MATRIX_INSTALL}; then + if brew install pkgconf libpsl libssh2 ${INSTALL_PACKAGES} ${MATRIX_INSTALL}; then break 2 else echo "Error: wait to try again: $i" @@ -435,7 +454,7 @@ jobs: echo '::group::macros predefined'; "${CC}" -dM -E - < /dev/null | sort || true; echo '::endgroup::' echo '::group::brew packages installed'; ls -l "$(brew --prefix)"/opt; echo '::endgroup::' - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -445,6 +464,7 @@ jobs: - name: 'configure' env: + CFLAGS: '${{ matrix.build.CFLAGS }}' MATRIX_CHKPREFILL: '${{ matrix.build.chkprefill }}' MATRIX_CONFIGURE: '${{ matrix.build.configure }}' MATRIX_GENERATE: '${{ matrix.build.generate }}' @@ -479,7 +499,6 @@ jobs: false fi else - export CFLAGS if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then options+=" --target=$(uname -m)-apple-darwin" fi @@ -504,6 +523,16 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + if command -v pccritic >/dev/null 2>&1; then + pccritic --version + pccritic --min-score 96 bld/libcurl.pc + fi + - name: 'test configs' run: grep -H -v '^#' bld/tests/config bld/tests/http/config.ini || true @@ -558,11 +587,20 @@ jobs: TFLAGS+=' --buildinfo' # only test-ci sets this by default, set it manually for test-torture fi source ~/venv/bin/activate - if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test'* ]]; then + if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test1'* ]]; then locale || true - export LC_ALL=C + unset LANG + unset LC_ALL + unset LC_COLLATE + unset LC_MESSAGES + unset LC_MONETARY + unset LC_TIME export LC_CTYPE=C export LC_NUMERIC=fr_FR.UTF-8 + elif [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test2'* ]]; then + locale || true + unset LC_ALL + export LC_TIME=fr_FR fi rm -f ~/.curlrc if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -646,7 +684,7 @@ jobs: # Reduce build combinations, by dropping less interesting ones - { image: macos-26, compiler: 'gcc-13' } - { compiler: 'gcc-14' , build: cmake } - # Reduce autotools to just one job that is also build with cmake + # Reduce autotools to only one job that is also build with cmake - { compiler: 'gcc-13' , build: autotools } - { compiler: 'gcc-14' , build: autotools } - { compiler: 'gcc-15' , build: autotools } @@ -662,7 +700,7 @@ jobs: # shellcheck disable=SC2181 while [[ $? == 0 ]]; do for i in 1 2 3; do - if brew update && brew install automake libtool; then + if brew install automake libtool; then break 2 else echo "Error: wait to try again: $i" @@ -685,7 +723,7 @@ jobs: echo '::group::macros predefined'; "${CC}" -dM -E - < /dev/null | sort || true; echo '::endgroup::' echo '::group::brew packages preinstalled'; ls -l "$(brew --prefix)"/opt; echo '::endgroup::' - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -725,6 +763,7 @@ jobs: -DUSE_NGHTTP2=OFF -DUSE_LIBIDN2=OFF \ -DCURL_USE_LIBPSL=OFF -DCURL_USE_LIBSSH2=OFF \ -DUSE_APPLE_IDN=ON -DUSE_APPLE_SECTRUST=ON \ + -DCURL_USE_GSSAPI=ON -DCURL_GSS_FLAVOR=Apple \ ${options} else export CFLAGS @@ -744,6 +783,7 @@ jobs: --without-nghttp2 --without-libidn2 \ --without-libpsl \ --with-apple-idn --with-apple-sectrust \ + --enable-gssapi-apple \ ${options} fi @@ -756,6 +796,16 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + if command -v pccritic >/dev/null 2>&1; then + pccritic --version + pccritic --min-score 94 bld/libcurl.pc + fi + - name: 'build / ${{ matrix.build }}' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index c8a0f2156bea..dfe0f16acbb0 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -8,6 +8,7 @@ name: 'non-native' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'non-native' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -37,204 +39,337 @@ env: DO_NOT_TRACK: '1' jobs: - netbsd: - name: 'NetBSD, CM clang openssl ${{ matrix.arch }}' + cross: + name: "${{ matrix.os }} ${{ matrix.version }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.cc }} ${{ matrix.desc }} ${{ matrix.arch }}" runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 + defaults: + run: + shell: cpa.sh {0} # zizmor: ignore[misfeature] + env: + CC: '${{ matrix.cc }}' + MAKEFLAGS: -j 3 + MATRIX_ARCH: '${{ matrix.arch }}' + MATRIX_BUILD: '${{ matrix.build }}' + MATRIX_INSTALL: '${{ matrix.install }}' + MATRIX_OPTIONS: '${{ matrix.options }}' + MATRIX_OS: '${{ matrix.os }}' + MATRIX_VERSION: '${{ matrix.version }}' strategy: matrix: - arch: ['x86_64'] + include: + # https://github.com/DragonFlyBSD/DPorts + # { os: 'dragonflybsd', version: '6.4.2', build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skiprun', + # install: 'autoconf automake libtool openldap26-client libidn2', + # options: '--with-openssl --enable-ldap --enable-ldaps --with-libidn2' } + + # { os: 'dragonflybsd', version: '6.4.2', build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skipall', + # install: 'cmake ninja' } + + # https://ports.freebsd.org/ + - { os: 'freebsd' , version: '15.1', build: 'autotools', arch: 'x86_64' , cc: 'clang', desc: 'openssl', + install: 'autoconf automake libtool krb5-devel openldap26-client libidn2 stunnel', + options: '--with-openssl --with-gssapi --enable-ldap --enable-ldaps --with-libidn2' } + + - { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'x86_64' , cc: 'clang', desc: 'openssl !unity !examples', tflags: 'skiprun', + install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2', + options: '-DCURL_USE_GSSAPI=ON -DCMAKE_UNITY_BUILD=OFF' } + + # { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'riscv64', cc: 'clang', desc: 'openssl !examples', tflags: 'HTTP --min=0 --subset=0/10 -R --seed', + # install: 'cmake-core ninja perl5', + # options: '-D_CURL_PREFILL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_DISABLE_TYPECHECK=ON' } + + - { os: 'freebsd' , version: '14.3', build: 'autotools', arch: 'arm64' , cc: 'clang', desc: 'openssl !examples', tflags: 'skipall', + install: 'autoconf automake libtool krb5-devel openldap26-client libidn2 stunnel', + options: '--with-openssl --with-gssapi --enable-ldap --enable-ldaps --with-libidn2 --disable-typecheck' } + + - { os: 'freebsd' , version: '14.3', build: 'cmake' , arch: 'arm64' , cc: 'clang', desc: 'openssl', tflags: 'skiprun', + install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2 stunnel', + options: '-DCURL_USE_GSSAPI=ON -DCURL_DISABLE_TYPECHECK=ON' } + + # https://app.midnightbsd.org/ + # https://man.midnightbsd.org/cgi-bin/man.cgi/mport + # { os: 'midnightbsd' , version: '4.0.4', build: 'autotools', arch: 'x86_64' , cc: 'clang', desc: 'gnutls !examples', tflags: 'skipall', + # install: 'autoconf autoconf-archive automake libtool gnutls', + # options: '--with-gnutls' } + + - { os: 'midnightbsd' , version: '4.0.4', build: 'cmake' , arch: 'x86_64' , cc: 'clang', desc: 'gnutls', tflags: 'skiprun', + install: 'cmake-core ninja perl5 gnutls openldap26-client libidn2', + options: '-DCURL_USE_GNUTLS=ON' } + + # https://pkgsrc.se/ + - { os: 'netbsd' , version: '10.1' , build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl !examples', tflags: 'skipall', + install: 'autoconf automake libtool mit-krb5', + options: '--with-openssl --with-gssapi' } + + - { os: 'netbsd' , version: '11.0' , build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', + install: 'cmake ninja-build mit-krb5 openldap-client libidn2', + options: '-DCURL_USE_GSSAPI=ON' } + + # https://openbsd.app/ + # https://www.openbsd.org/faq/faq15.html + # https://github.com/OpenMPT/openmpt/blob/master/.github/workflows/OpenBSD-Autotools.yml + - { os: 'openbsd' , version: '7.9' , build: 'autotools', arch: 'x86_64' , cc: 'clang', desc: 'libressl !examples', tflags: 'skipall', + install: 'autoconf-2.72p0 automake-1.18.1 libtool', # NOTE: also sync these versions with the autoreconf step! + options: '--with-openssl' } + + # Skip test 2707 'ws: Peculiar frame sizes' on suspicion of hangs + - { os: 'openbsd' , version: '7.9' , build: 'cmake' , arch: 'x86_64' , cc: 'clang', desc: 'libressl', tflags: '!2707', + install: 'cmake ninja openldap-client-- libidn2' } + + fail-fast: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: 'cmake' - uses: cross-platform-actions/action@233156312992f3f169d8d0c633c21d12a5d30455 # v1.0.0 - env: - MATRIX_ARCH: '${{ matrix.arch }}' + + - name: 'setup VM' + uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0 with: - environment_variables: CURL_CI CURL_TEST_MIN DO_NOT_TRACK MATRIX_ARCH - operating_system: 'netbsd' - version: '10.1' - architecture: ${{ matrix.arch }} - run: | - # https://pkgsrc.se/ - time sudo pkgin -y install cmake ninja-build pkg-config perl brotli mit-krb5 openldap-client libssh2 libidn2 libpsl nghttp2 py311-impacket - time cmake -B bld -G Ninja \ - -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ - -DCMAKE_UNITY_BUILD=ON \ - -DCURL_WERROR=ON \ - -DENABLE_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug \ - -DCURL_USE_OPENSSL=ON \ - -DCURL_USE_GSSAPI=ON \ - -DCURL_ENABLE_NTLM=ON \ - || { cat bld/CMakeFiles/CMake*.yaml; false; } - echo '::group::curl_config.h (raw)'; cat bld/lib/curl_config.h || true; echo '::endgroup::' - echo '::group::curl_config.h'; grep -F '#define' bld/lib/curl_config.h | sort || true; echo '::endgroup::' - time cmake --build bld - time cmake --install bld - bld/src/curl --disable --version - if [ "${MATRIX_ARCH}" = 'x86_64' ]; then # Slow on emulated CPU - time cmake --build bld --target testdeps - export TFLAGS='-j8' - time cmake --build bld --target test-ci + environment_variables: 'CC CURL_CI CURL_TEST_MIN DO_NOT_TRACK MAKEFLAGS MATRIX_ARCH MATRIX_BUILD MATRIX_INSTALL MATRIX_OPTIONS MATRIX_OS MATRIX_VERSION TFLAGS' + operating_system: '${{ matrix.os }}' + version: '${{ matrix.version }}' + architecture: '${{ matrix.arch }}' + + - name: 'install prereqs' + run: | + if [ "${MATRIX_OS}" = 'dragonflybsd' ]; then + sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} + elif [ "${MATRIX_OS}" = 'freebsd' ]; then + sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} + elif [ "${MATRIX_OS}" = 'midnightbsd' ]; then + if [ "${MATRIX_BUILD}" = 'autotools' ]; then + sudo mport index | grep -v -E 'Downloading.+%' + sudo mport upgrade | grep -v -E '(Downloading.+%|^/usr/local)' + fi + sudo mport install pkgconf brotli libnghttp2 ${MATRIX_INSTALL} | grep -v -E '(Downloading.+%|^/usr/local)' || true + elif [ "${MATRIX_OS}" = 'netbsd' ]; then + sudo pkgin -y install pkg-config perl brotli libssh2 libpsl nghttp2 ${MATRIX_INSTALL} + elif [ "${MATRIX_OS}" = 'openbsd' ]; then + sudo pkg_add -I brotli libssh2 libpsl nghttp2 ${MATRIX_INSTALL} + if [ "${MATRIX_BUILD}" = 'autotools' ]; then + sudo pkg_delete -I curl # to avoid autotools build linking against system libcurl fi - echo '::group::build examples' - time cmake --build bld --target curl-examples-build - echo '::endgroup::' + fi - openbsd: - name: 'OpenBSD, CM clang libressl ${{ matrix.arch }}' - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - matrix: - arch: ['x86_64'] - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: 'cmake' - uses: cross-platform-actions/action@233156312992f3f169d8d0c633c21d12a5d30455 # v1.0.0 - env: - MATRIX_ARCH: '${{ matrix.arch }}' - with: - environment_variables: CURL_CI CURL_TEST_MIN DO_NOT_TRACK MATRIX_ARCH - operating_system: 'openbsd' - version: '7.7' - architecture: ${{ matrix.arch }} - run: | - # https://openbsd.app/ - # https://www.openbsd.org/faq/faq15.html - time sudo pkg_add cmake ninja brotli openldap-client-- libssh2 libidn2 libpsl nghttp2 py3-six py3-impacket - time cmake -B bld -G Ninja \ - -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ - -DCMAKE_UNITY_BUILD=ON \ - -DCURL_WERROR=ON \ - -DENABLE_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug \ - -DCURL_USE_OPENSSL=ON \ - -DCURL_ENABLE_NTLM=ON \ - || { cat bld/CMakeFiles/CMake*.yaml; false; } - echo '::group::curl_config.h (raw)'; cat bld/lib/curl_config.h || true; echo '::endgroup::' - echo '::group::curl_config.h'; grep -F '#define' bld/lib/curl_config.h | sort || true; echo '::endgroup::' - time cmake --build bld - time cmake --install bld - bld/src/curl --disable --version - if [ "${MATRIX_ARCH}" = 'x86_64' ]; then # Slow on emulated CPU - time cmake --build bld --target testdeps - export TFLAGS='-j8 !2707' # Skip 2707 'ws: Peculiar frame sizes' on suspicion of hangs - time cmake --build bld --target test-ci + - name: 'autoreconf' + if: ${{ matrix.build == 'autotools' }} + run: | + if [ "${MATRIX_OS}" = 'openbsd' ]; then + if [ "${MATRIX_VERSION}" = '7.9' ]; then + export AUTOCONF_VERSION=2.72 + export AUTOMAKE_VERSION=1.18 fi - echo '::group::build examples' - time cmake --build bld --target curl-examples-build - echo '::endgroup::' + fi + autoreconf -fi + + - name: 'configure' + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake -B bld -G Ninja -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ + -DCMAKE_C_COMPILER="${CC}" \ + -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON -DENABLE_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug \ + -DCURL_ENABLE_NTLM=ON ${MATRIX_OPTIONS} + else + if [ "${MATRIX_ARCH}" != 'x86_64' ]; then + options='--disable-manual --disable-docs' # Slow with autotools, skip on emulated CPU + fi + mkdir bld && cd bld + ../configure --prefix="$HOME"/curl-install --enable-unity --enable-debug --enable-warnings --enable-werror --disable-static \ + --disable-dependency-tracking --enable-option-checking=fatal \ + --with-brotli --with-libssh2 --with-nghttp2 \ + ${options} ${MATRIX_OPTIONS} + fi + + - name: 'configure log' + if: ${{ !cancelled() }} + run: cat bld/config.log bld/CMakeFiles/CMakeConfigureLog.yaml 2>/dev/null || true - freebsd: - name: "FreeBSD, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }} openssl${{ matrix.desc }} ${{ matrix.arch }}" + - name: 'curl_config.h' + run: | + echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' + grep -F '#define' bld/lib/curl_config.h | sort || true + + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + if command -v pccritic >/dev/null 2>&1; then + pccritic --version + pccritic --min-score 98 bld/libcurl.pc + fi + + - name: 'build' + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld + else + make -C bld V=1 + fi + + - name: 'curl -V' + run: | + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + bld/src/curl --disable --version + + - name: 'curl install' + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --install bld + else + make -C bld install + fi + + - name: 'build tests' + if: ${{ matrix.tflags != 'skipall' }} # Slow on emulated CPU + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld --verbose --target testdeps + else + make -C bld V=1 -C tests + fi + + - name: 'run tests' + if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} # Slow on emulated CPU + env: + TFLAGS: '${{ matrix.tflags }}' + run: | + TFLAGS="-j8 ${TFLAGS}" + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld --verbose --target test-ci + else + make -C bld V=1 test-ci + fi + + - name: 'build examples' + if: ${{ !contains(matrix.desc, '!examples') }} + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld --target curl-examples-build + else + make -C bld examples + fi + + amiga: + name: "AmigaOS, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} gcc AmiSSL m68k" runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 5 + env: + MAKEFLAGS: -j 5 + MATRIX_BUILD: '${{ matrix.build }}' + AMISSL_VERSION: '5.27' + AMISSL_SHA256: 5003bef8c5930354d16b0ce7196d71b2811891c42fad38a9238c5ce4098ad42a + TOOLCHAIN_VERSION: 6.5.0 + TOOLCHAIN_SHA256: 381e227c9ef552f073771d6f851cfdf873b574f3cf5db7c1c0107ea5d7146edc strategy: matrix: - include: - - { build: 'autotools', arch: 'x86_64', compiler: 'clang' } - - { build: 'cmake' , arch: 'x86_64', compiler: 'clang', options: '-DCMAKE_UNITY_BUILD=OFF', desc: ' !unity !runtests !examples' } - - { build: 'autotools', arch: 'arm64' , compiler: 'clang', desc: ' !examples' } - - { build: 'cmake' , arch: 'arm64' , compiler: 'clang' } + build: [autotools, cmake] fail-fast: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 'cache compiler' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: cache-compiler with: - persist-credentials: false - - name: '${{ matrix.build }}' - uses: cross-platform-actions/action@233156312992f3f169d8d0c633c21d12a5d30455 # v1.0.0 - env: - CC: '${{ matrix.compiler }}' - MATRIX_ARCH: '${{ matrix.arch }}' - MATRIX_BUILD: '${{ matrix.build }}' - MATRIX_DESC: '${{ matrix.desc }}' - MATRIX_OPTIONS: '${{ matrix.options }}' + path: ~/opt/amiga + key: ${{ runner.os }}-amigaos-${{ env.TOOLCHAIN_VERSION }}-${{ env.AMISSL_VERSION }}-amd64 + + - name: 'install compiler' + if: ${{ !steps.cache-compiler.outputs.cache-hit }} + run: | + cd ~ + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 3 --retry-connrefused \ + https://franke.ms/download/amiga-gcc.tgz --output pkg.bin + sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${TOOLCHAIN_SHA256}" && tar -xf pkg.bin && rm -f pkg.bin + cd opt/amiga + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ + --location --proto-redir =https "https://github.com/jens-maus/amissl/releases/download/${AMISSL_VERSION}/AmiSSL-${AMISSL_VERSION}-SDK.lha" --output pkg.bin + sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${AMISSL_SHA256}" && 7z x -bd -y pkg.bin && rm -f pkg.bin + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - environment_variables: CC CURL_CI CURL_TEST_MIN DO_NOT_TRACK MATRIX_ARCH MATRIX_BUILD MATRIX_DESC MATRIX_OPTIONS - operating_system: 'freebsd' - version: '14.3' - architecture: ${{ matrix.arch }} - run: | - export CURL_CI=github - - # https://ports.freebsd.org/ - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time sudo pkg install -y cmake-core ninja perl5 \ - pkgconf brotli krb5-devel openldap26-client libidn2 libnghttp2 stunnel py311-impacket - else - time sudo pkg install -y autoconf automake libtool \ - pkgconf brotli krb5-devel openldap26-client libidn2 libnghttp2 stunnel py311-impacket - export MAKEFLAGS=-j3 - fi + persist-credentials: false - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time cmake -B bld -G Ninja \ - -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ - -DCMAKE_C_COMPILER="${CC}" \ - -DCMAKE_UNITY_BUILD=ON \ - -DCURL_WERROR=ON \ - -DENABLE_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug \ - -DCURL_USE_OPENSSL=ON \ - -DCURL_USE_GSSAPI=ON \ - ${MATRIX_OPTIONS} \ - || { cat bld/CMakeFiles/CMake*.yaml; false; } - else - time autoreconf -fi - if [ "${MATRIX_ARCH}" != 'x86_64' ]; then - options='--disable-manual --disable-docs' # Slow with autotools, skip on emulated CPU - fi - mkdir bld && cd bld - time ../configure --prefix="$HOME"/curl-install --enable-unity --enable-debug --enable-warnings --enable-werror --disable-static \ - --disable-dependency-tracking --enable-option-checking=fatal \ - --with-openssl \ - --with-brotli --enable-ldap --enable-ldaps --with-libidn2 --with-libssh2 --with-nghttp2 --with-gssapi \ - ${options} \ - ${MATRIX_OPTIONS} \ - || { tail -n 1000 config.log; false; } - cd .. - fi + - name: 'configure' + run: | + ln -s ~/opt/amiga /opt + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake -B bld -G Ninja \ + -DAMIGA=1 \ + -DCMAKE_SYSTEM_NAME=Generic \ + -DCMAKE_SYSTEM_PROCESSOR=m68k \ + -DCMAKE_C_COMPILER_TARGET=m68k-unknown-amigaos \ + -DCMAKE_C_COMPILER=/opt/amiga/bin/m68k-amigaos-gcc \ + -DCMAKE_C_FLAGS='-O0 -msoft-float -mcrt=clib2' \ + -DCMAKE_UNITY_BUILD=ON \ + -DCURL_WERROR=ON \ + -DCURL_USE_LIBPSL=OFF \ + -DAMISSL_INCLUDE_DIR=/opt/amiga/AmiSSL/Developer/include \ + -DAMISSL_STUBS_LIBRARY=/opt/amiga/AmiSSL/Developer/lib/AmigaOS3/libamisslstubs.a \ + -DAMISSL_AUTO_LIBRARY=/opt/amiga/AmiSSL/Developer/lib/AmigaOS3/libamisslauto.a + else + autoreconf -fi + mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror \ + --disable-dependency-tracking --enable-option-checking=fatal \ + CC=/opt/amiga/bin/m68k-amigaos-gcc \ + AR=/opt/amiga/bin/m68k-amigaos-ar \ + RANLIB=/opt/amiga/bin/m68k-amigaos-ranlib \ + --host=m68k-amigaos \ + --disable-shared \ + --without-libpsl \ + --with-amissl \ + LDFLAGS=-L/opt/amiga/AmiSSL/Developer/lib/AmigaOS3 \ + CPPFLAGS=-I/opt/amiga/AmiSSL/Developer/include \ + CFLAGS='-O0 -msoft-float -mcrt=clib2' \ + LIBS='-lnet -lm -latomic' + fi - echo '::group::curl_config.h (raw)'; cat bld/lib/curl_config.h || true; echo '::endgroup::' - echo '::group::curl_config.h'; grep -F '#define' bld/lib/curl_config.h | sort || true; echo '::endgroup::' + - name: 'configure log' + if: ${{ !cancelled() }} + run: cat bld/config.log bld/CMakeFiles/CMake*.yaml 2>/dev/null || true - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time cmake --build bld - time cmake --install bld - else - time make -C bld install - fi + - name: 'curl_config.h' + run: | + echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' + grep -F '#define' bld/lib/curl_config.h | sort || true - bld/src/curl --disable --version - - if [ "${MATRIX_ARCH}" = 'x86_64' ]; then # Slow on emulated CPU - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time cmake --build bld --target testdeps - else - time make -C bld -C tests - fi - if [ "${MATRIX_DESC#*!runtests*}" = "${MATRIX_DESC}" ]; then - export TFLAGS='-j8' - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time cmake --build bld --verbose --target test-ci - else - time make -C bld V=1 test-ci - fi - fi - fi + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done - if [ "${MATRIX_DESC#*!examples*}" = "${MATRIX_DESC}" ]; then - echo '::group::build examples' - if [ "${MATRIX_BUILD}" = 'cmake' ]; then - time cmake --build bld --target curl-examples-build - else - time make -C bld examples - fi - echo '::endgroup::' - fi + - name: 'build' + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld + else + make -C bld + fi + + - name: 'curl info' + run: | + find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 file -- + find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + + - name: 'build tests' + if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld --target testdeps + else + make -C bld -C tests + fi + + - name: 'build examples' + if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time + run: | + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + cmake --build bld --target curl-examples-build + else + make -C bld examples + fi android: name: "Android ${{ matrix.platform }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.name }} arm64" @@ -261,7 +396,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -302,6 +437,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'build' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -340,7 +481,7 @@ jobs: MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' # renovate: datasource=github-releases depName=andrewwutw/build-djgpp versioning=semver-coerced registryUrl=https://github.com - TOOLCHAIN_VERSION: 3.4 + TOOLCHAIN_VERSION: '3.4' TOOLCHAIN_SHA256: 8464f17017d6ab1b2bb2df4ed82357b5bf692e6e2b7fee37e315638f3d505f00 strategy: matrix: @@ -350,22 +491,24 @@ jobs: - name: 'install packages' timeout-minutes: 2 run: | + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + sudo apt-get -o Dpkg::Use-Pty=0 update sudo apt-get -o Dpkg::Use-Pty=0 install libfl2 - name: 'cache compiler (djgpp)' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-compiler with: path: ~/djgpp key: ${{ runner.os }}-djgpp-${{ env.TOOLCHAIN_VERSION }}-amd64 - name: 'install compiler (djgpp)' - if: ${{ steps.cache-compiler.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-compiler.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 3 --retry-connrefused \ - --location "https://github.com/andrewwutw/build-djgpp/releases/download/v${TOOLCHAIN_VERSION}/djgpp-linux64-gcc1220.tar.bz2" --output pkg.bin + --location --proto-redir =https "https://github.com/andrewwutw/build-djgpp/releases/download/v${TOOLCHAIN_VERSION}/djgpp-linux64-gcc1220.tar.bz2" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${TOOLCHAIN_SHA256}" && tar -xjf pkg.bin && rm -f pkg.bin cd djgpp curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ @@ -375,7 +518,7 @@ jobs: https://www.delorie.com/pub/djgpp/current/v2tk/zlb13b.zip --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF f3d2fa8129e7591c7e79074306d8ab91a70ec172cc01baedeae74992285dd3a3 && unzip -q pkg.bin && rm -f pkg.bin - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -415,6 +558,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'build' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6b4366a7f916..73ec97abc1b2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -8,6 +8,7 @@ name: 'Windows' push: branches: - master + - 'curl-*' - '*/ci' paths-ignore: - '**/*.md' @@ -18,6 +19,7 @@ name: 'Windows' pull_request: branches: - master + - 'curl-*' paths-ignore: - '**/*.md' - '.circleci/**' @@ -39,8 +41,8 @@ env: OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview OPENSSH_WINDOWS_SHA256_ARM64: 698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42 OPENSSH_WINDOWS_SHA256_WIN64: 23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5 - STUNNEL_VERSION: 5.78 - STUNNEL_SHA256: 32a88dcc5654f955266109be8bf10fd7d56fa4e125cab821ee508230570e46c5 + STUNNEL_VERSION: '5.80' + STUNNEL_SHA256: 25947bd268e2e670e1c7f915cefd54585aaa440d09eb20c6109cdbeaf3140bc4 jobs: build-cache: @@ -51,7 +53,7 @@ jobs: image: [windows-11-arm, windows-2022] # Cannot share cache between arm and intel: https://github.com/actions/cache/issues/1622 steps: - name: 'cache test prereqs (stunnel)' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-stunnel with: path: C:\my-stunnel @@ -59,7 +61,7 @@ jobs: lookup-only: true - name: 'install test prereqs (stunnel)' - if: ${{ steps.cache-stunnel.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-stunnel.outputs.cache-hit }} timeout-minutes: 2 shell: bash run: | @@ -93,10 +95,9 @@ jobs: build: 'cmake', platform: 'x86_64', tflags: '', config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', install: 'libssl-devel libssh2-devel' } - fail-fast: false steps: - - uses: cygwin/cygwin-install-action@711d29f3da23c9f4a1798e369a6f01198c13b11a # v6.1 + - uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.0.2 with: platform: ${{ matrix.platform }} work-vol: 'D:' @@ -106,7 +107,7 @@ jobs: # https://cygwin.com/cgi-bin2/package-grep.cgi packages: >- ${{ matrix.build == 'autotools' && 'autoconf automake libtool make' || 'cmake ninja' }} - gcc-core binutils perl + gcc-core binutils perl pkgconf openssh libpsl-devel zlib-devel @@ -115,7 +116,7 @@ jobs: libnghttp2-devel ${{ matrix.install }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -157,6 +158,14 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + pccritic --version + pccritic --min-score 100 bld/libcurl.pc + - name: 'build' timeout-minutes: 10 run: | @@ -192,7 +201,7 @@ jobs: - name: 'cache test prereqs (stunnel)' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-stunnel with: path: C:\my-stunnel @@ -255,36 +264,36 @@ jobs: config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-proxy --enable-ntlm', install: 'openssl-devel libssh2-devel' } - { name: 'default', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: 'skiprun' , + build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-ntlm', install: 'openssl-devel libssh2-devel' } - { name: 'default', - build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: '' , + build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: '', config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', install: 'openssl-devel libssh2-devel' } - { name: 'default R', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '' , + build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '', config: '--with-openssl --enable-ntlm', install: 'openssl-devel libssh2-devel' } # MinGW - { name: 'default', - build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun' , + build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib', install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2' } - - { name: 'c-ares U', - build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '' , - config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2' } + - { name: 'wolfssl c-ares U', + build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '', + config: '--enable-debug --with-wolfssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', + install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'schannel c-ares U', type: 'Debug', build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '--min=1720', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2' } # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --min=820 1 to 950', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } - { name: 'schannel U torture 2', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --min=820 951 to 9999', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=885', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } # WARNING: libssh uses hard-coded world-writable paths (C:ProgramData/, /etc/..., ~/.ssh/) @@ -293,36 +302,40 @@ jobs: # Holds true after CVE-2025-14821 mitigations in 0.12.0. # https://github.com/curl/curl-for-win/blob/471a065705a16c61a343b15d3e4ef195e2df2f9e/libssh.sh#L6-L94 - { name: 'gnutls libssh', type: 'Debug', openssh: 'OpenSSH-Windows', - build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '' , + build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_GNUTLS=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DCURL_ENABLE_NTLM=ON', install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh unzip' } - { name: 'schannel R', type: 'Release', image: 'windows-11-arm', - build: 'cmake' , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun' , + build: 'cmake' , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun', config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON', install: 'mingw-w64-clang-aarch64-libssh2' } - - { name: 'openssl', type: 'Release', chkprefill: '_chkprefill', - build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun' , - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON', + - { name: 'openssl', type: 'Debug', chkprefill: '_chkprefill', + build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DUSE_PROXY_HTTP3=ON', install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh2' } - - { name: 'schannel', type: 'Release', test: 'uwp', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun' , - config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON', + - { name: 'openssl', type: 'Release', test: 'uwp', + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', + config: '-DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON', install: 'mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-libssh2' } # { name: 'schannel', type: 'Release', test: 'uwp', - # build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun' , + # build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', # config: '--without-debug --with-schannel --disable-static', # install: 'mingw-w64-ucrt-x86_64-libssh2' } - { name: 'schannel dev debug', type: 'Debug', cppflags: '-DCURL_SCHANNEL_DEV_DEBUG', image: 'windows-2025', - build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun' , - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCMAKE_VERBOSE_MAKEFILE=ON', - install: 'mingw-w64-x86_64-libssh2' } + build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCURL_DISABLE_HTTPSIG=OFF', + install: 'mingw-w64-x86_64-libssh2 mingw-w64-x86_64-wolfssl' } - { name: 'MultiSSL R', type: 'Release', - build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun' , + build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun', config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=ON', install: 'mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl' } fail-fast: false steps: - - uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0 + - name: 'preinstalled tools' + shell: bash + run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' + + - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 if: ${{ matrix.sys == 'msys' }} with: msystem: ${{ matrix.sys }} @@ -338,18 +351,19 @@ jobs: libpsl-devel ${{ matrix.install }} - - uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0 + - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 if: ${{ matrix.sys != 'msys' }} with: msystem: ${{ matrix.sys }} + update: ${{ contains(matrix.install, 'wolfssl') && 'true' || 'false' }} install: >- mingw-w64-${{ matrix.env }}-cc mingw-w64-${{ matrix.env }}-${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || '' }} - mingw-w64-${{ matrix.env }}-diffutils + ${{ matrix.sys != 'mingw32' && format('mingw-w64-{0}-diffutils', matrix.env) || '' }} mingw-w64-${{ matrix.env }}-libpsl ${{ matrix.install }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -368,6 +382,7 @@ jobs: MATRIX_TYPE: '${{ matrix.type }}' TFLAGS: '${{ matrix.tflags }}' run: | + CPPFLAGS+=' -DCURL_DEBUG_NO_WIN32_WND' if [ "${MATRIX_TEST}" = 'uwp' ]; then CPPFLAGS+=' -DWINSTORECOMPAT -DWINAPI_FAMILY=WINAPI_FAMILY_APP -D_WIN32_WINNT=0x0a00' if [[ "${MATRIX_ENV}" != 'clang'* ]]; then @@ -416,7 +431,17 @@ jobs: run: | echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true - cat bld/cmake_install.cmake || true + + - name: 'libcurl.pc, curl-config, cmake_install.cmake' + run: | + for f in libcurl.pc curl-config cmake_install.cmake; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + if command -v pccritic >/dev/null 2>&1; then + sed -i.bak 's/\x0d//g' bld/libcurl.pc # to suppress pccritic warning: style/PC061 + pccritic --version + pccritic --min-score 100 bld/libcurl.pc + fi - name: 'build' timeout-minutes: 10 @@ -463,7 +488,7 @@ jobs: - name: 'cache test prereqs (stunnel)' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-stunnel with: path: C:\my-stunnel @@ -486,11 +511,11 @@ jobs: cd /c # no D: drive on windows-11-arm runners if [[ "${MATRIX_IMAGE}" = *'-arm'* ]]; then curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-ARM64.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-ARM64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_ARM64}" && unzip pkg.bin && rm -f pkg.bin else curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-Win64.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-Win64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_WIN64}" && unzip pkg.bin && rm -f pkg.bin fi fi @@ -512,21 +537,15 @@ jobs: unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 fi - if [[ "${TFLAGS}" = *'-t'* ]]; then - TFLAGS+=' !2300' # Leaks memory and file handle via tool_doswin.c / win32_stdin_read_thread() - export CURL_TEST_NO_TASKKILL=1 # experiment to see if it reduces flaky failures - fi if [[ "${MATRIX_INSTALL} " = *'-libssh '* && \ "${MATRIX_ENV}" = *'x86_64'* ]]; then export CURL_TEST_SSH_DISABLE_KEX=mlkem768x25519-sha256 # broken with libssh 0.12.0 Windows x64 fi fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows - TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004' # SCP + TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004 ~3022' # SCP if [[ "${MATRIX_INSTALL} " = *'libssh '* ]]; then TFLAGS+=' ~614' # 'SFTP pre-quote chmod' SFTP, pre-quote, directory - else - TFLAGS+=' ~3022' # 'SCP correct sha256 host key' SCP, server sha256 key check fi fi if [ "${MATRIX_OPENSSH}" = 'OpenSSH-Windows' ]; then @@ -577,15 +596,16 @@ jobs: strategy: matrix: include: - - name: 'schannel +analyzer' # mingw-w64 12.0 + - name: 'schannel +analyzer' # mingw-w64 14.0 sys: 'mingw64' dir: 'w64devkit' env: 'x86_64' - ver: '15.1.0' - url: 'https://github.com/skeeto/w64devkit/releases/download/v2.2.0/w64devkit-x64-2.2.0.7z.exe' - SHA256: e02de30b97196329662007d64bc4509fbd7f5e14339d344075c7f1223dead4a2 - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON' - type: 'Release' + ver: '16.1.0' + url: 'https://github.com/skeeto/w64devkit/releases/download/v2.8.0/w64devkit-x64-2.8.0.7z.exe' + SHA256: 6252bf34fe2231a55ac7f03d482b36d2c7c58697990551bba508102cfb3f342e + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBSSH=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' + install: perl mingw-w64-x86_64-libssh + type: 'Debug' - name: 'schannel' # mingw-w64 10.0 sys: 'mingw64' dir: 'mingw64' @@ -593,7 +613,7 @@ jobs: ver: '9.5.0' url: 'https://github.com/brechtsanders/winlibs_mingw/releases/download/9.5.0-10.0.0-msvcrt-r1/winlibs-x86_64-posix-seh-gcc-9.5.0-mingw-w64msvcrt-10.0.0-r1.7z' SHA256: 41637132ea7dc36a7f86a1961eaa334c380b5a3423d36aecb481cabcd006e3fe - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCURL_DISABLE_VERBOSE_STRINGS=ON' + config: '-DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCURL_DISABLE_VERBOSE_STRINGS=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' type: 'Release' tflags: 'skiprun' - name: 'schannel mbedtls U' # mingw-w64 6.0 @@ -605,7 +625,7 @@ jobs: SHA256: 9dc08c9c2bdd5d8173f87791bed644f6e290624f739de474f117b590dfd8a721 config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_USE_MBEDTLS=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' install: mingw-w64-x86_64-mbedtls - type: 'Release' + type: 'Debug' tflags: 'skiprun' - name: 'schannel !unity' # mingw-w64 5.0 sys: 'mingw32' @@ -614,8 +634,8 @@ jobs: ver: '6.4.0' url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' SHA256: 12d2c62ad4527ec8a52275ea8485678dcbe20bec4716a3c7ba274f225d696085 - config: '-DENABLE_DEBUG=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBSSH=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' - install: mingw-w64-i686-libssh + config: '-DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' + install: perl mingw-w64-i686-libssh2 type: 'Debug' tflags: 'skiprun' - name: 'schannel !examples' # mingw-w64 3.0 @@ -631,7 +651,11 @@ jobs: chkprefill: '' # Set it once to silence actionlint fail-fast: false steps: - - uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0 + - name: 'preinstalled tools' + shell: bash + run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' + + - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 with: msystem: ${{ matrix.sys }} release: false @@ -643,14 +667,14 @@ jobs: ${{ matrix.install }} - name: 'cache compiler (gcc ${{ matrix.ver }}-${{ matrix.env }})' - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-compiler with: path: D:\my-cache key: ${{ runner.os }}-mingw-w64-${{ matrix.ver }}-${{ matrix.env }} - name: 'install compiler (gcc ${{ matrix.ver }}-${{ matrix.env }})' - if: ${{ steps.cache-compiler.outputs.cache-hit != 'true' }} + if: ${{ !steps.cache-compiler.outputs.cache-hit }} timeout-minutes: 5 env: MATRIX_URL: '${{ matrix.url }}' @@ -664,7 +688,7 @@ jobs: pwd sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${MATRIX_SHA256}" && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin && ls -l - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -688,6 +712,7 @@ jobs: -DCURL_DROP_UNUSED=ON \ -DCURL_WERROR=ON \ -DUSE_LIBIDN2=OFF \ + -DCURL_DISABLE_HTTPSIG=OFF \ ${MATRIX_CONFIG} done if [ -d bld_chkprefill ] && ! diff -u bld/lib/curl_config.h bld_chkprefill/lib/curl_config.h; then @@ -704,6 +729,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'build' timeout-minutes: 5 run: | @@ -727,7 +758,7 @@ jobs: - name: 'cache test prereqs (stunnel)' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-stunnel with: path: C:\my-stunnel @@ -738,7 +769,7 @@ jobs: if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} timeout-minutes: 2 run: | - if "bld/src/curl.exe" --disable -V 2>/dev/null | grep smb; then + if bld/src/curl.exe --disable -V 2>/dev/null | grep smb; then python3 -m pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt fi @@ -770,7 +801,7 @@ jobs: linux-cross-mingw-w64: name: "linux-mingw, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }}" - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 env: LDFLAGS: -s @@ -784,17 +815,20 @@ jobs: include: - { build: 'autotools', compiler: 'gcc' } - { build: 'cmake' , compiler: 'gcc' } - - { build: 'cmake' , compiler: 'clang-tidy', install_packages: 'clang-20 clang-tidy-20' } + - { build: 'cmake' , compiler: 'clang-tidy', install_packages: 'clang-22 clang-tidy-22', CFLAGS: '-Wunused-macros' } steps: - name: 'install packages' timeout-minutes: 2 env: MATRIX_INSTALL_PACKAGES: '${{ matrix.install_packages }}' run: | + ls -l /etc/apt/sources.list.d + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt + sudo apt-get -o Dpkg::Use-Pty=0 update sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 ${MATRIX_INSTALL_PACKAGES} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -803,13 +837,15 @@ jobs: run: autoreconf -fi - name: 'configure' + env: + CFLAGS: '${{ matrix.CFLAGS }}' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then if [ "${MATRIX_COMPILER}" = 'clang-tidy' ]; then - options+=' -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-20' + options+=' -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-22' options+=' -DENABLE_UNICODE=ON -DUSE_SSLS_EXPORT=ON' - options+=' -DCMAKE_C_COMPILER=clang-20' - options+=" -DCMAKE_RC_COMPILER=llvm-windres-$(clang-20 -dumpversion | cut -d '.' -f 1)" + options+=' -DCMAKE_C_COMPILER=clang-22' + options+=" -DCMAKE_RC_COMPILER=llvm-windres-$(clang-22 -dumpversion | cut -d '.' -f 1)" else options+=" -DCMAKE_C_COMPILER=${TRIPLET}-gcc" fi @@ -840,6 +876,12 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'build' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -909,6 +951,7 @@ jobs: - name: 'openssl +examples' install-msys2: >- + perl mingw-w64-ucrt-x86_64-brotli mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-zstd @@ -922,7 +965,7 @@ jobs: env: 'ucrt-x86_64' plat: 'windows' type: 'Debug' - image: 'windows-2025-vs2026' + image: 'windows-2025' chkprefill: '_chkprefill' tflags: '--min=1850' config: >- @@ -951,7 +994,7 @@ jobs: -DNGTCP2_LIBRARY=/ucrt64/lib/libngtcp2.dll.a -DNGTCP2_CRYPTO_OSSL_LIBRARY=/ucrt64/lib/libngtcp2_crypto_ossl.dll.a -DCURL_CA_NATIVE=ON - -DCURL_ENABLE_NTLM=ON + -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'schannel U' install-vcpkg: 'zlib libssh2[core,zlib]' @@ -971,7 +1014,11 @@ jobs: fail-fast: false steps: - - uses: msys2/setup-msys2@cafece8e6baf9247cf9b1bf95097b0b983cc558d # v2.31.0 + - name: 'preinstalled tools' + shell: bash + run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' + + - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 with: msystem: ${{ matrix.arch == 'arm64' && 'clangarm64' || 'ucrt64' }} release: ${{ contains(matrix.image, 'arm') }} @@ -993,7 +1040,7 @@ jobs: timeout-minutes: 45 run: vcpkg x-set-installed ${MATRIX_INSTALL_VCPKG} --triplet="${MATRIX_ARCH}-${MATRIX_PLAT}" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -1010,6 +1057,7 @@ jobs: cflags='' rcflags='' ldflags='' + cflags+=' -DCURL_DEBUG_NO_WIN32_WND' if [ "${MATRIX_PLAT}" = 'uwp' ]; then options+=' -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0' cflags+=' -DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP' @@ -1029,8 +1077,11 @@ jobs: # Officially this requires the vcvarsall.bat MS-DOS batch file (as of # VS2022). Since it integrates badly with CI steps and shell scripts, # reproduce the necessary build configuration manually, without envs. + # The option to disable the MSBuild heuristics is likely called: + # IgnoreStandardErrorWarningFormat=true, but there seems to be no + # facility to pass it via CMake or CLI. MSVC_EDITION='2022/Enterprise/vc/tools/msvc' - [[ "${MATRIX_IMAGE}" = *'vs2026'* ]] && MSVC_EDITION='18/Enterprise/vc/tools/msvc' + [[ "${MATRIX_IMAGE}" = *'windows-2025'* ]] && MSVC_EDITION='18/Enterprise/vc/tools/msvc' [[ "$(uname -s)" = *'ARM64'* ]] && MSVC_HOST='arm64' || MSVC_HOST='x64' # x86 MSVC_ROOTD="$(cygpath --mixed --short-name "$PROGRAMFILES/Microsoft Visual Studio")" # to avoid spaces in directory names MSVC_ROOTU="$(/usr/bin/find "$(cygpath --unix "$MSVC_ROOTD/$MSVC_EDITION")" -mindepth 1 -maxdepth 1 -type d -name '*.*' | sort | tail -n 1)" @@ -1068,6 +1119,7 @@ jobs: options+=" -DVCPKG_INSTALLED_DIR=$VCPKG_INSTALLATION_ROOT/installed" options+=" -DVCPKG_TARGET_TRIPLET=${MATRIX_ARCH}-${MATRIX_PLAT}" options+=" -DCMAKE_C_COMPILER_TARGET=${MATRIX_ARCH}-${MATRIX_PLAT}" + options+=' -DVCPKG_APPLOCAL_DEPS=OFF' fi cmake -B "bld${_chkprefill}" ${options} \ -DCMAKE_C_FLAGS="${cflags}" \ @@ -1096,17 +1148,29 @@ jobs: echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::' grep -F '#define' bld/lib/curl_config.h | sort || true + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'build' timeout-minutes: 5 run: cmake --build bld --config "${MATRIX_TYPE}" --parallel 5 + - name: 'libcurl.pc, curl-config' + run: | + for f in libcurl.pc curl-config; do + echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' + done + - name: 'curl -V' timeout-minutes: 1 run: | /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 file -- /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_PLAT}" != 'uwp' ]; then # Missing: ucrtbased.dll, VCRUNTIME140D.dll, VCRUNTIME140D_APP.dll - PATH="$PWD/bld/lib/${MATRIX_TYPE}:$PATH" + PATH="$PWD/bld/lib/${MATRIX_TYPE}:$(cygpath --unix "${VCPKG_INSTALLATION_ROOT}")/installed/${MATRIX_ARCH}-${MATRIX_PLAT}/${MATRIX_TYPE}/bin:$PATH" "bld/src/${MATRIX_TYPE}/curl.exe" --disable --version fi @@ -1117,7 +1181,7 @@ jobs: - name: 'cache test prereqs (stunnel)' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: cache-stunnel with: path: C:\my-stunnel @@ -1140,11 +1204,11 @@ jobs: cd /c # no D: drive on windows-11-arm runners if [[ "${MATRIX_IMAGE}" = *'-arm'* ]]; then curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-ARM64.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-ARM64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_ARM64}" && unzip pkg.bin && rm -f pkg.bin else curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-Win64.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${OPENSSH_WINDOWS_VERSION}/OpenSSH-Win64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_WIN64}" && unzip pkg.bin && rm -f pkg.bin fi fi @@ -1183,7 +1247,7 @@ jobs: PATH="/c/OpenSSH-Win64:$PATH" fi fi - PATH="$PWD/bld/lib/${MATRIX_TYPE}:$PATH:/c/my-stunnel/bin" + PATH="$PWD/bld/lib/${MATRIX_TYPE}:$(cygpath --unix "${VCPKG_INSTALLATION_ROOT}")/installed/${MATRIX_ARCH}-${MATRIX_PLAT}/${MATRIX_TYPE}/bin:$PATH:/c/my-stunnel/bin" cmake --build bld --config "${MATRIX_TYPE}" --target test-ci - name: 'build examples' diff --git a/.gitignore b/.gitignore index 0ac100295697..84d2f3b4fcdf 100644 --- a/.gitignore +++ b/.gitignore @@ -66,9 +66,6 @@ test-driver stamp-h* scripts/_curl scripts/curl.fish -curl_fuzzer -curl_fuzzer_seed_corpus.zip -libstandaloneengine.a tests/string tests/config tests/ech-log/ diff --git a/.mailmap b/.mailmap index d8558a2ec3e3..22b0f256b8ce 100644 --- a/.mailmap +++ b/.mailmap @@ -84,8 +84,8 @@ Tobias Nyholm Timur Artikov Michał Antoniak <47522782+MAntoniak@users.noreply.github.com> Gleb Ivanovsky -Max Dymond -Max Dymond +Max Dymond +Max Dymond Abhinav Singh Malik Idrees Hasan Khan <77000356+MalikIdreesHasanKhan@users.noreply.github.com> Yongkang Huang @@ -122,3 +122,6 @@ Stephen Farrell Calvin Ruocco Hamza Bensliman Kaixuan Li +Darren Banfi +Alhuda Khan +Ralf Mueller <217359725+hunterinvariants@users.noreply.github.com> diff --git a/.omc/state/hud-stdin-cache.json b/.omc/state/hud-stdin-cache.json new file mode 100644 index 000000000000..5c38afa8087d --- /dev/null +++ b/.omc/state/hud-stdin-cache.json @@ -0,0 +1 @@ +{"session_id":"c92dcc11-d084-4fad-aa93-f44ef2ff2cc9","transcript_path":"/Users/mohammad.alam/.claude/projects/-opt-UnitySrc-cve-libcurl-updater--claude-worktrees-anthropic-route-default/c92dcc11-d084-4fad-aa93-f44ef2ff2cc9.jsonl","cwd":"/opt/UnitySrc/cve-libcurl-updater/workspace/_write/fork/fork","prompt_id":"a96a208d-a932-4ff8-8bd2-1d03cbcccdb7","agent_type":"claude","effort":{"level":"high"},"session_name":"cve approval curl","model":{"id":"claude-opus-5[1m]","display_name":"Opus 5 (1M context)"},"workspace":{"current_dir":"/opt/UnitySrc/cve-libcurl-updater/workspace/_write/fork/fork","project_dir":"/opt/UnitySrc/cve-libcurl-updater","added_dirs":[]},"version":"2.1.226","output_style":{"name":"default"},"cost":{"total_cost_usd":383.74143874999936,"total_duration_ms":377433905,"total_api_duration_ms":11683887,"total_lines_added":8265,"total_lines_removed":460},"context_window":{"total_input_tokens":702821,"total_output_tokens":182,"context_window_size":1000000,"current_usage":{"input_tokens":2,"output_tokens":182,"cache_creation_input_tokens":3193,"cache_read_input_tokens":699626},"used_percentage":70,"remaining_percentage":30},"exceeds_200k_tokens":true,"fast_mode":false,"thinking":{"enabled":true},"agent":{"name":"claude"}} \ No newline at end of file diff --git a/.omc/state/sessions/c92dcc11-d084-4fad-aa93-f44ef2ff2cc9/hud-state.json b/.omc/state/sessions/c92dcc11-d084-4fad-aa93-f44ef2ff2cc9/hud-state.json new file mode 100644 index 000000000000..1859042dd55c --- /dev/null +++ b/.omc/state/sessions/c92dcc11-d084-4fad-aa93-f44ef2ff2cc9/hud-state.json @@ -0,0 +1,6 @@ +{ + "timestamp": "2026-09-17T16:56:06.052Z", + "backgroundTasks": [], + "sessionStartTimestamp": "2026-09-13T22:10:55.878Z", + "sessionId": "c92dcc11-d084-4fad-aa93-f44ef2ff2cc9" +} \ No newline at end of file diff --git a/CMake/CurlTests.c b/CMake/CurlTests.c index 2cf306b58853..e8117410fd20 100644 --- a/CMake/CurlTests.c +++ b/CMake/CurlTests.c @@ -48,7 +48,7 @@ int main(void) { /* O_NONBLOCK source test */ int flags = 0; - if(0 != fcntl(0, F_SETFL, flags | O_NONBLOCK)) + if(fcntl(0, F_SETFL, flags | O_NONBLOCK)) return 1; return 0; } @@ -65,7 +65,7 @@ int main(void) #include int main(void) { - const char *address = "example.com"; + static const char address[] = "localhost"; struct hostent h; int rc = 0; #if defined(HAVE_GETHOSTBYNAME_R_3) || \ @@ -114,17 +114,6 @@ int main(void) } #endif -#ifdef STDC_HEADERS -#include -#include -#include -#include -int main(void) -{ - return 0; -} -#endif - #ifdef HAVE_FILE_OFFSET_BITS #include /* Check that off_t can represent 2**63 - 1 correctly. @@ -144,7 +133,7 @@ int main(void) #ifdef HAVE_IOCTLSOCKET #ifdef _WIN32 -# include +#include #endif int main(void) { @@ -162,7 +151,7 @@ int main(void) int main(void) { /* IoctlSocket source code */ - if(0 != IoctlSocket(0, 0, 0)) + if(IoctlSocket(0, 0, 0)) return 1; return 0; } @@ -171,13 +160,13 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_CAMEL_FIONBIO #include #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif int main(void) { /* IoctlSocket source code */ long flags = 0; - if(0 != IoctlSocket(0, FIONBIO, &flags)) + if(IoctlSocket(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -186,12 +175,12 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_FIONBIO #ifdef _WIN32 -# include +#include #endif int main(void) { unsigned long flags = 0; - if(0 != ioctlsocket(0, FIONBIO, &flags)) + if(ioctlsocket(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -201,24 +190,24 @@ int main(void) #ifdef HAVE_IOCTL_FIONBIO /* headers for FIONBIO test */ #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif int main(void) { int flags = 0; - if(0 != ioctl(0, FIONBIO, &flags)) + if(ioctl(0, FIONBIO, &flags)) return 1; (void)flags; return 0; @@ -226,27 +215,27 @@ int main(void) #endif #ifdef HAVE_IOCTL_SIOCGIFADDR -/* headers for FIONBIO test */ +/* headers for SIOCGIFADDR test */ #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif #include int main(void) { struct ifreq ifr; - if(0 != ioctl(0, SIOCGIFADDR, &ifr)) + if(ioctl(0, SIOCGIFADDR, &ifr)) return 1; (void)ifr; return 0; @@ -255,17 +244,17 @@ int main(void) #ifdef HAVE_SETSOCKOPT_SO_NONBLOCK #ifdef _WIN32 -# include +#include #endif #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifndef _WIN32 -# include +#include #endif int main(void) { - if(0 != setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) + if(setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) return 1; return 0; } @@ -283,7 +272,7 @@ static void check(char c) int main(void) { char buffer[1024]; - /* This will not compile if strerror_r does not return a char* */ + /* This does not compile if strerror_r does not return a char* */ /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))[0]); return 0; @@ -303,7 +292,7 @@ static void check(float f) int main(void) { char buffer[1024]; - /* This will not compile if strerror_r does not return an int */ + /* This does not compile if strerror_r does not return an int */ /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))); return 0; @@ -349,13 +338,13 @@ int main(void) #ifdef HAVE_ATOMIC #ifdef HAVE_SYS_TYPES_H -# include +#include #endif #ifdef HAVE_UNISTD_H -# include +#include #endif #ifdef HAVE_STDATOMIC_H -# include +#include #endif int main(void) { @@ -367,10 +356,10 @@ int main(void) #ifdef HAVE_WIN32_WINNT #ifdef _WIN32 -# ifndef NOGDI -# define NOGDI -# endif -# include +#ifndef NOGDI +#define NOGDI +#endif +#include #endif #define enquote(x) #x @@ -385,7 +374,7 @@ int main(void) #ifdef MINGW64_VERSION #ifdef __MINGW32__ -# include <_mingw.h> +#include <_mingw.h> #endif #define enquote(x) #x diff --git a/CMake/FindGSS.cmake b/CMake/FindGSS.cmake index 9237fb30b192..46d768699de6 100644 --- a/CMake/FindGSS.cmake +++ b/CMake/FindGSS.cmake @@ -23,17 +23,20 @@ ########################################################################### # Find the GSS Kerberos library # +# This module accepts optional COMPONENTS to control the GSS library flavor: +# +# - Apple +# # Input variables: # -# - `GSS_ROOT_DIR`: Absolute path to the root installation of GSS. (also supported as environment) +# - `GSS_ROOT_DIR`: Absolute path to the root installation of GSS. (also supported as environment) # # Defines: # -# - `GSS_FOUND`: System has a GSS library. -# - `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" -# - `CURL::gss`: GSS library target. -# - `CURL_GSS_FLAVOUR`: Custom property. "GNU" or "MIT" if detected. +# - `GSS_FOUND`: System has GSS. +# - `GSS_VERSION`: Version of GSS. +# - `CURL::gss`: GSS library target. +# - `INTERFACE_CURL_GSS_FLAVOR`: Custom property. "Apple", "GNU" or "MIT" if detected. set(_gnu_modname "gss") set(_mit_modname "mit-krb5-gssapi") @@ -47,11 +50,15 @@ set(_gss_root_hints "${GSS_ROOT_DIR}" "$ENV{GSS_ROOT_DIR}") set(_gss_CFLAGS "") set(_gss_LIBRARY_DIRS "") +if(NOT APPLE AND GSS_FIND_COMPONENTS STREQUAL "Apple") + set(GSS_FIND_COMPONENTS "") +endif() + # 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(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}" AND NOT GSS_FIND_COMPONENTS STREQUAL "Apple") if(CURL_USE_PKGCONFIG) find_package(PkgConfig QUIET) - pkg_search_module(_gss ${_gnu_modname} ${_mit_modname}) + pkg_search_module(_gss ${_mit_modname} ${_gnu_modname}) list(APPEND _gss_root_hints "${_gss_PREFIX}") set(_gss_version "${_gss_VERSION}") endif() @@ -61,156 +68,164 @@ if(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}") endif() if(NOT _gss_FOUND) # Not found by pkg-config. Let us take more traditional approach. - find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin" HINTS ${_gss_root_hints} - 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(_gss_configure_script) - - set(_gss_INCLUDE_DIRS "") - set(_gss_LIBRARIES "") - - execute_process(COMMAND ${_gss_configure_script} "--cflags" "gssapi" - OUTPUT_VARIABLE _gss_cflags_raw - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "FindGSS krb5-config --cflags: ${_gss_cflags_raw}") - - if(NOT _gss_configure_failed) # 0 means success - # Should also work in an odd case when multiple directories are given. - string(STRIP "${_gss_cflags_raw}" _gss_cflags_raw) - string(REGEX REPLACE " +-(I)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - string(REGEX REPLACE " +-([^I][^ \\t;]*)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - - foreach(_flag IN LISTS _gss_cflags_raw) - if(_flag MATCHES "^-I") - string(REGEX REPLACE "^-I" "" _flag "${_flag}") - list(APPEND _gss_INCLUDE_DIRS "${_flag}") - else() - list(APPEND _gss_CFLAGS "${_flag}") - endif() - endforeach() - endif() + if(GSS_FIND_COMPONENTS STREQUAL "Apple") + find_path(_gss_INCLUDE_DIRS NAMES "GSS/gssapi.h" PATH_SUFFIXES "include") + find_library(_gss_LIBRARIES NAMES "GSS") - execute_process(COMMAND ${_gss_configure_script} "--libs" "gssapi" - OUTPUT_VARIABLE _gss_lib_flags - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) - 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. - 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 IN LISTS _gss_lib_flags) - if(_flag MATCHES "^-l") - string(REGEX REPLACE "^-l" "" _flag "${_flag}") - list(APPEND _gss_LIBRARIES "${_flag}") - elseif(_flag MATCHES "^-L") - string(REGEX REPLACE "^-L" "" _flag "${_flag}") - list(APPEND _gss_LIBRARY_DIRS "${_flag}") - endif() - endforeach() + if(_gss_INCLUDE_DIRS AND _gss_LIBRARIES) + message(STATUS "Found AppleGSS: ${_gss_INCLUDE_DIRS}") + set(_gss_flavor "Apple") endif() + else() + find_file(_gss_configure_script NAMES "krb5-config" PATH_SUFFIXES "bin" HINTS ${_gss_root_hints} + 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") - execute_process(COMMAND ${_gss_configure_script} "--version" - OUTPUT_VARIABLE _gss_version - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) + if(_gss_configure_script) - # Older versions may not have the "--version" parameter. In this case we just do not care. - if(_gss_configure_failed) - set(_gss_version 0) - else() - # Strip prefix string to leave the version number only - string(REPLACE "Kerberos 5 release " "" _gss_version "${_gss_version}") - endif() + set(_gss_INCLUDE_DIRS "") + set(_gss_LIBRARIES "") - execute_process(COMMAND ${_gss_configure_script} "--vendor" - OUTPUT_VARIABLE _gss_vendor - RESULT_VARIABLE _gss_configure_failed - OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${_gss_configure_script} "--cflags" "gssapi" + OUTPUT_VARIABLE _gss_cflags_raw + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "FindGSS krb5-config --cflags: ${_gss_cflags_raw}") - # Older versions may not have the "--vendor" parameter. In this case we just do not care. - if(NOT _gss_configure_failed AND NOT _gss_vendor MATCHES "Heimdal|heimdal") - set(_gss_flavour "MIT") # assume a default, should not really matter - endif() - - else() # Either there is no config script or we are on a platform that does not provide one (Windows?) + if(NOT _gss_configure_failed) # 0 means success + # Should also work in an odd case when multiple directories are given. + string(STRIP "${_gss_cflags_raw}" _gss_cflags_raw) + string(REGEX REPLACE " +-(I)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") + string(REGEX REPLACE " +-([^I][^ \\t;]*)" ";-\\1" _gss_cflags_raw "${_gss_cflags_raw}") - find_path(_gss_INCLUDE_DIRS NAMES "gssapi/gssapi.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include" "inc") + foreach(_flag IN LISTS _gss_cflags_raw) + if(_flag MATCHES "^-I") + string(REGEX REPLACE "^-I" "" _flag "${_flag}") + list(APPEND _gss_INCLUDE_DIRS "${_flag}") + else() + list(APPEND _gss_CFLAGS "${_flag}") + endif() + endforeach() + endif() - if(_gss_INCLUDE_DIRS) # We have found something - set(_gss_libdir_suffixes "") + execute_process(COMMAND ${_gss_configure_script} "--libs" "gssapi" + OUTPUT_VARIABLE _gss_lib_flags + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "FindGSS krb5-config --libs: ${_gss_lib_flags}") - 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) - cmake_pop_check_state() + if(NOT _gss_configure_failed) # 0 means success + # This script gives us libraries and link directories. + 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(_gss_have_mit_headers) - set(_gss_flavour "MIT") - if(WIN32) - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - list(APPEND _gss_libdir_suffixes "lib/AMD64") - set(_gss_libname "gssapi64") - else() - list(APPEND _gss_libdir_suffixes "lib/i386") - set(_gss_libname "gssapi32") + foreach(_flag IN LISTS _gss_lib_flags) + if(_flag MATCHES "^-l") + string(REGEX REPLACE "^-l" "" _flag "${_flag}") + list(APPEND _gss_LIBRARIES "${_flag}") + elseif(_flag MATCHES "^-L") + string(REGEX REPLACE "^-L" "" _flag "${_flag}") + list(APPEND _gss_LIBRARY_DIRS "${_flag}") endif() - else() - list(APPEND _gss_libdir_suffixes "lib" "lib64") # those suffixes are not checked for HINTS - set(_gss_libname "gssapi_krb5") - endif() + endforeach() endif() - 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 ${_gnu_modname}) - set(_gss_libname "gss") + execute_process(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 do not care. + if(_gss_configure_failed) + set(_gss_version 0) + else() + # Strip prefix string to leave the version number only + string(REPLACE "Kerberos 5 release " "" _gss_version "${_gss_version}") + endif() + + execute_process(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 do not care. + if(NOT _gss_configure_failed AND NOT _gss_vendor MATCHES "Heimdal|heimdal") + set(_gss_flavor "MIT") # assume a default, should not really matter endif() - endif() - # If we have headers, look up libraries - if(_gss_flavour) - set(_gss_libdir_hints ${_gss_root_hints}) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) - cmake_path(GET _gss_INCLUDE_DIRS PARENT_PATH _gss_calculated_potential_root) + else() # Either there is no config script or we are on a platform that does not provide one (Windows?) + + find_path(_gss_INCLUDE_DIRS NAMES "gssapi/gssapi.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include" "inc") + + if(_gss_INCLUDE_DIRS) # We have found something + set(_gss_libdir_suffixes "") + + 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) + cmake_pop_check_state() + + if(_gss_have_mit_headers) + set(_gss_flavor "MIT") + if(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND _gss_libdir_suffixes "lib/AMD64") + set(_gss_libname "gssapi64") + else() + list(APPEND _gss_libdir_suffixes "lib/i386") + set(_gss_libname "gssapi32") + endif() + else() + list(APPEND _gss_libdir_suffixes "lib" "lib64") # those suffixes are not checked for HINTS + set(_gss_libname "gssapi_krb5") + endif() + endif() else() - get_filename_component(_gss_calculated_potential_root "${_gss_INCLUDE_DIRS}" DIRECTORY) + find_path(_gss_INCLUDE_DIRS NAMES "gss.h" HINTS ${_gss_root_hints} PATH_SUFFIXES "include") + + if(_gss_INCLUDE_DIRS) + set(_gss_flavor "GNU") + set(_gss_pc_requires ${_gnu_modname}) + set(_gss_libname "gss") + endif() endif() - list(APPEND _gss_libdir_hints ${_gss_calculated_potential_root}) - find_library(_gss_LIBRARIES NAMES ${_gss_libname} HINTS ${_gss_libdir_hints} PATH_SUFFIXES ${_gss_libdir_suffixes}) + # If we have headers, look up libraries + if(_gss_flavor) + set(_gss_libdir_hints ${_gss_root_hints}) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + cmake_path(GET _gss_INCLUDE_DIRS PARENT_PATH _gss_calculated_potential_root) + else() + get_filename_component(_gss_calculated_potential_root "${_gss_INCLUDE_DIRS}" DIRECTORY) + endif() + list(APPEND _gss_libdir_hints ${_gss_calculated_potential_root}) + + find_library(_gss_LIBRARIES NAMES ${_gss_libname} HINTS ${_gss_libdir_hints} PATH_SUFFIXES ${_gss_libdir_suffixes}) + endif() endif() endif() - if(NOT _gss_flavour) - message(FATAL_ERROR "GNU or MIT GSS is required") + if(NOT _gss_flavor) + message(FATAL_ERROR "MIT, GNU or Apple GSS is required") endif() else() - # _gss_MODULE_NAME set since CMake 3.16. - # _pkg_check_modules_pkg_name is undocumented and used as a fallback for CMake <3.16 versions. - if(_gss_MODULE_NAME STREQUAL _gnu_modname OR _pkg_check_modules_pkg_name STREQUAL _gnu_modname) - set(_gss_flavour "GNU") + if(_gss_MODULE_NAME STREQUAL _gnu_modname) + set(_gss_flavor "GNU") set(_gss_pc_requires ${_gnu_modname}) - elseif(_gss_MODULE_NAME STREQUAL _mit_modname OR _pkg_check_modules_pkg_name STREQUAL _mit_modname) - set(_gss_flavour "MIT") + elseif(_gss_MODULE_NAME STREQUAL _mit_modname) + set(_gss_flavor "MIT") set(_gss_pc_requires ${_mit_modname}) else() - message(FATAL_ERROR "GNU or MIT GSS is required") + message(FATAL_ERROR "MIT, GNU or Apple GSS is required") endif() - message(STATUS "Found GSS/${_gss_flavour} (via pkg-config): ${_gss_INCLUDE_DIRS} (found version \"${_gss_version}\")") + message(STATUS "Found GSS/${_gss_flavor} (via pkg-config): ${_gss_INCLUDE_DIRS} (found version \"${_gss_version}\")") endif() set(GSS_VERSION ${_gss_version}) if(NOT GSS_VERSION) - if(_gss_flavour STREQUAL "MIT") + if(_gss_flavor STREQUAL "MIT" AND WIN32) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) cmake_host_system_information(RESULT _mit_version QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/MIT/Kerberos/SDK/CurrentVersion" VALUE "VersionString") @@ -218,12 +233,8 @@ if(NOT GSS_VERSION) get_filename_component(_mit_version "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MIT\\Kerberos\\SDK\\CurrentVersion;VersionString]" NAME CACHE) endif() - if(WIN32 AND _mit_version) - set(GSS_VERSION "${_mit_version}") - else() - set(GSS_VERSION "MIT Unknown") - endif() - else() # GNU + set(GSS_VERSION "${_mit_version}") + elseif(_gss_flavor 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}") @@ -238,7 +249,7 @@ endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GSS REQUIRED_VARS - _gss_flavour + _gss_flavor _gss_LIBRARIES VERSION_VAR GSS_VERSION @@ -261,7 +272,7 @@ if(GSS_FOUND) if(NOT TARGET CURL::gss) add_library(CURL::gss INTERFACE IMPORTED) set_target_properties(CURL::gss PROPERTIES - INTERFACE_CURL_GSS_FLAVOUR "${_gss_flavour}" + INTERFACE_CURL_GSS_FLAVOR "${_gss_flavor}" INTERFACE_LIBCURL_PC_MODULES "${_gss_pc_requires}" INTERFACE_COMPILE_OPTIONS "${_gss_CFLAGS}" INTERFACE_INCLUDE_DIRECTORIES "${_gss_INCLUDE_DIRS}" diff --git a/CMake/FindLibgsasl.cmake b/CMake/FindLibgsasl.cmake index 7fbaa7128f9f..25bae0750333 100644 --- a/CMake/FindLibgsasl.cmake +++ b/CMake/FindLibgsasl.cmake @@ -46,6 +46,7 @@ endif() if(_libgsasl_FOUND) set(Libgsasl_FOUND TRUE) set(LIBGSASL_FOUND TRUE) + set(LIBGSASL_VERSION ${_libgsasl_VERSION}) message(STATUS "Found Libgsasl (via pkg-config): ${_libgsasl_INCLUDE_DIRS} (found version \"${LIBGSASL_VERSION}\")") else() find_path(LIBGSASL_INCLUDE_DIR NAMES "gsasl.h") diff --git a/CMake/FindNGHTTP2.cmake b/CMake/FindNGHTTP2.cmake index f93113f404e1..bca7cf8a8181 100644 --- a/CMake/FindNGHTTP2.cmake +++ b/CMake/FindNGHTTP2.cmake @@ -61,7 +61,7 @@ if(_nghttp2_FOUND) elseif(nghttp2_CONFIG) set(NGHTTP2_FOUND TRUE) set(NGHTTP2_VERSION ${nghttp2_VERSION}) - if(NGHTTP2_USE_STATIC_LIBS) + if(NGHTTP2_USE_STATIC_LIBS OR NOT TARGET nghttp2::nghttp2) set(_nghttp2_LIBRARIES nghttp2::nghttp2_static) else() set(_nghttp2_LIBRARIES nghttp2::nghttp2) diff --git a/CMake/FindNGHTTP3.cmake b/CMake/FindNGHTTP3.cmake index 427c139f21c4..ed671b7a4929 100644 --- a/CMake/FindNGHTTP3.cmake +++ b/CMake/FindNGHTTP3.cmake @@ -61,7 +61,7 @@ if(_nghttp3_FOUND) elseif(nghttp3_CONFIG) set(NGHTTP3_FOUND TRUE) set(NGHTTP3_VERSION ${nghttp3_VERSION}) - if(NGHTTP3_USE_STATIC_LIBS) + if(NGHTTP3_USE_STATIC_LIBS OR NOT TARGET nghttp3::nghttp3) set(_nghttp3_LIBRARIES nghttp3::nghttp3_static) else() set(_nghttp3_LIBRARIES nghttp3::nghttp3) diff --git a/CMake/FindNGTCP2.cmake b/CMake/FindNGTCP2.cmake index e4929163777c..bf2f48877749 100644 --- a/CMake/FindNGTCP2.cmake +++ b/CMake/FindNGTCP2.cmake @@ -49,27 +49,28 @@ # # - `NGTCP2_FOUND`: System has ngtcp2. # - `NGTCP2_VERSION`: Version of ngtcp2. +# - `NGTCP2_CRYPTO_BACKEND`: Name of the crypto library component. (Empty if COMPONENTS was not used.) # - `CURL::ngtcp2`: ngtcp2 library target. +set(NGTCP2_CRYPTO_BACKEND "") if(NGTCP2_FIND_COMPONENTS) - set(_ngtcp2_crypto_backend "") foreach(_component IN LISTS NGTCP2_FIND_COMPONENTS) if(_component MATCHES "^(BoringSSL|GnuTLS|LibreSSL|ossl|quictls|wolfSSL)") - if(_ngtcp2_crypto_backend) + if(NGTCP2_CRYPTO_BACKEND) message(FATAL_ERROR "NGTCP2: Only one crypto library can be selected") endif() - set(_ngtcp2_crypto_backend ${_component}) + set(NGTCP2_CRYPTO_BACKEND ${_component}) endif() endforeach() - if(_ngtcp2_crypto_backend) - string(TOLOWER "ngtcp2_crypto_${_ngtcp2_crypto_backend}" _crypto_library_lower) - string(TOUPPER "ngtcp2_crypto_${_ngtcp2_crypto_backend}" _crypto_library_upper) + 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() set(_ngtcp2_pc_requires "libngtcp2") -if(_ngtcp2_crypto_backend) +if(NGTCP2_CRYPTO_BACKEND) list(APPEND _ngtcp2_pc_requires "lib${_crypto_library_lower}") endif() @@ -81,7 +82,7 @@ if(NOT DEFINED NGTCP2_INCLUDE_DIR AND pkg_check_modules(_ngtcp2 ${_ngtcp2_pc_requires}) set(_tried_pkgconfig TRUE) endif() - if(NOT _ngtcp2_FOUND AND CURL_USE_CMAKECONFIG) + if(NOT _ngtcp2_FOUND AND CURL_USE_CMAKECONFIG AND NGTCP2_CRYPTO_BACKEND) find_package(ngtcp2 CONFIG QUIET) # Skip using it if the crypto library target is not available if(ngtcp2_CONFIG AND @@ -105,7 +106,7 @@ if(_ngtcp2_FOUND) elseif(ngtcp2_CONFIG) set(NGTCP2_FOUND TRUE) set(NGTCP2_VERSION ${ngtcp2_VERSION}) - if(NGTCP2_USE_STATIC_LIBS) + if(NGTCP2_USE_STATIC_LIBS OR NOT TARGET ngtcp2::ngtcp2) set(_ngtcp2_LIBRARIES ngtcp2::ngtcp2_static ngtcp2::${_crypto_library_lower}_static) else() set(_ngtcp2_LIBRARIES ngtcp2::ngtcp2 ngtcp2::${_crypto_library_lower}) @@ -130,7 +131,7 @@ else() unset(_version_str) endif() - if(_ngtcp2_crypto_backend) + if(NGTCP2_CRYPTO_BACKEND) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) cmake_path(GET NGTCP2_LIBRARY PARENT_PATH _ngtcp2_library_dir) else() @@ -145,7 +146,7 @@ else() endif() if(${_crypto_library_upper}_LIBRARY) - set(NGTCP2_${_ngtcp2_crypto_backend}_FOUND TRUE) + set(NGTCP2_${NGTCP2_CRYPTO_BACKEND}_FOUND TRUE) set(NGTCP2_CRYPTO_LIBRARY ${${_crypto_library_upper}_LIBRARY}) endif() endif() diff --git a/CMake/FindNettle.cmake b/CMake/FindNettle.cmake index c963180cba0e..f14cf23886ef 100644 --- a/CMake/FindNettle.cmake +++ b/CMake/FindNettle.cmake @@ -25,19 +25,21 @@ # # Input variables: # -# - `NETTLE_INCLUDE_DIR`: Absolute path to nettle include directory. -# - `NETTLE_LIBRARY`: Absolute path to `nettle` library. +# - `NETTLE_INCLUDE_DIR`: Absolute path to nettle include directory. +# - `NETTLE_HOGWEED_LIBRARY`: Absolute path to `hogweed` library. +# - `NETTLE_LIBRARY`: Absolute path to `nettle` library. # # Defines: # -# - `NETTLE_FOUND`: System has nettle. -# - `NETTLE_VERSION`: Version of nettle. -# - `CURL::nettle`: nettle library target. +# - `NETTLE_FOUND`: System has nettle. +# - `NETTLE_VERSION`: Version of nettle. +# - `CURL::nettle`: nettle library target. -set(_nettle_pc_requires "nettle") +set(_nettle_pc_requires "hogweed" "nettle") if(CURL_USE_PKGCONFIG AND NOT DEFINED NETTLE_INCLUDE_DIR AND + NOT DEFINED NETTLE_HOGWEED_LIBRARY AND NOT DEFINED NETTLE_LIBRARY) find_package(PkgConfig QUIET) pkg_check_modules(_nettle ${_nettle_pc_requires}) @@ -46,10 +48,11 @@ endif() if(_nettle_FOUND) set(Nettle_FOUND TRUE) set(NETTLE_FOUND TRUE) - set(NETTLE_VERSION ${_nettle_VERSION}) + set(NETTLE_VERSION ${_nettle_nettle_VERSION}) 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_HOGWEED_LIBRARY NAMES "hogweed") find_library(NETTLE_LIBRARY NAMES "nettle") unset(NETTLE_VERSION CACHE) @@ -71,6 +74,7 @@ else() find_package_handle_standard_args(Nettle REQUIRED_VARS NETTLE_INCLUDE_DIR + NETTLE_HOGWEED_LIBRARY NETTLE_LIBRARY VERSION_VAR NETTLE_VERSION @@ -78,10 +82,10 @@ else() if(NETTLE_FOUND) set(_nettle_INCLUDE_DIRS ${NETTLE_INCLUDE_DIR}) - set(_nettle_LIBRARIES ${NETTLE_LIBRARY}) + set(_nettle_LIBRARIES ${NETTLE_HOGWEED_LIBRARY} ${NETTLE_LIBRARY}) endif() - mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_LIBRARY) + mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_HOGWEED_LIBRARY NETTLE_LIBRARY) endif() if(NETTLE_FOUND) diff --git a/CMake/FindZstd.cmake b/CMake/FindZstd.cmake index 8dc620a1009f..5a2db76bb1a2 100644 --- a/CMake/FindZstd.cmake +++ b/CMake/FindZstd.cmake @@ -53,12 +53,12 @@ if(NOT DEFINED ZSTD_INCLUDE_DIR AND pkg_check_modules(_zstd ${_zstd_pc_requires}) endif() if(NOT _zstd_FOUND AND CURL_USE_CMAKECONFIG) - find_package(Zstd CONFIG QUIET) - # Skip using if older than v1.4.5 - if(Zstd_CONFIG AND + find_package(zstd CONFIG QUIET) + # Only use if 1.4.5 or greater + if(zstd_CONFIG AND NOT TARGET zstd::libzstd_static AND NOT TARGET zstd::libzstd_shared) - unset(Zstd_CONFIG) + unset(zstd_CONFIG) endif() endif() endif() @@ -74,17 +74,18 @@ if(_zstd_FOUND) set(_zstd_LIBRARIES "${_zstd_STATIC_LIBRARIES}") endif() message(STATUS "Found Zstd (via pkg-config): ${_zstd_INCLUDE_DIRS} (found version \"${ZSTD_VERSION}\")") -elseif(Zstd_CONFIG) +elseif(zstd_CONFIG) + set(Zstd_FOUND TRUE) set(ZSTD_FOUND TRUE) - set(ZSTD_VERSION ${Zstd_VERSION}) + set(ZSTD_VERSION ${zstd_VERSION}) if(ZSTD_USE_STATIC_LIBS) set(_zstd_LIBRARIES zstd::libzstd_static) elseif(TARGET zstd::libzstd) - set(_zstd_LIBRARIES zstd::libzstd) # v1.5.6+ + set(_zstd_LIBRARIES zstd::libzstd) # 1.5.6+ else() set(_zstd_LIBRARIES zstd::libzstd_shared) endif() - message(STATUS "Found Zstd (via CMake Config): ${Zstd_CONFIG} (found version \"${ZSTD_VERSION}\")") + message(STATUS "Found Zstd (via CMake Config): ${zstd_CONFIG} (found version \"${ZSTD_VERSION}\")") else() find_path(ZSTD_INCLUDE_DIR NAMES "zstd.h") if(ZSTD_USE_STATIC_LIBS) diff --git a/CMake/Macros.cmake b/CMake/Macros.cmake index 5e26c384696f..503074a0a02b 100644 --- a/CMake/Macros.cmake +++ b/CMake/Macros.cmake @@ -43,7 +43,7 @@ macro(curl_internal_test _curl_test) message(STATUS "Performing Test ${_curl_test}") try_compile(${_curl_test} ${PROJECT_BINARY_DIR} - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c" + "${PROJECT_SOURCE_DIR}/CMake/CurlTests.c" COMPILE_DEFINITIONS "-D${_curl_test}" ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${CMAKE_REQUIRED_DEFINITIONS} LINK_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}" OUTPUT_VARIABLE CURL_TEST_OUTPUT) @@ -255,7 +255,7 @@ macro(curl_collect_target_link_options _target) get_target_property(_val ${_target} IMPORTED) if(_val) # LOCATION is empty for interface library targets and safe to ignore. - # Explicitly skip this query to avoid CMake v3.18 and older erroring out. + # Explicitly skip this query to avoid CMake 3.18 and older erroring out. get_target_property(_val ${_target} TYPE) if(NOT "${_val}" STREQUAL "INTERFACE_LIBRARY") get_target_property(_val ${_target} LOCATION) @@ -267,6 +267,8 @@ macro(curl_collect_target_link_options _target) get_target_property(_val ${_target} INTERFACE_LINK_LIBRARIES) if(_val) foreach(_lib IN LISTS _val) + # Extract imported target name from e.g. "$" set by libssh2 + string(REGEX REPLACE "^\\\$\$" "\\1" _lib "${_lib}") if(TARGET "${_lib}") curl_collect_target_link_options(${_lib}) else() diff --git a/CMake/OtherTests.cmake b/CMake/OtherTests.cmake index 6619f3ab3e88..b0727d32e434 100644 --- a/CMake/OtherTests.cmake +++ b/CMake/OtherTests.cmake @@ -77,12 +77,14 @@ if(WIN32) set(HAVE_GETADDRINFO_THREADSAFE ${HAVE_GETADDRINFO}) elseif(NOT HAVE_GETADDRINFO) set(HAVE_GETADDRINFO_THREADSAFE FALSE) -elseif(APPLE OR - AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR - CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR +elseif(APPLE OR # Darwin 9+ / macOS 10.5+ + AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR # 5.2+ + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR # 2.2.0+ + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR # 5.5+ + CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR # 11.11+ + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR # all versions + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR # 4+ + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR # 5.4+ CMAKE_SYSTEM_NAME STREQUAL "SunOS") set(HAVE_GETADDRINFO_THREADSAFE TRUE) elseif(BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD") diff --git a/CMake/PickyWarnings.cmake b/CMake/PickyWarnings.cmake index aca79ec947b7..e412cd351dec 100644 --- a/CMake/PickyWarnings.cmake +++ b/CMake/PickyWarnings.cmake @@ -97,7 +97,7 @@ if(PICKY_COMPILER) # to suppress undesired warnings in case -Weverything is passed as a custom option. # Assume these options always exist with both clang and gcc. - # Require clang 3.0 / gcc 2.95 or later. + # Require clang 3.0 / gcc 2.95 or greater. list(APPEND _picky_enable -Wbad-function-cast # clang 2.7 gcc 2.95 -Wconversion # clang 2.7 gcc 2.95 @@ -215,7 +215,7 @@ if(PICKY_COMPILER) 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)) list(APPEND _picky_enable - -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 9.0 # We do silencing for clang 10.0 and above only + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 9.0 # We do silencing for clang 10.0+ only -Wxor-used-as-pow # clang 10.0 gcc 13.0 appleclang 12.0 ) endif() @@ -250,7 +250,7 @@ if(PICKY_COMPILER) if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 19.1) OR (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 17.0)) list(APPEND _picky_enable - -Wno-format-signedness # clang 19.1 gcc 5.1 appleclang 17.0 # In clang-cl enums are signed ints by default + -Wformat-signedness # clang 19.1 gcc 5.1 appleclang 17.0 # In clang-cl enums are signed ints by default ) endif() if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 21.1) OR @@ -300,13 +300,14 @@ if(PICKY_COMPILER) list(APPEND _picky_enable -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.1 -Wformat=2 # clang 2.7 gcc 4.8 + -Wlogical-op # gcc 4.4 -Wtrampolines # gcc 4.6 ) endif() if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.0) list(APPEND _picky_enable -Warray-bounds=2 # clang 2.9 gcc 5.0 (clang default: -Warray-bounds) - -Wno-format-signedness # clang 19.1 gcc 5.1 appleclang 17.0 + -Wformat-signedness # clang 19.1 gcc 5.1 appleclang 17.0 ) endif() if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.0) @@ -391,7 +392,7 @@ if(PICKY_COMPILER) list(APPEND _picky "-Wno-conversion") # Avoid false positives endif() endif() - elseif(MSVC AND MSVC_VERSION LESS_EQUAL 1950) # Skip for untested/unreleased newer versions + elseif(MSVC AND MSVC_VERSION LESS_EQUAL 1951) # Enable for tested versions only 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)' @@ -407,7 +408,7 @@ if(PICKY_COMPILER) list(APPEND _picky "-wd4746") 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 + list(APPEND _picky "-wd5045") # Compiler inserts Spectre mitigation for memory load if /Qspectre switch specified endif() endif() endif() @@ -437,7 +438,7 @@ if(CMAKE_C_STANDARD STREQUAL 90) endif() if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.9) OR (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.1)) - list(APPEND _picky "-Wno-comma") # Just silly + list(APPEND _picky "-Wno-comma") # Silly endif() endif() @@ -450,6 +451,9 @@ if(_picky_nocheck OR _picky) string(REPLACE ";" " " _picky_tmp "${_picky_tmp}") string(STRIP "${_picky_tmp}" _picky_tmp) message(STATUS "Picky compiler options: ${_picky_tmp}") + if(_CURL_SAVE_PICKY_OPTIONS) + file(WRITE "${PROJECT_BINARY_DIR}/picky-options.txt" "${_picky_tmp}") + endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "${_picky_nocheck}" "${_picky}") # Apply to all feature checks diff --git a/CMake/Utilities.cmake b/CMake/Utilities.cmake index f86a6aa1e832..319160b0a57f 100644 --- a/CMake/Utilities.cmake +++ b/CMake/Utilities.cmake @@ -37,24 +37,30 @@ 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() + if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.5) + cmake_language(PRINT_VARIABLES ALL) + else() + 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() + endif() message("::endgroup::") endfunction() # Dump all target properties function(curl_dumptargetprops _target) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND TARGET "${_target}") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.5) + cmake_language(PRINT_PROPERTIES TARGETS "${_target}" ALL) + elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND TARGET "${_target}") execute_process(COMMAND "${CMAKE_COMMAND}" "--help-property-list" OUTPUT_VARIABLE _cmake_property_list) string(REPLACE "\n" ";" _cmake_property_list "${_cmake_property_list}") list(REMOVE_DUPLICATES _cmake_property_list) @@ -64,21 +70,22 @@ function(curl_dumptargetprops _target) if(NOT _target_imported) list(REMOVE_ITEM _cmake_property_list "LOCATION" "LOCATION_" "MACOSX_PACKAGE_LOCATION" "VS_DEPLOYMENT_LOCATION") endif() + list(SORT _cmake_property_list) foreach(_prop IN LISTS _cmake_property_list) if(_prop MATCHES "") - foreach(_config IN ITEMS "DEBUG" "RELEASE" "MINSIZEREL" "RELWITHDEBINFO") + foreach(_config IN ITEMS "DEBUG" "MINSIZEREL" "RELEASE" "RELWITHDEBINFO") string(REPLACE "" "${_config}" _propconfig "${_prop}") get_property(_is_set TARGET "${_target}" PROPERTY "${_propconfig}" SET) if(_is_set) get_target_property(_val "${_target}" "${_propconfig}") - message("${_target}.${_propconfig} = '${_val}'") + message(" ${_target}.${_propconfig} = \"${_val}\"") endif() endforeach() else() get_property(_is_set TARGET "${_target}" PROPERTY "${_prop}" SET) if(_is_set) get_target_property(_val "${_target}" "${_prop}") - message("${_target}.${_prop} = '${_val}'") + message(" ${_target}.${_prop} = \"${_val}\"") endif() endif() endforeach() diff --git a/CMake/cmake_uninstall.in.cmake b/CMake/cmake_uninstall.in.cmake index bb95d85a0a72..30e812b1246b 100644 --- a/CMake/cmake_uninstall.in.cmake +++ b/CMake/cmake_uninstall.in.cmake @@ -21,30 +21,28 @@ # SPDX-License-Identifier: curl # ########################################################################### -if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +set(_manifest "@PROJECT_BINARY_DIR@/install_manifest.txt") +if(NOT EXISTS "${_manifest}") + message(FATAL_ERROR "Cannot find install manifest: ${_manifest}") endif() -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@") +set(_destdir "$ENV{DESTDIR}") +if(NOT _destdir STREQUAL "") + message(STATUS "DESTDIR environment: ${_destdir}") endif() -message(${CMAKE_INSTALL_PREFIX}) -file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" _files) +file(READ "${_manifest}" _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 remove "$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}") +foreach(_file IN LISTS _files) + set(_target "${_destdir}${_file}") + if(IS_SYMLINK "${_target}" OR EXISTS "${_target}") + file(REMOVE "${_target}") + if(IS_SYMLINK "${_target}" OR EXISTS "${_target}") + message(STATUS "Failed to delete: ${_target}") + else() + message(STATUS "Uninstalled: ${_target}") endif() else() - message(STATUS "File $ENV{DESTDIR}${_file} does not exist.") + message(STATUS "File does not exist: ${_target}") endif() endforeach() diff --git a/CMake/curl-config.in.cmake b/CMake/curl-config.in.cmake index 317477197f51..ee49478d5fbc 100644 --- a/CMake/curl-config.in.cmake +++ b/CMake/curl-config.in.cmake @@ -30,7 +30,7 @@ option(CURL_USE_PKGCONFIG "Enable pkg-config to detect @PROJECT_NAME@ dependenci if(CMAKE_VERSION VERSION_LESS @CMAKE_MINIMUM_REQUIRED_VERSION@) message(STATUS "@PROJECT_NAME@: @PROJECT_NAME@-specific Find modules require " - "CMake @CMAKE_MINIMUM_REQUIRED_VERSION@ or upper, found: ${CMAKE_VERSION}.") + "CMake @CMAKE_MINIMUM_REQUIRED_VERSION@ or greater, found: ${CMAKE_VERSION}.") endif() include(CMakeFindDependencyMacro) @@ -74,7 +74,7 @@ if("@USE_ARES@") list(APPEND _curl_libs CURL::cares) endif() if("@HAVE_GSSAPI@") - find_dependency(GSS MODULE) + find_dependency(GSS MODULE COMPONENTS "@GSS_FLAVOR@") list(APPEND _curl_libs CURL::gss) endif() if("@USE_BACKTRACE@") @@ -122,7 +122,7 @@ if("@USE_NGHTTP3@") list(APPEND _curl_libs CURL::nghttp3) endif() if("@USE_NGTCP2@") - find_dependency(NGTCP2 MODULE) + find_dependency(NGTCP2 MODULE COMPONENTS "@NGTCP2_CRYPTO_BACKEND@") list(APPEND _curl_libs CURL::ngtcp2) endif() if("@USE_GNUTLS@") diff --git a/CMake/unix-cache.cmake b/CMake/unix-cache.cmake index 8ecd20618645..8ec2cd48c193 100644 --- a/CMake/unix-cache.cmake +++ b/CMake/unix-cache.cmake @@ -21,7 +21,7 @@ # SPDX-License-Identifier: curl # ########################################################################### -# Based on CI runs for Cygwin/MSYS2, Linux, macOS, FreeBSD, NetBSD, OpenBSD +# Based on CI runs for Cygwin/MSYS2, Linux, macOS/iOS, DragonFly BSD, FreeBSD, MidnightBSD, NetBSD, OpenBSD if(NOT UNIX) message(FATAL_ERROR "This file should be included on Unix platforms only") endif() @@ -30,7 +30,9 @@ if(APPLE OR CYGWIN) set(HAVE_ACCEPT4 0) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") set(HAVE_ACCEPT4 1) @@ -58,13 +60,26 @@ set(HAVE_DECL_FSEEKO 1) set(HAVE_DIRENT_H 1) if(APPLE OR CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" 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 "MidnightBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD") set(HAVE_EVENTFD 1) endif() +if(ANDROID AND ANDROID_PLATFORM_LEVEL GREATER_EQUAL 34) + set(HAVE_MEMSET_EXPLICIT 1) +endif() +if((APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.9) OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR # 6+ + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR # 11.2+ + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") # 1.3+ + set(HAVE_MEMSET_S 1) +elseif(NOT APPLE) + set(HAVE_MEMSET_S 0) +endif() set(HAVE_FCNTL 1) set(HAVE_FCNTL_H 1) set(HAVE_FCNTL_O_NONBLOCK 1) @@ -76,7 +91,9 @@ if(APPLE) set(HAVE_FSETXATTR 1) set(HAVE_FSETXATTR_5 0) set(HAVE_FSETXATTR_6 1) -elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR +elseif(CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") set(HAVE_FSETXATTR 0) set(HAVE_FSETXATTR_5 0) @@ -89,12 +106,7 @@ elseif(CYGWIN OR set(HAVE_FSETXATTR_6 0) endif() 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") +if(CYGWIN) set(HAVE_GETADDRINFO_THREADSAFE 1) endif() set(HAVE_GETEUID 1) @@ -104,14 +116,19 @@ if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") set(HAVE_GETHOSTBYNAME_R 0) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") 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") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") set(HAVE_GETHOSTBYNAME_R_6 1) set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 1) else() @@ -127,7 +144,9 @@ endif() if(APPLE OR CYGWIN OR CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") set(HAVE_GETPASS_R 0) elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") @@ -148,8 +167,6 @@ 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) @@ -185,6 +202,7 @@ else() set(HAVE_NETINET_IN6_H 0) endif() set(HAVE_NETINET_IN_H 1) +set(HAVE_NETINET_IP_H 1) set(HAVE_NETINET_TCP_H 1) set(HAVE_NETINET_UDP_H 1) set(HAVE_NET_IF_H 1) @@ -194,11 +212,7 @@ if(APPLE OR CYGWIN) set(HAVE_PIPE2 0) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - BSD OR - CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR + BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD" OR CMAKE_SYSTEM_NAME STREQUAL "SunOS") set(HAVE_PIPE2 1) endif() @@ -214,10 +228,10 @@ 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) + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD") set(HAVE_SENDMMSG 0) else() set(HAVE_SENDMMSG 1) @@ -246,7 +260,7 @@ 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 + set(HAVE_STROPTS_H 0) # glibc 2.30 or greater. https://sourceware.org/legacy-ml/libc-alpha/2019-08/msg00029.html endif() set(HAVE_STRUCT_SOCKADDR_STORAGE 1) set(HAVE_STRUCT_TIMEVAL 1) @@ -255,10 +269,12 @@ if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "iOS") endif() if(APPLE OR CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" 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 "MidnightBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD") set(HAVE_SYS_EVENTFD_H 1) endif() @@ -299,5 +315,4 @@ set(HAVE_UTIME 1) set(HAVE_UTIMES 1) set(HAVE_UTIME_H 1) set(HAVE_WRITABLE_ARGV 1) -set(STDC_HEADERS 1) set(USE_UNIX_SOCKETS 1) diff --git a/CMake/win32-cache.cmake b/CMake/win32-cache.cmake index bd9bb6e3fc89..2d8506eb69f4 100644 --- a/CMake/win32-cache.cmake +++ b/CMake/win32-cache.cmake @@ -28,10 +28,7 @@ endif() if(MINGW) set(HAVE_BASENAME 1) set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H - set(HAVE_DIRENT_H 1) - set(HAVE_GETTIMEOFDAY 1) set(HAVE_LIBGEN_H 1) - set(HAVE_OPENDIR 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() @@ -40,24 +37,22 @@ if(MINGW) set(HAVE_UNISTD_H 1) set(HAVE_UTIME_H 1) # wrapper to sys/utime.h else() - set(HAVE_DIRENT_H 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_UTIME_H 0) if(MSVC) - set(HAVE_UNISTD_H 0) + set(HAVE_BASENAME 0) 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() if(MSVC_VERSION GREATER_EQUAL 1800) + set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H set(HAVE_STDBOOL_H 1) else() + set(HAVE_BOOL_T 0) # = HAVE_STDBOOL_H set(HAVE_STDBOOL_H 0) endif() - set(HAVE_BOOL_T "${HAVE_STDBOOL_H}") - set(HAVE_BASENAME 0) + set(HAVE_UNISTD_H 0) endif() endif() @@ -104,8 +99,6 @@ set(HAVE_GETSOCKNAME 1) set(HAVE_GLIBC_STRERROR_R 0) set(HAVE_GMTIME_R 0) set(HAVE_IFADDRS_H 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) @@ -120,6 +113,7 @@ set(HAVE_MEMRCHR 0) set(HAVE_NETDB_H 0) set(HAVE_NETINET_IN6_H 0) set(HAVE_NETINET_IN_H 0) +set(HAVE_NETINET_IP_H 0) set(HAVE_NETINET_TCP_H 0) set(HAVE_NETINET_UDP_H 0) set(HAVE_NET_IF_H 0) @@ -130,7 +124,6 @@ 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) @@ -163,7 +156,6 @@ set(HAVE_TERMIO_H 0) set(HAVE_TIME_T_UNSIGNED 0) set(HAVE_UTIME 1) set(HAVE_UTIMES 0) -set(STDC_HEADERS 1) # Types and sizes diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dd9c7aa7027..693d93d01417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,13 +61,14 @@ unset(_curl_version_h_contents) message(STATUS "curl version=[${_curl_version}]") string(REGEX REPLACE "([0-9]+\.[0-9]+\.[0-9]+).+" "\\1" _curl_version_sem "${_curl_version}") +# Initializes PROJECT_BINARY_DIR, PROJECT_SOURCE_DIR 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 + # CMake (as of 3.31.2) gets confused and applies the MSVC rc.exe command-line # template to windres. Reset it to the windres template as in 'Modules/Platform/Windows-windres.cmake': set(CMAKE_RC_COMPILE_OBJECT " -O coff ") elseif(DOS AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # DJGPP @@ -89,7 +90,7 @@ endif() if(UNIX) string(APPEND _target_flags " UNIX") endif() -if(BSD) +if(BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD") string(APPEND _target_flags " BSD") endif() if(ANDROID) @@ -229,7 +230,7 @@ if(WIN32) if(MINGW64_VERSION) message(STATUS "Found MINGW64_VERSION=${MINGW64_VERSION}") if(MINGW64_VERSION VERSION_LESS 3.0) - message(FATAL_ERROR "mingw-w64 3.0 or upper is required") + message(FATAL_ERROR "mingw-w64 3.0 or greater required") endif() endif() endif() @@ -256,8 +257,11 @@ if(CYGWIN OR CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "G 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) +option(ENABLE_DEBUG "Enable curl debug features (for developing curl)" OFF) if(ENABLE_DEBUG) + if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Debug-enabled (aka development mode) curl requires the Debug configuration") + endif() message(WARNING "This curl build is Debug-enabled and insecure, do not use in production.") endif() @@ -358,14 +362,14 @@ elseif(BUILD_STATIC_CURL AND NOT BUILD_STATIC_LIBS) set(BUILD_STATIC_CURL OFF) endif() -# Lib flavour selected for curl tool +# Lib flavor 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. +# Lib flavor selected for example and test programs. if(BUILD_SHARED_LIBS) set(LIB_SELECTED ${LIB_SHARED}) else() @@ -420,8 +424,6 @@ mark_as_advanced(CURL_ENABLE_EXPORT_TARGET) option(CURL_DISABLE_ALTSVC "Disable alt-svc support" OFF) mark_as_advanced(CURL_DISABLE_ALTSVC) -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_BASIC_AUTH "Disable Basic authentication" OFF) @@ -456,6 +458,8 @@ option(CURL_DISABLE_HTTP "Disable HTTP" OFF) mark_as_advanced(CURL_DISABLE_HTTP) option(CURL_DISABLE_HTTP_AUTH "Disable all HTTP authentication methods" OFF) mark_as_advanced(CURL_DISABLE_HTTP_AUTH) +option(CURL_DISABLE_HTTPSIG "Disable HTTP Message Signatures (RFC 9421) (experimental)" ON) +mark_as_advanced(CURL_DISABLE_HTTPSIG) option(CURL_DISABLE_IMAP "Disable IMAP" OFF) mark_as_advanced(CURL_DISABLE_IMAP) option(CURL_DISABLE_LDAP "Disable LDAP" OFF) @@ -592,7 +596,7 @@ option(BUILD_MISC_DOCS "Build misc man pages (e.g. curl-config and mk-ca-bundle) option(ENABLE_CURL_MANUAL "Build the man page for curl and enable its -M/--manual option" ON) if((ENABLE_CURL_MANUAL OR BUILD_LIBCURL_DOCS) AND NOT Perl_FOUND) - message(WARNING "Perl not found. Will not build manuals.") + message(WARNING "Perl not found. Cannot build manuals.") endif() # If we are on AIX, do the _ALL_SOURCE magic @@ -618,13 +622,13 @@ include(CheckSymbolExists) include(CheckTypeSize) include(CheckCSourceCompiles) -option(_CURL_PREFILL "Fast-track known feature detection results (Windows, some Apple)" "${WIN32}") +option(_CURL_PREFILL "Pre-fill known feature detection results (Windows, some Apple)" "${WIN32}") mark_as_advanced(_CURL_PREFILL) if(_CURL_PREFILL) if(WIN32) - include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/win32-cache.cmake") + include("${PROJECT_SOURCE_DIR}/CMake/win32-cache.cmake") elseif(UNIX) - include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/unix-cache.cmake") + include("${PROJECT_SOURCE_DIR}/CMake/unix-cache.cmake") message(STATUS "Pre-filling feature detection results for UNIX") endif() elseif(WIN32) @@ -681,6 +685,7 @@ elseif(AMIGA) set(OPENSSL_CRYPTO_LIBRARY "${AMISSL_AUTO_LIBRARY}") set(CURL_USE_OPENSSL ON) set(CURL_CA_FALLBACK ON CACHE BOOL "") + list(PREPEND CURL_NETWORK_AND_TIME_LIBS "${AMISSL_STUBS_LIBRARY}" "${AMISSL_AUTO_LIBRARY}") endif() elseif(NOT WIN32 AND NOT APPLE) check_library_exists("socket" "connect" "" HAVE_LIBSOCKET) @@ -738,7 +743,7 @@ endif() cmake_dependent_option(CURL_USE_MBEDTLS "Enable mbedTLS for SSL/TLS" OFF CURL_ENABLE_SSL 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) +cmake_dependent_option(CURL_USE_RUSTLS "Enable Rustls for SSL/TLS (experimental)" OFF CURL_ENABLE_SSL OFF) if(WIN32 OR CURL_USE_SCHANNEL OR @@ -803,6 +808,14 @@ else() set(USE_APPLE_SECTRUST OFF) endif() +set(USE_APPLE_FAST_UDP 0) +if(APPLE) + option(CURL_ENABLE_APPLE_FAST_UDP "Use Apple fast UDP (experimental)" OFF) + if(CURL_ENABLE_APPLE_FAST_UDP) + set(USE_APPLE_FAST_UDP 1) + endif() +endif() + if(_use_core_foundation_and_core_services) find_library(COREFOUNDATION_FRAMEWORK NAMES "CoreFoundation") mark_as_advanced(COREFOUNDATION_FRAMEWORK) @@ -835,26 +848,26 @@ if(CURL_USE_OPENSSL) 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) - endif() if(NOT DEFINED HAVE_AWSLC) check_symbol_exists("OPENSSL_IS_AWSLC" "openssl/base.h" HAVE_AWSLC) endif() - if(NOT DEFINED HAVE_LIBRESSL) + if(NOT DEFINED HAVE_BORINGSSL AND NOT HAVE_AWSLC) + check_symbol_exists("OPENSSL_IS_BORINGSSL" "openssl/base.h" HAVE_BORINGSSL) + endif() + if(NOT DEFINED HAVE_LIBRESSL AND NOT HAVE_AWSLC AND NOT HAVE_BORINGSSL) check_symbol_exists("LIBRESSL_VERSION_NUMBER" "openssl/opensslv.h" HAVE_LIBRESSL) endif() cmake_pop_check_state() - if(HAVE_BORINGSSL OR HAVE_AWSLC) - if(NOT MSVC AND NOT ANDROID) # BoringSSL/AWS-LC MSVC builds use native Windows threads + if(HAVE_AWSLC OR HAVE_BORINGSSL) + if(NOT MSVC AND NOT ANDROID) # AWS-LC/BoringSSL MSVC builds use native Windows threads find_package(Threads) if(CMAKE_USE_PTHREADS_INIT) set(HAVE_THREADS_POSIX_BORINGSSL 1) list(APPEND CURL_NETWORK_AND_TIME_LIBS Threads::Threads) list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads) elseif(OPENSSL_USE_STATIC_LIBS) - message(WARNING "BoringSSL/AWS-LC requires POSIX Threads.") + message(WARNING "AWS-LC/BoringSSL requires POSIX Threads.") endif() endif() if(OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") @@ -863,17 +876,17 @@ if(CURL_USE_OPENSSL) endif() endif() - if(HAVE_BORINGSSL) + if(USE_AMISSL) + set(_openssl "AmiSSL") + elseif(HAVE_AWSLC) + set(_openssl "AWS-LC") + elseif(HAVE_BORINGSSL) if(BORINGSSL_VERSION) set(CURL_BORINGSSL_VERSION "\"${BORINGSSL_VERSION}\"") endif() 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() @@ -882,7 +895,7 @@ endif() if(CURL_USE_MBEDTLS) find_package(MbedTLS MODULE REQUIRED) if(MBEDTLS_VERSION VERSION_LESS 3.2.0) - message(FATAL_ERROR "mbedTLS v3.2.0 or newer is required.") + message(FATAL_ERROR "mbedTLS 3.2.0 or greater required") endif() set(_ssl_enabled ON) set(USE_MBEDTLS ON) @@ -916,7 +929,7 @@ if(CURL_USE_WOLFSSL) set(_curl_ca_bundle_supported TRUE) if(USE_OPENSSL AND WOLFSSL_VERSION VERSION_LESS 5.7.6) - message(FATAL_ERROR "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL.") + message(FATAL_ERROR "wolfSSL 5.7.6 or greater required to coexist with OpenSSL") endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "WOLFSSL_OPTIONS_IGNORE_SYS") @@ -934,15 +947,6 @@ if(CURL_USE_GNUTLS) set(_valid_default_ssl_backend TRUE) endif() set(_curl_ca_bundle_supported TRUE) - - if(NOT DEFINED HAVE_GNUTLS_SRP AND NOT CURL_DISABLE_SRP) - cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gnutls) - # In GnuTLS 3.8.0 (2023-02-10) and upper, this check always succeeds. - # Detecting actual TLS-SRP support needs poking the API at runtime. - check_symbol_exists("gnutls_srp_verifier" "gnutls/gnutls.h" HAVE_GNUTLS_SRP) - cmake_pop_check_state() - endif() endif() if(CURL_USE_RUSTLS) @@ -962,7 +966,7 @@ if(CURL_USE_RUSTLS) 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.") + message(FATAL_ERROR "rustls-ffi library does not provide rustls_supported_hpke function. Required version is 0.15 or greater.") endif() if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "rustls") @@ -1001,7 +1005,7 @@ if(ZSTD_FOUND) set(HAVE_ZSTD ON) list(APPEND CURL_LIBS CURL::zstd) else() - message(WARNING "zstd v1.0.0 or newer is required, disabling zstd support.") + message(WARNING "zstd 1.0.0 or greater required, disabling zstd support") endif() endif() @@ -1010,7 +1014,6 @@ 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) list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) endif() @@ -1024,7 +1027,7 @@ macro(curl_openssl_check_exists) 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) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL 5.5.4) endif() if(WIN32) list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32") # for OpenSSL/wolfSSL @@ -1075,13 +1078,10 @@ 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) +option(USE_HTTPSRR "Enable HTTPS RR support (experimental)" OFF) +option(USE_ECH "Enable ECH support (experimental)" OFF) if(USE_ECH) if(USE_OPENSSL OR USE_WOLFSSL OR USE_RUSTLS) # Be sure that the TLS library actually supports ECH. @@ -1097,7 +1097,7 @@ if(USE_ECH) set(HAVE_ECH 1) endif() if(NOT HAVE_ECH) - message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/AWS-LC/wolfSSL/rustls-ffi") + message(FATAL_ERROR "ECH support missing in AWS-LC/BoringSSL/OpenSSL/Rustls/wolfSSL") else() message(STATUS "ECH enabled") # ECH wants HTTPSRR @@ -1105,11 +1105,16 @@ if(USE_ECH) message(STATUS "HTTPSRR enabled") endif() else() - message(FATAL_ERROR "ECH requires ECH-enabled OpenSSL, BoringSSL, AWS-LC, wolfSSL or rustls-ffi") + message(FATAL_ERROR "ECH requires ECH-enabled AWS-LC, BoringSSL, OpenSSL, Rustls or wolfSSL") endif() endif() -option(USE_SSLS_EXPORT "Enable SSL session export support" OFF) +if(NOT CURL_DISABLE_HTTPSIG AND NOT USE_GNUTLS AND NOT USE_OPENSSL AND NOT USE_WOLFSSL) + message(WARNING "HTTPSIG requires GnuTLS, OpenSSL or wolfSSL. HTTPSIG support disabled.") + set(CURL_DISABLE_HTTPSIG ON) +endif() + +option(USE_SSLS_EXPORT "Enable SSL session import/export (experimental)" OFF) if(USE_SSLS_EXPORT) if(_ssl_enabled) message(STATUS "SSL export enabled.") @@ -1118,6 +1123,8 @@ if(USE_SSLS_EXPORT) endif() endif() +option(USE_PROXY_HTTP3 "Enable HTTP/3 proxy support (experimental)" OFF) + option(USE_NGHTTP2 "Use nghttp2 library" ON) if(USE_NGHTTP2) find_package(NGHTTP2 MODULE) @@ -1140,7 +1147,7 @@ if(USE_NGTCP2) elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) find_package(NGTCP2 MODULE REQUIRED COMPONENTS "ossl") if(NGTCP2_VERSION VERSION_LESS 1.12.0) - message(FATAL_ERROR "ngtcp2 1.12.0 or upper required for OpenSSL") + message(FATAL_ERROR "ngtcp2 1.12.0 or greater required for OpenSSL") endif() set(OPENSSL_QUIC_API2 1) elseif(HAVE_LIBRESSL) @@ -1165,7 +1172,7 @@ if(USE_NGTCP2) list(APPEND CURL_LIBS CURL::nghttp3) endif() -option(USE_QUICHE "Use quiche library for HTTP/3 support" OFF) +option(USE_QUICHE "Use quiche library for HTTP/3 support (experimental)" OFF) if(USE_QUICHE) if(USE_NGTCP2) message(FATAL_ERROR "Only one HTTP/3 backend can be selected") @@ -1186,8 +1193,18 @@ if(USE_QUICHE) endif() endif() -if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) - set(USE_TLS_SRP 1) +if(USE_PROXY_HTTP3) + if(CURL_DISABLE_PROXY) + message(FATAL_ERROR "USE_PROXY_HTTP3 requires proxy support") + elseif(CURL_DISABLE_HTTP) + message(FATAL_ERROR "USE_PROXY_HTTP3 requires HTTP support") + elseif(NOT USE_NGTCP2 OR NOT USE_NGHTTP3) + message(FATAL_ERROR "USE_PROXY_HTTP3 requires ngtcp2 + nghttp3") + elseif(NOT USE_OPENSSL) + message(FATAL_ERROR "USE_PROXY_HTTP3 currently requires OpenSSL") + else() + message(STATUS "HTTP/3 proxy support enabled (experimental)") + endif() endif() if(NOT CURL_DISABLE_LDAP) @@ -1319,6 +1336,9 @@ option(CURL_USE_GSASL "Use libgsasl" OFF) mark_as_advanced(CURL_USE_GSASL) if(CURL_USE_GSASL) find_package(Libgsasl MODULE REQUIRED) + if(LIBGSASL_VERSION VERSION_LESS 1.6.0) + message(FATAL_ERROR "libgsasl 1.6.0 or greater required") + endif() list(APPEND CURL_LIBS CURL::libgsasl) set(USE_GSASL ON) endif() @@ -1327,18 +1347,27 @@ option(CURL_USE_GSSAPI "Use GSSAPI implementation" OFF) mark_as_advanced(CURL_USE_GSSAPI) if(CURL_USE_GSSAPI) - find_package(GSS MODULE) + set(CURL_GSS_FLAVOR "" CACHE STRING "Set preferred GSSAPI implementation ('Apple'). Default: MIT, then GNU (experimental)") + + find_package(GSS MODULE COMPONENTS "${CURL_GSS_FLAVOR}") set(HAVE_GSSAPI ${GSS_FOUND}) if(GSS_FOUND) list(APPEND CURL_LIBS CURL::gss) - get_target_property(_gss_flavour CURL::gss INTERFACE_CURL_GSS_FLAVOUR) - if(_gss_flavour STREQUAL "GNU") + get_target_property(GSS_FLAVOR CURL::gss INTERFACE_CURL_GSS_FLAVOR) + if(GSS_FLAVOR STREQUAL "Apple") + set(HAVE_GSSAPPLE 1) + elseif(GSS_FLAVOR STREQUAL "GNU") set(HAVE_GSSGNU 1) elseif(GSS_VERSION) # MIT set(CURL_KRB5_VERSION "\"${GSS_VERSION}\"") endif() + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gss) + check_function_exists("gss_set_neg_mechs" HAVE_GSS_SET_NEG_MECHS) + cmake_pop_check_state() else() message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.") endif() @@ -1528,7 +1557,6 @@ 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) @@ -1539,6 +1567,7 @@ check_include_file_concat_curl("net/if.h" HAVE_NET_IF_H) # sys/select.h 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("netinet/ip.h" HAVE_NETINET_IP_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) @@ -1609,7 +1638,6 @@ 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 @@ -1617,14 +1645,12 @@ check_symbol_exists("recv" "${CURL_INCLUDES}" HAVE_RECV) # proto/bsd 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("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) @@ -1653,7 +1679,9 @@ check_function_exists("getrlimit" HAVE_GETRLIMIT) check_function_exists("setlocale" HAVE_SETLOCALE) check_function_exists("setrlimit" HAVE_SETRLIMIT) -if(NOT APPLE) +if(APPLE) + check_function_exists("mach_absolute_time" HAVE_MACH_ABSOLUTE_TIME) +else() # Apple platforms do not offer pipe2(), but the iPhone Simulator-specific # /usr/lib/system/libsystem_sim_kernel.dylib exports it. To avoid false # detection, omit this feature check for Apple targets. @@ -1661,12 +1689,22 @@ if(NOT APPLE) endif() if(NOT WIN32) + check_include_file("dirent.h" HAVE_DIRENT_H) + + check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h check_function_exists("if_nametoindex" HAVE_IF_NAMETOINDEX) # net/if.h check_function_exists("realpath" HAVE_REALPATH) 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) + + check_symbol_exists("opendir" "dirent.h" HAVE_OPENDIR) + check_symbol_exists("strcasecmp" "string.h" HAVE_STRCASECMP) + check_symbol_exists("stricmp" "string.h" HAVE_STRICMP) + check_symbol_exists("strcmpi" "string.h" HAVE_STRCMPI) + + check_symbol_exists("memset_s" "string.h" HAVE_MEMSET_S) + if(NOT HAVE_MEMSET_S) + check_function_exists("memset_explicit" HAVE_MEMSET_EXPLICIT) + endif() endif() if(AMIGA) @@ -1677,14 +1715,6 @@ if(NOT _ssl_enabled) check_symbol_exists("arc4random" "${CURL_INCLUDES};stdlib.h" HAVE_ARC4RANDOM) 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() - check_symbol_exists("fsetxattr" "sys/xattr.h" HAVE_FSETXATTR) if(HAVE_FSETXATTR) curl_internal_test(HAVE_FSETXATTR_5) @@ -1713,7 +1743,6 @@ foreach(_curl_test IN ITEMS HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6 HAVE_BOOL_T - STDC_HEADERS HAVE_ATOMIC ) curl_internal_test(${_curl_test}) @@ -1782,7 +1811,7 @@ 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) + int main(int argc, char *argv[]) { (void)argc; argv[0][0] = ' '; @@ -1794,7 +1823,8 @@ if(NOT CMAKE_CROSSCOMPILING) include(CheckCSourceRuns) check_c_source_runs(" #include - int main(void) { + int main(void) + { time_t t = -1; return t < 0; }" HAVE_TIME_T_UNSIGNED) @@ -1918,9 +1948,8 @@ include(GNUInstallDirs) 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(_project_config "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake") +set(_version_config "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") option(BUILD_TESTING "Build tests" ON) if(BUILD_TESTING AND Perl_FOUND) @@ -2035,15 +2064,16 @@ 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("HTTPSIG" NOT CURL_DISABLE_HTTPSIG) 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" CURL_ENABLE_NTLM AND (_use_curl_ntlm_core OR USE_WINDOWS_SSPI)) -curl_add_if("TLS-SRP" USE_TLS_SRP) curl_add_if("HTTP2" USE_NGHTTP2) curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE) +curl_add_if("proxy-HTTP3" USE_PROXY_HTTP3) 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_MBEDTLS OR @@ -2067,12 +2097,12 @@ message(STATUS "Features: ${SUPPORT_FEATURES}") # Clear list and collect SSL backends set(_items "") -curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) -curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL) -curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) -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) +curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) +curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL) +curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) +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 CASE INSENSITIVE) @@ -2161,8 +2191,8 @@ if(NOT CURL_DISABLE_INSTALL) set(_explicit_libs "") get_target_property(_imported "${_lib}" 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. + # Reading the LOCATION property on non-imported target errors out. + # Assume the user does not need this information in the .pc file. continue() endif() set(_libdirs "") @@ -2175,7 +2205,7 @@ if(NOT CURL_DISABLE_INSTALL) endif() if(_lib STREQUAL OpenSSL::SSL AND NOT HAVE_BORINGSSL) # BoringSSL does not provide openssl.pc set(_modules "openssl") - elseif(_lib STREQUAL ZLIB::ZLIB) + elseif(_lib STREQUAL ZLIB::ZLIB AND NOT ANDROID) # Android does not provide zlib.pc set(_modules "zlib") else() get_target_property(_modules "${_lib}" INTERFACE_LIBCURL_PC_MODULES) @@ -2209,6 +2239,9 @@ if(NOT CURL_DISABLE_INSTALL) list(APPEND LIBCURL_PC_LIBS_PRIVATE "${_lib}") list(APPEND LIBCURL_PC_LIBS_PRIVATE_LIST "${_lib}") endif() + elseif(_lib MATCHES "^-") # '-option' + list(APPEND _ldflags "${_lib}") + list(APPEND LIBCURL_PC_LIBS_PRIVATE_LIST "${_lib}") else() list(APPEND LIBCURL_PC_LIBS_PRIVATE "-l${_lib}") list(APPEND LIBCURL_PC_LIBS_PRIVATE_LIST "${_lib}") @@ -2239,6 +2272,17 @@ if(NOT CURL_DISABLE_INSTALL) string(REPLACE ";" "," LIBCURL_PC_REQUIRES_PRIVATE "${LIBCURL_PC_REQUIRES_PRIVATE}") endif() if(LIBCURL_PC_LIBS_PRIVATE) + # Remove duplicates listed next to each other + set(_libs "") + set(_prev "") + foreach(_lib IN LISTS LIBCURL_PC_LIBS_PRIVATE) + if(NOT _prev STREQUAL _lib) + list(APPEND _libs "${_lib}") + set(_prev "${_lib}") + endif() + endforeach() + set(LIBCURL_PC_LIBS_PRIVATE "${_libs}") + string(REPLACE ";" " " LIBCURL_PC_LIBS_PRIVATE "${LIBCURL_PC_LIBS_PRIVATE}") endif() if(_ldflags) @@ -2262,6 +2306,7 @@ if(NOT CURL_DISABLE_INSTALL) set(LIBCURL_PC_REQUIRES "${LIBCURL_PC_REQUIRES_PRIVATE}") set(LIBCURL_PC_LIBS "${LIBCURL_PC_LIBS_PRIVATE}") set(LIBCURL_PC_CFLAGS "${LIBCURL_PC_CFLAGS_PRIVATE}") + set(LIBCURL_PC_REQUIRES_PRIVATE "") endif() if(BUILD_STATIC_LIBS) set(ENABLE_STATIC "yes") @@ -2301,6 +2346,7 @@ if(NOT CURL_DISABLE_INSTALL) # Generate a pkg-config file matching this config. # Consumed variables: + # CURL_PACKAGE_MAINTAINER # CURLVERSION # exec_prefix # includedir @@ -2320,9 +2366,18 @@ if(NOT CURL_DISABLE_INSTALL) # 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/ + if(NOT "$ENV{CURL_CI}" STREQUAL "") + set(CURL_PACKAGE_MAINTAINER "https://curl.se/") + endif() configure_file( "${PROJECT_SOURCE_DIR}/libcurl.pc.in" "${PROJECT_BINARY_DIR}/libcurl.pc" @ONLY) + # Strip trailing spaces, duplicate spaces after colon, empty properties + file(READ "${PROJECT_BINARY_DIR}/libcurl.pc" _libcurl_pc) + string(REGEX REPLACE " +\n" "\n" _libcurl_pc "${_libcurl_pc}") + string(REGEX REPLACE "\nLibs\.private: +" "\nLibs.private: " _libcurl_pc "${_libcurl_pc}") + string(REGEX REPLACE "\n([A-Za-z.]+:\n)+" "\n" _libcurl_pc "${_libcurl_pc}") + file(WRITE "${PROJECT_BINARY_DIR}/libcurl.pc" "${_libcurl_pc}") install(FILES "${PROJECT_BINARY_DIR}/libcurl.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") @@ -2344,7 +2399,7 @@ if(NOT CURL_DISABLE_INSTALL) endif() ${_generated_version_config}") - # Consumed custom variables: + # Consumed variables: # CMAKE_MINIMUM_REQUIRED_VERSION # CURLVERSION # LIBCURL_PC_LIBS_PRIVATE_LIST @@ -2356,6 +2411,7 @@ if(NOT CURL_DISABLE_INSTALL) # CURL_SUPPORTED_PROTOCOLS_LIST # CURL_USE_CMAKECONFIG # CURL_USE_PKGCONFIG + # GSS_FLAVOR # HAVE_BROTLI # HAVE_GSSAPI # HAVE_LIBIDN2 @@ -2374,7 +2430,7 @@ if(NOT CURL_DISABLE_INSTALL) # USE_MBEDTLS # USE_NGHTTP2 # USE_NGHTTP3 - # USE_NGTCP2 + # USE_NGTCP2 NGTCP2_CRYPTO_BACKEND # USE_OPENSSL OPENSSL_VERSION_MAJOR # USE_QUICHE # USE_RUSTLS @@ -2395,37 +2451,38 @@ if(NOT CURL_DISABLE_INSTALL) FILES ${_version_config} ${_project_config} - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindBrotli.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindCares.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindGSS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindGnuTLS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLDAP.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibbacktrace.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibgsasl.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibidn2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibpsl.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibuv.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindMbedTLS.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGHTTP2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGHTTP3.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNGTCP2.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindNettle.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindQuiche.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindRustls.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindWolfSSL.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindZstd.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindBrotli.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindCares.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindGSS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindGnuTLS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLDAP.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibbacktrace.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibgsasl.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibidn2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibpsl.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibssh.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibssh2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindLibuv.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindMbedTLS.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGHTTP2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGHTTP3.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNGTCP2.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindNettle.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindQuiche.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindRustls.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindWolfSSL.cmake" + "${PROJECT_SOURCE_DIR}/CMake/FindZstd.cmake" DESTINATION ${_install_cmake_dir}) if(NOT TARGET curl_uninstall) + # Consumed variable: + # PROJECT_BINARY_DIR configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.in.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake" - @ONLY) + "${PROJECT_SOURCE_DIR}/CMake/cmake_uninstall.in.cmake" + "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake" @ONLY) add_custom_target(curl_uninstall - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake") + COMMAND ${CMAKE_COMMAND} -P "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake") endif() if(BUILD_CURL_EXE) diff --git a/Dockerfile b/Dockerfile index 67027b9402e8..934d4b119caf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # $ ./scripts/maketgz 8.7.1 # To update, get the latest digest e.g. from https://hub.docker.com/_/debian/tags -FROM debian:bookworm-slim@sha256:f9c6a2fd2ddbc23e336b6257a5245e31f996953ef06cd13a59fa0a1df2d5c252 +FROM debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171 RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \ build-essential make autoconf automake libtool git perl zip zlib1g-dev gawk && \ @@ -32,7 +32,7 @@ RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \ ARG UID=1000 GID=1000 -RUN groupadd --gid $UID dev && \ +RUN groupadd --gid $GID dev && \ useradd --uid $UID --gid dev --shell /bin/bash --create-home dev USER dev:dev diff --git a/GIT-INFO.md b/GIT-INFO.md index ee912560fd5a..3c2e9efc4f66 100644 --- a/GIT-INFO.md +++ b/GIT-INFO.md @@ -28,6 +28,6 @@ Daniel uses a configure line similar to this for easier development: ## REQUIREMENTS -See [docs/INTERNALS.md][0] for requirement details. +See [docs/DEPENDENCIES.md][0] for requirement details. -[0]: docs/INTERNALS.md +[0]: docs/DEPENDENCIES.md diff --git a/Makefile.am b/Makefile.am index 83fdadf035fe..c4e37231cece 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,12 +64,37 @@ CMAKE_DIST = \ tests/cmake/test.cpp \ tests/cmake/test.sh -EXTRA_DIST = CHANGES.md COPYING RELEASE-NOTES Dockerfile .clang-tidy.yml .editorconfig $(CMAKE_DIST) +EXTRA_DIST = \ + .clang-tidy.yml \ + .editorconfig \ + CHANGES.md \ + COPYING \ + Dockerfile \ + RELEASE-NOTES \ + $(CMAKE_DIST) DISTCLEANFILES = buildinfo.txt bin_SCRIPTS = curl-config +CURL_ETAGS_FILES = \ + $(srcdir)/include/curl/curl.h \ + $(srcdir)/include/curl/curlver.h \ + $(srcdir)/include/curl/easy.h \ + $(srcdir)/include/curl/header.h \ + $(srcdir)/include/curl/mprintf.h \ + $(srcdir)/include/curl/multi.h \ + $(srcdir)/include/curl/options.h \ + $(srcdir)/include/curl/stdcheaders.h \ + $(srcdir)/include/curl/system.h \ + $(srcdir)/include/curl/typecheck-gcc.h \ + $(srcdir)/include/curl/urlapi.h \ + $(srcdir)/include/curl/websockets.h + +ETAGS_ARGS = $(CURL_ETAGS_FILES) + +TAGS_DEPENDENCIES = $(CURL_ETAGS_FILES) + SUBDIRS = lib docs src scripts DIST_SUBDIRS = $(SUBDIRS) tests projects include docs @@ -88,6 +113,7 @@ dist-hook: check: test examples check-docs if CROSSCOMPILING +test-quiet: test test-full: test test-nonflaky: test test-torture: test @@ -102,7 +128,9 @@ test: else -test: +test: test-quiet + +test-quiet: @(cd tests; $(MAKE) all quiet-test) test-full: diff --git a/README b/README index 4ee7e43a2c84..bc04a79dbc5e 100644 --- a/README +++ b/README @@ -41,7 +41,7 @@ GIT git clone https://github.com/curl/curl - (you will get a directory named curl created, filled with the source code) + (you get a directory named curl, filled with the source code) SECURITY PROBLEMS diff --git a/RELEASE-NOTES b/RELEASE-NOTES index fcf392d822ba..8342fec4564d 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,307 +1,325 @@ -curl and libcurl 8.20.0 +curl and libcurl 8.22.0 - Public curl releases: 274 - Command line options: 273 - curl_easy_setopt() options: 308 + Public curl releases: 276 + Command line options: 278 + curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1463 - Contributors: 3664 + Authors: 1518 + Contributors: 3786 This release includes the following changes: - o async-thrdd: use thread queue for resolving [144] - o build: make NTLM disabled by default [90] - o cmake: drop support for CMake 3.17 and older [108] - o lib: add thread pool and queue [74] - o lib: drop support for < c-ares 1.16.0 [64] - o lib: make SMB support opt-in [18] - o multi.h: add CURLMNWC_CLEAR_ALL [127] - o rtmp: drop support [91] + o gssapi: add support for Apple GSS Framework [72] + o hardening: add API guards [64] + o RFC 9421 HTTP Message Signatures support [108] + o spnego: block NTLM fallback in SPNEGO negotiation [151] + o TLS: drop support for TLS-SRP [71] + o vquic: add option to use Apple fast UDP [137] This release includes the following bugfixes: - o altsvc: cap the list at 5,000 entries [183] - o altsvc: drop the prio field from the struct [185] - o altsvc: skip expired entries read from file [187] - o asyn-ares: connect async [220] - o asyn-ares: drop orphaned variable references [86] - o asyn-ares: fix HTTPS-lookup when not on port 443 [100] - o asyn-thrdd: drop redundant `result` check [291] - o asyn-thrdd: fix clang-tidy unused value warning [125] - o async-ares: fix query counter handling [195] - o autotools: limit checksrc target to ignore non-repo test sources [12] - o badwords-all: exit with correct code on errors [50] - o badwords: combine the whitelisting into a single regex [1] - o badwords: detect the the and with with [51] - o badwords: only check comments and strings in source code [61] - o badwords: rework exceptions, fix many of them [15] - o boringssl: fix more coexist cases with Schannel/WinCrypt [170] - o build: adjust/add casts to fix `-Wformat-signedness` [218] - o build: assume `snprintf()` in `mprintf`, drop feature check [107] - o build: compiler warning silencing tidy-ups [4] - o build: drop `openssl` module dependency for BoringSSL from `libcurl.pc` [33] - o build: drop duplicate `pthread.h` includes [158] - o build: drop redundant `USE_QUICHE` guards [159] - o build: enable `-Wimplicit-int-enum-cast` compiler warning, fix issues [84] - o build: fix `-Wformat-signedness` by adjusting printf masks [226] - o build: link `bcrypt.lib` via vcxproj files [239] - o build: skip detecting `pipe2()` for Apple targets [227] - o build: stop building and installing `runtests.1` and `testcurl.1` [235] - o cf-https-connect: silence `-Wimplicit-int-enum-cast` with HTTPS-RR [132] - o cf-https-connect: silence `-Wimplicit-int-enum-cast` with HTTPS-RR [63] - o cf-ip-happy: limit concurrent attempts [191] - o cf-socket: avoid low risk integer overflow on ancient Solaris [56] - o cfilters: fix Curl_pollset_poll() return code mixup [206] - o clang-tidy: avoid assignments in `if` expressions [175] - o clang-tidy: enable more checks, fix fallouts [254] - o cmake: add CMake Config-based dependency detection [87] - o cmake: add CMake Config-based dependency detection for c-ares, wolfSSL [134] - o cmake: do not install `wcurl` when `BUILD_CURL_EXE=OFF` [265] - o cmake: do not install shell completions when `BUILD_CURL_EXE=OFF` [263] - o cmake: document functions used from Windows system DLLs [103] - o cmake: enable pthreads for BoringSSL/AWS-LC [196] - o cmake: resolve targets recursively when generating `libcurl.pc` [45] - o cmake: rework binutils ld hack to not read `LOCATION` property [41] - o cmake: silence bad library `Threads::Threads` warning [131] - o cmake: use `AIX` built-in variable (with CMake 4.0+) [163] - o config2setopts: make --capath work in proxy disabled builds [113] - o configure: fix `--with-ngtcp2=` option for crypto libs [26] - o configure: fix LibreSSL ngtcp2 1.15.0+ crypto lib selection logic [3] - o configure: prefer dependency-specific variables over `$withval` [35] - o configure: remove superfluous experimental warning for HTTP/3 [169] - o configure: silence useless clang warnings in C89 builds [156] - o configure: tidy up comments [202] - o connect: fix typo on error message - o cookie: fix rejection when tabs in value [189] - o curl-wolfssl.m4: fix to use the correct value for pkg-config directory [36] - o curl.h: replace macros with C++-friendly method to enforce 3 args [110] - o curl_ctype.h: fix spelling in a couple of locally used macros [28] - o curl_get_line: error out on read errors [9] - o curl_get_line: fix potential infinite loop when filename is a directory [46] - o curl_ngtcp2: extend and update callbacks for 1.22.0+ [165] - o curl_ntlm_core: drop redundant PP condition [140] - o curl_ntlm_core: use wolfCrypt DES API with wolfSSL [200] - o curl_setup.h: drop stray/unused `USE_OPENSSL_QUIC` guard [210] - o curl_sha512_256: support delegating to wolfSSL API [149] - o curl_version_info.md: clarify age details [69] - o CURLOPT_HAPROXY_CLIENT_IP.md: mention assumption on data format [96] - o CURLOPT_RTSP_SESSION_ID.md: clarify reuse "dangers" [270] - o CURLOPT_RTSP_SESSION_ID.md: expand the comment [267] - o CURLOPT_RTSP_SESSION_ID.md: minor language fix - o CURLOPT_SOCKS5_AUTH.md: an access property [212] - o CURLOPT_SSL_CTX_FUNCTION.md: expand on effects connection reuse [105] - o CURLOPT_UPLOAD_FLAGS.md: expand [223] - o curlx_now(), prevent zero timestamp [93] - o DEPRECATE: fix minor release number typo - o digest: pass in the user name quoted (as well) [34] - o dns: https-eyeballing async [229] - o dnscache: own source file, improvements [116] - o docs/cmdline-opts/write-out.md: tls_earlydata was adeded in 8.13.0 - o docs/cmdline-opts: tidy up retry-connrefused [190] - o docs/lib: fix typos [53] - o docs/libcurl: improve easy setopt examples [266] - o docs: clarify retry-max-time timing [294] - o docs: CURLOPT_LOGIN_OPTIONS is a login property [228] - o docs: enable more compiler warnings for C snippets, fix 3 finds [71] - o docs: list more dependencies for running Python HTTP tests [123] - o docs: mention more zip bomb precautions [166] - o docs: minor wording tweaks - o docs: noproxy wants the punycoded hostname version [214] - o docs: SSH host verification is done at connect time [197] - o docs: use the correct CURLOPT_WRITEFUNCTION signature [142] - o doh: fix memory-leak when doing a second DoH resolve [55] - o doh: remove superfluous doh_req check [222] - o examples/websocket: fix to sleep more on Windows [92] - o examples: drop warning silencers no longer hit [14] - o examples: fix typo in comment [75] - o file: init fd to -1 to prevent close fd 0 on early failure [40] - o fopen: for temp files, inherit permissions only for owner [146] - o ftp: do not strdup DATA hostname [29] - o ftp: make the MDTM date parser stricter (again) [115] - o ftp: reject PWD responses containing control characters [95] - o gcc: guard `#pragma diagnostic` in core code for <4.6 [94] - o generate.bat: remove extra % from VC11 and VC12 runs - o genserv.pl: make external calls safe [119] - o getinfo: initialize `PureInfo` field `used_proxy` [43] - o getinfo: repair CURLINFO_TLS_SESSION [193] - o gnutls: fix clang-tidy warning with !verbose [126] - o gtls: fail for large files in `load_file()` [174] - o h3: HTTPS-RR use in HTTP/3 [221] - o Happy Eyeballs: add resolution time delay [238] - o haproxy: use correct ip version on client supplied address [275] - o hostip: clear the sockaddr_in6 structure before use [20] - o hostip: init the curl_jmpenv_lock appropriately [278] - o hostip: resolve user supplied ip addresses [259] - o HSTS: cap the list [177] - o hsts: make the HSTS read callback handle name dupes [141] - o hsts: skip expired HSTS entries read from file [188] - o hsts: when a dupe host adds subdomains, use that [130] - o http2: clear the h2 session at delete [99] - o http2: prevent secure schemes pushed over insecure connections [181] - o http2: return error on OOM in push headers [65] - o HTTP3.md: drop outdated mentions of OpenSSL-QUIC [2] - o http: clear credentials better on redirect [204] - o http: clear digest nonce on cross-orgin redirect [269] - o http: clear the proxy credentials as well on port or scheme change [246] - o http: fix auth_used and auth_avail [154] - o http: fix Curl_compareheader for multi value headers [11] - o http: make Curl_compareheader handle multiple commas in header - o http: on 303, switch to GET [208] - o http: use header_has_value() instead of duplicate code [251] - o imap: reset the UIDVALIDITY state between transfers [7] - o include: drop 'will' from public headers [73] - o INSTALL.md: update Cygwin instructions [198] - o keylog.h: replace literal number with macro in declaration [171] - o keylog: drop unused/redundant includes and guards [172] - o ldap: drop duplicate `ldap_set_option()` on Windows [42] - o ldap: fix to initialize cleartext connection on Windows [49] - o lib1560: fix comment typo - o lib1960: fix test failure [255] - o lib: accept larger input to md5/hmac/sha256/sha512 functions [194] - o lib: always use Curl_1st_fatal instead of Curl_1st_err [89] - o lib: fix typos in comments [240] - o lib: make resolving HTTPS DNS records reliable: [176] - o lib: minor comment typos [237] - o lib: move request specific allocations to the request struct [256] - o lib: replace `PRI*32` printf masks with C89 ones [201] - o libssh2: allocate libssh2-friendly memory in kbd_callback [225] - o libssh2: fix error handling on quote errors [21] - o libssh: fix 64-bit printf mask for mingw-w64 <=6.0.0 [215] - o libssh: fix `-Wsign-compare` in 32-bit builds [217] - o libssh: path length precaution [164] - o libssh: propagate error back in SFTP function [178] - o libtest: drop duplicate include [111] - o location/follow: mention netrc [138] - o man: fix argument type for `CURLSHOPT_[UN]SHARE` options [211] - o mbedtls: cleanup more without care for 'initialized' [262] - o mbedtls: fix ECJPAKE matching [135] - o mbedtls: remove failf() call with first argument as NULL [249] - o md4, md5: switch to wolfCrypt API in wolfSSL builds [139] - o mime: only allow 40 levels of calls [241] - o misc: fix code quality findings [209] - o mk-ca-bundle.pl: make `ca-bundle.crt` timestamp match `certdata.txt`'s [44] - o multi: enhance pending handles fairness [284] - o multi: fix connection retry for non-http [180] - o multi: improve wakeup and wait code [118] - o netrc: find login-less password when user is given in URL [6] - o netrc: remove unused parsenetrc() macro for netrc-disabled [121] - o netrc: skip malformed macdef lines [67] - o openssl channel_binding: lookup digest algorithm without NID [117] - o openssl: drop obsolete SSLv2 logic [27] - o openssl: fix build with 4.0.0-beta1 no-deprecated [184] - o openssl: fix memory leaks in ECH code (OpenSSL 3) [78] - o openssl: fix unused variable warnings in !verbose builds [252] - o openssl: trace count of found / imported Windows native CA roots [8] - o OS400: add new definitions to the ILE/RPG binding. [153] - o os400sys: fix typo in comment (symetry -> symmetry) [58] - o parsedate: bsearch the time zones [232] - o parsedate: fix wrong treatment of "military time zones" [182] - o parsedate: refactor [230] - o perl: harden external command invocations [133] - o progress: count amount of data "delivered" to application [66] - o protocol.h: fix the CURLPROTO_MASK [31] - o protocol: disable connection reuse for SMB(S) [199] - o protocol: use scheme names lowercase [38] - o proxy: chunked response, error code [143] - o pytest: add additional quiche check for flaky test_05_01 [22] - o pytest: check 429 handling [268] - o rand: use `BCryptGenRandom()` in UWP builds [88] - o ratelimit: reset on start [150] - o request: reset resp_trailer in new requests [186] - o runtests: skip setting ed25519 SSH key format [264] - o rustls: fix memory leak on repeated SSLKEYLOGFILE fails [280] - o rustls: handle EOF during initial handshake [203] - o schannel: increase renegotiation timeout to 60 seconds [261] - o scripts: drop redundant double-quotes: `"$var"` -> `$var` (Perl) [109] - o scripts: harden / tidy up more Perl `system()` calls [70] - o sectrust: fail on missing OCSP stapling [250] - o sendf: fix CR detection if no LF is in the chunk [219] - o setopt: clear proxy auth properties when switching [192] - o setopt: fix typos in comments [257] - o setopt: move CURLOPT_CURLU [260] - o setup connection filter: mark as setup [234] - o sha256, sha512_256: switch to wolfCrypt API [147] - o sha256: support delegating to wolfSSL API [148] - o share: concurrency handling, easy updates [104] - o share: do bitshifts after the type is checked to be valid [216] - o socks: reject zero-length GSSAPI/SSPI tokens from proxy [157] - o socks: use dns filter for resolving [244] - o spelling: fix typos [173] - o src: use ftruncate() unconditionally [128] - o sshserver.pl: harden more `system()` calls [81] - o sshserver.pl: pass command-line to `system()` safely [82] - o strerr: correct the strerror_s() return code condition [25] - o sws: fix potential OOB write [80] - o synctime: fix off-by-one read and write to a read-only buffer (Windows) [85] - o test 766: flag as timing-dependent [136] - o test1675: unit tests for URL API helper functions [248] - o test459: switch to mode="warn" for stderr check [5] - o testcurl.pl: replace shell commands with Perl `rmtree()` [76] - o tests/unit/README: describe how to unit test static functions [60] - o tests: avoid infinite recursion for `make check` [253] - o tests: use %b64[] instead of "raw" base64 [245] - o tool: check for curlinfo->age when determining if ssh backend [77] - o tool: fix memory mixups [106] - o tool: fix retries in parallel mode [137] - o tool: fix two more allocator mismatches [155] - o tool_cb_hdr: only truncate etags output when regular file [129] - o tool_cb_rea: make waitfd() return void [168] - o tool_cb_wrt: fix no-clobber error handling [39] - o tool_cfgable: free the SSL signature algorithms [62] - o tool_dirhie: fix to create drive-relative directory [276] - o tool_formparse: propagate my_get_line errors when reading headers [102] - o tool_getparam: use correct free function for libcurl memory [68] - o tool_ipfs: accept IPFS gateway URL without set port number [13] - o tool_msgs: avoid null pointer deref for early errors [98] - o tool_operate: actually apply the --parallel-max-host limit [167] - o tool_operate: drop the scheme-guessing in the -G handling [54] - o tool_operate: fix condition for loading `curl-ca-bundle.crt` (Windows) [79] - o tool_operate: fix memory-leak on failed uploads [124] - o tool_operate: fix minor memory-leak on early error [23] - o tool_operate: reset the upload glob counter for next URL [162] - o tool_operhlp: fix `add_file_name_to_url()` result on OOM [32] - o tool_operhlp: iterate through all slashes to find name [114] - o tool_operhlp: propagate low-level OOM in `add_file_name_to_url()` [112] - o tool_setopt: return error on OOM correctly [152] - o tool_urlglob: fix memory-leak on glob range overflow [19] - o top-complexity: prevent filename-based shell injection risk [101] - o transfer: clear the old autoreferer [236] - o transfer: clear the URL pointer in OOM to avoid UAF [179] - o transfer: enable custom methods again on next transfer [30] - o transfer: enhance secure check [10] - o unit1675: fix `-Wformat-signedness` [274] - o url: do not reuse a non-tls starttls connection if new requires TLS [145] - o url: improve connection reuse on negotiate [160] - o url: init req.no_body in DO so that it works for h2 push [161] - o url: set default upload flags to CURLULFLAG_SEEN [224] - o url: use the socks type for socks proxy [47] - o url: use URL for url even in comments [52] - o urlapi: fix handling of "file:///" [122] - o urlapi: make dedotdotify handle leading dots correctly [97] - o urlapi: same origin tests [213] - o urlapi: stop extracting hostname from file:// URLs on Windows [247] - o urlapi: verify the last letter of a scheme when set explicitly [16] - o urldata.h: fix typo and lingering backtick [279] - o urldata: connection bit ipv6_ip is wrong [59] - o urldata: import port types and conn destination format [57] - o urldata: make hstslist only present in HSTS builds [120] - o urldata: make speeder_c uint32 [37] - o urldata: move cookiehost to struct SingleRequest [242] - o urldata: remove trailers_state [17] - o vquic: fix variable name in fallback code [207] - o vtls: fix comment typos and tidy up a type [285] - o vtls: log when key logging is enabled. [288] - o vtls_scache: check reentrancy [243] - o vtls_scache: include cert_blob independently of verifypeer [231] - o wolfssl: document v5.0.0 (2021-11-01) as minimum required [151] - o wolfssl: fix `-Wmissing-prototypes` [233] - o wolfssl: fix handling of abrupt connection close [24] - o write-out.md: minor language fix [273] - o write-out.md: tls_earlydata was adeded in 8.13.0 - o ws: fix a blocking curl_ws_send() to report written length correctly [258] - o x509asn1: fix to return error in an error case from `encodeOID()` [83] - o x509asn1: fixed and adapted for ASN1tostr unit testing [48] - o x509asn1: improve encodeOID [72] + o altsvc: continue after unknown parameters [198] + o asyn-thrdd: retry link-local ipv6 if missing scope id [118] + o autotools: minor fixes and improvements [33] + o build: always use local `inet_pton()`/`inet_ntop()` implementations [56] + o build: assume POSIX `select()` is available [166] + o build: clear `Require.private` for static-only builds in `libcurl.pc` [188] + o build: drop `dirent.h` and `opendir()` detections on Windows [186] + o build: drop detecting `gettimeofday()` on Windows [184] + o build: drop superfluous `STDC_HEADERS` macro [51] + o build: enable thread-safe `getaddrinfo()` for OpenBSD [35] + o build: minor debug option message fixes/improvements [200] + o build: require `!NDEBUG` for debug-enabled (aka development) builds [202] + o build: strip duplicate spaces after `Libs.private:` in `libcurl.pc` [191] + o build: strip trailing spaces from `libcurl.pc` [194] + o cd2nroff: fix backslashes for 4-space indent lines [104] + o cd2nroff: stricter checks for asterisks for italics [73] + o cf-ngtcp2-cmn: de-duplicate `ngtcp2_conn_client_new()` call code [156] + o cf-ngtcp2-cmn: initialize new callback ptr for ngtcp2 1.24.0+ [52] + o cf-socket: avoid broken NetBSD SOCK_NONBLOCK [275] + o cf-socket: disable TCP SYN retransmissions for localhost on Windows [164] + o cfilters: fix event-based connection shutdown [91] + o clock: save one call [286] + o cmake/FindLibgsasl: fix to set `LIBGSASL_VERSION` with pkg-config detection [229] + o cmake: check libgsasl version at configure time [277] + o cmake: dedupe expressions into local vars in `cmake_uninstall.in.cmake` [9] + o cmake: fix not to build `tunits` when `BUILD_CURL_EXE=OFF` [7] + o cmake: flatten build tree, tidy up base dir variables [12] + o cmake: minor improvements to `cmake_uninstall.in.cmake` [54] + o cmake: optimize OpenSSL fork detection [228] + o cmake: replace `remove` command with `rm` and pass arg safely [11] + o cmake: robustify base path in local file reference [15] + o cmake: stop probing unused `float.h` for `STDC_HEADERS` [10] + o cmake: use built-in variable and target property dump functions with CMake 4.5+ [155] + o config-riscos.h: delete handcrafted RISC OS config header, in favor of autotools [178] + o config-win32.h: drop UWP, c-ares, simplify more [231] + o config-win32.h: limit use to MSVC IDE Project builds [193] + o configure: clarify --enable-debug option [133] + o configure: fix misleading error messages [42] + o configure: link `-lcrypt32` instead of `-lm` for wolfSSL on Windows [79] + o configure: only check in the watt library if WATT_ROOT is set [120] + o configure: remove double check for GnuTLS [21] + o configure: set ldap lib to no by default for non-finds [18] + o conncache: apply multi limits to transfers using a shared pool [41] + o conncache: conn upkeep/alive: move and enhance [152] + o conncache: connection alive checks intervals [20] + o conncache: don't assume curl_off_t increment wrap-around [138] + o conncache: guess maxconnects different [289] + o connect: connection close tweaks [112] + o connect: only set connect timer on first socket [206] + o connection reuse: age check [261] + o connection reuse: check SSL configs when doing a scheme upgrade [249] + o connections: use admin handles only for maintenance [213] + o content_encoding: exact-match the identity transfer-coding token [189] + o content_encoding: give a clear error on multi-member gzip [46] + o cookie: cookies set for an exact PSL domain is host-only [304] + o cookie: improve TAB handling [258] + o cookie: refuse to load cookies set against a PSL domain [139] + o CREDENTIALS.md: remove comment about empty user/pass [50] + o ctype: exclude control bytes from ISPRINT and ISGRAPH [119] + o curl: help category cleanups [169] + o curl_gssapi: document/update feature availability [145] + o curl_threads: always use native threads/mutex on Windows [185] + o curl_trc: remove unused expire timers [147] + o curl_url_set.md: expand the CURLU_NO_AUTHORITY description [134] + o curl_ws_meta.md: polish and better vocabulary [19] + o CURLOPT_HEADERFUNCTION.md: document folded header unfolding [53] + o CURLOPT_SOCKOPTFUNCTION.md: ALREADY_CONNECTED does not work for HTTP/3 [262] + o CURLOPT_SSH_*_KEYFILE: used for setting up, then no more [48] + o CURLOPT_UNRESTRICTED_AUTH.md: 'Authorization', not 'Authentication' [74] + o CURLOPT_USERNAME.md: ambient username caveats [271] + o CURLSHOPT_(UN)SHARE.md: do not modify shares while in use [44] + o curlx_inet_ntop: return `CURLcode`, drop setting `errno` [237] + o curlx_inet_pton: drop setting `errno` on error [236] + o DEPRECATE.md: HTTP/2 Server Push gets removed in March 2027 [174] + o dict: avoid busy-loop in sendf() when the socket is not writable [99] + o dist: fix to drop test bundle .c files from the source tarball [305] + o dnsd: fix bounds check in `read_https_alpn_part()` [143] + o docs/INTERNALS.md -> docs/DEPENDENCIES.md [127] + o docs: clarify that cookies need domain set to match [224] + o docs: connection reuse behavior for socket callbacks [219] + o docs: make 5 example snippets compile cleanly with clang [192] + o docs: mention possible auth option conflicts [114] + o docs: remove doubled word in SECURITY-ADVISORY.md [183] + o DoH: improvements [203] + o easy: fix unused global on non-Windows [292] + o easy_lock: silence `portability-no-assembler` with clang-tidy 23.1.0+ [291] + o FAQ: correct an option typo [278] + o file: support directory listing on Windows [205] + o filter: change time reporting [235] + o FTP: fix TLS session reuse on the data connection [80] + o ftp: reject control bytes in ACCT and alternative-to-user [26] + o gitignore: maintenance updates [170] + o gopher: fix partial sends of CRLF [288] + o gopher: reject CR and LF in the selector [1] + o h2 push: use squeaky clean easy handle [246] + o h2: bootstrap max streams from multi handle if in use [132] + o h3-proxy: fix NULL deref when non-:status header arrives before :status [167] + o Happy Eyeballing v3: resolution delay of 25ms [232] + o header api: add guards [168] + o headers: name the arguments the way the definitions name them [234] + o HISTORY.md: PSL support in 2015 + o HISTORY: add when c-ares support was introduced (2004) + o HISTORY: September 1999: started using CVS + o hostip: only cache negative resolves for authoritative answers [16] + o hsts: only match the exact strings [269] + o http digest: tie peer/credentials on input [264] + o http2: make server push transfers inherit share from parent [81] + o http2: remove assert in ingress processing [272] + o http: avoid length underflow in Curl_compareheader [78] + o http: custom Authorization: header overrides Negotiate [223] + o http: fix non-tunneling proxy hostname use [116] + o http: stop dropping large custom headers [69] + o http: trim custom header name before the Authorization drop [17] + o httpsrr: DoH with HTTPS, fix response handling [113] + o idn: restore `MultiByteToWideChar()` `MB_ERR_INVALID_CHARS` flag [103] + o imap: APPEND CRLF fix [256] + o include: include when building for modern Linux. [308] + o INSTALL.md: add building-from-source overview section [29] + o INTERNALS.md: require quiche 0.20.0+ [101] + o ipv6 scope_id: set from first peer [242] + o keylog: add a random size argument to Curl_tls_keylog_write() [180] + o ldap: base64-encode LDIF values beginning with colon or less-than [218] + o ldap: reject control characters in URL-decoded filter values [196] + o ldap: support empty username and password [106] + o ldap: support insecure mode for Windows native LDAP [3] + o lib1587: fix gcc `-Wconversion` with LibreSSL on Windows, test in CI [6] + o lib2405: adjust for non-threaded builds [149] + o lib: add "Curl_" prefix to two global functions [84] + o lib: add multi_wakeup_internal [86] + o lib: drop unused `system_win32.h` includes [290] + o lib: fix 'ns' -> 'us' in trace messages [57] + o lib: new easy option string storage [215] + o lib: optimize struct layouts for reduced memory usage [212] + o lib: ratelimit timestamps [14] + o lib: silence gcc-16 compiler warnings `-Wmaybe-uninitialized` [243] + o lib: update mentions of the legacy "sessionhandle" [157] + o libcurl.md: emphasize that the output needs checking [259] + o libcurl.pc: add `License` tag [190] + o libcurl.pc: add Copyright tag to the pkgconf file + o libcurl.pc: add the Link.ABI and Source tags [210] + o macos sectrust: fail ocsp verify when not builtin [252] + o Makefile.am: improve etags [257] + o mbedtls: enforce verifyhost when verifypeer is disabled [208] + o mbedtls: replace `memset()` with `psa_hash_operation_init()` [28] + o md5: replace magic numbers with `MD5_DIGEST_LEN` [122] + o mime.c: avoid integer overflow in base64 size calculation [105] + o mime: reject CR and LF in mail part name and filename [30] + o mod_curltest: fix compiler warnings [49] + o mprintf: acknowledge %F [245] + o mprintf: avoid never-ending loop for positive-infinite [247] + o mprintf: fix long double output [250] + o mqtt: reject control bytes in the topic [43] + o multi: cap expire times to INT_MAX internally [216] + o multi: forbid curl_easy_pause from within multi socket callback [22] + o multi: hold timeout values in 'int' instead of 'long' [165] + o multi: remove #if 0'ed code that uses old struct [150] + o multi: shrink expire timer indices [199] + o multi: timeout improvements [209] + o multi: use index list for expire timeouts [197] + o multi: xfer table initial size and growth [255] + o multihandle: move two struct fields [163] + o ngtcp2+openssL: fix early data [225] + o ngtcp2: avoid NULL deref in cf_ngtcp2_send [260] + o ngtcp2: clean up after ngtcp2 in `curl_global_cleanup` [126] + o ngtcp2: let verify failures win over expiry processing errors [98] + o openldap: handle Curl_sasl_continue() returns better [45] + o openssl+sectrust: fix session reuse [4] + o openssl+sectrust: move session verified set into result check [82] + o openssl: avoid conn reuse if provider is used [214] + o openssl: avoid strlen() on the data from OpenSSL [280] + o openssl: aws-lc ocsp workaround [263] + o openssl: drop unused pre-OpenSSL3 `ctx_option_t` typedef [8] + o openssl: fix DER buffer leak in Apple SecTrust verification [217] + o openssl: no server cert is only okay if also not pinned [226] + o openssl: prefer modern API flavors for `EVP_MD_CTX` new/free [47] + o openssl: replace stray legacy API variant with `EVP_DigestInit_ex()` [27] + o os400: port latest header files changes to ILE/RPG interface [241] + o os400: rewrite upper ebcdic wrappers using dynbuf [227] + o progress: cleanup, less memory [179] + o protocol: simpler Curl_getn_scheme runs faster [239] + o proxy: CONNECT trailers handling [251] + o psl: update a comment to understandable English [162] + o pytest: update two H3 tests for nghttp3 1.18.0+ [158] + o quic: upload improvements [276] + o quiche: set the max field section size [100] + o rtsp: refactor method handling and improve error checks [161] + o runtests: allow comments in `setenv` section, merge sections in test433 [89] + o runtests: fix `mode="warn"` tests passing unconditionally, fix test 1752 [66] + o runtests: flush cached test parts when (re)loading a file [95] + o runtests: restore `-k` option and actively process as no-op [32] + o sasl: fix zero-length response encoding [36] + o schannel: add ALPN support for mingw-w64 <9 and =128-bit pointers [107] + o url: fix handling of empty user in NTLM matching [221] + o url: fix negotiate/ntlm connection reuse [176] + o url: reject control codes in credentials set via CURLOPT [70] + o urlapi: allow URLs to not have userauth (hostname) [92] + o urlapi: avoid dedotdotify() if possible [182] + o urlapi: clear password buffer on error path [121] + o urlapi: do not keep an internal port string [31] + o urlapi: improved return codes [148] + o urlapi: preserve empty markers in relative URLs [61] + o urldata: cleanups [175] + o urldata: drop four strings from the aptr struct [136] + o urldata: sort the connectdata struct fields by size [177] + o VERSIONS.md: document Rock-solid curl releases [201] + o vms: fix symbol typo and missing closing quotes in `config_h.com` [124] + o vquic: add Curl_ prefix to some global functions [76] + o vquic: initialize new callback slot for nghttp3 v1.18.0+ [87] + o vquic: silence `-Wmissing-field-initializers` for nghttp3/ngtcp2 callback tables [159] + o vquic: use ngtcp2 v1.25.0 new close2 callback [154] + o vssh: keyfile use cleanups [83] + o vssh: silence gcc-11 `-Wnull-dereference`, dedupe `CURL_EASY_STR()` calls [240] + o vtls: move 'native_ca_store' ssl_config_data => ssl_primary_config [211] + o vtls_scache: use case sensitive path match + o VULN-DISCLOSURE-POLICY.md: issues that should be found by tests are LOW [5] + o wcurl: import v2026.08.30 [279] + o websocket: pause writing and meta data fix [135] + o winsock: drop redundant version checks at initialization [284] + o wolfssl: do not run Curl_wssl_setup_x509_store() twice [265] + o wolfssl: fix build for wolfssl without bio chain support [75] + o ws: fix write callback error handling [204] + o ws: pause/unpause write handling [55] This release includes the following known bugs: @@ -313,316 +331,333 @@ For all changes ever done in curl: Planned upcoming removals include: + o HTTP/2 Server Push o local crypto implementations o NTLM o SMB - o TLS-SRP support See https://curl.se/dev/deprecate.html This release would not have looked like this without help, code, reports and advice from friends like these: - Alex Hamilton, am-perip on hackerone, Arkadi Vainbrand, bird on github, - BlackFuffey on github, Carlos Carrillo, Carlos Henrique Lima Melara, - crawfordxx, Cutiapreta on hackerone, Dag-Erling Smørgrav, Dan Arnfield, - Dan Fandrich, Daniel McCarney, Daniel Schulte, Daniel Stenberg, - dependabot[bot], Dexter Gerig, Dio Putra, Dwij Mehta, Ercan Ermis, - fds242 on github, finkjsc on github, Fiona Klute, Flavio Amieiro, - Geeknik Labs, Greg Kroah-Hartman, Harry Sintonen, Henrique Pereira, - herbenderbler on github, Ian Spence, Izan on hackerone, James Fuller, - Jason Stangroome, John Haugabook, Juan Belón, Kai Pastor, Kaixuan Li, kpcyrd, - lg_oled77c5pua on hackerone, M42kL33 on hackerone, m777m0 on hackerone, - Marcel Raad, Martin Dürrmeier, Mehtab Zafar, Michael Hendricks, - Michael Kaufmann, Muhamad Arga Reksapati, Ngoc Hieu, nitrogene on github, - Orgad Shaneh, Osama Hamad, Otis Cui Lei, Patrick Monnerat, Quac Tran, - Ray Satiro, renovate[bot], Richard Tollerton, Rob Crittenden, - Samuel Henrique, Scott Boudreaux, Sergey Fedorov, sergio-nsk on github, - Stefan Eissing, Ted Lyngmo, Terrance Wong, Tim Omta, Viktor Szakats, - Vladimír Marek, xkilua on hackerone, Yalguun Tumenkhuu, Yedaya Katsman, - Yiwei Hou, Yoshiro Yoneya - (73 contributors) + 11soda11, 1rhino2 on hackerone, accl on hackerone, AlanKingPL, Alb3e3, + Alexey Samsonov, Alhuda Khan, Anton Karpov, anupamme, Arham Wani, + Athos Ribeiro, Bartel Sielski, Bigtang on hackerone, Bill Mill, + Bryan Henderson, Carlos Henrique Lima Melara, CatboxParadox, + Christian Ullrich, Christoph Reiter, claudex on github, Collin Funk, + cybertron10 on github, Dan Fandrich, Daniel Gustafsson, Daniel Stenberg, + dependabot[bot], ed0d2b2ce19451f2 on github, Emmanuel Ugwu, Eunsoo Kim, + felix h, firexinghe on github, Fred Klassen, GLaDOS-418 on github, + Graham Campbell, Hendrik Hübner, HwangRock, Igor Morgenstern, itzTanos29, + Jace Laquerre, Jayanth Vennamreddy, Joel Depooter, Johannes Schindelin, + John Verne, Keng-Yu Lin, Kieran Gannon, kit-ty-kate on github, + Laurent Sabourin, Manuel Sánchez-Guijarro, Marcel Jamin, marco-jardim, + Martin Dukek, martin-fzi on github, Matthew John Cheetham, Max Dymond, + Memduh Çelik, Patrick Monnerat, Pavel Sobolev, pszemus on github, + Ralf Mueller, Ramesh Adhikari, Rarylson Freitas, Ray Satiro, renovate[bot], + Rito Rhymes, RMMoreton on github, Roger Leigh, Ron Kuper, Ross Burton, + Sameeh Jubran, Sam James, Samuel Dainard, Samuel Henrique, Scott Talbert, + sdgh179 on github, Sergei Zimmerman, smaeljaish on hackerone, Stanislav Fort, + Stefan Eissing, Stephan Zeisberg, stze on hackerone, Thomas Chauchefoin, + Viktor Szakats, xmoezzz on github, Yoshiro Yoneya, zaveshaa + (85 contributors) References to bug reports and discussions on issues: - [1] = https://curl.se/bug/?i=20880 - [2] = https://curl.se/bug/?i=20914 - [3] = https://curl.se/bug/?i=20889 - [4] = https://curl.se/bug/?i=20908 - [5] = https://curl.se/bug/?i=20910 - [6] = https://curl.se/bug/?i=20950 - [7] = https://curl.se/bug/?i=20962 - [8] = https://curl.se/bug/?i=20899 - [9] = https://curl.se/bug/?i=20958 - [10] = https://curl.se/bug/?i=20951 - [11] = https://curl.se/bug/?i=20894 - [12] = https://curl.se/bug/?i=20898 - [13] = https://curl.se/bug/?i=20957 - [14] = https://curl.se/bug/?i=20896 - [15] = https://curl.se/bug/?i=20886 - [16] = https://curl.se/bug/?i=20893 - [17] = https://curl.se/bug/?i=20960 - [18] = https://curl.se/bug/?i=20846 - [19] = https://curl.se/bug/?i=20956 - [20] = https://curl.se/bug/?i=20885 - [21] = https://curl.se/bug/?i=20883 - [22] = https://curl.se/bug/?i=20952 - [23] = https://curl.se/bug/?i=20954 - [24] = https://curl.se/bug/?i=21002 - [25] = https://curl.se/bug/?i=20955 - [26] = https://curl.se/bug/?i=18022 - [27] = https://curl.se/bug/?i=20945 - [28] = https://curl.se/bug/?i=20810 - [29] = https://curl.se/bug/?i=20953 - [30] = https://curl.se/bug/?i=21037 - [31] = https://curl.se/bug/?i=21031 - [32] = https://curl.se/bug/?i=21011 - [33] = https://curl.se/bug/?i=20926 - [34] = https://curl.se/bug/?i=20940 - [35] = https://curl.se/bug/?i=20944 - [36] = https://curl.se/bug/?i=20943 - [37] = https://curl.se/bug/?i=21036 - [38] = https://curl.se/bug/?i=21033 - [39] = https://curl.se/bug/?i=20939 - [40] = https://curl.se/bug/?i=21029 - [41] = https://curl.se/bug/?i=20839 - [42] = https://curl.se/bug/?i=20930 - [43] = https://curl.se/bug/?i=21020 - [44] = https://curl.se/bug/?i=20528 - [45] = https://curl.se/bug/?i=20840 - [46] = https://curl.se/bug/?i=20823 - [47] = https://curl.se/bug/?i=21025 - [48] = https://curl.se/bug/?i=21013 - [49] = https://curl.se/bug/?i=20927 - [50] = https://curl.se/bug/?i=20934 - [51] = https://curl.se/bug/?i=20934 - [52] = https://curl.se/bug/?i=20935 - [53] = https://curl.se/bug/?i=20933 - [54] = https://curl.se/bug/?i=20992 - [55] = https://curl.se/bug/?i=20929 - [56] = https://curl.se/bug/?i=21111 - [57] = https://curl.se/bug/?i=20918 - [58] = https://curl.se/bug/?i=20923 - [59] = https://curl.se/bug/?i=20919 - [60] = https://curl.se/bug/?i=21018 - [61] = https://curl.se/bug/?i=20909 - [62] = https://curl.se/bug/?i=20915 - [63] = https://curl.se/bug/?i=21057 - [64] = https://curl.se/bug/?i=20911 - [65] = https://hackerone.com/reports/3636044 - [66] = https://curl.se/bug/?i=20787 - [67] = https://curl.se/bug/?i=21049 - [68] = https://curl.se/bug/?i=21075 - [69] = https://curl.se/bug/?i=21052 - [70] = https://curl.se/bug/?i=21007 - [71] = https://curl.se/bug/?i=21006 - [72] = https://curl.se/bug/?i=21003 - [73] = https://curl.se/bug/?i=21005 - [74] = https://curl.se/bug/?i=20916 - [75] = https://curl.se/bug/?i=21001 - [76] = https://curl.se/bug/?i=21053 - [77] = https://curl.se/bug/?i=21050 - [78] = https://curl.se/bug/?i=20993 - [79] = https://curl.se/bug/?i=20989 - [80] = https://curl.se/bug/?i=20988 - [81] = https://curl.se/bug/?i=20997 - [82] = https://curl.se/bug/?i=20996 - [83] = https://curl.se/bug/?i=20991 - [84] = https://curl.se/bug/?i=20990 - [85] = https://curl.se/bug/?i=20987 - [86] = https://curl.se/bug/?i=20999 - [87] = https://curl.se/bug/?i=20814 - [88] = https://curl.se/bug/?i=20983 - [89] = https://curl.se/bug/?i=20980 - [90] = https://curl.se/bug/?i=20698 - [91] = https://curl.se/bug/?i=20673 - [92] = https://curl.se/bug/?i=20978 - [93] = https://curl.se/bug/?i=21034 - [94] = https://curl.se/bug/?i=20892 - [95] = https://curl.se/bug/?i=20949 - [96] = https://curl.se/bug/?i=21042 - [97] = https://curl.se/bug/?i=20974 - [98] = https://curl.se/bug/?i=20967 - [99] = https://curl.se/bug/?i=20975 - [100] = https://curl.se/bug/?i=20966 - [101] = https://curl.se/bug/?i=20969 - [102] = https://curl.se/bug/?i=20963 - [103] = https://curl.se/bug/?i=20965 - [104] = https://curl.se/bug/?i=20870 - [105] = https://curl.se/bug/?i=21164 - [106] = https://curl.se/bug/?i=21099 - [107] = https://curl.se/bug/?i=20763 - [108] = https://curl.se/bug/?i=20407 - [109] = https://curl.se/bug/?i=21009 - [110] = https://curl.se/bug/?i=20709 - [111] = https://curl.se/bug/?i=21046 - [112] = https://curl.se/bug/?i=21011 - [113] = https://curl.se/bug/?i=21063 - [114] = https://curl.se/bug/?i=21165 - [115] = https://curl.se/bug/?i=21041 - [116] = https://curl.se/bug/?i=20864 - [117] = https://curl.se/bug/?i=20590 - [118] = https://curl.se/bug/?i=20832 - [119] = https://curl.se/bug/?i=20971 - [120] = https://curl.se/bug/?i=21068 - [121] = https://curl.se/bug/?i=21067 - [122] = https://curl.se/bug/?i=21070 - [123] = https://curl.se/bug/?i=21110 - [124] = https://curl.se/bug/?i=21062 - [125] = https://curl.se/bug/?i=21061 - [126] = https://curl.se/bug/?i=21060 - [127] = https://curl.se/bug/?i=20968 - [128] = https://curl.se/bug/?i=21109 - [129] = https://curl.se/bug/?i=21103 - [130] = https://curl.se/bug/?i=21108 - [131] = https://curl.se/bug/?i=21170 - [132] = https://curl.se/bug/?i=21167 - [133] = https://curl.se/bug/?i=21097 - [134] = https://curl.se/bug/?i=21098 - [135] = https://curl.se/bug/?i=21264 - [136] = https://curl.se/bug/?i=21155 - [137] = https://curl.se/bug/?i=20669 - [138] = https://curl.se/bug/?i=21091 - [139] = https://curl.se/bug/?i=21093 - [140] = https://curl.se/bug/?i=21096 - [141] = https://curl.se/bug/?i=21201 - [142] = https://curl.se/bug/?i=21265 - [143] = https://curl.se/bug/?i=21084 - [144] = https://curl.se/bug/?i=20936 - [145] = https://curl.se/bug/?i=21082 - [146] = https://curl.se/bug/?i=21092 - [147] = https://curl.se/bug/?i=21090 - [148] = https://curl.se/bug/?i=21078 - [149] = https://curl.se/bug/?i=21077 - [150] = https://curl.se/bug/?i=21086 - [151] = https://curl.se/bug/?i=21080 - [152] = https://curl.se/bug/?i=21083 - [153] = https://curl.se/bug/?i=20672 - [154] = https://curl.se/bug/?i=21274 - [155] = https://curl.se/bug/?i=21150 - [156] = https://curl.se/bug/?i=21263 - [157] = https://curl.se/bug/?i=21159 - [158] = https://curl.se/bug/?i=21144 - [159] = https://curl.se/bug/?i=21135 - [160] = https://curl.se/bug/?i=21203 - [161] = https://curl.se/bug/?i=21194 - [162] = https://curl.se/bug/?i=21402 - [163] = https://curl.se/bug/?i=21134 - [164] = https://curl.se/bug/?i=21193 - [165] = https://curl.se/bug/?i=21152 - [166] = https://curl.se/bug/?i=21143 - [167] = https://curl.se/bug/?i=21147 - [168] = https://curl.se/bug/?i=21127 - [169] = https://curl.se/bug/?i=21139 - [170] = https://curl.se/bug/?i=21136 - [171] = https://curl.se/bug/?i=21141 - [172] = https://curl.se/bug/?i=21137 - [173] = https://curl.se/bug/?i=21198 - [174] = https://curl.se/bug/?i=21256 - [175] = https://curl.se/bug/?i=21256 - [176] = https://curl.se/bug/?i=21175 - [177] = https://curl.se/bug/?i=21190 - [178] = https://curl.se/bug/?i=21122 - [179] = https://curl.se/bug/?i=21123 - [180] = https://curl.se/bug/?i=21121 - [181] = https://curl.se/bug/?i=21113 - [182] = https://curl.se/bug/?i=21251 - [183] = https://curl.se/bug/?i=21183 - [184] = https://curl.se/bug/?i=21119 - [185] = https://curl.se/bug/?i=21188 - [186] = https://curl.se/bug/?i=21112 - [187] = https://curl.se/bug/?i=21187 - [188] = https://curl.se/bug/?i=21186 - [189] = https://curl.se/bug/?i=21185 - [190] = https://curl.se/bug/?i=21182 - [191] = https://curl.se/bug/?i=21252 - [192] = https://curl.se/bug/?i=21453 - [193] = https://curl.se/bug/?i=21290 - [194] = https://curl.se/bug/?i=21174 - [195] = https://curl.se/bug/?i=21399 - [196] = https://curl.se/bug/?i=21168 - [197] = https://curl.se/bug/?i=21173 - [198] = https://curl.se/bug/?i=20995 - [199] = https://curl.se/bug/?i=21238 - [200] = https://curl.se/bug/?i=21247 - [201] = https://curl.se/bug/?i=21234 - [202] = https://curl.se/bug/?i=21246 - [203] = https://curl.se/bug/?i=21242 - [204] = https://curl.se/bug/?i=21345 - [206] = https://curl.se/bug/?i=21231 - [207] = https://curl.se/bug/?i=21281 - [208] = https://curl.se/bug/?i=20715 - [209] = https://curl.se/bug/?i=21393 - [210] = https://curl.se/bug/?i=21235 - [211] = https://curl.se/bug/?i=21232 - [212] = https://curl.se/bug/?i=21230 - [213] = https://curl.se/bug/?i=21328 - [214] = https://curl.se/bug/?i=21228 - [215] = https://curl.se/bug/?i=21229 - [216] = https://curl.se/bug/?i=21224 - [217] = https://curl.se/bug/?i=21225 - [218] = https://curl.se/bug/?i=21339 - [219] = https://curl.se/bug/?i=21221 - [220] = https://curl.se/bug/?i=21205 - [221] = https://curl.se/bug/?i=21253 - [222] = https://curl.se/bug/?i=21216 - [223] = https://curl.se/bug/?i=21218 - [224] = https://curl.se/bug/?i=21217 - [225] = https://curl.se/bug/?i=21336 - [226] = https://curl.se/bug/?i=21335 - [227] = https://curl.se/bug/?i=21236 - [228] = https://curl.se/bug/?i=21215 - [229] = https://curl.se/bug/?i=21267 - [230] = https://curl.se/bug/?i=21394 - [231] = https://curl.se/bug/?i=21222 - [232] = https://curl.se/bug/?i=21266 - [233] = https://curl.se/bug/?i=21392 - [234] = https://curl.se/bug/?i=21437 - [235] = https://curl.se/bug/?i=21461 - [236] = https://curl.se/bug/?i=21322 - [237] = https://curl.se/bug/?i=21388 - [238] = https://curl.se/bug/?i=21354 - [239] = https://curl.se/bug/?i=21386 - [240] = https://curl.se/bug/?i=21385 - [241] = https://curl.se/bug/?i=21384 - [242] = https://curl.se/bug/?i=21312 - [243] = https://curl.se/bug/?i=21383 - [244] = https://curl.se/bug/?i=21297 - [245] = https://curl.se/bug/?i=21313 - [246] = https://curl.se/bug/?i=21304 - [247] = https://curl.se/bug/?i=21296 - [248] = https://curl.se/bug/?i=21296 - [249] = https://curl.se/bug/?i=21441 - [250] = https://curl.se/bug/?i=21444 - [251] = https://curl.se/bug/?i=21302 - [252] = https://curl.se/bug/?i=21380 - [253] = https://curl.se/bug/?i=21378 - [254] = https://curl.se/bug/?i=20794 - [255] = https://curl.se/bug/?i=21377 - [256] = https://curl.se/bug/?i=21301 - [257] = https://curl.se/bug/?i=21303 - [258] = https://curl.se/bug/?i=21372 - [259] = https://curl.se/bug/?i=21146 - [260] = https://curl.se/bug/?i=21298 - [261] = https://curl.se/bug/?i=21270 - [262] = https://curl.se/bug/?i=21440 - [263] = https://curl.se/bug/?i=21460 - [264] = https://curl.se/bug/?i=21360 - [265] = https://curl.se/bug/?i=21458 - [266] = https://curl.se/bug/?i=21364 - [267] = https://curl.se/bug/?i=21363 - [268] = https://curl.se/bug/?i=21357 - [269] = https://curl.se/bug/?i=21359 - [270] = https://curl.se/bug/?i=21358 - [273] = https://curl.se/bug/?i=21455 - [274] = https://curl.se/bug/?i=21351 - [275] = https://curl.se/bug/?i=21340 - [276] = https://curl.se/bug/?i=21449 - [278] = https://curl.se/bug/?i=21432 - [279] = https://curl.se/bug/?i=21430 - [280] = https://curl.se/bug/?i=21427 - [284] = https://curl.se/bug/?i=21396 - [285] = https://curl.se/bug/?i=21421 - [288] = https://curl.se/bug/?i=19814 - [291] = https://curl.se/bug/?i=21415 - [294] = https://curl.se/bug/?i=21411 + [1] = https://curl.se/bug/?i=22116 + [2] = https://curl.se/bug/?i=22158 + [3] = https://curl.se/bug/?i=22152 + [4] = https://curl.se/bug/?i=22235 + [5] = https://curl.se/bug/?i=22190 + [6] = https://curl.se/bug/?i=22195 + [7] = https://curl.se/bug/?i=22198 + [8] = https://curl.se/bug/?i=22197 + [9] = https://curl.se/bug/?i=22194 + [10] = https://curl.se/bug/?i=22191 + [11] = https://curl.se/bug/?i=22193 + [12] = https://curl.se/bug/?i=22192 + [13] = https://curl.se/bug/?i=22344 + [14] = https://curl.se/bug/?i=22292 + [15] = https://curl.se/bug/?i=22187 + [16] = https://curl.se/bug/?i=22302 + [17] = https://curl.se/bug/?i=22178 + [18] = https://curl.se/bug/?i=22308 + [19] = https://curl.se/bug/?i=22233 + [20] = https://curl.se/bug/?i=22169 + [21] = https://curl.se/bug/?i=22307 + [22] = https://curl.se/bug/?i=22179 + [23] = https://curl.se/bug/?i=22182 + [24] = https://curl.se/bug/?i=22232 + [25] = https://curl.se/bug/?i=22231 + [26] = https://curl.se/bug/?i=22301 + [27] = https://curl.se/bug/?i=22222 + [28] = https://curl.se/bug/?i=22220 + [29] = https://curl.se/bug/?i=22113 + [30] = https://curl.se/bug/?i=22247 + [31] = https://curl.se/bug/?i=22167 + [32] = https://curl.se/bug/?i=22100 + [33] = https://curl.se/bug/?i=22154 + [34] = https://curl.se/bug/?i=22150 + [35] = https://curl.se/bug/?i=22148 + [36] = https://curl.se/bug/?i=22218 + [37] = https://curl.se/bug/?i=22119 + [38] = https://curl.se/bug/?i=22165 + [39] = https://curl.se/bug/?i=22164 + [40] = https://curl.se/bug/?i=22136 + [41] = https://curl.se/bug/?i=22265 + [42] = https://curl.se/bug/?i=22300 + [43] = https://curl.se/bug/?i=22112 + [44] = https://curl.se/bug/?i=22217 + [45] = https://curl.se/bug/?i=22213 + [46] = https://curl.se/bug/?i=22156 + [47] = https://curl.se/bug/?i=22219 + [48] = https://curl.se/bug/?i=22211 + [49] = https://curl.se/bug/?i=22214 + [50] = https://curl.se/bug/?i=22212 + [51] = https://curl.se/bug/?i=22206 + [52] = https://curl.se/bug/?i=22205 + [53] = https://curl.se/bug/?i=22296 + [54] = https://curl.se/bug/?i=22201 + [55] = https://curl.se/bug/?i=22273 + [56] = https://curl.se/bug/?i=22170 + [57] = https://curl.se/bug/?i=22200 + [58] = https://curl.se/bug/?i=22280 + [59] = https://curl.se/bug/?i=22277 + [60] = https://curl.se/bug/?i=22294 + [61] = https://curl.se/bug/?i=22298 + [62] = https://curl.se/bug/?i=22303 + [63] = https://curl.se/bug/?i=22286 + [64] = https://curl.se/bug/?i=22237 + [65] = https://curl.se/bug/?i=22487 + [66] = https://curl.se/bug/?i=22388 + [67] = https://curl.se/bug/?i=22340 + [68] = https://curl.se/bug/?i=22253 + [69] = https://curl.se/bug/?i=22336 + [70] = https://curl.se/bug/?i=22236 + [71] = https://curl.se/bug/?i=21965 + [72] = https://curl.se/bug/?i=22052 + [73] = https://curl.se/bug/?i=22257 + [74] = https://curl.se/bug/?i=22256 + [75] = https://curl.se/bug/?i=22255 + [76] = https://curl.se/bug/?i=22254 + [77] = https://curl.se/bug/?i=22250 + [78] = https://curl.se/bug/?i=22338 + [79] = https://curl.se/bug/?i=22251 + [80] = https://curl.se/bug/?i=22225 + [81] = https://curl.se/bug/?i=22488 + [82] = https://curl.se/bug/?i=22248 + [83] = https://curl.se/bug/?i=22243 + [84] = https://curl.se/bug/?i=22245 + [85] = https://curl.se/bug/?i=22431 + [86] = https://curl.se/bug/?i=22272 + [87] = https://curl.se/bug/?i=22399 + [88] = https://curl.se/bug/?i=22210 + [89] = https://curl.se/bug/?i=22389 + [90] = https://curl.se/bug/?i=22346 + [91] = https://curl.se/bug/?i=22282 + [92] = https://curl.se/bug/?i=22279 + [93] = https://curl.se/bug/?i=22316 + [94] = https://curl.se/bug/?i=22433 + [95] = https://curl.se/bug/?i=22319 + [96] = https://curl.se/bug/?i=22323 + [97] = https://curl.se/bug/?i=22318 + [98] = https://curl.se/bug/?i=22317 + [99] = https://curl.se/bug/?i=22576 + [100] = https://curl.se/bug/?i=22331 + [101] = https://curl.se/bug/?i=22333 + [102] = https://curl.se/bug/?i=22334 + [103] = https://curl.se/bug/?i=22326 + [104] = https://curl.se/bug/?i=22393 + [105] = https://curl.se/bug/?i=22320 + [106] = https://curl.se/bug/?i=22162 + [107] = https://curl.se/bug/?i=22299 + [108] = https://curl.se/bug/?i=22386 + [109] = https://curl.se/bug/?i=22312 + [110] = https://curl.se/bug/?i=22374 + [111] = https://curl.se/bug/?i=22387 + [112] = https://curl.se/bug/?i=22379 + [113] = https://curl.se/bug/?i=22372 + [114] = https://curl.se/bug/?i=22648 + [115] = https://curl.se/bug/?i=22276 + [116] = https://curl.se/bug/?i=22382 + [117] = https://curl.se/bug/?i=22305 + [118] = https://curl.se/bug/?i=22330 + [119] = https://curl.se/bug/?i=22371 + [120] = https://curl.se/bug/?i=22380 + [121] = https://curl.se/bug/?i=21637 + [122] = https://curl.se/bug/?i=22377 + [123] = https://curl.se/bug/?i=22376 + [124] = https://curl.se/bug/?i=22375 + [125] = https://curl.se/bug/?i=21637 + [126] = https://curl.se/bug/?i=22363 + [127] = https://curl.se/bug/?i=22430 + [128] = https://curl.se/bug/?i=22369 + [129] = https://curl.se/bug/?i=22354 + [130] = https://curl.se/bug/?i=21467 + [131] = https://curl.se/bug/?i=22037 + [132] = https://curl.se/bug/?i=22418 + [133] = https://curl.se/bug/?i=22512 + [134] = https://curl.se/bug/?i=22515 + [135] = https://curl.se/bug/?i=22413 + [136] = https://curl.se/bug/?i=22603 + [137] = https://curl.se/bug/?i=22341 + [138] = https://curl.se/bug/?i=22569 + [139] = https://curl.se/bug/?i=22500 + [140] = https://curl.se/bug/?i=22568 + [141] = https://curl.se/bug/?i=22422 + [142] = https://curl.se/bug/?i=22421 + [143] = https://curl.se/bug/?i=22420 + [144] = https://curl.se/bug/?i=22415 + [145] = https://curl.se/bug/?i=22419 + [146] = https://curl.se/bug/?i=22471 + [147] = https://curl.se/bug/?i=22468 + [148] = https://curl.se/bug/?i=22337 + [149] = https://curl.se/bug/?i=22414 + [150] = https://curl.se/bug/?i=22467 + [151] = https://curl.se/bug/?i=21315 + [152] = https://curl.se/bug/?i=21806 + [153] = https://curl.se/bug/?i=22409 + [154] = https://curl.se/bug/?i=22270 + [155] = https://curl.se/bug/?i=22566 + [156] = https://curl.se/bug/?i=22401 + [157] = https://curl.se/bug/?i=22463 + [158] = https://curl.se/bug/?i=22397 + [159] = https://curl.se/bug/?i=22400 + [160] = https://curl.se/bug/?i=22460 + [161] = https://curl.se/bug/?i=22505 + [162] = https://curl.se/bug/?i=22502 + [163] = https://curl.se/bug/?i=22565 + [164] = https://curl.se/bug/?i=22494 + [165] = https://curl.se/bug/?i=22564 + [166] = https://curl.se/bug/?i=22448 + [167] = https://curl.se/bug/?i=22449 + [168] = https://curl.se/bug/?i=22530 + [169] = https://curl.se/bug/?i=22447 + [170] = https://curl.se/bug/?i=22445 + [171] = https://curl.se/bug/?i=22444 + [172] = https://curl.se/bug/?i=22443 + [173] = https://curl.se/bug/?i=22647 + [174] = https://curl.se/bug/?i=22490 + [175] = https://curl.se/bug/?i=22604 + [176] = https://curl.se/bug/?i=22528 + [177] = https://curl.se/bug/?i=22678 + [178] = https://curl.se/bug/?i=22598 + [179] = https://curl.se/bug/?i=22547 + [180] = https://curl.se/bug/?i=22560 + [181] = https://curl.se/bug/?i=22555 + [182] = https://curl.se/bug/?i=22557 + [183] = https://curl.se/bug/?i=22642 + [184] = https://curl.se/bug/?i=22594 + [185] = https://curl.se/bug/?i=22593 + [186] = https://curl.se/bug/?i=22588 + [187] = https://curl.se/bug/?i=22552 + [188] = https://curl.se/bug/?i=22548 + [189] = https://curl.se/bug/?i=22541 + [190] = https://curl.se/bug/?i=22545 + [191] = https://curl.se/bug/?i=22544 + [192] = https://curl.se/bug/?i=22638 + [193] = https://curl.se/bug/?i=22595 + [194] = https://curl.se/bug/?i=22536 + [195] = https://curl.se/bug/?i=22540 + [196] = https://curl.se/bug/?i=22524 + [197] = https://curl.se/bug/?i=22473 + [198] = https://curl.se/bug/?i=22644 + [199] = https://curl.se/bug/?i=22591 + [200] = https://curl.se/bug/?i=22532 + [201] = https://curl.se/bug/?i=22636 + [202] = https://curl.se/bug/?i=22484 + [203] = https://curl.se/bug/?i=22514 + [204] = https://curl.se/bug/?i=22627 + [205] = https://curl.se/bug/?i=22577 + [206] = https://curl.se/bug/?i=22587 + [207] = https://curl.se/bug/?i=22672 + [208] = https://curl.se/bug/?i=22475 + [209] = https://curl.se/bug/?i=22584 + [210] = https://curl.se/bug/?i=22519 + [211] = https://curl.se/bug/?i=22668 + [212] = https://curl.se/bug/?i=22585 + [213] = https://curl.se/bug/?i=22567 + [214] = https://curl.se/bug/?i=22665 + [215] = https://curl.se/bug/?i=22628 + [216] = https://curl.se/bug/?i=22579 + [217] = https://curl.se/bug/?i=22631 + [218] = https://curl.se/bug/?i=22339 + [219] = https://curl.se/bug/?i=20521 + [220] = https://curl.se/bug/?i=22623 + [221] = https://curl.se/bug/?i=22629 + [222] = https://curl.se/bug/?i=22721 + [223] = https://curl.se/bug/?i=22610 + [224] = https://curl.se/bug/?i=22621 + [225] = https://curl.se/bug/?i=22649 + [226] = https://curl.se/bug/?i=22660 + [227] = https://curl.se/bug/?i=22675 + [228] = https://curl.se/bug/?i=22664 + [229] = https://curl.se/bug/?i=22662 + [230] = https://curl.se/bug/?i=22466 + [231] = https://curl.se/bug/?i=22613 + [232] = https://curl.se/bug/?i=22612 + [233] = https://curl.se/bug/?i=22611 + [234] = https://curl.se/bug/?i=22550 + [235] = https://curl.se/bug/?i=22587 + [236] = https://curl.se/bug/?i=22607 + [237] = https://curl.se/bug/?i=22229 + [238] = https://curl.se/bug/?i=22383 + [239] = https://curl.se/bug/?i=22658 + [240] = https://curl.se/bug/?i=22656 + [241] = https://curl.se/bug/?i=22695 + [242] = https://curl.se/bug/?i=22717 + [243] = https://curl.se/bug/?i=22651 + [244] = https://curl.se/bug/?i=22718 + [245] = https://curl.se/bug/?i=22719 + [246] = https://curl.se/bug/?i=22715 + [247] = https://curl.se/bug/?i=22713 + [248] = https://curl.se/bug/?i=22692 + [249] = https://curl.se/bug/?i=22710 + [250] = https://curl.se/bug/?i=22706 + [251] = https://curl.se/bug/?i=22708 + [252] = https://curl.se/bug/?i=22690 + [253] = https://curl.se/bug/?i=22774 + [254] = https://curl.se/bug/?i=22709 + [255] = https://curl.se/bug/?i=22705 + [256] = https://curl.se/bug/?i=22702 + [257] = https://curl.se/bug/?i=22680 + [258] = https://curl.se/bug/?i=22699 + [259] = https://curl.se/bug/?i=22700 + [260] = https://curl.se/bug/?i=22698 + [261] = https://curl.se/bug/?i=22671 + [262] = https://curl.se/bug/?i=22696 + [263] = https://curl.se/bug/?i=22694 + [264] = https://curl.se/bug/?i=22683 + [265] = https://curl.se/bug/?i=22689 + [268] = https://curl.se/bug/?i=22687 + [269] = https://curl.se/bug/?i=22643 + [270] = https://curl.se/bug/?i=22681 + [271] = https://curl.se/bug/?i=22768 + [272] = https://curl.se/bug/?i=22776 + [273] = https://curl.se/bug/?i=22685 + [274] = https://curl.se/bug/?i=22682 + [275] = https://curl.se/bug/?i=22586 + [276] = https://curl.se/bug/?i=22669 + [277] = https://curl.se/bug/?i=22667 + [278] = https://curl.se/bug/?i=22679 + [279] = https://curl.se/bug/?i=22758 + [280] = https://curl.se/bug/?i=22767 + [281] = https://curl.se/bug/?i=22763 + [282] = https://curl.se/bug/?i=22739 + [284] = https://curl.se/bug/?i=22754 + [286] = https://curl.se/bug/?i=22761 + [288] = https://curl.se/bug/?i=22714 + [289] = https://curl.se/bug/?i=22759 + [290] = https://curl.se/bug/?i=22757 + [291] = https://curl.se/bug/?i=22751 + [292] = https://curl.se/bug/?i=22750 + [295] = https://curl.se/bug/?i=22739 + [296] = https://curl.se/bug/?i=22738 + [297] = https://curl.se/bug/?i=22737 + [301] = https://curl.se/bug/?i=22726 + [304] = https://curl.se/bug/?i=22730 + [305] = https://curl.se/bug/?i=22686 + [306] = https://curl.se/bug/?i=22725 + [307] = https://curl.se/bug/?i=22716 + [308] = https://curl.se/bug/?i=22635 diff --git a/REUSE.toml b/REUSE.toml index c2e8b7928889..9e6d2d166b44 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -2,8 +2,8 @@ # SPDX-FileCopyrightText: Daniel Stenberg, , et al. # This file describes the licensing and copyright situation for files that -# cannot be annotated directly, for example because of being -# uncommentable. Unless this is the case, a file should be annotated directly. +# cannot be annotated directly, for example because of being uncommentable. +# Unless this is the case, a file should be annotated directly. # # This follows the REUSE specification: https://reuse.software/spec-3.2/#reusetoml @@ -13,6 +13,7 @@ SPDX-PackageDownloadLocation = "https://curl.se/" [[annotations]] path = [ + ".github/pull_request_template.md", "docs/INSTALL", "docs/libcurl/symbols-in-versions", "docs/options-in-versions", diff --git a/acinclude.m4 b/acinclude.m4 index d98d4b1bef5e..310d680600c3 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -25,11 +25,11 @@ dnl CURL_CHECK_DEF (SYMBOL, [INCLUDES], [SILENT]) dnl ------------------------------------------------- dnl Use the C preprocessor to find out if the given object-style symbol -dnl is defined and get its expansion. This macro will not use default -dnl includes even if no INCLUDES argument is given. This macro will run +dnl is defined and get its expansion. This macro does not use default +dnl includes even if no INCLUDES argument is given. This macro runs dnl silently when invoked with three arguments. If the expansion would -dnl result in a set of double-quoted strings the returned expansion will -dnl actually be a single double-quoted string concatenating all them. +dnl result in a set of double-quoted strings the returned expansion is +dnl actually a single double-quoted string concatenating all them. AC_DEFUN([CURL_CHECK_DEF], [ AC_REQUIRE([CURL_CPP_P]) @@ -79,9 +79,9 @@ AC_DEFUN([CURL_CHECK_DEF], [ dnl CURL_CHECK_DEF_CC (SYMBOL, [INCLUDES], [SILENT]) dnl ------------------------------------------------- dnl Use the C compiler to find out only if the given symbol is defined -dnl or not, this can not find out its expansion. This macro will not use +dnl or not, this can not find out its expansion. This macro does not use dnl default includes even if no INCLUDES argument is given. This macro -dnl will run silently when invoked with three arguments. +dnl runs silently when invoked with three arguments. AC_DEFUN([CURL_CHECK_DEF_CC], [ AS_VAR_PUSHDEF([ac_HaveDef], [curl_cv_have_def_$1]) @@ -860,7 +860,7 @@ AC_DEFUN([CURL_CHECK_LIBS_CLOCK_GETTIME_MONOTONIC], [ case X-"$curl_cv_gclk_LIBS" in X-unknown) AC_MSG_RESULT([cannot find clock_gettime]) - AC_MSG_WARN([HAVE_CLOCK_GETTIME_MONOTONIC will not be defined]) + AC_MSG_WARN([HAVE_CLOCK_GETTIME_MONOTONIC is not defined]) curl_func_clock_gettime="no" ;; X-) @@ -869,7 +869,7 @@ AC_DEFUN([CURL_CHECK_LIBS_CLOCK_GETTIME_MONOTONIC], [ ;; *) if test "$dontwant_rt" = "yes"; then - AC_MSG_WARN([needs -lrt but asked not to use it, HAVE_CLOCK_GETTIME_MONOTONIC will not be defined]) + AC_MSG_WARN([needs -lrt but asked not to use it, HAVE_CLOCK_GETTIME_MONOTONIC is not defined]) curl_func_clock_gettime="no" else if test -z "$curl_cv_save_LIBS"; then @@ -885,7 +885,7 @@ AC_DEFUN([CURL_CHECK_LIBS_CLOCK_GETTIME_MONOTONIC], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$curl_func_clock_gettime" = "yes"; then + test "$curl_func_clock_gettime" = "yes"; then AC_MSG_CHECKING([if monotonic clock_gettime works]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -908,7 +908,7 @@ AC_DEFUN([CURL_CHECK_LIBS_CLOCK_GETTIME_MONOTONIC], [ AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) - AC_MSG_WARN([HAVE_CLOCK_GETTIME_MONOTONIC will not be defined]) + AC_MSG_WARN([HAVE_CLOCK_GETTIME_MONOTONIC is not defined]) curl_func_clock_gettime="no" LIBS="$curl_cv_save_LIBS" ]) @@ -972,63 +972,10 @@ AC_DEFUN([CURL_CHECK_LIBS_CONNECT], [ ]) -dnl CURL_CHECK_FUNC_SELECT -dnl ------------------------------------------------- -dnl Test if the socket select() function is available. - -AC_DEFUN([CURL_CHECK_FUNC_SELECT], [ - AC_REQUIRE([CURL_CHECK_STRUCT_TIMEVAL]) - AC_REQUIRE([CURL_INCLUDES_BSDSOCKET]) - AC_CHECK_HEADERS(sys/select.h) - - AC_MSG_CHECKING([for select]) - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([[ - #undef inline - #ifdef _WIN32 - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #include - #else - #include - #include - #endif - #ifdef HAVE_SYS_TYPES_H - #include - #endif - #include - #ifndef _WIN32 - #ifdef HAVE_SYS_SELECT_H - #include - #elif defined(HAVE_UNISTD_H) - #include - #endif - $curl_includes_bsdsocket - #endif - ]],[[ - select(0, 0, 0, 0, 0); - ]]) - ],[ - AC_MSG_RESULT([yes]) - curl_cv_select="yes" - ],[ - AC_MSG_RESULT([no]) - curl_cv_select="no" - ]) - - if test "$curl_cv_select" = "yes"; then - AC_DEFINE_UNQUOTED(HAVE_SELECT, 1, - [Define to 1 if you have the select function.]) - curl_cv_func_select="yes" - fi -]) - - dnl CURL_VERIFY_RUNTIMELIBS dnl ------------------------------------------------- dnl Verify that the shared libs found so far can be used when running -dnl programs, since otherwise the situation will create odd configure errors +dnl programs, since otherwise the situation creates odd configure errors dnl that are misleading people. dnl dnl Make sure this test is run BEFORE the first test in the script that @@ -1040,7 +987,7 @@ AC_DEFUN([CURL_VERIFY_RUNTIMELIBS], [ dnl this test is of course not sensible if we are cross-compiling! if test "$cross_compiling" != "yes"; then - dnl just run a program to verify that the libs checked for previous to this + dnl run a program to verify that the libs checked for previous to this dnl point also is available runtime! AC_MSG_CHECKING([runtime libs availability]) CURL_RUN_IFELSE([ @@ -1063,7 +1010,7 @@ dnl CURL_CHECK_CA_BUNDLE dnl ------------------------------------------------- dnl Check if a default ca-bundle should be used dnl -dnl regarding the paths this will scan: +dnl regarding the paths this scans: dnl /etc/ssl/certs/ca-certificates.crt Debian systems dnl /etc/pki/tls/certs/ca-bundle.crt Redhat and Mandriva dnl /usr/share/ssl/certs/ca-bundle.crt old(er) Redhat @@ -1127,7 +1074,7 @@ AS_HELP_STRING([--without-ca-path], [Do not use a default CA path]), capath="$want_capath" ca="no" elif test "$ca_native" != "no"; then - dnl native ca configured, do not look further + dnl native CA configured, do not look further ca="no" capath="no" else @@ -1161,7 +1108,7 @@ AS_HELP_STRING([--without-ca-path], [Do not use a default CA path]), fi done fi - AC_MSG_NOTICE([want $want_capath ca $ca]) + AC_MSG_NOTICE([want $want_capath CA $ca]) if test "x$want_capath" = "xunset"; then check_capath="/etc/ssl/certs" fi @@ -1197,13 +1144,13 @@ AS_HELP_STRING([--without-ca-path], [Do not use a default CA path]), if test "x$ca" != "xno"; then CURL_CA_BUNDLE="$ca" - AC_DEFINE_UNQUOTED(CURL_CA_BUNDLE, "$ca", [Location of default ca bundle]) + AC_DEFINE_UNQUOTED(CURL_CA_BUNDLE, "$ca", [Location of default CA bundle]) AC_SUBST(CURL_CA_BUNDLE) AC_MSG_RESULT([$ca]) fi if test "x$capath" != "xno"; then CURL_CA_PATH="\"$capath\"" - AC_DEFINE_UNQUOTED(CURL_CA_PATH, "$capath", [Location of default ca path]) + AC_DEFINE_UNQUOTED(CURL_CA_PATH, "$capath", [Location of default CA path]) AC_MSG_RESULT([$capath (capath)]) fi if test "x$ca" = "xno" && test "x$capath" = "xno"; then @@ -1258,7 +1205,7 @@ AS_HELP_STRING([--without-ca-embed], [Do not embed a default CA bundle in the cu AC_MSG_RESULT([$want_ca_embed]) else AC_MSG_RESULT([no]) - AC_MSG_WARN([perl was not found. Will not do CA embed.]) + AC_MSG_WARN([perl was not found. Cannot do CA embed.]) fi else AC_MSG_RESULT([no]) @@ -1441,15 +1388,13 @@ AC_DEFUN([CURL_PREPARE_BUILDINFO], [ *-*-*bsd*) curl_pflags="${curl_pflags} BSD";; esac - case $host in - *-*-android*) - curl_pflags="${curl_pflags} ANDROID" - ANDROID_PLATFORM_LEVEL=`echo "$host_os" | $SED -ne 's/.*android\(@<:@0-9@:>@*\).*/\1/p'` - if test -n "${ANDROID_PLATFORM_LEVEL}"; then - curl_pflags="${curl_pflags}-${ANDROID_PLATFORM_LEVEL}" - fi - ;; - esac + if test "$curl_cv_android" = "yes"; then + curl_pflags="${curl_pflags} ANDROID" + ANDROID_PLATFORM_LEVEL=`echo "$host_os" | $SED -ne 's/.*android\(@<:@0-9@:>@*\).*/\1/p'` + if test -n "${ANDROID_PLATFORM_LEVEL}"; then + curl_pflags="${curl_pflags}-${ANDROID_PLATFORM_LEVEL}" + fi + fi if test "$curl_cv_native_windows" = "yes"; then curl_pflags="${curl_pflags} WIN32" fi @@ -1498,7 +1443,7 @@ dnl CURL_CPP_P dnl dnl Check if $cpp -P should be used for extract define values due to gcc 5 dnl splitting up strings and defines between line outputs. gcc by default -dnl (without -P) will show TEST EINVAL TEST as +dnl (without -P) shows TEST EINVAL TEST as dnl dnl # 13 "conftest.c" dnl TEST diff --git a/appveyor.sh b/appveyor.sh index 56bfc8848d6c..64e8b6d13182 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -39,13 +39,14 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then *) openssl_suffix='-Win64';; esac - if [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2022' ]; then + if [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2026' ]; then openssl_root_win="C:/OpenSSL-v36${openssl_suffix}" - openssl_root="$(cygpath "${openssl_root_win}")" + elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2022' ]; then + openssl_root_win="C:/OpenSSL-v35${openssl_suffix}" elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2019' ]; then openssl_root_win="C:/OpenSSL-v30${openssl_suffix}" - openssl_root="$(cygpath "${openssl_root_win}")" fi + [ -n "${openssl_root_win:-}" ] && openssl_root="$(cygpath "${openssl_root_win}")" # Install custom cmake version if [ -n "${CMAKE_VERSION:-}" ]; then @@ -58,9 +59,9 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then fn="cmake-${CMAKE_VERSION}-win64-x64" fi curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${fn}.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${fn}.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${CMAKE_SHA256}" && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin - PATH="$PWD/${fn}/bin:$PATH" + PATH="$(pwd)/${fn}/bin:$PATH" fi # Set env CHKPREFILL to the value '_chkprefill' to compare feature detection @@ -78,6 +79,7 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then -DCURL_STATIC_CRT=ON \ -DCURL_DROP_UNUSED=ON \ -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBPSL=OFF \ + -DCURL_DISABLE_HTTPSIG=OFF \ ${CMAKE_GENERATE:-} \ ${options} \ || { cat _bld/CMakeFiles/CMake* 2>/dev/null; false; } @@ -87,8 +89,9 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then false fi echo 'curl_config.h'; grep -F '#define' _bld/lib/curl_config.h | sort || true + echo 'libcurl.pc'; grep -v '^#' '_bld/libcurl.pc' || true time cmake --build _bld --config "${PRJ_CFG}" --parallel 2 - [[ "${CMAKE_GENERATE:-}" != *'-DBUILD_SHARED_LIBS=OFF'* ]] && PATH="$PWD/_bld/lib/${PRJ_CFG}:$PATH" + [[ "${CMAKE_GENERATE:-}" != *'-DBUILD_SHARED_LIBS=OFF'* ]] && PATH="$(pwd)/_bld/lib/${PRJ_CFG}:$PATH" [[ "${CMAKE_GENERATE:-}" = *'-DCURL_USE_OPENSSL=ON'* ]] && { PATH="${openssl_root}:$PATH"; cp "${openssl_root}"/*.dll "_bld/src/${PRJ_CFG}"; } curl="_bld/src/${PRJ_CFG}/curl.exe" else diff --git a/appveyor.yml b/appveyor.yml index 899bb497c4c7..a08285efc523 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,14 +36,16 @@ environment: matrix: # CMake Visual Studio builds - - job_name: 'CM VS2022, Release, x64, OpenSSL 3.6, Shared, Build-tests' + - job_name: 'CM VS2022, Release, x64, OpenSSL 3.5, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=OFF -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' - - job_name: 'CM VS2022, Release, arm64, Schannel, Static, !DEBUGBUILD, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' - CMAKE_GENERATOR: 'Visual Studio 17 2022' + - job_name: 'CM VS2026, Release, arm64, Schannel, Static, Build-tests' + APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' + CMAKE_VERSION: 4.2.1 + CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 + CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A ARM64 -DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2010, Debug, x64, Schannel, Shared, Build-tests & examples' @@ -58,7 +60,7 @@ environment: CMAKE_VERSION: 3.21.7 CMAKE_SHA256: 4c4840e2dc2bf82e8a16081ff506bba54f3a228b91ce36317129fed4035ef2e3 CMAKE_GENERATOR: 'Visual Studio 11 2012' - CMAKE_GENERATE: '-A Win32' + CMAKE_GENERATE: '-A Win32 -DENABLE_DEBUG=OFF' - job_name: 'CM VS2013, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' @@ -86,20 +88,24 @@ environment: CMAKE_GENERATOR: 'Visual Studio 16 2019' CMAKE_GENERATE: '-A x64 -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' - - job_name: 'CM VS2022, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' - CMAKE_GENERATOR: 'Visual Studio 17 2022' + - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.5 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' + APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' + CMAKE_VERSION: 4.2.1 + CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 + CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' - - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, !DEBUGBUILD, Build-tests' + - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' ENABLE_UNICODE: 'ON' CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON -DENABLE_DEBUG=OFF' - - job_name: 'CM VS2022, Debug, x64, !ssl, Static, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' - CMAKE_GENERATOR: 'Visual Studio 17 2022' + - job_name: 'CM VS2026, Debug, x64, !ssl, Static, Build-tests' + APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' + CMAKE_VERSION: 4.2.1 + CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 + CMAKE_GENERATOR: 'Visual Studio 18 2026' CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' - job_name: 'CM VS2022, Debug, x64, !ssl, Static, HTTP-only, Build-tests' @@ -110,7 +116,7 @@ environment: # VisualStudioSolution builds - job_name: 'VisualStudioSolution VS2010, Release, x86, Schannel' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2013' + APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' PRJ_CFG: 'DLL Release - DLL Windows SSPI - DLL WinIDN' PLAT: 'Win32' VC_VERSION: VC10 diff --git a/configure.ac b/configure.ac index f7a92ea1b076..ef99b44cd616 100644 --- a/configure.ac +++ b/configure.ac @@ -54,6 +54,30 @@ CURL_CHECK_OPTION_RT CURL_CHECK_OPTION_HTTPSRR CURL_CHECK_OPTION_ECH CURL_CHECK_OPTION_SSLS_EXPORT +AC_MSG_CHECKING([whether to enable HTTP/3 proxy support]) +OPT_PROXY_HTTP3="default" +AC_ARG_ENABLE(proxy-http3, +AS_HELP_STRING([--enable-proxy-http3],[Enable HTTP/3 proxy support (experimental)]) +AS_HELP_STRING([--disable-proxy-http3],[Disable HTTP/3 proxy support (experimental)]), + OPT_PROXY_HTTP3=$enableval) +case "$OPT_PROXY_HTTP3" in + no) + want_proxy_http3="no" + curl_proxy_http3_msg="no (--enable-proxy-http3)" + AC_MSG_RESULT([no]) + ;; + default) + want_proxy_http3="no" + curl_proxy_http3_msg="no (--enable-proxy-http3)" + AC_MSG_RESULT([no]) + ;; + *) + want_proxy_http3="yes" + curl_proxy_http3_msg="enabled (--disable-proxy-http3)" + AC_MSG_RESULT([yes]) + ;; +esac +USE_PROXY_HTTP3=0 XC_CHECK_PATH_SEPARATOR @@ -143,37 +167,38 @@ AC_SUBST(VERSIONNUM) dnl dnl initialize all the info variables - curl_ssl_msg="no (--with-{openssl,gnutls,mbedtls,unitytls,wolfssl,schannel,secure-transport,amissl,rustls} )" - curl_ssh_msg="no (--with-{libssh,libssh2})" - curl_zlib_msg="no (--with-zlib)" - curl_brotli_msg="no (--with-brotli)" - curl_zstd_msg="no (--with-zstd)" - curl_gss_msg="no (--with-gssapi)" - curl_gsasl_msg="no (--with-gsasl)" -curl_tls_srp_msg="no (--enable-tls-srp)" - curl_res_msg="default (--enable-ares / --enable-threaded-resolver)" - curl_ipv6_msg="no (--enable-ipv6)" -curl_unix_sockets_msg="no (--enable-unix-sockets)" - curl_idn_msg="no (--with-{libidn2,winidn})" - curl_docs_msg="enabled (--disable-docs)" - curl_manual_msg="no (--enable-manual)" -curl_libcurl_msg="enabled (--disable-libcurl-option)" -curl_typecheck_msg="enabled (--disable-typecheck)" -curl_verbose_msg="enabled (--disable-verbose)" - curl_sspi_msg="no (--enable-sspi)" - curl_ldap_msg="no (--enable-ldap / --with-ldap-lib / --with-lber-lib)" - curl_ldaps_msg="no (--enable-ldaps)" - curl_rtsp_msg="no (--enable-rtsp)" - curl_rtmp_msg="no (--with-librtmp)" - curl_psl_msg="no (--with-libpsl)" - curl_altsvc_msg="enabled (--disable-alt-svc)" -curl_headers_msg="enabled (--disable-headers-api)" - curl_hsts_msg="enabled (--disable-hsts)" - curl_ws_msg="no (--enable-websockets)" - ssl_backends= - curl_h1_msg="enabled (internal)" - curl_h2_msg="no (--with-nghttp2)" - curl_h3_msg="no (--with-ngtcp2 --with-nghttp3, --with-quiche, --with-openssl-quic)" + curl_ssl_msg="no (--with-{openssl,gnutls,mbedtls,unitytls,wolfssl,schannel,secure-transport,amissl,rustls} )" + curl_ssh_msg="no (--with-{libssh,libssh2})" + curl_zlib_msg="no (--with-zlib)" + curl_brotli_msg="no (--with-brotli)" + curl_zstd_msg="no (--with-zstd)" + curl_gss_msg="no (--with-gssapi)" + curl_gsasl_msg="no (--with-gsasl)" + curl_tls_srp_msg="no (--enable-tls-srp)" + curl_res_msg="blocking (--enable-ares / --enable-threaded-resolver)" + curl_ipv6_msg="no (--enable-ipv6)" +curl_unix_sockets_msg="no (--enable-unix-sockets)" + curl_idn_msg="no (--with-{libidn2,winidn})" + curl_docs_msg="enabled (--disable-docs)" + curl_manual_msg="no (--enable-manual)" + curl_libcurl_msg="enabled (--disable-libcurl-option)" + curl_typecheck_msg="enabled (--disable-typecheck)" + curl_verbose_msg="enabled (--disable-verbose)" + curl_sspi_msg="no (--enable-sspi)" + curl_ldap_msg="no (--enable-ldap / --with-ldap-lib / --with-lber-lib)" + curl_ldaps_msg="no (--enable-ldaps)" + curl_ipfs_msg="no (--enable-ipfs)" + curl_rtsp_msg="no (--enable-rtsp)" + curl_rtmp_msg="no (--with-librtmp)" + curl_psl_msg="no (--with-libpsl)" + curl_altsvc_msg="enabled (--disable-alt-svc)" + curl_headers_msg="enabled (--disable-headers-api)" + curl_hsts_msg="enabled (--disable-hsts)" + curl_ws_msg="no (--enable-websockets)" + ssl_backends= + curl_h1_msg="enabled (internal)" + curl_h2_msg="no (--with-nghttp2)" + curl_h3_msg="no (--with-ngtcp2 --with-nghttp3, --with-quiche, --with-openssl-quic)" enable_altsvc="yes" hsts="yes" @@ -204,7 +229,7 @@ exec $CC "$@" EOF dnl ********************************************************************** -dnl See which TLS backend(s) that are requested. Just do all the +dnl See which TLS backend(s) that are requested. Do all the dnl TLS AC_ARG_WITH() invokes here and do the checks later dnl ********************************************************************** OPT_SCHANNEL=no @@ -289,7 +314,7 @@ AS_HELP_STRING([--with-rustls=PATH],[where to look for Rustls, PATH points to th fi ]) -OPT_APPLE_SECTRUST=$curl_cv_apple +OPT_APPLE_SECTRUST=no AC_ARG_WITH(apple-sectrust, AS_HELP_STRING([--with-apple-sectrust],[enable Apple OS native certificate verification]),[ OPT_APPLE_SECTRUST=$withval @@ -325,6 +350,22 @@ AS_HELP_STRING([--with-test-caddy=PATH],[where to find caddy for testing]), ) AC_SUBST(CADDY) +if test -x /usr/local/bin/h2o; then + H2O=/usr/local/bin/h2o +elif test -x /usr/bin/h2o; then + H2O=/usr/bin/h2o +elif test -x "`brew --prefix 2>/dev/null`/bin/h2o"; then + H2O=`brew --prefix`/bin/h2o +fi +AC_ARG_WITH(test-h2o,dnl +AS_HELP_STRING([--with-test-h2o=PATH],[where to find h2o for testing]), + H2O=$withval + if test "x$H2O" = "xno"; then + H2O="" + fi +) +AC_SUBST(H2O) + if test -x /usr/sbin/vsftpd; then VSFTPD=/usr/sbin/vsftpd elif test -x /usr/local/sbin/vsftpd; then @@ -687,8 +728,8 @@ esac AM_CONDITIONAL(BUILD_UNITTESTS, test "$supports_unittests" = "yes") -dnl In order to detect support of sendmmsg() and accept4(), we need to escape the POSIX -dnl jail by defining _GNU_SOURCE or will not expose it. +dnl In order to detect support of sendmmsg() and accept4(), we need to escape +dnl the POSIX jail by defining _GNU_SOURCE or does not expose it. case $host_os in *linux*|cygwin*|msys*|gnu*) CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" @@ -703,8 +744,10 @@ dnl ********************************************************************** CURL_CHECK_WIN32_CRYPTO +curl_cv_android='no' curl_cv_apple='no' case $host in + *-*-android*) curl_cv_android='yes';; *-apple-*) curl_cv_apple='yes';; esac @@ -742,7 +785,7 @@ AS_HELP_STRING([--disable-unity],[Disable unity (default)]), AC_MSG_RESULT([no]) ) if test -z "$PERL" && test "$want_unity" = "yes"; then - AC_MSG_WARN([perl was not found. Will not enable unity.]) + AC_MSG_WARN([perl was not found. Cannot enable unity.]) want_unity='no' fi AM_CONDITIONAL([USE_UNITY], [test "$want_unity" = "yes"]) @@ -1159,7 +1202,7 @@ AS_HELP_STRING([--disable-docs],[Disable documentation]), BUILD_DOCS=1 ) if test -z "$PERL" && test "$BUILD_DOCS" != "0"; then - AC_MSG_WARN([perl was not found. Will not build documentation.]) + AC_MSG_WARN([perl was not found. Cannot build documentation.]) BUILD_DOCS=0 fi @@ -1245,7 +1288,7 @@ if test "$HAVE_GETHOSTBYNAME" != "1"; then ) fi -if test "$HAVE_GETHOSTBYNAME" != "1"; then +if test "$HAVE_GETHOSTBYNAME" != "1" && test -n "$WATT_ROOT"; then dnl gethostbyname in the watt lib? clean_CPPFLAGS=$CPPFLAGS clean_LDFLAGS=$LDFLAGS @@ -1521,7 +1564,10 @@ else dnl replace 'HAVE_LIBZ' in the automake makefile.ams AMFIXLIB="1" AC_MSG_NOTICE([found both libz and libz.h header]) - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE zlib" + dnl Android does not provide zlib.pc + if test "$curl_cv_android" = "no"; then + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE zlib" + fi curl_zlib_msg="enabled" fi fi @@ -1574,7 +1620,7 @@ if test "x$OPT_BROTLI" != "xno"; then DIR_BROTLI=`echo $LD_BROTLI | $SED -e 's/^-L//'` ;; off) - dnl no --with-brotli option given, just check default places + dnl no --with-brotli option given, check default places ;; *) dnl use the given --with-brotli spot @@ -1605,7 +1651,7 @@ if test "x$OPT_BROTLI" != "xno"; then if test "x$OPT_BROTLI" != "xoff" && test "$HAVE_BROTLI" != "1"; then - AC_MSG_ERROR([BROTLI libs and/or directories were not found where specified!]) + AC_MSG_ERROR([BROTLI libs and/or directories were not found!]) fi if test "$HAVE_BROTLI" = "1"; then @@ -1664,7 +1710,7 @@ if test "x$OPT_ZSTD" != "xno"; then ;; off) - dnl no --with-zstd option given, just check default places + dnl no --with-zstd option given, check default places ;; *) dnl use the given --with-zstd spot @@ -1806,11 +1852,11 @@ dnl ********************************************************************** AC_MSG_CHECKING([if argv can be written to]) CURL_RUN_IFELSE([[ -int main(int argc, char **argv) +int main(int argc, char *argv[]) { #ifdef _WIN32 /* on Windows, writing to the argv does not hide the argument in - process lists so it can just be skipped */ + process lists so it can be skipped */ (void)argc; (void)argv; return 1; @@ -1878,6 +1924,27 @@ AC_ARG_WITH(gssapi, ] ) +want_gss_apple="no" +if test "$curl_cv_apple" = "yes"; then + AC_MSG_CHECKING([whether to use Apple GSS Framework for GSS-API]) + AC_ARG_ENABLE(gssapi-apple, +AS_HELP_STRING([--enable-gssapi-apple],[Enable Apple GSS Framework (experimental)]) +AS_HELP_STRING([--disable-gssapi-apple],[Disable Apple GSS Framework (experimental) (default)]), [ + case "$enableval" in + yes) + want_gss="yes" + want_gss_apple="yes" + AC_MSG_RESULT([yes]) + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac + ], + AC_MSG_RESULT([no]) + ) +fi + : ${KRB5CONFIG:="$GSSAPI_ROOT/bin/krb5-config"} save_CPPFLAGS="$CPPFLAGS" @@ -1885,130 +1952,146 @@ AC_MSG_CHECKING([if GSS-API support is requested]) if test "$want_gss" = "yes"; then AC_MSG_RESULT(yes) - if test "$GSSAPI_ROOT" != "/usr"; then - CURL_CHECK_PKGCONFIG(mit-krb5-gssapi, $GSSAPI_ROOT/lib/pkgconfig) + if test "$want_gss_apple" = "yes"; then + AC_CHECK_HEADER(GSS/gssapi.h, [ + AC_DEFINE(HAVE_GSSAPPLE, 1, [if you have Apple GSS Framework]) + ],[ + AC_MSG_ERROR([--enable-gssapi-apple was specified, but GSS/gssapi.h was not found]) + ]) else - CURL_CHECK_PKGCONFIG(mit-krb5-gssapi) - fi - if test -z "$GSSAPI_INCS"; then - if test -n "$host_alias" && test -f "$GSSAPI_ROOT/bin/$host_alias-krb5-config"; then - GSSAPI_INCS=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --cflags gssapi` - elif test "$PKGCONFIG" != "no"; then - GSSAPI_INCS=`$PKGCONFIG --cflags mit-krb5-gssapi` - elif test -f "$KRB5CONFIG"; then - GSSAPI_INCS=`$KRB5CONFIG --cflags gssapi` - elif test "$GSSAPI_ROOT" != "yes"; then - GSSAPI_INCS="-I$GSSAPI_ROOT/include" + if test "$GSSAPI_ROOT" != "/usr"; then + CURL_CHECK_PKGCONFIG(mit-krb5-gssapi, $GSSAPI_ROOT/lib/pkgconfig) + else + CURL_CHECK_PKGCONFIG(mit-krb5-gssapi) + fi + if test -z "$GSSAPI_INCS"; then + if test -n "$host_alias" && test -f "$GSSAPI_ROOT/bin/$host_alias-krb5-config"; then + GSSAPI_INCS=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --cflags gssapi` + elif test "$PKGCONFIG" != "no"; then + GSSAPI_INCS=`$PKGCONFIG --cflags mit-krb5-gssapi` + elif test -f "$KRB5CONFIG"; then + GSSAPI_INCS=`$KRB5CONFIG --cflags gssapi` + elif test "$GSSAPI_ROOT" != "yes"; then + GSSAPI_INCS="-I$GSSAPI_ROOT/include" + fi fi - fi - CPPFLAGS="$CPPFLAGS $GSSAPI_INCS" + CPPFLAGS="$CPPFLAGS $GSSAPI_INCS" - AC_CHECK_HEADER(gss.h, - [ - dnl found in the given dirs - AC_DEFINE(HAVE_GSSGNU, 1, [if you have GNU GSS]) - gnu_gss=yes - ], - [ - dnl not found, check for MIT - AC_CHECK_HEADERS( - [gssapi/gssapi.h gssapi/gssapi_generic.h gssapi/gssapi_krb5.h], - [], - [not_mit=1]) - if test "$not_mit" = "1"; then - dnl MIT not found - AC_MSG_ERROR([MIT or GNU GSS library required, but not found]) - fi - ] - ) + AC_CHECK_HEADER(gss.h, + [ + dnl found in the given dirs + AC_DEFINE(HAVE_GSSGNU, 1, [if you have GNU GSS]) + gnu_gss=yes + ], + [ + dnl not found, check for MIT + AC_CHECK_HEADERS( + [gssapi/gssapi.h gssapi/gssapi_generic.h gssapi/gssapi_krb5.h], + [], + [not_mit=1]) + if test "$not_mit" = "1"; then + dnl MIT not found + AC_MSG_ERROR([MIT or GNU GSS library required, but not found]) + fi + ] + ) + fi else AC_MSG_RESULT(no) fi if test "$want_gss" = "yes"; then AC_DEFINE(HAVE_GSSAPI, 1, [if you have GSS-API libraries]) HAVE_GSSAPI=1 - curl_gss_msg="enabled (MIT Kerberos)" - link_pkgconfig='' - - if test -n "$gnu_gss"; then - curl_gss_msg="enabled (GNU GSS)" - LDFLAGS="$LDFLAGS $GSSAPI_LIB_DIR" - LDFLAGSPC="$LDFLAGSPC $GSSAPI_LIB_DIR" - LIBS="-lgss $LIBS" - link_pkgconfig=1 - elif test -z "$GSSAPI_LIB_DIR"; then - if test "$curl_cv_apple" = "yes"; then - LIBS="-lgssapi_krb5 -lresolv $LIBS" - else - if test "$GSSAPI_ROOT" != "/usr"; then - CURL_CHECK_PKGCONFIG(mit-krb5-gssapi, $GSSAPI_ROOT/lib/pkgconfig) + + if test "$want_gss_apple" = "yes"; then + curl_gss_msg="enabled (Apple GSS)" + GSS_LDFLAGS='-framework GSS' + LDFLAGS="$LDFLAGS $GSS_LDFLAGS" + LDFLAGSPC="$LDFLAGSPC $GSS_LDFLAGS" + else + curl_gss_msg="enabled (MIT Kerberos)" + link_pkgconfig='' + + if test -n "$gnu_gss"; then + curl_gss_msg="enabled (GNU GSS)" + LDFLAGS="$LDFLAGS $GSSAPI_LIB_DIR" + LDFLAGSPC="$LDFLAGSPC $GSSAPI_LIB_DIR" + LIBS="-lgss $LIBS" + link_pkgconfig=1 + elif test -z "$GSSAPI_LIB_DIR"; then + if test "$curl_cv_apple" = "yes"; then + LIBS="-lgssapi_krb5 -lresolv $LIBS" else - CURL_CHECK_PKGCONFIG(mit-krb5-gssapi) + if test "$GSSAPI_ROOT" != "/usr"; then + CURL_CHECK_PKGCONFIG(mit-krb5-gssapi, $GSSAPI_ROOT/lib/pkgconfig) + else + CURL_CHECK_PKGCONFIG(mit-krb5-gssapi) + fi + if test -n "$host_alias" && test -f "$GSSAPI_ROOT/bin/$host_alias-krb5-config"; then + dnl krb5-config does not have --libs-only-L or similar, put everything + dnl into LIBS + gss_libs=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --libs gssapi` + LIBS="$gss_libs $LIBS" + elif test "$PKGCONFIG" != "no"; then + gss_libs=`$PKGCONFIG --libs mit-krb5-gssapi` + LIBS="$gss_libs $LIBS" + link_pkgconfig=1 + elif test -f "$KRB5CONFIG"; then + dnl krb5-config does not have --libs-only-L or similar, put everything + dnl into LIBS + gss_libs=`$KRB5CONFIG --libs gssapi` + LIBS="$gss_libs $LIBS" + link_pkgconfig=1 + else + case $host in + *-hp-hpux*) + gss_libname="gss" + ;; + *) + gss_libname="gssapi" + ;; + esac + + if test "$GSSAPI_ROOT" != "yes"; then + LDFLAGS="$LDFLAGS -L$GSSAPI_ROOT/lib$libsuff" + LDFLAGSPC="$LDFLAGSPC -L$GSSAPI_ROOT/lib$libsuff" + LIBS="-l$gss_libname $LIBS" + else + LIBS="-l$gss_libname $LIBS" + fi + fi fi + gss_version="" if test -n "$host_alias" && test -f "$GSSAPI_ROOT/bin/$host_alias-krb5-config"; then - dnl krb5-config does not have --libs-only-L or similar, put everything - dnl into LIBS - gss_libs=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --libs gssapi` - LIBS="$gss_libs $LIBS" + gss_version=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --version | $SED 's/Kerberos 5 release //'` elif test "$PKGCONFIG" != "no"; then - gss_libs=`$PKGCONFIG --libs mit-krb5-gssapi` - LIBS="$gss_libs $LIBS" - link_pkgconfig=1 + gss_version=`$PKGCONFIG --modversion mit-krb5-gssapi` elif test -f "$KRB5CONFIG"; then - dnl krb5-config does not have --libs-only-L or similar, put everything - dnl into LIBS - gss_libs=`$KRB5CONFIG --libs gssapi` - LIBS="$gss_libs $LIBS" - link_pkgconfig=1 - else - case $host in - *-hp-hpux*) - gss_libname="gss" - ;; - *) - gss_libname="gssapi" - ;; - esac - - if test "$GSSAPI_ROOT" != "yes"; then - LDFLAGS="$LDFLAGS -L$GSSAPI_ROOT/lib$libsuff" - LDFLAGSPC="$LDFLAGSPC -L$GSSAPI_ROOT/lib$libsuff" - LIBS="-l$gss_libname $LIBS" - else - LIBS="-l$gss_libname $LIBS" - fi + gss_version=`$KRB5CONFIG --version | $SED 's/Kerberos 5 release //'` + fi + if test -n "$gss_version"; then + AC_MSG_NOTICE([GSS-API MIT Kerberos version detected: $gss_version]) + AC_DEFINE_UNQUOTED([CURL_KRB5_VERSION], ["$gss_version"], [MIT Kerberos version]) fi - fi - gss_version="" - if test -n "$host_alias" && test -f "$GSSAPI_ROOT/bin/$host_alias-krb5-config"; then - gss_version=`$GSSAPI_ROOT/bin/$host_alias-krb5-config --version | $SED 's/Kerberos 5 release //'` - elif test "$PKGCONFIG" != "no"; then - gss_version=`$PKGCONFIG --modversion mit-krb5-gssapi` - elif test -f "$KRB5CONFIG"; then - gss_version=`$KRB5CONFIG --version | $SED 's/Kerberos 5 release //'` - fi - if test -n "$gss_version"; then - AC_MSG_NOTICE([GSS-API MIT Kerberos version detected: $gss_version]) - AC_DEFINE_UNQUOTED([CURL_KRB5_VERSION], ["$gss_version"], [MIT Kerberos version]) - fi - else - LDFLAGS="$LDFLAGS $GSSAPI_LIB_DIR" - LDFLAGSPC="$LDFLAGSPC $GSSAPI_LIB_DIR" - case $host in - *-hp-hpux*) - LIBS="-lgss $LIBS" - ;; - *) - LIBS="-lgssapi $LIBS" - ;; - esac - fi - if test -n "$link_pkgconfig"; then - if test -n "$gnu_gss"; then - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE gss" else - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE mit-krb5-gssapi" + LDFLAGS="$LDFLAGS $GSSAPI_LIB_DIR" + LDFLAGSPC="$LDFLAGSPC $GSSAPI_LIB_DIR" + case $host in + *-hp-hpux*) + LIBS="-lgss $LIBS" + ;; + *) + LIBS="-lgssapi $LIBS" + ;; + esac + fi + if test -n "$link_pkgconfig"; then + if test -n "$gnu_gss"; then + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE gss" + else + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE mit-krb5-gssapi" + fi fi fi else @@ -2025,6 +2108,7 @@ if test "$want_gss" = "yes"; then AC_MSG_RESULT([no]) AC_MSG_ERROR([--with-gssapi was specified, but a GSS-API library was not found.]) ]) + AC_CHECK_FUNCS([gss_set_neg_mechs]) fi build_libstubgss=no @@ -2080,7 +2164,7 @@ fi case "x$SSL_DISABLED$OPENSSL_ENABLED$GNUTLS_ENABLED$MBEDTLS_ENABLED$WOLFSSL_ENABLED$SCHANNEL_ENABLED$RUSTLS_ENABLED" in x) - AC_MSG_ERROR([TLS not detected, you will not be able to use HTTPS, FTPS, NTLM and more. + AC_MSG_ERROR([TLS not detected, cannot build with HTTPS, FTPS, NTLM and more. Use --with-openssl, --with-gnutls, --with-wolfssl, --with-mbedtls, --with-schannel, --with-amissl or --with-rustls to address this.]) ;; x1) @@ -2092,7 +2176,7 @@ Use --with-openssl, --with-gnutls, --with-wolfssl, --with-mbedtls, --with-schann dnl explicitly built without TLS ;; xD*) - AC_MSG_ERROR([--without-ssl has been set together with an explicit option to use an ssl library + AC_MSG_ERROR([--without-ssl has been set together with an explicit option to use an SSL library (e.g. --with-openssl, --with-gnutls, --with-wolfssl, --with-mbedtls, --with-schannel, --with-amissl, --with-rustls). Since these are conflicting parameters, verify which is the desired one and drop the other.]) ;; @@ -2282,7 +2366,7 @@ if test "x$OPT_LIBPSL" != "xno"; then ) if test "$USE_LIBPSL" != "1"; then - AC_MSG_ERROR([libpsl libs and/or directories were not found where specified!]) + AC_MSG_ERROR([libpsl libs and/or directories were not found!]) fi fi AM_CONDITIONAL([USE_LIBPSL], [test "$curl_psl_msg" = "enabled"]) @@ -2401,7 +2485,7 @@ if test "x$OPT_LIBSSH2" != "xno"; then ;; off) - dnl no --with-libssh2 option given, just check default places + dnl no --with-libssh2 option given, check default places ;; *) dnl use the given --with-libssh2 spot @@ -2422,7 +2506,7 @@ if test "x$OPT_LIBSSH2" != "xno"; then CPPFLAGS="$CPPFLAGS $CPP_SSH2" LIBS="$LIB_SSH2 $LIBS" - dnl check for function added in libssh2 version 1.9.0 + dnl check for function added in libssh2 1.9.0 AC_CHECK_LIB(ssh2, libssh2_agent_get_identity_path) AC_CHECK_HEADER(libssh2.h, @@ -2433,7 +2517,7 @@ if test "x$OPT_LIBSSH2" != "xno"; then if test "x$OPT_LIBSSH2" != "xoff" && test "$USE_LIBSSH2" != "1"; then - AC_MSG_ERROR([libssh2 libs and/or directories were not found where specified!]) + AC_MSG_ERROR([libssh2 libs and/or directories were not found!]) fi if test "$USE_LIBSSH2" = "1"; then @@ -2478,7 +2562,7 @@ elif test "x$OPT_LIBSSH" != "xno"; then ;; off) - dnl no --with-libssh option given, just check default places + dnl no --with-libssh option given, check default places ;; *) dnl use the given --with-libssh spot @@ -2509,7 +2593,7 @@ elif test "x$OPT_LIBSSH" != "xno"; then if test "x$OPT_LIBSSH" != "xoff" && test "$USE_LIBSSH" != "1"; then - AC_MSG_ERROR([libssh libs and/or directories were not found where specified!]) + AC_MSG_ERROR([libssh libs and/or directories were not found!]) fi if test "$USE_LIBSSH" = "1"; then @@ -2599,6 +2683,9 @@ if test "$CURL_DISABLE_LDAP" != "1" && test "$want_ldap" != "no"; then CURL_CHECK_HEADER_LDAP CURL_CHECK_HEADER_LDAP_SSL + dnl Default to failure; AC_CHECK_LIB success paths set this to yes. + ldap_lib_ok="no" + if test -z "$LDAPLIBNAME"; then if test "$curl_cv_native_windows" = "yes" && test "$curl_cv_winuwp" != "yes"; then dnl Windows uses a single and unique LDAP library name @@ -2726,7 +2813,7 @@ dnl ********************************************************************** dnl Check for linker switch for versioned symbols dnl ********************************************************************** -versioned_symbols_flavour= +versioned_symbols_flavor= AC_MSG_CHECKING([whether versioned symbols are wanted]) AC_ARG_ENABLE(versioned-symbols, AS_HELP_STRING([--enable-versioned-symbols], [Enable versioned symbols in shared library]) @@ -2738,28 +2825,28 @@ AS_HELP_STRING([--disable-versioned-symbols], [Disable versioned symbols in shar *) AC_MSG_RESULT(yes) AC_MSG_CHECKING([if libraries can be versioned]) - GLD=`$LD --help < /dev/null 2>/dev/null | grep version-script` + GLD=`$LD --help < /dev/null 2>/dev/null | $GREP version-script` if test -z "$GLD"; then AC_MSG_RESULT(no) AC_MSG_WARN([You need an ld version supporting the --version-script option]) else AC_MSG_RESULT(yes) if test "x$enableval" != "xyes"; then - versioned_symbols_flavour="$enableval" + versioned_symbols_flavor="$enableval" elif test "$CURL_WITH_MULTI_SSL" = "1"; then - versioned_symbols_flavour="MULTISSL_" + versioned_symbols_flavor="MULTISSL_" elif test "$OPENSSL_ENABLED" = "1"; then - versioned_symbols_flavour="OPENSSL_" + versioned_symbols_flavor="OPENSSL_" elif test "$MBEDTLS_ENABLED" = "1"; then - versioned_symbols_flavour="MBEDTLS_" + versioned_symbols_flavor="MBEDTLS_" elif test "$WOLFSSL_ENABLED" = "1"; then - versioned_symbols_flavour="WOLFSSL_" + versioned_symbols_flavor="WOLFSSL_" elif test "$GNUTLS_ENABLED" = "1"; then - versioned_symbols_flavour="GNUTLS_" + versioned_symbols_flavor="GNUTLS_" elif test "$RUSTLS_ENABLED" = "1"; then - versioned_symbols_flavour="RUSTLS_" + versioned_symbols_flavor="RUSTLS_" else - versioned_symbols_flavour="" + versioned_symbols_flavor="" fi versioned_symbols="yes" fi @@ -2771,11 +2858,33 @@ AS_HELP_STRING([--disable-versioned-symbols], [Disable versioned symbols in shar ] ) -AC_SUBST([CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX], ["$versioned_symbols_flavour"]) +AC_SUBST([CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX], ["$versioned_symbols_flavor"]) AC_SUBST([CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME], ["4"]) dnl Keep in sync with VERSIONCHANGE - VERSIONDEL in lib/Makefile.soname AM_CONDITIONAL([CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS], [test "$versioned_symbols" = "yes"]) +dnl --------------------------- +dnl check Apple fast UDP option +dnl --------------------------- + +if test "$curl_cv_apple" = "yes"; then + AC_MSG_CHECKING([whether to use Apple fast UDP]) + AC_ARG_ENABLE(apple-fast-udp, +AS_HELP_STRING([--enable-apple-fast-udp],[Enable using Apple fast UDP (experimental)]) +AS_HELP_STRING([--disable-apple-fast-udp],[Disable using Apple fast UDP (experimental) (default)]), + [ case "$enableval" in + yes) + AC_MSG_RESULT([yes]) + AC_DEFINE(USE_APPLE_FAST_UDP, 1, [to use Apple fast UDP (SYS_recvmsg_x, SYS_sendmsg_x)]) + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac ], + AC_MSG_RESULT([no]) + ) +fi + dnl ---------------------------- dnl check Windows Unicode option dnl ---------------------------- @@ -3677,8 +3786,8 @@ if test "$disable_http" = "yes" || test "$USE_NGTCP" = "1"; then fi AC_ARG_WITH(quiche, -AS_HELP_STRING([--with-quiche=PATH],[Enable quiche usage]) -AS_HELP_STRING([--without-quiche],[Disable quiche usage]), +AS_HELP_STRING([--with-quiche=PATH],[Enable quiche usage (experimental)]) +AS_HELP_STRING([--without-quiche],[Disable quiche usage (experimental)]), [OPT_QUICHE=$withval]) case "$OPT_QUICHE" in no) @@ -3739,7 +3848,7 @@ if test "$want_quiche" != "no"; then AC_CHECK_LIB(quiche, quiche_conn_send_ack_eliciting, [ AC_CHECK_HEADERS(quiche.h, - experimental="$experimental HTTP3" + experimental="$experimental quiche" AC_MSG_NOTICE([HTTP3 support is experimental]) curl_h3_msg="enabled (quiche)" AC_DEFINE(USE_QUICHE, 1, [if quiche is in use]) @@ -3882,7 +3991,7 @@ case "$OPT_ZSH_FPATH" in ;; esac if test -z "$PERL" && test -n "$ZSH_FUNCTIONS_DIR"; then - AC_MSG_WARN([perl was not found. Will not install zsh completions.]) + AC_MSG_WARN([perl was not found. Cannot install zsh completions.]) ZSH_FUNCTIONS_DIR='' fi AM_CONDITIONAL(USE_ZSH_COMPLETION, test -n "$ZSH_FUNCTIONS_DIR") @@ -3917,7 +4026,7 @@ case "$OPT_FISH_FPATH" in ;; esac if test -z "$PERL" && test -n "$FISH_FUNCTIONS_DIR"; then - AC_MSG_WARN([perl was not found. Will not install fish completions.]) + AC_MSG_WARN([perl was not found. Cannot install fish completions.]) FISH_FUNCTIONS_DIR='' fi AM_CONDITIONAL(USE_FISH_COMPLETION, test -n "$FISH_FUNCTIONS_DIR") @@ -3937,6 +4046,7 @@ AC_CHECK_HEADERS( linux/tcp.h \ netinet/tcp.h \ netinet/udp.h \ + netinet/ip.h \ netdb.h \ sys/sockio.h \ sys/param.h \ @@ -4061,7 +4171,8 @@ case $host_os in CURL_RUN_IFELSE( [ #include - int main(void) { + int main(void) + { time_t t = -1; return t < 0; } @@ -4079,8 +4190,6 @@ esac TYPE_SOCKADDR_STORAGE -CURL_CHECK_FUNC_SELECT - CURL_CHECK_FUNC_RECV CURL_CHECK_FUNC_SEND @@ -4129,7 +4238,6 @@ AC_CHECK_FUNCS([\ getpwuid \ getpwuid_r \ getrlimit \ - gettimeofday \ mach_absolute_time \ pipe \ poll \ @@ -4152,15 +4260,19 @@ fi if test "$curl_cv_native_windows" != "yes"; then AC_CHECK_FUNCS([\ + gettimeofday \ if_nametoindex \ realpath \ sched_yield \ ]) - CURL_CHECK_FUNC_INET_NTOP - CURL_CHECK_FUNC_INET_PTON CURL_CHECK_FUNC_STRCASECMP CURL_CHECK_FUNC_STRCMPI CURL_CHECK_FUNC_STRICMP + + CURL_CHECK_FUNC_MEMSET_S + if test "$curl_cv_func_memset_s" = "no"; then + AC_CHECK_FUNCS([memset_explicit]) + fi fi if test -z "$ssl_backends"; then @@ -4280,11 +4392,13 @@ elif test "$USE_ARES" = "1"; then curl_res_msg="c-ares" fi -AC_CHECK_HEADER(dirent.h, - [ AC_DEFINE(HAVE_DIRENT_H, 1, [if you have ]) - AC_CHECK_FUNC(opendir, AC_DEFINE(HAVE_OPENDIR, 1, [if you have opendir]) ) - ] -) +if test "$curl_cv_native_windows" != "yes"; then + AC_CHECK_HEADER(dirent.h, + [ AC_DEFINE(HAVE_DIRENT_H, 1, [if you have ]) + AC_CHECK_FUNC(opendir, AC_DEFINE(HAVE_OPENDIR, 1, [if you have opendir]) ) + ] + ) +fi CURL_CONVERT_INCLUDE_TO_ISYSTEM @@ -4469,16 +4583,16 @@ AS_HELP_STRING([--disable-negotiate-auth],[Disable negotiate authentication]), ) dnl ************************************************************ -dnl disable aws +dnl disable aws-sigv4 dnl -AC_MSG_CHECKING([whether to enable aws sig methods]) +AC_MSG_CHECKING([whether to enable aws-sigv4 methods]) AC_ARG_ENABLE(aws, -AS_HELP_STRING([--enable-aws],[Enable AWS sig support (default)]) -AS_HELP_STRING([--disable-aws],[Disable AWS sig support]), +AS_HELP_STRING([--enable-aws],[Enable aws-sigv4 support (default)]) +AS_HELP_STRING([--disable-aws],[Disable aws-sigv4 support]), [ case "$enableval" in no) AC_MSG_RESULT(no) - AC_DEFINE(CURL_DISABLE_AWS, 1, [to disable AWS sig support]) + AC_DEFINE(CURL_DISABLE_AWS, 1, [to disable aws-sigv4 support]) ;; *) AC_MSG_RESULT(yes) @@ -4487,6 +4601,35 @@ AS_HELP_STRING([--disable-aws],[Disable AWS sig support]), AC_MSG_RESULT(yes) ) +dnl ************************************************************ +dnl disable httpsig (RFC 9421) +dnl +AC_MSG_CHECKING([whether to enable HTTP Message Signatures (RFC 9421)]) +AC_ARG_ENABLE(httpsig, +AS_HELP_STRING([--enable-httpsig],[Enable HTTP Message Signatures support (experimental)]) +AS_HELP_STRING([--disable-httpsig],[Disable HTTP Message Signatures support (experimental) (default)]), +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + want_httpsig="no" + ;; + *) + AC_MSG_RESULT(yes) + want_httpsig="yes" + ;; + esac ], + AC_MSG_RESULT(no) + want_httpsig="no" +) + +if test "$want_httpsig" = "yes" && test "$GNUTLS_ENABLED" != "1" && test "$OPENSSL_ENABLED" != "1" && test "$WOLFSSL_ENABLED" != "1"; then + AC_MSG_WARN([HTTPSIG requires GnuTLS, OpenSSL or wolfSSL. HTTPSIG support disabled.]) + want_httpsig="no" +fi +if test "$want_httpsig" != "yes"; then + AC_DEFINE(CURL_DISABLE_HTTPSIG, 1, [to disable HTTP Message Signatures support]) +fi + dnl ************************************************************ dnl disable NTLM support dnl @@ -4507,33 +4650,6 @@ AS_HELP_STRING([--disable-ntlm],[Disable NTLM support]), AC_MSG_RESULT(no) ) -dnl ************************************************************ -dnl disable TLS-SRP authentication -dnl -AC_MSG_CHECKING([whether to enable TLS-SRP authentication]) -AC_ARG_ENABLE(tls-srp, -AS_HELP_STRING([--enable-tls-srp],[Enable TLS-SRP authentication]) -AS_HELP_STRING([--disable-tls-srp],[Disable TLS-SRP authentication]), -[ case "$enableval" in - no) - AC_MSG_RESULT(no) - want_tls_srp=no - ;; - *) - AC_MSG_RESULT(yes) - want_tls_srp=yes - ;; - esac ], - AC_MSG_RESULT(yes) - want_tls_srp=yes -) - -if test "$want_tls_srp" = "yes" && (test "$HAVE_GNUTLS_SRP" = "1" || test "$HAVE_OPENSSL_SRP" = "1"); then - AC_DEFINE(USE_TLS_SRP, 1, [Use TLS-SRP authentication]) - USE_TLS_SRP=1 - curl_tls_srp_msg="enabled" -fi - dnl ************************************************************ dnl disable Unix domain sockets support dnl @@ -5027,6 +5143,28 @@ if test "$want_ssls_export" != "no"; then fi fi +dnl ************************************************************* +dnl check whether experimental HTTP/3 proxy support is enabled +dnl +if test "$want_proxy_http3" = "yes"; then + AC_MSG_CHECKING([whether HTTP/3 proxy support is available]) + + if test "$CURL_DISABLE_PROXY" = "1"; then + AC_MSG_ERROR([--enable-proxy-http3 requires proxy support]) + elif test "$CURL_DISABLE_HTTP" = "1"; then + AC_MSG_ERROR([--enable-proxy-http3 requires HTTP support]) + elif test "$USE_NGTCP2_H3" != "1"; then + AC_MSG_ERROR([--enable-proxy-http3 requires ngtcp2 + nghttp3]) + elif test "x$OPENSSL_ENABLED" != "x1"; then + AC_MSG_ERROR([--enable-proxy-http3 currently requires OpenSSL]) + else + AC_DEFINE(USE_PROXY_HTTP3, 1, [if HTTP/3 proxy support is available]) + USE_PROXY_HTTP3=1 + AC_MSG_RESULT([yes]) + experimental="$experimental proxy-HTTP3" + fi +fi + dnl ************************************************************ dnl hiding of library internal symbols dnl @@ -5085,6 +5223,7 @@ dnl Merge pkg-config private fields into public ones when static-only if test "$enable_shared" = "no"; then LIBCURL_PC_REQUIRES=$LIBCURL_PC_REQUIRES_PRIVATE LIBCURL_PC_LIBS=$LIBCURL_PC_LIBS_PRIVATE + LIBCURL_PC_REQUIRES_PRIVATE= else LIBCURL_PC_REQUIRES= LIBCURL_PC_LIBS= @@ -5092,7 +5231,12 @@ fi AC_SUBST(LIBCURL_PC_REQUIRES) AC_SUBST(LIBCURL_PC_LIBS) -rm $compilersh +if test -n "$CURL_CI"; then + CURL_PACKAGE_MAINTAINER=https://curl.se/ + AC_SUBST(CURL_PACKAGE_MAINTAINER) +fi + +rm "$compilersh" dnl dnl For keeping supported features and protocols also in pkg-config file @@ -5140,6 +5284,10 @@ if test "$curl_psl_msg" = "enabled"; then SUPPORT_FEATURES="$SUPPORT_FEATURES PSL" fi +if test "$USE_PROXY_HTTP3" = "1"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES proxy-HTTP3" +fi + if test "$curl_gsasl_msg" = "enabled"; then SUPPORT_FEATURES="$SUPPORT_FEATURES gsasl" fi @@ -5176,10 +5324,6 @@ if test "$CURL_ENABLE_NTLM" = "1"; then fi fi -if test "$USE_TLS_SRP" = "1"; then - SUPPORT_FEATURES="$SUPPORT_FEATURES TLS-SRP" -fi - if test "$USE_NGHTTP2" = "1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTP2" fi @@ -5205,7 +5349,6 @@ if test "$CURL_DISABLE_HTTP" != "1"; then test "$GNUTLS_ENABLED" = "1" || test "$RUSTLS_ENABLED" = "1" || test "$SCHANNEL_ENABLED" = "1" || - test "$GNUTLS_ENABLED" = "1" || test "$MBEDTLS_ENABLED" = "1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTPS-proxy" AC_MSG_RESULT([yes]) @@ -5240,6 +5383,11 @@ if test "$want_httpsrr" != "no"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTPSRR" fi +if test "$want_httpsig" = "yes"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES HTTPSIG" + experimental="$experimental HTTPSIG" +fi + if test "$SSLS_EXPORT_ENABLED" = "1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES SSLS-EXPORT" fi @@ -5420,17 +5568,21 @@ AC_CONFIG_FILES([\ tests/configurehelp.pm \ tests/certs/Makefile \ tests/data/Makefile \ - tests/server/Makefile \ - tests/libtest/Makefile \ - tests/unit/Makefile \ - tests/tunit/Makefile \ tests/http/config.ini \ tests/http/Makefile \ + tests/libtest/Makefile \ + tests/perf/Makefile \ + tests/server/Makefile \ + tests/tunit/Makefile \ + tests/unit/Makefile \ projects/Makefile \ - projects/vms/Makefile \ - libcurl.pc + projects/vms/Makefile ]) AC_CONFIG_FILES([curl-config], [chmod +x curl-config]) +AC_CONFIG_FILES([libcurl.pc], [ + dnl strip trailing spaces, duplicate spaces after colon, empty properties + "$SED" -e 's/ *$//g' -e 's/^Libs\.private: */Libs.private: /' -e '/^@<:@A-Za-z.@:>@*:$/d' libcurl.pc > libcurl.pc.tmp && mv libcurl.pc.tmp libcurl.pc +], [SED="$SED"]) AC_OUTPUT SUPPORT_PROTOCOLS_LOWER=`echo "$SUPPORT_PROTOCOLS" | tr A-Z a-z` @@ -5455,7 +5607,6 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: zstd: ${curl_zstd_msg} GSS-API: ${curl_gss_msg} GSASL: ${curl_gsasl_msg} - TLS-SRP: ${curl_tls_srp_msg} resolver: ${curl_res_msg} IPv6: ${curl_ipv6_msg} Unix sockets: ${curl_unix_sockets_msg} @@ -5468,11 +5619,11 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: Verbose errors: ${curl_verbose_msg} Code coverage: ${curl_coverage_msg} SSPI: ${curl_sspi_msg} - ca native: ${ca_native} - ca cert bundle: ${ca}${ca_warning} - ca cert path: ${capath}${capath_warning} - ca cert embed: ${CURL_CA_EMBED_msg} - ca fallback: ${with_ca_fallback} + CA native: ${ca_native} + CA cert bundle: ${ca}${ca_warning} + CA cert path: ${capath}${capath_warning} + CA cert embed: ${CURL_CA_EMBED_msg} + CA fallback: ${with_ca_fallback} LDAP: ${curl_ldap_msg} LDAPS: ${curl_ldaps_msg} IPFS/IPNS: ${curl_ipfs_msg} @@ -5484,6 +5635,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: HTTP1: ${curl_h1_msg} HTTP2: ${curl_h2_msg} HTTP3: ${curl_h3_msg} + proxy-HTTP3: ${curl_proxy_http3_msg} ECH: ${curl_ech_msg} HTTPS RR: ${curl_httpsrr_msg} SSLS-EXPORT: ${curl_ssls_export_msg} @@ -5491,6 +5643,10 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: Features: ${SUPPORT_FEATURES} ]) +if test "$want_debug" = "yes"; then + AC_MSG_WARN([This curl build is Debug-enabled and insecure, do not use in production.]) +fi + if test -n "$experimental"; then for a in $experimental; do AC_MSG_WARN([$a is enabled but marked EXPERIMENTAL. Use with caution!]) diff --git a/docs/BINDINGS.md b/docs/BINDINGS.md index 9a53f81f29f8..6c9aba61054b 100644 --- a/docs/BINDINGS.md +++ b/docs/BINDINGS.md @@ -61,17 +61,17 @@ Go: [go-curl](https://github.com/andelf/go-curl) by ShuYu Wang [Haskell](https://hackage.haskell.org/package/curl) Written by Galois, Inc -[Hollywood](https://web.archive.org/web/20250116185836/www.hollywood-mal.com/download.html) hURL by Andreas Falkenhahn +[Hollywood](https://www.hollywood-mal.com/download.html) hURL by Andreas Falkenhahn [Java](https://github.com/covers1624/curl4j) [Julia](https://github.com/JuliaWeb/LibCURL.jl) Written by Amit Murthy -[Kapito](https://github.com/puzza007/katipo) is an Erlang HTTP library around libcurl. +[Katipo](https://github.com/puzza007/katipo) is an Erlang HTTP library around libcurl. [Lisp](https://common-lisp.net/project/cl-curl/) Written by Liam Healy -[LibQurl](https://github.com/Qriist/LibQurl) a feature rich AutoHotKey v2 (AHKv2) wrapper around libcurl. +[LibQurl](https://github.com/Qriist/LibQurl) a feature-rich AutoHotKey v2 (AHKv2) wrapper around libcurl. Lua: [luacurl](https://web.archive.org/web/20201205052437/luacurl.luaforge.net/) by Alexander Marinov, [Lua-curl](https://github.com/Lua-cURL) by Jürgen Hötzel @@ -110,7 +110,7 @@ Bailiff and Bálint Szilakszi, [R](https://cran.r-project.org/package=curl) -[Rexx](https://rexxcurl.sourceforge.net/) Written Mark Hessling +[Rexx](https://rexxcurl.sourceforge.net/) Written by Mark Hessling [Ring](https://ring-lang.github.io/doc1.24/libcurl.html) RingLibCurl by Mahmoud Fayed diff --git a/docs/CIPHERS.md b/docs/CIPHERS.md index 060d3da94983..0c1c3db666ce 100644 --- a/docs/CIPHERS.md +++ b/docs/CIPHERS.md @@ -96,10 +96,10 @@ are NULL ciphers, offering no encryption whatsoever.) ### TLS 1.2 (1.1, 1.0) cipher suites -Setting TLS 1.2 cipher suites is supported by curl with OpenSSL, LibreSSL, -BoringSSL, mbedTLS (curl 8.8.0+), wolfSSL (curl 7.53.0+). Schannel does not -support setting cipher suites directly, but does support setting algorithms -(curl 7.61.0+), see Schannel notes below. +Setting TLS 1.2 cipher suites is supported by curl with AWS-LC, BoringSSL, +LibreSSL, mbedTLS (curl 8.8.0+), OpenSSL, wolfSSL (curl 7.53.0+). Schannel +does not support setting cipher suites directly, but does support setting +algorithms (curl 7.61.0+), see Schannel notes below. For TLS 1.2 cipher suites there are multiple naming schemes, the two most used are with OpenSSL names (e.g. `ECDHE-RSA-AES128-GCM-SHA256`) and IANA names @@ -188,12 +188,13 @@ mbedTLS and wolfSSL. ```sh curl \ --tlsv1.3 \ + --tls-max 1.3 \ --tls13-ciphers TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256 \ https://example.com/ ``` Restrict to only TLS 1.3 with `aes128-gcm` and `chacha20` ciphers. Works with -OpenSSL, LibreSSL, mbedTLS, wolfSSL and Schannel. +OpenSSL, LibreSSL, mbedTLS and wolfSSL. ```sh curl \ @@ -223,9 +224,8 @@ the following way: * When the set string starts with '+', '-' or '!' it is *appended* to the priority string libcurl itself generates (separated by ':'). This initial - priority depends other settings such as CURLOPT_SSLVERSION(3), - CURLOPT_TLSAUTH_USERNAME(3) (for SRP) or if HTTP/3 (QUIC) - is being negotiated. + priority depends other settings such as CURLOPT_SSLVERSION(3) or if HTTP/3 + (QUIC) is being negotiated. * Otherwise, the set string fully *replaces* the libcurl generated one. While giving full control to the application, the set priority needs to provide for everything the transfer may need to negotiate. Example: if @@ -238,7 +238,7 @@ other keywords that tweak its operations. Applications or a system may define new alias names for priority strings that can then be used here. Since the order of items in priority strings is significant, it makes no -sense for curl to puzzle other ssl options somehow together. `--ciphers` +sense for curl to puzzle other SSL options somehow together. `--ciphers` is the single way to change priority. ### Examples @@ -269,10 +269,10 @@ Restrict to only TLS 1.2 with the `CAMELLIA-128-GCM` cipher. ## Further reading -- [OpenSSL cipher suite names documentation](https://docs.openssl.org/master/man1/openssl-ciphers/#cipher-suite-names) -- [wolfSSL cipher support documentation](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter04.html#cipher-support) +- [GnuTLS Priority Strings](https://gnutls.org/manual/html_node/Priority-Strings.html) +- [IANA cipher suites list](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4) - [mbedTLS cipher suites reference](https://mbed-tls.readthedocs.io/projects/api/en/development/api/file/ssl__ciphersuites_8h/) +- [OpenSSL cipher suite names documentation](https://docs.openssl.org/master/man1/openssl-ciphers/#cipher-suite-names) - [Schannel cipher suites documentation](https://learn.microsoft.com/windows/win32/secauthn/cipher-suites-in-schannel) -- [IANA cipher suites list](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4) - [Wikipedia cipher suite article](https://en.wikipedia.org/wiki/Cipher_suite) -- [GnuTLS Priority Strings](https://gnutls.org/manual/html_node/Priority-Strings.html) +- [wolfSSL cipher support documentation](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter04.html#cipher-support) diff --git a/docs/CODE_REVIEW.md b/docs/CODE_REVIEW.md index da7bb2c9d78b..a80d2eec3ce2 100644 --- a/docs/CODE_REVIEW.md +++ b/docs/CODE_REVIEW.md @@ -156,14 +156,14 @@ Maybe use of `realloc()` should rather use the dynbuf functions? Do not allow new code that grows buffers without using dynbuf. -Use of C functions that rely on a terminating zero must only be used on data -that really do have a null-terminating zero. +Use of C functions that rely on a null-terminator must only be used on data +that really do have a null-terminator (`\0` byte). ## Dangerous "data styles" -Make extra precautions and verify that memory buffers that need a terminating -zero always have exactly that. Buffers *without* a null-terminator must not be -used as input to string functions. +Make extra precautions and verify that memory buffers that need +null-terminator always have exactly that. Buffers *without* a null-terminator +must not be used as input to string functions. # Commit messages diff --git a/docs/CONTRIBUTE.md b/docs/CONTRIBUTE.md index 316809193059..f9b473c09a35 100644 --- a/docs/CONTRIBUTE.md +++ b/docs/CONTRIBUTE.md @@ -143,10 +143,10 @@ it into a pull request for you, to have the CI jobs verify it proper before it can be merged. Be prepared that some feedback on the proposed change might then come on GitHub. -Your changes be reviewed and discussed and you are expected to correct flaws -pointed out and update accordingly, or the change risks stalling and -eventually getting deleted without action. As a submitter of a change, you are -the owner of that change until it has been merged. +As your changes are reviewed and discussed, you are expected to address any +flaws pointed out and update accordingly. Otherwise your changes risk stalling +and eventually being deleted without action. As a submitter of a change, you +are the owner of that change until it has been merged. Respond on the list or on GitHub about the change and answer questions and/or fix nits/flaws. This is important. We take lack of replies as a sign that you @@ -261,7 +261,7 @@ work. has already been closed. - `Ref: URL` to more information about the commit; use `Bug:` instead for a - reference to a bug on another bug tracker] + reference to a bug on another bug tracker. - `Fixes #1234` - if this fixes a GitHub issue; GitHub closes the issue once this commit is merged. diff --git a/docs/CURL-DISABLE.md b/docs/CURL-DISABLE.md index a6a1ea16614c..83494cf18b4f 100644 --- a/docs/CURL-DISABLE.md +++ b/docs/CURL-DISABLE.md @@ -42,6 +42,10 @@ Disable support for the negotiate authentication methods. Disable **aws-sigv4** support. +## `CURL_DISABLE_HTTPSIG` + +Disable RFC 9421 HTTP Message Signatures support. + ## `CURL_DISABLE_CA_SEARCH` Disable unsafe CA bundle search in PATH on Windows. diff --git a/docs/CURLDOWN.md b/docs/CURLDOWN.md index ce19b5f5d606..c804eae746b2 100644 --- a/docs/CURLDOWN.md +++ b/docs/CURLDOWN.md @@ -97,7 +97,7 @@ option. The available TLS backends are: - `GnuTLS` - `mbedTLS` -- `OpenSSL` (also covers BoringSSL, LibreSSL, quictls, AWS-LC and AmiSSL) +- `OpenSSL` (also covers AmiSSL, AWS-LC, BoringSSL, LibreSSL and quictls) - `rustls` - `Schannel` - `wolfSSL` diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md new file mode 100644 index 000000000000..efa659cb9350 --- /dev/null +++ b/docs/DEPENDENCIES.md @@ -0,0 +1,77 @@ + + +# curl dependencies + +Supported minimum versions of libs and build tools. + +## Portability + +We write curl and libcurl to compile with C89 compilers on 32-bit and up +machines. Most of libcurl assumes more or less POSIX compliance but that is +not a requirement. The compiler must support a 64-bit integer type as well as +supply a stdint.h header file that defines C99-style fixed-width integer types +like uint32_t. + +We write libcurl to build and work with lots of third party tools, and we +want it to remain functional and buildable with these and later versions +(older versions may still work but is not what we work hard to maintain): + +## Dependencies + +We aim to support these or later versions: + +- brotli 1.0.0 (2017-09-21) +- c-ares 1.16.0 (2020-03-13) +- GnuTLS 3.6.5 (2018-12-01) +- libidn2 2.0.0 (2017-03-29) +- libgsasl 1.6.0 (2010-12-14) +- libpsl 0.16.0 (2016-12-10) +- LibreSSL 2.9.1 (2019-04-21) +- libssh 0.9.0 (2019-06-28) +- libssh2 1.9.0 (2019-06-20) +- mbedTLS 3.2.0 (2022-07-11) +- MIT Kerberos 1.3 (2003-07-31) +- nettle 3.4.1 (2018-12-04) +- nghttp2 1.15.0 (2016-09-25) +- nghttp3 1.0.0 (2023-10-15) +- ngtcp2 1.0.0 (2023-10-15), with OpenSSL 3.5.0+: 1.12.0 (2025-04-16) +- OpenLDAP 2.0 (2000-08-01) +- OpenSSL 3.0.0 (2021-09-07) +- quiche 0.20.0 (2023-12-12) +- Windows Vista 6.0 (2006-11-08 - 2012-04-10) +- wolfSSL 5.0.0 (2021-11-01) +- zlib 1.2.5.2 (2011-12-11) +- zstd 1.0 (2016-08-31) + +## Build tools + +When writing code (mostly for generating stuff included in release tarballs) +we use a few "build tools" and we make sure that we remain functional with +these or later versions: + +- clang-tidy 17.0.0 (2023-09-19), recommended: 19.1.0 (2024-09-17) +- cmake 3.18 (2020-07-15) +- GNU autoconf 2.59 (2003-11-06) +- GNU automake 1.7 (2002-09-25) +- GNU libtool 1.4.2 (2001-09-11) +- GNU m4 1.4 (2007-09-21) +- mingw-w64 3.0 (2013-09-20) +- perl 5.8 (2002-07-19), on Windows: 5.22 (2015-06-01) +- Visual Studio 2010 10.0 (2010-04-12 - 2020-07-14) + +## Testing + +Certain tests require the following packages to operate. In some cases, tests +that do not find the necessary requirements are automatically skipped. + +- OpenSSL (see above) +- nghttp2 (see above) +- OpenSSH +- perl (see above) +- pytest +- Python 3.8 (2019-10-14) +- stunnel diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index fe00189182bb..e20fba5d617d 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -12,14 +12,6 @@ email the as soon as possible and explain to us why this is a problem for you and how your use case cannot be satisfied properly using a workaround. -## TLS-SRP Authentication - -Transport Layer Security Secure Remote Password is a TLS feature that does not -work with TLS 1.3 or QUIC and is virtually unused by curl users and in -general. - -TLS-SRP support gets removed in August 2026. - ## drop SMB support The SMB protocol has weak security and is rarely used these days. @@ -55,6 +47,16 @@ future curl versions when built without TLS support. For example Digest. Local crypto gets removed in October 2026. +## HTTP/2 Server Push + +This protocol feature has been deprecated in specifications, by major browsers, +and in server implementations. It was never supported by the curl command line +tool, only by libcurl. + +We estimate that barely any libcurl users still use this feature. + +HTTP/2 Server Push gets removed in March 2027. + ## Past removals - axTLS (removed in 7.63.0) @@ -83,3 +85,4 @@ Local crypto gets removed in October 2026. - SMB (became opt-in in 8.20.0) - NTLM (became opt-in in 8.20.0) - c-ares < 1.16.0 (removed in 8.20.0) +- TLS-SRP (removed in 8.22.0) diff --git a/docs/EARLY-RELEASE.md b/docs/EARLY-RELEASE.md index 8ec74c3e20c7..833a785ce064 100644 --- a/docs/EARLY-RELEASE.md +++ b/docs/EARLY-RELEASE.md @@ -57,7 +57,7 @@ the three ones above are all 'no'. - Can the bug be fixed "easily" by applying a patch? - Does the bug break the build? Most users do not build curl themselves. - How long is it until the already scheduled next release? -- Can affected users safely rather revert to a former release until the next +- Can affected users safely revert to a former release until the next scheduled release? - Is it a performance regression with no functionality side-effects? If so it has to be substantial. diff --git a/docs/ECH.md b/docs/ECH.md index 2a670edd01ca..8a0153209d8c 100644 --- a/docs/ECH.md +++ b/docs/ECH.md @@ -8,8 +8,8 @@ SPDX-License-Identifier: curl We have added support for ECH to curl. It can use HTTPS RRs published in the DNS if curl uses DoH, or else can accept the relevant ECHConfigList values -from the command line. This works with OpenSSL, wolfSSL, BoringSSL, AWS-LC -or rustls-ffi as the TLS provider. +from the command line. This works with AWS-LC, BoringSSL, OpenSSL, Rustls or +wolfSSL as the TLS provider. This feature is EXPERIMENTAL. DO NOT USE IN PRODUCTION. @@ -135,7 +135,7 @@ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo There is a reason to want this command line option - for use before publishing an ECHConfigList in the DNS as per the Internet-draft [A well-known URI for -publishing ECHConfigList values](https://datatracker.ietf.org/doc/draft-ietf-tls-wkech/). +publishing ECHConfigList values](https://datatracker.ietf.org/doc/html/draft-ietf-tls-wkech/). If you do use a wrong ECHConfigList value, then the server might return a good value, via the `retry_configs` mechanism. You can see that value in @@ -153,7 +153,7 @@ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo ``` At that point, you could copy the base64 encoded value above and try again. -For now, this only works for the OpenSSL and BoringSSL/AWS-LC builds. +For now, this only works for the OpenSSL and AWS-LC/BoringSSL builds. ## Default settings @@ -338,11 +338,11 @@ WARNING: ECH HTTPSRR enabled but marked EXPERIMENTAL. Use with caution. make ``` -The BoringSSL/AWS-LC APIs are fairly similar to those in our ECH-enabled +The AWS-LC/BoringSSL APIs are fairly similar to those in our ECH-enabled OpenSSL fork, so code changes are also in `lib/vtls/openssl.c`, protected via `#ifdef OPENSSL_IS_BORINGSSL` and are mostly obvious API variations. -The BoringSSL/AWS-LC APIs however do not support the `--ech pn:` command +The AWS-LC/BoringSSL APIs however do not support the `--ech pn:` command line variant as of now. ## wolfSSL build @@ -405,7 +405,7 @@ Then there are some functional code changes: The lack of support for `--ech false` is because wolfSSL has decided to always at least GREASE if built to support ECH. In other words, GREASE is a compile time choice for wolfSSL, but a runtime choice for OpenSSL or -BoringSSL/AWS-LC. (Both are reasonable.) +AWS-LC/BoringSSL. (Both are reasonable.) ## Additional notes @@ -471,7 +471,7 @@ get the HTTPS RR and pass the ECHConfigList from that on the command line, if needed, or one can access the value from command line output in verbose more and then reuse that in another invocation. -Both our OpenSSL fork and BoringSSL/AWS-LC have APIs for both controlling GREASE +Both our OpenSSL fork and AWS-LC/BoringSSL have APIs for both controlling GREASE and accessing and logging `retry_configs`, it seems wolfSSL has neither. ### Testing ECH diff --git a/docs/EXPERIMENTAL.md b/docs/EXPERIMENTAL.md index 43fc0fdeed88..51407743ee39 100644 --- a/docs/EXPERIMENTAL.md +++ b/docs/EXPERIMENTAL.md @@ -21,6 +21,8 @@ Experimental support in curl means: to our API/ABI rules as we do for regular features, as long as it is marked experimental. 5. Experimental features are clearly marked so in documentation. Beware. +6. Vulnerabilities in experimental features are not considered security + problems; please report them as regular bugs/feedback instead. ## Graduation @@ -32,18 +34,27 @@ Experimental support in curl means: provided by the experiment and then the disabling should be managed inside each affected test case. -## Experimental features right now - -### HTTP/3 support (non-ngtcp2 backends) +## The quiche QUIC and HTTP/3 backend Graduation requirements: -- The used libraries should be considered out-of-beta with a reasonable - expectation of a stable API going forward. +- The library should be considered out-of-beta + +- a reasonable expectation of a stable API going forward + +- HTTP/3 with the given build should perform without risking busy-loops + +## HTTP/3 proxy and CONNECT-UDP support + +Support for HTTP/3 proxy and CONNECT-UDP tunneling is experimental and +requires an explicit build-time opt-in (`--enable-proxy-http3` for +autotools, `-DUSE_PROXY_HTTP3=ON` for CMake). + +Graduation requirements: -- Using HTTP/3 with the given build should perform without risking busy-loops +- implementation stability over time with no known severe regressions -### The Rustls backend +## The Rustls TLS backend Graduation requirements: @@ -88,3 +99,39 @@ Graduation requirements: - HTTPS records can control ALPN and port number, at least - There are options to control HTTPS use + +## HTTP Message Signatures (RFC 9421) + +Sign outgoing HTTP requests according to RFC 9421 using the +`--httpsig-algo`, `--httpsig-key`, `--httpsig-keyid` and +`--httpsig-headers` command line options, or the corresponding +`CURLOPT_HTTPSIG_*` libcurl options. Built only when configured with +`--enable-httpsig`. + +Graduation requirements: + +- the option set (names, arguments, defaults) is settled + +- interoperability has been verified against at least two independent + RFC 9421 implementations + +- no test cases are disabled for the feature + +- feedback from users saying the API works for their use cases + +## Apple fast UDP + +There are undocumented system calls available in Apple operating systems that +when used allow for faster sending and receiving of UDP messages. + +The undocumented and thus unsupported-by-Apple nature of these functions +brings a risk that they are removed in a future OS update, or perhaps worse: +marginally modified to instead cause subtle and hard-to-spot bugs. + +Graduation requirements: + +- testimonials from users that these work reliably + +- measurements that show they make a measurable performance impact + +- an easy way to (re-)build curl to not use these functions diff --git a/docs/FAQ.md b/docs/FAQ.md index 05f7eda38299..6cbba7d81110 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -33,7 +33,7 @@ platforms. The [internals document](https://curl.se/docs/install.html#Ports) lists more than 110 operating systems and 28 CPU architectures on which curl has been reported to run. -libcurl is free, thread-safe, IPv6 compatible, feature rich, well supported +libcurl is free, thread-safe, IPv6 compatible, feature-rich, well supported and fast. ### curl @@ -176,7 +176,7 @@ fix and agree on a time schedule for publication etc. That way we produce a fix in a timely manner before the flaw is announced to the world, reducing the impact the problem risks having on existing users. -Security issues can also be taking to the curl security team by emailing +Security issues can also be taken to the curl security team by emailing security at curl.se (closed list of receivers, mails are not disclosed). ## Where do I buy commercial support for curl? @@ -204,20 +204,20 @@ world wide. ## Why do you not update ca-bundle.crt In the curl project we have decided not to attempt to keep this file updated -(or even present) since deciding what to add to a ca cert bundle is an +(or even present) since deciding what to add to a CA cert bundle is an undertaking we have not been ready to accept, and the one we can get from Mozilla is perfectly fine so there is no need to duplicate that work. Today, with many services performed over HTTPS, every operating system should -come with a default ca cert bundle that can be deemed somewhat trustworthy and +come with a default CA cert bundle that can be deemed somewhat trustworthy and that collection (if reasonably updated) should be deemed to be a lot better than a private curl version. -If you want the most recent collection of ca certs that Mozilla Firefox uses, -we recommend that using our online [CA certificate +If you want the most recent collection of CA certs that Mozilla Firefox uses, +we recommend using our online [CA certificate service](https://curl.se/docs/caextract.html) setup for this purpose. -## I have a problem who, can I chat with? +## I have a problem, who can I chat with? There is a bunch of friendly people hanging out in the #curl channel on the IRC network libera.chat. If you are polite and nice, chances are good that you @@ -253,7 +253,7 @@ to the curl-library mailing list. We are many subscribers there and there are lots of people who can review patches, comment on them and receive them properly. -Lots of more details are found in the +Many more details are found in the [contribute](https://curl.se/dev/contribute.html) and [internals](https://curl.se/dev/internals.html) documents. @@ -276,7 +276,7 @@ You may find that configure fails to properly detect the entire dependency chain of libraries when you provide static versions of the libraries that configure checks for. -The reason why static libraries is much harder to deal with is that for them +The reason why static libraries are much harder to deal with is that for them we do not get any help but the script itself must know or check what more libraries that are needed (with shared libraries, that dependency chain is handled automatically). This is an error-prone process and one that also tends @@ -294,10 +294,10 @@ curl has been written to use a generic SSL function layer internally, and that SSL functionality can then be provided by one out of many different SSL backends. -curl can be built to use one of the following SSL alternatives: OpenSSL, -LibreSSL, BoringSSL, AWS-LC, GnuTLS, wolfSSL, mbedTLS, Schannel (native -Windows) or Rustls. They all have their pros and cons, and we maintain [a TLS -library comparison](https://curl.se/docs/ssl-compared.html). +curl can be built to use one of the following SSL alternatives: AWS-LC, +BoringSSL, GnuTLS, LibreSSL, OpenSSL, mbedTLS, Rustls, Schannel (native +Windows), or wolfSSL. They all have their pros and cons, and we maintain +[a TLS library comparison](https://curl.se/docs/ssl-compared.html). ## How do I upgrade curl.exe in Windows? @@ -368,7 +368,7 @@ transfer. Study the `-Q`/`--quote` option. Since curl is used for file transfers, you do not normally use curl to perform FTP commands without transferring anything. Therefore you must always specify a URL to transfer to/from even when doing custom FTP commands, or use `-I` -which implies the *no body*" option sent to libcurl. +which implies the *no body* option sent to libcurl. ## How can I disable the Accept: header? @@ -634,7 +634,7 @@ does for you, you can override those request methods by specifying `-X `curl -X DELETE [URL]`. It is thus pointless to do `curl -XGET [URL]` as GET would be used anyway. In -the same vein it is pointless to do `curl -X POST -d data [URL`. You can make +the same vein it is pointless to do `curl -X POST -d data [URL]`. You can make a fun and somewhat rare request that sends a request-body in a GET request with something like `curl -X GET -d data [URL]`. @@ -854,11 +854,11 @@ results and fetches the new URL. curl supports FTPS (sometimes known as FTP-SSL) both implicit and explicit mode. -When a URL is used that starts with `FTPS://`, curl assumes implicit SSL on +When a URL is used that starts with `ftps://`, curl assumes implicit SSL on the control connection and therefore immediately connects and tries to speak -SSL. `FTPS://` connections default to port 990. +SSL. `ftps://` connections default to port 990. -To use explicit FTPS, you use an `FTP://` URL and the `--ssl-reqd` option (or +To use explicit FTPS, you use an `ftp://` URL and the `--ssl-reqd` option (or one of its related flavors). This is the most common method, and the one mandated by RFC 4217. This kind of connection then of course uses the standard FTP port 21 by default. @@ -893,7 +893,7 @@ software or similar that accepts the connection but does not actually do anything else. This makes (lib)curl to consider the connection connected and thus the connect timeout does not trigger. -## file:// URLs containing drive letters (Windows, NetWare) +## `file://` URLs containing drive letters (Windows, NetWare) When using curl to try to download a local file, one might use a URL in this format: @@ -986,7 +986,7 @@ programs. libcurl uses thread-safe functions instead of non-safe ones if your system has such. Note that you must never share the same handle in multiple threads. -There may be some exceptions to thread safety depending on how libcurl was +There may be some exceptions to thread-safety depending on how libcurl was built. Please review [the guidelines for thread safety](https://curl.se/libcurl/c/threadsafe.html) to learn more. @@ -1017,7 +1017,7 @@ WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)data; - mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + mem->memory = realloc(mem->memory, mem->size + realsize + 1); if(mem->memory) { memcpy(&(mem->memory[mem->size]), ptr, realsize); mem->size += realsize; @@ -1230,9 +1230,9 @@ wildcard to download multiple files from an FTP directory. ## I want a different time-out -Sometimes users realize that `CURLOPT_TIMEOUT` and `CURLOPT_CONNECTIMEOUT` are -not sufficiently advanced or flexible to cover all the various use cases and -scenarios applications end up with. +Sometimes users realize that `CURLOPT_TIMEOUT` and `CURLOPT_CONNECTTIMEOUT` +are not sufficiently advanced or flexible to cover all the various use cases +and scenarios applications end up with. libcurl offers many more ways to time-out operations. A common alternative is to use the `CURLOPT_LOW_SPEED_LIMIT` and `CURLOPT_LOW_SPEED_TIME` options to diff --git a/docs/HISTORY.md b/docs/HISTORY.md index c376905ebbae..479d9116d523 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -82,8 +82,10 @@ OpenSSL took over and SSLeay was abandoned. May: first Debian package. -August: LDAP:// and FILE:// support added. The curl website gets 1300 visits -weekly. Moved site to curl.haxx.nu. +August: `ldap://` and `file://` support added. The curl website gets 1300 +visits weekly. Moved site to curl.haxx.nu. + +September: started using CVS for source code version control. September: Released curl 6.0. 15000 lines of code. @@ -124,7 +126,7 @@ deemed "GPL incompatible".) March 22: curl supports HTTP 1.1 starting with the release of 7.7. This also introduced libcurl's ability to do persistent connections. 24000 lines of code. The libcurl major SONAME number was bumped to 2 due to this overhaul. -The first experimental ftps:// support was added. +The first experimental `ftps://` support was added. August: The curl website gets 8000 visits weekly. Curl Corporation contacted Daniel to discuss "the name issue". After Daniel's reply, they have never @@ -173,6 +175,8 @@ December: full-fledged SSL for FTP is supported. January: curl 7.11.0 introduced large file support. +March: added asynch name resolves using the c-ares library + June: curl 7.12.0 introduced IDN support. 10 official web mirrors. This release bumped the major SONAME to 3 due to the removal of the @@ -313,6 +317,8 @@ September: started "everything curl". A separate stand-alone book documenting curl and related info in perhaps a more tutorial style rather than a reference, +September: initial support for the Public Suffix List for cookies. + December: Public Suffix List ## 2016 @@ -465,7 +471,7 @@ December 21: dropped hyper ## 2025 -February 5: first 0RTT for QUIC, ssl session import/export +February 5: first 0RTT for QUIC, SSL session import/export February: experimental HTTPS RR support diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 77fa9664278c..07535f1559fa 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -215,14 +215,16 @@ but in case of problems, we recommend their latest release tag. ## Build -Build quiche and BoringSSL: +Build quiche and BoringSSL (described here for quiche v0.29.1, the locations +where BoringSSL is to be found vary with version): - % git clone --depth 1 --branch 0.24.7 --recursive https://github.com/cloudflare/quiche + % git clone --depth 1 --branch 0.29.1 --recursive https://github.com/cloudflare/quiche % cd quiche % cargo build --package quiche --release --features ffi,pkg-config-meta,qlog % ln -s libquiche.so target/release/libquiche.so.0 - % mkdir quiche/deps/boringssl/src/lib - % ln -vnf $(find target/release -name libcrypto.a -o -name libssl.a) quiche/deps/boringssl/src/lib/ + % mkdir -p boringssl/lib + % find target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' boringssl/lib \; + % find target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../{}' boringssl \; Build curl: @@ -230,8 +232,7 @@ Build curl: % git clone --depth 1 https://github.com/curl/curl % cd curl % autoreconf -fi - % ./configure LDFLAGS="-Wl,-rpath,$PWD/../quiche/target/release" \ - --with-openssl=$PWD/../quiche/quiche/deps/boringssl/src --with-quiche=$PWD/../quiche/target/release + % ./configure --with-openssl=$PWD/../quiche/boringssl --with-quiche=$PWD/../quiche/target/release % make % make install diff --git a/docs/HTTPSRR.md b/docs/HTTPSRR.md index bb96526b3985..e046e512ea0c 100644 --- a/docs/HTTPSRR.md +++ b/docs/HTTPSRR.md @@ -51,7 +51,7 @@ or The list of ALPN IDs is parsed but may not be completely respected because of what the HTTP version preference is set to, which is a problem we are working -on. Also, getting an `HTTP/1.1` ALPN in the HTTPS RR field for an HTTP:// +on. Also, getting an `HTTP/1.1` ALPN in the HTTPS RR field for an `http://` transfer should imply switching to HTTPS, HSTS style. Which curl currently does not. @@ -65,7 +65,7 @@ returned, curl parses it and stores the retrieved information. If DoH is not used for name resolving in an HTTPS RR enabled build, we must provide the ability using the regular resolver backends. We use the c-ares DNS -library for the HTTPS RR lookup. Version 1.28.0 or later. +library for the HTTPS RR lookup. Version 1.28.0 or greater. ### c-ares diff --git a/docs/INFRASTRUCTURE.md b/docs/INFRASTRUCTURE.md index 2f24845cddc4..ddcd1289d960 100644 --- a/docs/INFRASTRUCTURE.md +++ b/docs/INFRASTRUCTURE.md @@ -90,7 +90,7 @@ thus take up to 20 minutes until it takes effect on the origin server. The content on `curl.dev` is available and managed at https://github.com/curl/curl.dev/ -The content on `everything-curl.dev` is available and managed at +The content on `everything.curl.dev` is available and managed at https://github.com/curl/everything-curl/ The machine hosting the website contents for these three sites is owned by @@ -139,7 +139,7 @@ anycast access to the site. Should be snappy from virtually everywhere across the globe. The CDN servers support HTTP/1, HTTP/2 and HTTP/3. They set HSTS for a year. -The `HTTP://` version of the site redirects to `HTTPS://`. +The `http://` version of the site redirects to `https://`. Fastly manages the TLS certificates from Let's Encrypt for the servers they run on the behalf of curl. @@ -191,7 +191,7 @@ chat about curl and related topics. This done in the `#curl` channel on the `libra.chat` IRC network. **Daniel Stenberg** (`bagder`) is registered owner of the channel. We do not run any IRC servers or services ourselves. -`curelbot` is a service in the channel that shows details about GitHub issues +`curlbot` is a service in the channel that shows details about GitHub issues and pull requests when publicly mentioned using #[number]. The bot is run by user `TheAssassin`. diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index a84faf72f209..16a3580634ef 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -46,7 +46,7 @@ For the full list of CMake build configuration variables see ### Build system generator selection You can override CMake's default by using `-G `. For example -on Windows with multiple build systems if you have MinGW-w64 then you could use +on Windows with multiple build systems if you have mingw-w64 then you could use `-G "MinGW Makefiles"`. [List of generator names](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). @@ -103,16 +103,6 @@ arguments in the build. Building statically is not for the faint of heart. -### Fallback for CMake before version 3.13 - -CMake before version 3.13 does not support the `--build` option. In that -case, you have to `cd` to the build directory and use the building tool that -corresponds to the build files that CMake generated for you. This example -assumes that CMake generates `Makefile`: - - $ cd ../curl-build - $ make - # Testing (The test suite does not yet work with the cmake build) @@ -129,16 +119,6 @@ to set a custom install prefix for curl, set [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) when configuring the CMake build. -### Fallback for CMake before version 3.15 - -CMake before version 3.15 does not support the `--install` option. In that -case, you have to `cd` to the build directory and use the building tool that -corresponds to the build files that CMake generated for you. This example -assumes that CMake generates `Makefile`: - - $ cd ../curl-build - $ make install - # CMake usage This section describes how to locate and use curl/libcurl from CMake-based @@ -223,6 +203,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_COMPLETION_FISH_DIR`: Custom fish completion install directory. - `CURL_COMPLETION_ZSH`: Install zsh completions. Default: `OFF` - `CURL_COMPLETION_ZSH_DIR`: Custom zsh completion install directory. +- `CURL_DEBUG_GLOBAL_MEM`: Debug `curl_global_init_mem`. Default: `OFF` - `CURL_DEFAULT_SSL_BACKEND`: Override default TLS backend in MultiSSL builds. Accepted values in order of default priority: `wolfssl`, `gnutls`, `mbedtls`, `openssl`, `schannel`, `rustls` @@ -240,11 +221,13 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_TARGET_WINDOWS_VERSION`: Minimum target Windows version as hex string, e.g. `0x0a00` for Windows 10. - `CURL_WERROR`: Turn compiler warnings into errors. Default: `OFF` - `ENABLE_CURL_MANUAL`: Build the man page for curl and enable its `-M`/`--manual` option. Default: `ON` -- `ENABLE_DEBUG`: Enable curl debug features (for developing curl itself). Default: `OFF` +- `ENABLE_DEBUG`: Enable curl debug features (for developing curl). Default: `OFF` + This also requires Debug configuration enabled/selected at the same time, + via `CMAKE_BUILD_TYPE=Debug`, or `--config Debug` for multi-config generators. - `IMPORT_LIB_SUFFIX`: Import library suffix. Default: `_imp` for MSVC-like toolchains, otherwise empty. - `LIBCURL_OUTPUT_NAME`: Basename of the curl library. Default: `libcurl` - `PICKY_COMPILER`: Enable picky compiler options. Default: `ON` -- `SHARE_LIB_OBJECT`: Build shared and static libcurl in a single pass (requires CMake 3.12 or newer). Default: `ON` for Windows +- `SHARE_LIB_OBJECT`: Build shared and static libcurl in a single pass. Default: `ON` for Windows - `STATIC_LIB_SUFFIX`: Static library suffix. Default: (empty) ## Root CA options @@ -261,7 +244,9 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) ## Enabling features +- `CURL_ENABLE_APPLE_FAST_UDP`: Use Apple fast UDP (experimental). Default: `OFF` - `CURL_ENABLE_NTLM`: Enable NTLM support. Default: `OFF` +- `CURL_ENABLE_SMB`: Enable SMB. Default: `OFF` - `CURL_ENABLE_SSL`: Enable SSL support. Default: `ON` - `CURL_WINDOWS_SSPI`: Enable SSPI on Windows. Default: =`CURL_USE_SCHANNEL` - `ENABLE_IPV6`: Enable IPv6 support. Default: `ON` if target supports IPv6. @@ -269,9 +254,10 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `ENABLE_UNICODE`: Use the Unicode version of the Windows API functions. Default: `OFF` - `ENABLE_UNIX_SOCKETS`: Enable Unix domain sockets support. Default: `ON` - `USE_APPLE_IDN`: Use Apple built-in IDN support. Default: `OFF` -- `USE_ECH`: Enable ECH support. Default: `OFF` -- `USE_HTTPSRR`: Enable HTTPS RR support. Default: `OFF` -- `USE_SSLS_EXPORT`: Enable experimental SSL session import/export. Default: `OFF` +- `USE_ECH`: Enable ECH support (experimental). Default: `OFF` +- `USE_HTTPSRR`: Enable HTTPS RR support (experimental). Default: `OFF` +- `USE_PROXY_HTTP3`: Enable HTTP/3 proxy support (experimental). Default: `OFF` +- `USE_SSLS_EXPORT`: Enable SSL session import/export (experimental). Default: `OFF` - `USE_WIN32_IDN`: Use WinIDN for IDN support. Default: `OFF` - `USE_WIN32_LDAP`: Use Windows LDAP implementation. Default: `ON` @@ -296,6 +282,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_DISABLE_HSTS`: Disable HSTS support. Default: `OFF` - `CURL_DISABLE_HTTP`: Disable HTTP. Default: `OFF` - `CURL_DISABLE_HTTP_AUTH`: Disable all HTTP authentication methods. Default: `OFF` +- `CURL_DISABLE_HTTPSIG`: Disable HTTP Message Signatures (RFC 9421) (experimental). Default: `ON` - `CURL_DISABLE_IMAP`: Disable IMAP. Default: `OFF` - `CURL_DISABLE_INSTALL`: Disable installation targets. Default: `OFF` - `CURL_DISABLE_IPFS`: Disable IPFS. Default: `OFF` @@ -315,10 +302,8 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_DISABLE_RTSP`: Disable RTSP. Default: `OFF` - `CURL_DISABLE_SHA512_256`: Disable SHA-512/256 hash algorithm. Default: `OFF` - `CURL_DISABLE_SHUFFLE_DNS`: Disable shuffle DNS feature. Default: `OFF` -- `CURL_ENABLE_SMB`: Enable SMB. Default: `OFF` - `CURL_DISABLE_SMTP`: Disable SMTP. Default: `OFF` - `CURL_DISABLE_SOCKETPAIR`: Disable use of socketpair for curl_multi_poll(). Default: `OFF` -- `CURL_DISABLE_SRP`: Disable TLS-SRP support. Default: `OFF` - `CURL_DISABLE_TELNET`: Disable Telnet. Default: `OFF` - `CURL_DISABLE_TFTP`: Disable TFTP. Default: `OFF` - `CURL_DISABLE_TYPECHECK`: Disable curl_easy_setopt()/curl_easy_getinfo() type checking. Default: `OFF` @@ -332,6 +317,17 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_BUILDINFO`: Print `buildinfo.txt` if set. - `CURL_CI`: Assume running under CI if set. +## Environment (via CMake) + +- `CC`: Set C compiler. Alternative to `CMAKE_C_COMPILER` option. +- `CFLAGS`: Pass custom C compiler flags. Alternative to `CMAKE_C_FLAGS` option. +- `CMAKE_GENERATOR`: Alternative to `-G` command-line option. +- `DESTDIR`: Set install destination directory. +- `LDFLAGS`: Pass custom linker flags. + +Details via CMake +[envvars](https://cmake.org/cmake/help/latest/manual/cmake-env-variables.7.html). + ## CMake options - `CMAKE_BUILD_TYPE`: (see CMake) @@ -340,7 +336,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CMAKE_INSTALL_BINDIR` (see CMake) - `CMAKE_INSTALL_INCLUDEDIR` (see CMake) - `CMAKE_INSTALL_LIBDIR` (see CMake) -- `CMAKE_INSTALL_PREFIX` (see CMake) +- `CMAKE_INSTALL_PREFIX` (see CMake) (in CMake 3.29+ also supported as environment) - `CMAKE_STATIC_LIBRARY_SUFFIX` (see CMake) - `CMAKE_UNITY_BUILD_BATCH_SIZE`: Set the number of sources in a "unity" unit. Default: `0` (all) - `CMAKE_UNITY_BUILD`: Enable "unity" (aka "jumbo") builds. Default: `OFF` @@ -354,6 +350,7 @@ Details via CMake - `CURL_BROTLI`: Use brotli (`ON`, `OFF` or `AUTO`). Default: `AUTO` - `CURL_USE_CMAKECONFIG`: Enable detecting dependencies via CMake Config. Default: `ON` for MSVC (except under vcpkg), if not cross-compiling. (experimental) +- `CURL_GSS_FLAVOR`: Set preferred GSSAPI implementation (`Apple`). Default: MIT, then GNU (experimental) - `CURL_USE_GNUTLS`: Enable GnuTLS for SSL/TLS. Default: `OFF` - `CURL_USE_GSASL`: Use libgsasl. Default: `OFF` - `CURL_USE_GSSAPI`: Use GSSAPI implementation. Default: `OFF` @@ -367,7 +364,7 @@ Details via CMake - `CURL_USE_OPENSSL`: Enable OpenSSL for SSL/TLS. Default: `ON` if no other TLS backend was enabled. - `CURL_USE_PKGCONFIG`: Enable `pkg-config` to detect dependencies. Default: `ON` for Unix (except Android, Apple devices), vcpkg, MinGW if not cross-compiling. -- `CURL_USE_RUSTLS`: Enable Rustls for SSL/TLS. Default: `OFF` +- `CURL_USE_RUSTLS`: Enable Rustls for SSL/TLS (experimental). Default: `OFF` - `CURL_USE_SCHANNEL`: Enable Windows native SSL/TLS (Schannel). Default: `OFF` - `CURL_USE_WOLFSSL`: Enable wolfSSL for SSL/TLS. Default: `OFF` - `CURL_ZLIB`: Use zlib (`ON`, `OFF` or `AUTO`). Default: `AUTO` @@ -376,11 +373,11 @@ Details via CMake - `USE_LIBIDN2`: Use libidn2 for IDN support. Default: `ON` - `USE_NGHTTP2`: Use nghttp2 library. Default: `ON` - `USE_NGTCP2`: Use ngtcp2 and nghttp3 libraries for HTTP/3 support. Default: `OFF` -- `USE_QUICHE`: Use quiche library for HTTP/3 support. Default: `OFF` +- `USE_QUICHE`: Use quiche library for HTTP/3 support (experimental). Default: `OFF` ## Dependency options (via CMake) -- `OPENSSL_ROOT_DIR`: Absolute path to the root installation of OpenSSL (and forks). +- `OPENSSL_ROOT_DIR`: Absolute path to the installation root of OpenSSL (and forks). - `OPENSSL_INCLUDE_DIR`: Absolute path to OpenSSL include directory. - `OPENSSL_SSL_LIBRARY`: Absolute path to `ssl` library. With MSVC, CMake uses variables `SSL_EAY_DEBUG`/`SSL_EAY_RELEASE` instead. @@ -389,7 +386,8 @@ Details via CMake - `OPENSSL_USE_STATIC_LIBS`: Look for static OpenSSL libraries. - `ZLIB_INCLUDE_DIR`: Absolute path to zlib include directory. - `ZLIB_LIBRARY`: Absolute path to `zlib` library. -- `ZLIB_USE_STATIC_LIBS`: Look for static `zlib` library (requires CMake v3.24). +- `ZLIB_ROOT`: Absolute path to the installation root of zlib. +- `ZLIB_USE_STATIC_LIBS`: Look for static `zlib` library (requires CMake 3.24+). - `_DIR`: Absolute path to `` CMake Config directory where `*.cmake` files reside. Used when `CURL_USE_CMAKECONFIG` is enabled. `` may be: @@ -418,7 +416,7 @@ Details via CMake - `DL_LIBRARY`: Absolute path to `dl` library. (for Rustls) - `GNUTLS_INCLUDE_DIR`: Absolute path to GnuTLS include directory. - `GNUTLS_LIBRARY`: Absolute path to `gnutls` library. -- `GSS_ROOT_DIR`: Absolute path to the root installation of GSS. (also supported as environment) +- `GSS_ROOT_DIR`: Absolute path to the installation root of GSS. (also supported as environment) - `LDAP_INCLUDE_DIR`: Absolute path to LDAP include directory. - `LDAP_LIBRARY`: Absolute path to `ldap` library. - `LDAP_LBER_LIBRARY`: Absolute path to `lber` library. @@ -460,13 +458,14 @@ Details via CMake - `NGTCP2_CRYPTO_WOLFSSL_LIBRARY`: Absolute path to `ngtcp2_crypto_wolfssl` library. - `NGTCP2_USE_STATIC_LIBS`: Configure for static ngtcp2 libraries. (experimental) - `NETTLE_INCLUDE_DIR`: Absolute path to nettle include directory. +- `NETTLE_HOGWEED_LIBRARY`: Absolute path to `hogweed` library. - `NETTLE_LIBRARY`: Absolute path to `nettle` library. - `PTHREAD_LIBRARY`: Absolute path to `pthread` library. (for Rustls) - `QUICHE_INCLUDE_DIR`: Absolute path to quiche include directory. - `QUICHE_LIBRARY`: Absolute path to `quiche` library. - `RUSTLS_INCLUDE_DIR`: Absolute path to Rustls include directory. - `RUSTLS_LIBRARY`: Absolute path to `rustls` library. -- `WATT_ROOT`: Absolute path to the root installation of Watt-32. +- `WATT_ROOT`: Absolute path to the installation root of Watt-32. - `WOLFSSL_INCLUDE_DIR`: Absolute path to wolfSSL include directory. - `WOLFSSL_LIBRARY`: Absolute path to `wolfssl` library. - `ZSTD_INCLUDE_DIR`: Absolute path to zstd include directory. @@ -475,7 +474,7 @@ Details via CMake Examples: -- `-DLIBPSL_INCLUDE_DIR=/path/to/libpl/include`, +- `-DLIBPSL_INCLUDE_DIR=/path/to/libpsl/include`, which directory contains `libpsl.h`. No ending slash or backslash is necessary. @@ -501,11 +500,12 @@ Examples: - `APXS`: Absolute path. Default: search for `apxs` - `CADDY`: Absolute path. Default: search for `caddy` +- `H2O`: Absolute path. Default: search for `h2o` - `HTTPD_NGHTTPX`: Absolute path. Default: search for `nghttpx` - `HTTPD`: Absolute path. Default: search for `apache2` - `DANTED`: Absolute path. Default: search for `danted` - `TEST_NGHTTPX`: Absolute path. Default: search for `nghttpx` -- `VSFTPD`: Absolute path. Default: search for `vsftps` +- `VSFTPD`: Absolute path. Default: search for `vsftpd` - `SSHD`: Absolute path. Default: search for `sshd` - `SFTPD`: Absolute path. Default: search for `sftp-server` @@ -521,13 +521,13 @@ the parent project, ideally in the "extra" find package redirect file: Available variables: - `HAVE_DES_ECB_ENCRYPT`: `DES_ecb_encrypt` present in OpenSSL (or fork). -- `HAVE_GNUTLS_SRP`: `gnutls_srp_verifier` present in GnuTLS. +- `HAVE_GSS_SET_NEG_MECHS`: `gss_set_neg_mechs` present in GSS-API library. - `HAVE_LDAP_INIT_FD`: `ldap_init_fd` present in LDAP library. - `HAVE_LDAP_URL_PARSE`: `ldap_url_parse` present in LDAP library. - `HAVE_MBEDTLS_DES_CRYPT_ECB`: `mbedtls_des_crypt_ecb` present in mbedTLS <4. -- `HAVE_OPENSSL_SRP`: `SSL_CTX_set_srp_username` present in OpenSSL (or fork). - `HAVE_QUICHE_CONN_SET_QLOG_FD`: `quiche_conn_set_qlog_fd` present in quiche. -- `HAVE_RUSTLS_SUPPORTED_HPKE`: `rustls_supported_hpke` present in Rustls (unused if Rustls is detected via `pkg-config`). +- `HAVE_RUSTLS_SUPPORTED_HPKE`: `rustls_supported_hpke` present in Rustls + (unused if Rustls is detected via `pkg-config`). - `HAVE_SSL_SET0_WBIO`: `SSL_set0_wbio` present in OpenSSL (or fork). - `HAVE_SSL_SET1_ECH_CONFIG_LIST`: `SSL_set1_ech_config_list` present in OpenSSL (or fork). - `HAVE_SSL_SET_QUIC_TLS_CBS`: `SSL_set_quic_tls_cbs` in OpenSSL. @@ -549,27 +549,37 @@ Note: These variables are internal and subject to change. ## Useful build targets -- `testdeps`: Build test dependencies (test binaries, test certificates). - Test certificates: `build-certs` (clean with `clean-certs`) -- `tests`: Run tests (`runtests.pl`). Customize via the `TFLAGS` environment variable, e.g. `TFLAGS=1621`. - Other flavors: `test-am`, `test-ci`, `test-event`, `test-full`, `test-nonflaky`, `test-quiet`, `test-torture` +- `testdeps`: Build test dependencies (test binaries, + test certificates). + Test certificates: `build-certs` + (clean with `clean-certs`) +- `tests`: Run tests (`runtests.pl`). Customize via the `TFLAGS` + environment variable, e.g. `TFLAGS=1621`. + Other flavors: `test-am`, `test-ci`, `test-event`, + `test-full`, `test-nonflaky`, `test-quiet`, + `test-torture` - `tt`: Build test binaries (servers, tools). - Individual targets: `curlinfo`, `libtests`, `servers`, `tunits`, `units` + Individual targets: `curlinfo`, `libtests`, + `servers`, `tunits`, `units` - `curl-pytest`: Run tests (pytest). Other flavor: `curl-test-ci` - `curl-examples`: Build examples Individual targets: `curl-example-`, where is the .c filename without extension. -- `curl-examples-build`: Build examples quickly but without the ability to run them. (for build tests) +- `curl-examples-build`: Build examples quickly but without the ability + to run them. (for build tests) - `curl-man`: Build man pages. (built by default unless disabled) - `curl`: Build curl tool. - `curl_uninstall`: Uninstall curl. -- `curl-completion-fish`: Build shell completions for fish. (built by default if enabled) -- `curl-completion-zsh`: Build shell completions for zsh. (built by default if enabled) +- `curl-completion-fish`: Build shell completions for fish. + (built by default if enabled) +- `curl-completion-zsh`: Build shell completions for zsh. + (built by default if enabled) - `curl-ca-bundle`: Build the CA bundle via `scripts/mk-ca-bundle.pl`. - `curl-ca-firefox`: Build the CA bundle via `scripts/firefox-db2pem.sh`. - `curl-lint`: Run lint checks. -- `curl-listcats`: Generate help category constants for `src/tool_help.h` from documentation. +- `curl-listcats`: Generate help category constants for + `src/tool_help.h` from documentation. - `curl-listhelp`: Generate `src/tool_listhelp.c` from documentation. - `curl-optiontable`: Generate `lib/easyoptions.c` from documentation. @@ -580,7 +590,7 @@ We recommend using CMake to build curl with MSVC. The project build files reside in project/Windows/VC\* for VS2010, VS2012 and VS2013. -These CMake Visual Studio generators require CMake v3.24 or older. You can +These CMake Visual Studio generators require CMake 3.24 or older. You can download them from . You can also use `-G "NMake Makefiles"`, which is supported by all CMake diff --git a/docs/INSTALL.md b/docs/INSTALL.md index db743e0554e8..ce1b63040555 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -13,6 +13,24 @@ document does not describe how to install curl or libcurl using such a binary package. This document describes how to compile, build and install curl and libcurl from [source code](https://curl.se/download.html). +## Building from source + +You can use autotools or CMake to build curl from source. They work equally +well and have close to feature parity. + +autotools' advantages are wide portability and the Unix philosophy, while +CMake typically has faster configuration and build times, and supports MSVC. + +Option defaults and dependency detection details may differ. + +With both build tools, some features require Perl, and the pytest test suite +requires Python. + +You can find live examples for both tools and many build cases in curl's CI +scripts: `.github/workflows/*.yml`, `.circleci/*.yml` and `appveyor.*` (in the +Git repository only). For CMake-specific instructions, see +[INSTALL-CMAKE.md](https://github.com/curl/curl/blob/master/docs/INSTALL-CMAKE.md). + ## Building using vcpkg You can download and install curl and libcurl using @@ -128,8 +146,8 @@ Building statically is not for the faint of heart. ## Debug -If you are a curl developer and use gcc, you might want to enable more debug -options with the `--enable-debug` option. +If you are a curl developer and use gcc, you might want to enable curl debug +features (for developing curl) with the `--enable-debug` option. curl can be built to use a whole range of libraries to provide various useful services, and configure tries to auto-detect a decent default. If you want to @@ -146,7 +164,7 @@ These options are provided to select the TLS backend to use. - AmiSSL: `--with-amissl` - GnuTLS: `--with-gnutls`. - mbedTLS: `--with-mbedtls` -- OpenSSL: `--with-openssl` (also for BoringSSL, AWS-LC, LibreSSL, and quictls) +- OpenSSL: `--with-openssl` (also for AWS-LC, BoringSSL, LibreSSL, and quictls) - Rustls: `--with-rustls` - Schannel: `--with-schannel` - wolfSSL: `--with-wolfssl` @@ -161,7 +179,7 @@ runtime when curl starts up. ### Selecting TLS Trust Anchors Defaults -Verifying a server certificate established a chain of trust that needs to +Verifying a server certificate establishes a chain of trust that needs to start somewhere. Those "root" certificates make the set of Trust Anchors. While the build system tries to find good defaults on the platform you @@ -206,8 +224,8 @@ Building for Windows Vista/Server 2008 is required as a minimum. You can build curl with: -- Microsoft Visual Studio 2010 v10.0 or later (`_MSC_VER >= 1600`) -- MinGW-w64 3.0 or later (`__MINGW64_VERSION_MAJOR >= 3`) +- Microsoft Visual Studio 2010 10.0 or greater (`_MSC_VER >= 1600`) +- mingw-w64 3.0 or greater (`__MINGW64_VERSION_MAJOR >= 3`) ## Building Windows DLLs and C runtime (CRT) linkage issues @@ -293,17 +311,17 @@ curl from the source code: > [!Note] > If an error occurs during the installation, then try: -- Use `cmake` to configure and/or build -- Use `ninja` to build (***much** faster*) +- Using `cmake` to configure and/or build +- Using `ninja` to build (much faster) - Reinstalling the required Cygwin packages from the list above without passing `-I` to `setup-x86_64` -- Temporarily move Cygwin to the top of your path -- Install all of the Cygwin build packages using +- Temporarily moving Cygwin to the top of your path +- Installing all of the Cygwin build packages using `setup-x86_64 --build-depends curl` ## MS-DOS -You can use either autotools or cmake: +You can use either autotools or CMake: ```sh ./configure \ @@ -334,7 +352,7 @@ cmake . \ Notes: -- Requires DJGPP 2.04 or upper. +- Requires DJGPP 2.04 or greater. - Compile Watt-32 (and OpenSSL) with the same version of DJGPP. Otherwise things go wrong because things like FS-extensions and `errno` values have @@ -342,7 +360,7 @@ Notes: ## AmigaOS -You can use either autotools or cmake: +You can use either autotools or CMake: ```sh ./configure \ @@ -486,7 +504,7 @@ install `libssl.a` and `libcrypto.a` to `$TOOLCHAIN/sysroot/usr/lib` and copy for Android using OpenSSL like this: ```sh -# For OpenSSL/BoringSSL. In general, you need to the SSL/TLS layer's transitive +# For BoringSSL/OpenSSL. In general, you need to the SSL/TLS layer's transitive # dependencies if you are linking statically. LIBS='-lssl -lcrypto -lc++' ./configure --host aarch64-linux-android --with-pic --disable-shared --with-openssl="$TOOLCHAIN/sysroot/usr" @@ -548,10 +566,10 @@ export CC=ppc_405-gcc export NM=ppc_405-nm ./configure \ - --target=powerpc-hardhat-linux - --host=powerpc-hardhat-linux - --build=i586-pc-linux-gnu - --prefix=/opt/hardhat/devkit/ppc/405/target/usr/local + --target=powerpc-hardhat-linux \ + --host=powerpc-hardhat-linux \ + --build=i586-pc-linux-gnu \ + --prefix=/opt/hardhat/devkit/ppc/405/target/usr/local \ --exec-prefix=/usr/local ``` @@ -616,7 +634,6 @@ disabling support for some features (run `./configure --help` to see them all): - `--disable-proxy` (HTTP and SOCKS proxies) - `--disable-socketpair` (socketpair for asynchronous name resolving) - `--disable-threaded-resolver` (threaded name resolver) -- `--disable-tls-srp` (Secure Remote Password authentication for TLS) - `--disable-unix-sockets` (Unix sockets) - `--disable-verbose` (eliminates debugging strings and error code strings) - `--disable-versioned-symbols` (versioned symbols) diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md deleted file mode 100644 index c145690a2caf..000000000000 --- a/docs/INTERNALS.md +++ /dev/null @@ -1,68 +0,0 @@ - - -# curl internals - -The canonical libcurl internals documentation is now in the [everything -curl](https://everything.curl.dev/internals) book. This file lists supported -versions of libs and build tools. - -## Portability - -We write curl and libcurl to compile with C89 compilers on 32-bit and up -machines. Most of libcurl assumes more or less POSIX compliance but that is -not a requirement. The compiler must support a 64-bit integer type as well as -supply a stdint.h header file that defines C99-style fixed-width integer types -like uint32_t. - -We write libcurl to build and work with lots of third party tools, and we -want it to remain functional and buildable with these and later versions -(older versions may still work but is not what we work hard to maintain): - -## Dependencies - -We aim to support these or later versions. - -- brotli 1.0.0 (2017-09-21) -- c-ares 1.16.0 (2020-03-13) -- GnuTLS 3.6.5 (2018-12-01) -- libidn2 2.0.0 (2017-03-29) -- LibreSSL 2.9.1 (2019-04-22) -- libssh 0.9.0 (2019-06-28) -- libssh2 1.9.0 (2019-06-20) -- mbedTLS 3.2.0 (2022-07-11) -- MIT Kerberos 1.3 (2003-07-31) -- nghttp2 1.15.0 (2016-09-25) -- OpenLDAP 2.0 (2000-08-01) -- OpenSSL 3.0.0 (2021-09-07) -- Windows Vista 6.0 (2006-11-08 - 2012-04-10) -- wolfSSL 5.0.0 (2021-11-01) -- zlib 1.2.5.2 (2011-12-11) -- zstd 1.0 (2016-08-31) - -## Build tools - -When writing code (mostly for generating stuff included in release tarballs) -we use a few "build tools" and we make sure that we remain functional with -these versions: - -- clang-tidy 17.0.0 (2023-09-19), recommended: 19.1.0 or later (2024-09-17) -- cmake 3.18 (2020-07-15) -- GNU autoconf 2.59 (2003-11-06) -- GNU automake 1.7 (2002-09-25) -- GNU libtool 1.4.2 (2001-09-11) -- GNU m4 1.4 (2007-09-21) -- mingw-w64 3.0 (2013-09-20) -- perl 5.8 (2002-07-19), on Windows: 5.22 (2015-06-01) -- Visual Studio 2010 10.0 (2010-04-12 - 2020-07-14) - -## Library Symbols - -All symbols used internally in libcurl must use a `Curl_` prefix if they are -used in more than a single file. Single-file symbols must be made static. -Public ("exported") symbols must use a `curl_` prefix. Public API functions -are marked with `CURL_EXTERN` in the public header files so that all others -can be hidden on platforms where this is possible. diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index a3b0d37889a8..6d98f26957d4 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -25,14 +25,6 @@ instead seems to trigger a crash. See [curl issue 17626](https://github.com/curl/curl/issues/17626) -## Client cert handling with Issuer `DN` differs between backends - -When the specified client certificate does not match any of the -server-specified `DN` fields, the OpenSSL and GnuTLS backends behave -differently. The GitHub discussion may contain a solution. - -See [curl issue 1411](https://github.com/curl/curl/issues/1411) - ## Client cert (MTLS) issues with Schannel See [curl issue 3145](https://github.com/curl/curl/issues/3145) @@ -45,10 +37,6 @@ fail, resulting in error SEC_E_BUFFER_TOO_SMALL or SEC_E_MESSAGE_ALTERED. [curl issue 5488](https://github.com/curl/curl/issues/5488) -## `CURLOPT_CERTINFO` results in `CURLE_OUT_OF_MEMORY` with Schannel - -[curl issue 8741](https://github.com/curl/curl/issues/8741) - ## mbedTLS and CURLE_AGAIN handling [curl issue 15801](https://github.com/curl/curl/issues/15801) @@ -60,6 +48,10 @@ Certain Windows installations may be missing CA roots. [curl issue 20897](https://github.com/curl/curl/issues/20897) [curl issue 12303](https://github.com/curl/curl/issues/12303) +## ECH not working through Proxy Tunnels + +[curl issue 22043](https://github.com/curl/curl/issues/22043) + # Email protocols ## IMAP `SEARCH ALL` truncated response @@ -113,21 +105,6 @@ would do if you used `-T` file. See [curl issue 12171](https://github.com/curl/curl/issues/12171) -## Windows stdin relay accepts unauthenticated local connections - -curl features a Windows-only stdin relay in `src/tool_doswin.c` that creates a -loopback TCP listener and spawns a thread to accept the first incoming -connection, then forwards stdin to it. There is no authentication or peer -validation on the accepted socket. A local attacker can race to connect to the -ephemeral loopback port (discoverable via local port enumeration/scan) before -curl connects, causing the thread to send stdin/upload data to the attacker or -to disrupt the transfer. - -The function should verify the client-side with a random number similar to the -socketpair emulation function in libcurl. It cannot verify the source address -and port since there is this widespread habit on Windows to run tools that -MITM even local TCP connections for security. - # Build and portability issues ## OS400 port requires deprecated IBM library @@ -219,29 +196,26 @@ https://curl.se/mail/lib-2012-07/0073.html # Authentication +## `--aws-sigv4` does not handle multipart/form-data correctly + +[curl issue 13351](https://github.com/curl/curl/issues/13351) + ## Digest `auth-int` for PUT/POST We do not support auth-int for Digest using PUT or POST +## Digest does not care for `domain` + +libcurl ignores the `domain` directive in Digest authentication challenges +(`WWW-Authenticate:`). RFC 7616 defines it as a quoted, space-separated list +of URIs that define the protection space. + ## MIT Kerberos for Windows build libcurl fails to build with MIT Kerberos for Windows (`KfW`) due to its library header files exporting symbols/macros that should be kept private to the library. -## NTLM in system context uses wrong name - -NTLM authentication using SSPI (on Windows) when (lib)curl is running in -"system context" makes it use wrong(?) username - at least when compared to -what `winhttp` does. See https://curl.se/bug/view.cgi?id=535 - -## NTLM does not support password with Unicode 'SECTION SIGN' character - -Code point: U+00A7 - -https://en.wikipedia.org/wiki/Section_sign -[curl issue 2120](https://github.com/curl/curl/issues/2120) - ## libcurl can fail to try alternatives with `--proxy-any` When connecting via a proxy using `--proxy-any`, a failure to establish an @@ -269,24 +243,6 @@ code is not a documented error for `InitializeSecurityContext` (digest). [curl issue 6302](https://github.com/curl/curl/issues/6302) -## curl never completes Negotiate over HTTP - -Apparently it is not working correctly...? - -See [curl issue 5235](https://github.com/curl/curl/issues/5235) - -## Negotiate on Windows fails - -When using `--negotiate` (or NTLM) with curl on Windows, SSL/TLS handshake -fails despite having a valid kerberos ticket cached. Works without any issue -in Unix/Linux. - -[curl issue 5881](https://github.com/curl/curl/issues/5881) - -## Negotiate authentication against Hadoop - -[curl issue 8264](https://github.com/curl/curl/issues/8264) - # FTP ## FTP with ACCT @@ -422,7 +378,7 @@ See [curl issue 13350](https://github.com/curl/curl/issues/13350) ## `CURLOPT_CONNECT_TO` does not work for HTTPS proxy It is unclear if the same option should even cover the proxy connection or if -if requires a separate option. +it requires a separate option. See [curl issue 14481](https://github.com/curl/curl/issues/14481) @@ -490,42 +446,6 @@ then subsequently fails anyway if that was actually in use. [curl issue 8112](https://github.com/curl/curl/issues/8112) -# CMake - -## cmake outputs: no version information available - -Something in the SONAME generation seems to be wrong in the cmake build. - -[curl issue 11158](https://github.com/curl/curl/issues/11158) - -## uses `-lpthread` instead of `Threads::Threads` - -See [curl issue 6166](https://github.com/curl/curl/issues/6166) - -## generated `.pc` file contains strange entries - -The `Libs.private` field of the generated `.pc` file contains `-lgcc -lgcc_s --lc -lgcc -lgcc_s`. - -See [curl issue 6167](https://github.com/curl/curl/issues/6167) - -## CMake build with MIT Kerberos does not work - -Minimum CMake version was bumped in curl 7.71.0 (#5358) Since CMake 3.2 -try_compile started respecting the `CMAKE_EXE_FLAGS`. The code dealing with -MIT Kerberos detection sets few variables to potentially weird mix of space, -and ;-separated flags. It had to blow up at some point. All the CMake checks -that involve compilation are doomed from that point, the configured tree -cannot be built. - -[curl issue 6904](https://github.com/curl/curl/issues/6904) - -# Authentication - -## `--aws-sigv4` does not handle multipart/form-data correctly - -[curl issue 13351](https://github.com/curl/curl/issues/13351) - # HTTP/2 ## HTTP/2 prior knowledge over proxy diff --git a/docs/MANUAL.md b/docs/MANUAL.md index e6f5123d2b5b..d979455d8496 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -24,7 +24,7 @@ Get a directory listing of an FTP site: curl ftp://ftp.example.com/ -Get the all terms matching curl from a dictionary: +Get all terms matching curl from a dictionary: curl dict://dict.example.com/m:curl @@ -57,7 +57,7 @@ Get a file from an SSH server using SCP using a private key (password-protected) to authenticate: curl -u username: --key ~/.ssh/id_rsa --pass private_key_password - scp://example.com/~/file.txt + scp://example.com/~/file.txt Get the main page from an IPv6 web server: @@ -99,8 +99,8 @@ or specify them with the `-u` flag like It is like FTP, but you may also want to specify and use SSL-specific options for certificates etc. -Note that using `FTPS://` as prefix is the *implicit* way as described in the -standards while the recommended *explicit* way is done by using `FTP://` and +Note that using `ftps://` as prefix is the *implicit* way as described in the +standards while the recommended *explicit* way is done by using `ftp://` and the `--ssl-reqd` option. ### SFTP / SCP @@ -188,13 +188,13 @@ transfers, and curl's `-v` option to see exactly what curl is sending. ## Piping -Get a key file and add it with `apt-key` (when on a system that uses `apt` for -package management): +Get a key file and install it as a trusted one (when on a system that uses +`apt` for package management): - curl -L https://apt.example.org/llvm-snapshot.gpg.key | sudo apt-key add - + curl -L https://apt.example.org/llvm-snapshot.gpg.key | sudo tee + /etc/apt/trusted.gpg.d/llvm-snapshot.asc >/dev/null -The '|' pipes the output to STDIN. `-` tells `apt-key` that the key file -should be read from STDIN. +The '|' pipes the output to stdin. `tee` reads from stdin. ## Ranges diff --git a/docs/Makefile.am b/docs/Makefile.am index 77971ac77176..d99dffe126d5 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -53,6 +53,7 @@ INTERNALDOCS = \ internals/CLIENT-WRITERS.md \ internals/CODE_STYLE.md \ internals/CONNECTION-FILTERS.md \ + internals/CREDENTIALS.md \ internals/CURLX.md \ internals/DYNBUF.md \ internals/HASH.md \ @@ -61,6 +62,7 @@ INTERNALDOCS = \ internals/MQTT.md \ internals/MULTI-EV.md \ internals/NEW-PROTOCOL.md \ + internals/PEERS.md \ internals/PORTING.md \ internals/RATELIMITS.md \ internals/README.md \ @@ -81,14 +83,15 @@ EXTRA_DIST = \ BINDINGS.md \ BUG-BOUNTY.md \ BUGS.md \ - CIPHERS.md \ CIPHERS-TLS12.md \ + CIPHERS.md \ CMakeLists.txt \ CODE_OF_CONDUCT.md \ CODE_REVIEW.md \ CONTRIBUTE.md \ CURL-DISABLE.md \ CURLDOWN.md \ + DEPENDENCIES.md \ DEPRECATE.md \ DISTROS.md \ EARLY-RELEASE.md \ @@ -107,7 +110,6 @@ EXTRA_DIST = \ INSTALL \ INSTALL-CMAKE.md \ INSTALL.md \ - INTERNALS.md \ IPFS.md \ KNOWN_BUGS.md \ KNOWN_RISKS.md \ @@ -116,15 +118,15 @@ EXTRA_DIST = \ options-in-versions \ README.md \ RELEASE-PROCEDURE.md \ - RUSTLS.md \ ROADMAP.md \ + RUSTLS.md \ SECURITY-ADVISORY.md \ SPONSORS.md \ SSL-PROBLEMS.md \ SSLCERTS.md \ THANKS \ - TODO.md \ TheArtOfHttpScripting.md \ + TODO.md \ URL-SYNTAX.md \ VERIFY.md \ VERSIONS.md \ diff --git a/docs/RELEASE-PROCEDURE.md b/docs/RELEASE-PROCEDURE.md index 36c786cbfe90..81dcbc628455 100644 --- a/docs/RELEASE-PROCEDURE.md +++ b/docs/RELEASE-PROCEDURE.md @@ -97,14 +97,14 @@ pending release: - Release candidate two (**rc2**) ships nine days later, sixteen days before the release. On a Monday. Tagged like `rc-7_34_0-2`. -- Release candidate tree (**rc3**) ships nine days later, seven days before +- Release candidate three (**rc3**) ships nine days later, seven days before the release. On a Wednesday. Tagged like `rc-7_34_0-3`. Release candidate tarballs are ephemeral and each such tarball is only kept around for a few weeks. They are provided on their dedicated webpage at: https://curl.se/rc/ -The git tags for release candidate are temporary and remain set only for a +The git tags for release candidates are temporary and remain set only for a limited period of time. **Do not use release candidates in production**. They are work in progress. @@ -125,11 +125,9 @@ push for it. Based on the description above, here are some planned future release dates: -- March 11, 2026 -- April 29, 2026 - June 24, 2026 -- August 19, 2026 -- October 14, 2026 -- December 9, 2026 -- February 3, 2027 -- March 31, 2027 +- September 2, 2026 +- October 28, 2026 +- December 23, 2026 +- February 17, 2027 +- April 14, 2027 diff --git a/docs/RUSTLS.md b/docs/RUSTLS.md index b1c8044e8e3e..ed032f7fb628 100644 --- a/docs/RUSTLS.md +++ b/docs/RUSTLS.md @@ -56,7 +56,7 @@ Once downloaded, build `curl` using `--with-rustls` and the path to the extracte Building `rustls-ffi` from source requires both a rust compiler, and the [cargo-c] cargo plugin. To install a Rust compiler, use [rustup] or your package manager to install -the **1.73+** or newer toolchain. +the **1.73** or newer toolchain. To install `cargo-c`, use your [package manager][cargo-c pkg], download [a pre-built archive][cargo-c prebuilt], or build it from source with `cargo install cargo-c`. diff --git a/docs/SECURITY-ADVISORY.md b/docs/SECURITY-ADVISORY.md index 4f3e1df2c9ad..98f23e53c769 100644 --- a/docs/SECURITY-ADVISORY.md +++ b/docs/SECURITY-ADVISORY.md @@ -6,11 +6,14 @@ SPDX-License-Identifier: curl # Anatomy of a curl security advisory -As described in the [Security Process](https://curl.se/dev/secprocess.html) -document, when a security vulnerability has been reported to the project and -confirmed, we author an advisory document for the issue. It should ideally -be written in cooperation with the reporter to make sure all the angles and -details of the problem are gathered and described correctly and succinctly. +As described in the [vulnerability disclosure +policy](https://curl.se/dev/vuln-disclosure.html), when a vulnerability has +been reported to the project and it has been confirmed by the team, we +author an advisory document for the issue. + +

This advisory document should ideally be written in cooperation with the +reporter to make sure all the angles and details of the problem are gathered +and described correctly and succinctly. ## New document @@ -31,13 +34,22 @@ in the same directory. It holds a large array with all published curl vulnerabilities. All fields should be filled in accordingly, separated by a pipe character (`|`). -The eleven fields for each CVE in `vuln.pm` are, in order: - -HTML page name, first vulnerable version, last vulnerable version, name of -the issue, CVE Id, announce date (`YYYYMMDD`), report to the project date -(`YYYYMMDD`), CWE, awarded reward amount (USD), area (single word), C-issue -(`-` if not a C issue at all, `OVERFLOW` , `OVERREAD`, `DOUBLE_FREE`, -`USE_AFTER_FREE`, `NULL_MISTAKE`, `UNINIT`) +The fields for every CVE in `vuln.pm` are, in order: + +1. HTML page name +2. first vulnerable version +3. last vulnerable version +4. name of the issue +5. CVE Id +6. announce date (`YYYYMMDD`) +7. report to the project date (`YYYYMMDD`) +8. CWE +9. awarded reward amount (USD) +10. area (single word) +11. C-issue (`-` if not a C issue at all, `OVERFLOW` , `OVERREAD`, `DOUBLE_FREE`, `USE_AFTER_FREE`, `NULL_MISTAKE`, `UNINIT`, `BAD_FREE`) +12. affected components: `both`, `lib` or `tool` +13. severity: `low`, `medium`, `high` or `critical` +14. URL to the initial report (often on HackerOne) ### `Makefile` diff --git a/docs/SSLCERTS.md b/docs/SSLCERTS.md index 3506fbd787d1..d5110e18f0f4 100644 --- a/docs/SSLCERTS.md +++ b/docs/SSLCERTS.md @@ -71,6 +71,10 @@ cert file named `curl-ca-bundle.crt` in these directories and in this order: curl 8.11.0 added a build-time option to disable this search behavior, and another option to restrict search to the application's directory. +curl 8.19.0 added a build-time option to enable Native CA by default on +Windows. This build-time option by default also disables searching for +a `curl-ca-bundle.crt` on disk. + ### Use the native store In several environments, in particular on Microsoft and Apple operating diff --git a/docs/THANKS b/docs/THANKS index e02c36170096..36a75f3de390 100644 --- a/docs/THANKS +++ b/docs/THANKS @@ -6,9 +6,12 @@ 0xee on github 0xflotus on github +0xN3R3K3 +11soda11 12932 on github 1337vt on github 1ocalhost on github +1rhino2 on hackerone 3dyd on github 3eka on github 4lan.m @@ -25,6 +28,7 @@ Abdullah Alyan Abhinav Singh Abhinav Singhal Abram Pousada +accl on hackerone accountantM on github AceCrow on github ad0p on github @@ -50,6 +54,7 @@ Adrian Burcea Adriano Meirelles Adrian Peniak Adrian Schuur +Ady Elouej afengsoft on github afrind on github Aftab Alam @@ -58,6 +63,7 @@ ahodesuka on github aisle-research-bot ajak in #curl Ajit Dhumale +A Johnston Akhilesh Nema Akhil Kedia Aki Koskinen @@ -67,8 +73,11 @@ Akshay Vernekar Alain Danteny Alain Miniussi Alan Coopersmith +Alan De Smet Alan Jenkins +AlanKingPL Alan Pinstein +Alb3e3 Albert Chin-A-Young Albert Choy Alberto Leiva Popper @@ -122,6 +131,7 @@ Alexey Eremikhin Alexey Larikov Alexey Melnichuk Alexey Pesternikov +Alexey Samsonov Alexey Savchuk Alexey Simak Alexey Zakhlestin @@ -151,7 +161,10 @@ Alex Vinnik Alex Xu Alfonso Martone Alfred Gebert +Alhuda Khan +alhudz Alice Lee Poetics +alienowo on hackerone Ali Khodkar ALittleDruid on github Ali Utku Selen @@ -159,8 +172,10 @@ Allen Pulsifer Alois Klink Alona Rossen Amaury Denoyelle +ambikeesshh Ameda Amahru amishmm on github +amitbidlan Amit Katyal Ammar Faizi Amol Pattekar @@ -229,6 +244,7 @@ Andrew Kurushin Andrew Kvalheim Andrew Lambert Andrew Moise +Andrew Nesbitt Andrew Olsen Andrew Potter Andrew Robbins @@ -275,18 +291,22 @@ Anton Gerasimov Antonio Larrosa Antoni Villalonga Anton Kalmykov +Anton Karpov Anton Malov Antony74 on github Anton Yabchinskiy Antti Hätälä antypanty on hackerone Anubhav Rai +anupamme apparentorder on github April King Aquila Macedo arainchik on github Archangel_SDY on github +Arham Wani Arian van Putten +Aritra Basu Arjan van de Ven Arkadiusz Miskiewicz Arkadi Vainbrand @@ -314,6 +334,7 @@ Askar Safin Ask Bjørn Hansen AtariDreams on github Ates Goral +Athos Ribeiro atjg on github Augment code Augustus Saunders @@ -330,6 +351,7 @@ Axel Tillequin Ayesh Karunaratne Ayoub Boudhar Ayushman Singh Chauhan +azraelxuemo on hackerone b9a1 on github Bachue Zhou Baitinq on github @@ -344,15 +366,18 @@ BANADDA baranyaib90 on github Barry Abrahamson Barry Pollard +Bartel Sielski Bartosz Ruszczak Bart Whiteley Baruch Siach Bas Mevissen +Bastian Jesuiter Bastian Krause Bastien Bouclet Basuke Suzuki Bas van Schaik baumanj on github +BazaarAcc32 on github bdry on github beckenc on github behindtheblackwall on hackerone @@ -397,11 +422,13 @@ Bertrand Simonnet beslick5 on github Bevan Weiss Bhanu Prakash +Bigtang on hackerone Bill Doyle Bill Egert Bill Hoffman billionai on github Bill Middlecamp +Bill Mill Bill Nagel Bill Pyne Billy O'Neal @@ -501,6 +528,7 @@ buzo-ffm on github bxac on github Bylon2 on github Byrial Jensen +ByteRay on hackerone Cajus Pollmeier Caleb Raitto calm329 @@ -532,6 +560,7 @@ Carsten Lange Casey Bodley Casey O'Donnell Catalin Patulea +CatboxParadox Catena cyber causal-agent on github cbartl on github @@ -587,6 +616,7 @@ Christian Robottom Reis Christian Schmitz Christian Schmitza Christian Stewart +Christian Ullrich Christian Vogt Christian Weisgerber Christophe Demory @@ -602,18 +632,21 @@ Christopher Reid Christopher R. Palmer Christopher Sauer Christopher Stone +Christopher Wellons Christoph Jabs Christoph Krey Christoph M. Becker Christoph Reiter Chris Webb Chris Young +chrizilla on github chrysos349 on github Chungtsun Li Ciprian Badescu civodul on github Claes Jakobsson Clarence Gardner +claudex on github Claudio Neves claudiusaiz on github clbr on github @@ -625,6 +658,7 @@ Clint Clayton Cloudogu Siebels CMD cmfrolick on github +co-authors in libssh2 codesniffer13 on github Cody Jones Cody Mack @@ -640,6 +674,7 @@ Colin Leroy Colin Leroy-Mira Colin O'Dell Colin Watson +Collin Funk Colman Mbuya Colm Buckley Colton Willey @@ -667,6 +702,7 @@ CueXXIII on github curl.stunt430 Curt Bogmine Cutiapreta on hackerone +cybertron10 on github Cynthia Coan Cyril B Cyrill Osterwalder @@ -751,6 +787,7 @@ Dan Zitter Daphne Luong Darío Hereñú Dario Nieuwenhuis +Dario Vinella Dario Weißer Darren Banfi Darryl House @@ -766,6 +803,7 @@ Dave Nicolson Dave Reisner Dave Thompson Dave Vasilevsky +Dave Walker Davey Shafik David Bau David Benjamin @@ -818,6 +856,7 @@ David Woodhouse David Wright David Yan David Zhuang +daviey on hackerone Da-Yoon Chung dbalsom dbrowndan on github @@ -943,6 +982,7 @@ Duy Phan Thanh Dwarakanath Yadavalli dwickr Dwij Mehta +dyingc on github Dylam De La Torre Dylan Anthony Dylan Ellicott @@ -955,6 +995,7 @@ Ebe Janchivdorj ebejan on github Ebenezer Ikonne ed0d2b2ce19451f2 +ed0d2b2ce19451f2 on github Eddie Lumpkin Edgaras Janušauskas Edin Kadribasic @@ -980,6 +1021,7 @@ elelel on github elephoenix on github Elia Tufarolo Eli Schwartz +Elise Vance Elliot Killick Elliot Saba Elliott Balsley @@ -993,6 +1035,7 @@ emanruse on github Emanuele Bovisio Emanuele Torre Emanuel Komínek +Emanuel Krollmann Emil Engler Emiliano Ida Emilio Cobos Álvarez @@ -1002,6 +1045,7 @@ Emil Lerner Emil Österlund Emil Romanus Emmanuel Tychon +Emmanuel Ugwu Emre Çalışkan Enno Boland Enrico Scholz @@ -1059,9 +1103,11 @@ Ethan Glasser Camp Ethan Wilkes Etienne Simard Eugene Kotlyarov +Eunsoo Kim Evangelos Foutras Evan Jordan Even Rouault +evergarden1123 on hackerone Evert Pot Evgeny Grin (Karlson2k) Evgeny Turnaev @@ -1098,6 +1144,7 @@ feelingseas on github FeignClaims on github Feist Josselin Felipe Gasper +felix h Felix Hädicke Felix Kaiser Felix von Leitner @@ -1105,11 +1152,13 @@ Felix Yan Feng Tu Fernando Muñoz ffath-vo on github +Filipe Casal Filip Lundgren Filip Salomonsson finkjsc on github Fiona Klute Firefox OS +firexinghe on github Fizn-Ahmd on github fjaell on github Flameborn on github @@ -1148,6 +1197,7 @@ Frazer Smith Frederic Lepied Frederik B Frederik Wedel-Heinen +Fred Klassen Fred Machado Fred New Fred Noz @@ -1170,10 +1220,12 @@ galen11 on github Gambit Communications Ganesh Kamath Ganesh Viswanathan +Gao Liyou gaoxingwang on github Garrett Holmstrom Garrett Squire Gary Maxwell +Gary W. Swearingen Gaurav Malhotra Gautam Kachroo Gautam Mani @@ -1213,6 +1265,7 @@ Giuseppe Attardi Giuseppe D'Ambrosio Giuseppe Persico gkarracer on github +GLaDOS-418 on github Gleb Ivanovsky Glen A Johnson Jr. Glen Nakamura @@ -1255,6 +1308,8 @@ Griffin Downs Grigory Entin Grisha Levit Gruber Glass +Guancheng Li +Guannan Wang gudyuu on hackerone Guenole Bescon Guido Berhoerster @@ -1313,6 +1368,8 @@ Helge Klein Helmut Grohne Helmut K. C. Tessarek Helwing Lutz +Hem Parekh +Hendrik Hübner Hendrik Visage Henning Schild Henri Gomez @@ -1347,6 +1404,7 @@ Howard Blaise Howard Chu hsiao yi HsiehYuho on github +htasta htasta on github huanghuihui0904 Hubert Kario @@ -1357,6 +1415,7 @@ Hunt Darlener Huseyin Tintas Huzaifa Sidhpurwala huzunhao on github +HwangRock hydra3333 on github Hzhijun iammrtau on github @@ -1378,6 +1437,7 @@ Ignat Loskutov Igor Franchuk Igor Khristophorov Igor Makarov +Igor Morgenstern Igor Novoseltsev Igor Polyakov Igor Todorovski @@ -1412,6 +1472,7 @@ Ishan SinghLevett İsmail Dönmez Itay Bookstein Ithubg on github +itzTanos29 Ivan Ivan Avdeev ivanfywang @@ -1422,6 +1483,7 @@ Ivo Bellin Salarin iz8mbw on github Izan on hackerone Jacek Migacz +Jace Laquerre Jackarain on github JackBoosY on github Jack Boos Yu @@ -1505,6 +1567,7 @@ Javier Blazquez Javier G. Sogo Javier Navarro Javier Sixto +Jayanth Vennamreddy Jay Austin Jay Dommaschk Jayesh A Shah @@ -1528,6 +1591,7 @@ Jean-Philippe Barrette-LaPierre Jean-Philippe Menil Jeff Connelly Jeff Hodges +jeffhuang Jeff Johnson Jeff King Jeff Lawson @@ -1553,6 +1617,7 @@ Jeremy Huddleston Jeremy Lainé Jeremy Lin Jeremy Maitin-Shepard +Jeremy Nicoll Jeremy Pearson Jérémy Rabasco Jérémy Rocher @@ -1580,6 +1645,7 @@ jhauga jhoyla on github Jiacai Liu Jiang Wenjian +Jiashuo Liang Jiawen Geng Jicea Jie He @@ -1603,6 +1669,7 @@ Jishan Shaikh Jiwoo Park Jixinqi Jiyong Yang +jjchuck on hackerone jkamp-aws on github jmaggard10 on github jmdavitt on github @@ -1631,6 +1698,7 @@ Johannes Ernst Johannes G. Kristinsson Johannes Lesr Johannes Schindelin +Johannes Schlatow Johan Nilsson Johann Sebastian Schicho Johan van Selst @@ -1671,6 +1739,7 @@ John Simpson John Starks John Suprock John V. Chow +John Verne John Walker John Wanghui John Weismiller @@ -1714,6 +1783,7 @@ Jordan Brown Jörg Mueller-Tolk Jörn Hartroth Jose Alf +Josef Cejka Josef Wolf José Joaquín Atria Jose Kahan @@ -1822,6 +1892,7 @@ Keith McGuigan Keith Mok Kelly Kaoudis Ken Brown +Keng-Yu Lin Ken Hirsch Kenneth Davidson Kenneth Myhra @@ -1846,6 +1917,7 @@ Kevin Smith Kevin Sun Kevin Ushey Kev Jackson +Kieran Gannon Kim Minjoong Kimmo Kinnunen Kim Rinnewitz @@ -1929,6 +2001,7 @@ Laurent Bonnans Laurent Dufresne Laurențiu Nicola Laurent Rabret +Laurent Sabourin Laurie Clark-Michalek Lauri Kasanen Lawrence Gripper @@ -2057,6 +2130,7 @@ Mandy Wu Manfred Schwarb Manuel Einfalt Manuel Massing +Manuel Sánchez-Guijarro Manuel Strehl Manuj Bhatia Marc Aldorasi @@ -2065,6 +2139,7 @@ Marc Boucher Marc Deslauriers Marc Doughty Marcel Hernandez +Marcel Jamin Marcel Lang Marcelo Echeverria Marcelo Juchem @@ -2080,6 +2155,7 @@ Marcin Rataj Marc Kleine-Budde Marco Deckel Marco G. Salvagno +marco-jardim Marco Kamner Marco Maggi Marcos Diazr @@ -2102,6 +2178,7 @@ Mark Brand Mark Butler Mark Davies Mark Dodgson +Mark Esler Mark Gaiser Mark Hamilton Mark Huang @@ -2141,9 +2218,11 @@ Martin D'Aloia Martin Dorey Martin Drasar Martin Dreher +Martin Dukek Martin Dürrmeier martinevsky Martin Frodl +martin-fzi on github Martin Galvan Martin Gartner Martin Hager @@ -2196,6 +2275,7 @@ Matt Ford Matthew Blain Matthew Clarke Matthew Hall +Matthew John Cheetham Matthew Kerwin Matthew Thompson Matthew Whitehead @@ -2245,6 +2325,7 @@ Mekonikum Melissa Mears Melroy van den Berg Mel Zuser +Memduh Çelik Mert Yazıcıoğlu Mettgut Jamalla Micah Snyder @@ -2310,6 +2391,7 @@ Miguel Angel Miguel Diaz migueljcrum on github Mihai Ionescu +mik Mikael Johansson Mikael Sennerholm Mikalai Ananenka @@ -2324,6 +2406,7 @@ Mike Giancola Mike Hasselberg Mike Henshaw Mike Hommey +Mike-menny on github Mike Mio Mike Norton Mike Power @@ -2372,6 +2455,7 @@ Muhamad Arga Reksapati Muhammad Herdiansyah Muhammad Hussein Ammari Muhammed Yavuz Nuzumlalı +mulan_dh on hackerone Murugan Balraj musvaage on github Muz Dima @@ -2412,6 +2496,7 @@ Neil Spring NeimadTL nekopsykose on github Nemos2024 on github +netspacer.research neutric on github nevakrien on github nevv on HackerOne/curl @@ -2523,6 +2608,7 @@ opensignature on github opensslonzos-github on github Ophir Lojkine Orange Tsai +oreadvanthink on github Oren Souroujon Oren Tirosh Orgad Shaneh @@ -2547,6 +2633,7 @@ Palo Markovic pandada8 on github Paolo Mossino Paolo Piacentini +parasol-aser Paras Sethia parazyd on github Pascal Gaudette @@ -2604,6 +2691,7 @@ Pavel P Pavel Pavlov Pavel Raiskup Pavel Rochnyak +Pavel Sobolev Pavel Volgarev Pavol Markovic Pawel A. Gajda @@ -2621,6 +2709,7 @@ pendrek at hackerone Peng Li Peng-Yu Chen pennae on github +penpal Per Jensen Per Lundberg Per Malmberg @@ -2670,6 +2759,7 @@ Phil E. Taylor Philip Chan Philip Craig Philip Gladstone +Philip H. Philip Heiduck Philip Langdale Philippe Antoine @@ -2756,11 +2846,13 @@ Rajesh Naganathan Rajkumar Mandal Ralf A. Timmermann ralfjunker on github +Ralf Mueller Ralf S. Engelschall Ralph Beckmann Ralph Langendam Ralph Mitchell Ralph Sennhauser +Ramesh Adhikari Ramiro Garcia Ram Krushna Mishra rampageX on github @@ -2772,12 +2864,14 @@ Randy Armstrong Randy McMurchy Ran Mozes Raphael Gozzo +Rarylson Freitas r-a-sattarov on github Rasmus Melchior Jacobsen Rasmus Thomsen Raul Onitza-Klugman Ravi Pratap Ray Dassen +Raymond Steen Ray Pekowski Ray Satiro Razvan Cojocaru @@ -2801,7 +2895,7 @@ Renaud Guillard Renaud Lehoux Rene Bernhardt Rene Rebe -renovate[bot] +renjian on hackerone renovate[bot] RepoRascal on hackerone Reuven Wachtfogel @@ -2859,8 +2953,10 @@ Rikard Falkeborn rilysh Rinku Das rinsuki on github +Rito Rhymes rl1987 on github rmg-x on github +RMMoreton on github rm-rmonaghan on github Rob Boeckermann RobBotic1 on github @@ -2919,7 +3015,7 @@ Rolland Dudemaine Romain Coltel Romain Fliedel Romain Geissler -romamik om github +romamik on github Roman Koifman Roman Mamedov Roman Zharkov @@ -2927,6 +3023,7 @@ Romulo A. Ceccon Ronald Crane Ronan Pigott Ron Eldor +Ron Kuper Ronnie Mose Ron Parker Ron Zapp @@ -2973,6 +3070,7 @@ Salvador Dávila Salvatore Sorrentino Samanta Navarro Sam Deane +Sameeh Jubran Sam Hurst Sam James Sam Jessup @@ -2981,6 +3079,7 @@ Sampo Kellomaki Sam Roth Sam Schanken Samuel Chiang +Samuel Dainard Samuel Díaz García Samuel Dionne-Riel Samuel Henrique @@ -3005,6 +3104,7 @@ Sascha Swiercy Sascha Zengler Satadru Pramanik Satana de Sant'Ana +Saud Alshareef Saul good saurabhsingh-dev on github Saurav Babu @@ -3023,6 +3123,7 @@ Scott McCreary Scott Mutter Scott Talbert sd0 on hackerone +sdgh179 on github Sean Boudreau Sean Burford Sean MacLennan @@ -3046,6 +3147,7 @@ SendSonS on github Senthil Raja Velu Sergei Kuzmin Sergei Nikulov +Sergei Zimmerman Sergey Sergey Alirzaev Sergey Bronnikov @@ -3061,6 +3163,7 @@ Sergii Pylypenko Sergio Ballestrero Sergio Barresi Sergio Borghese +Sergio Correia Sergio Durigan Junior Sergio-IME on github Sergio Mijatovic @@ -3089,6 +3192,7 @@ Sh Diao Sheshadri.V Shikha Sharma Shine Fan +Shintomon Mathew Shiraz Kanga shithappens2016 on github Shlomi Fish @@ -3096,6 +3200,7 @@ Shmulik Regev Shohei Maeda Siddhartha Prakash Jain siddharthchhabrap on github +sideshowbarker on github Sidney San Martín Siegfried Gyuricsko silveja1 on github @@ -3115,15 +3220,19 @@ Siva Sivaraman Slaven Rezić SLDiggie on github Smackd0wn on github +smaeljaish on hackerone S. Moonesamy smuellerDD on github sn on hackerone sofaboss on github Sohom Datta +Sollace on github Somnath Kundu Song Ma +Song X. Gao Sonia Subramanian Sören Tempel +sourceturner southernedge on github Spacen Jasset spectreglobalsec on hackerone @@ -3178,6 +3287,7 @@ Stephan Lagerholm Stephan Mühlstrasser stephannn on github Stephan Szabo +Stephan Zeisberg Stephen Boost Stephen Brokenshire Stephen Collyer @@ -3211,6 +3321,7 @@ Stian Soiland-Reyes Stoned Elipot stootill on github Stuart Henderson +stze on hackerone Sukanya Hanumanthu SumatraPeter on github Sune Ahlgren @@ -3273,6 +3384,7 @@ Thomas Thomas1664 on github Thomas Bouzerar Thomas Braun +Thomas Chauchefoin Thomas Danielsson Thomas Ferguson Thomas Gamper @@ -3308,6 +3420,7 @@ Tim Friedrich Brüggemann Tim Harder Tim Heckman Tim Hill +Tim Martin Tim Mcdonough Timmy Schierling Tim Newsome @@ -3338,6 +3451,7 @@ tlahn on github tmkk on github Tobias Blomberg Tobias Bora +Tobias Frauenschläger Tobias Gabriel Tobias Hieta Tobias Hintze @@ -3452,9 +3566,12 @@ Valerii Zapodovnikov vanillajonathan on github Varnavas Papaioannou Vasiliy Faronov +Vasiliy-Kkk Vasiliy Ulyanov Vasily Lobaskin Vasy Okhin +vectorqueue on hackerone +vegagent on hackerone Venkat Akella Venkataramana Mokkapati Venkat Krishna R @@ -3479,6 +3596,7 @@ Vincent Le Normand Vincent Penquerc'h Vincent Sanders Vincent Torri +violet12331 on hackerone violetlige on github vitaha85 on github Vitaly Varyvdin @@ -3532,6 +3650,7 @@ Wez Furlong Wham Bang Wilfredo Sanchez Wilhelm von Thiele +Will Cosgrove Will Dietz Willem Hoek Willem Sparreboom @@ -3548,6 +3667,7 @@ Wojciech Zwiefka wolfsage on hackerone Wolf Vollprecht Wouter Van Rooy +wulin-nudt on github Wu Yongzheng Wu Zheng wxiaoguang on github @@ -3624,6 +3744,8 @@ Yves Lejeune YX Hao z2_ Zachary Seguin +Zartaj Majeed +zaveshaa Zdenek Pavlas Zekun Ni zelinchen on github @@ -3635,6 +3757,8 @@ Zespre Schmidt zhanghu on xiaomi Zhang Wen Zhang Xiuhua +zhanhb on github +Zhanpeng Liu Zhaoming Luo Zhaoyang Wu Zhao Yisha diff --git a/docs/THANKS-filter b/docs/THANKS-filter index cc964a49b2a9..f9e7e1c8d2d4 100644 --- a/docs/THANKS-filter +++ b/docs/THANKS-filter @@ -25,7 +25,7 @@ # This is a list of names we have recorded that already are thanked # appropriately in THANKS. This list contains variations of their names and # their "canonical" name. This file is used for scripting purposes to avoid -# duplicate entries and will not be included in release tarballs. +# duplicate entries and is not included in release tarballs. # When removing dupes that are not identical names from THANKS, add a line # here! # @@ -162,3 +162,4 @@ s/Maksim Sciepanienka/Maksim Ściepanienka/ s/Qriist.*/Qriist on github/ s/Viktor Szakatas/Viktor Szakats/ s/Val S\./Valerie Snyder/ +s/Andrew Nesbit$/Andrew Nesbitt/ diff --git a/docs/TODO.md b/docs/TODO.md index 2be796f8c92f..28f804eb41d6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -19,12 +19,6 @@ document](https://curl.se/docs/knownbugs.html) are subject for fixing. # libcurl -## Consult `%APPDATA%` also for `.netrc` - -`%APPDATA%\.netrc` is not considered when running on Windows. Should it not? - -See [curl issue 4016](https://github.com/curl/curl/issues/4016) - ## `struct lifreq` Use `struct lifreq` and `SIOCGLIFADDR` instead of `struct ifreq` and @@ -57,7 +51,7 @@ to share data in more powerful ways. ## updated DNS server while running If `/etc/resolv.conf` gets updated while a program using libcurl is running, it -is may cause name resolves to fail unless `res_init()` is called. We should +may cause name resolves to fail unless `res_init()` is called. We should consider calling `res_init()` + retry once unconditionally on all name resolve failures to mitigate against this. Firefox works like that. Note that Windows does not have `res_init()` or an alternative. @@ -269,7 +263,7 @@ This is not detailed in any FTP specification. ## Passive transfer could try other IP addresses -When doing FTP operations through a proxy at localhost, the reported spotted +When doing FTP operations through a proxy at localhost, the reporter spotted that curl only tried to connect once to the proxy, while it had multiple addresses and a failed connect on one address should make it try the next. @@ -284,8 +278,8 @@ See [curl issue 1508](https://github.com/curl/curl/issues/1508) When curl receives a body response from a CONNECT request to a proxy, it always reads and ignores it. It would make some users happy if curl instead -optionally would be able to make that responsible available. Via a new -callback? Through some other means? +optionally would be able to make that response available. Via a new callback? +Through some other means? See [curl issue 9513](https://github.com/curl/curl/issues/9513) @@ -431,37 +425,18 @@ LDAPS not possible with macOS and Windows with Certificate-Based Authentication [curl issue 9641](https://github.com/curl/curl/issues/9641) -# SMB - -## Support modern versions - -curl only supports version 1, which barely anyone is using anymore. - -## File listing support - -Add support for listing the contents of an SMB share. The output should -probably be the same as/similar to FTP. - -## Honor file timestamps - -The timestamp of the transferred file should reflect that of the original -file. - -## Use NTLMv2 +# WebSocket -Currently the SMB authentication uses NTLMv1. +## Support text frames with command line tool -## Create remote directories +libcurl defaults to using binary WebSocket frames, which makes some servers +not work as they require text. We should make it possible to tell the tool to +use text frames. -Support for creating remote directories when uploading a file to a directory -that does not exist on the server, like `--ftp-create-dirs`. +[curl issue 21997](https://github.com/curl/curl/issues/21997) -# FILE - -## Directory listing on non-POSIX - -Listing the contents of a directory accessed with FILE only works on platforms -with `opendir()`. Support could be added for more systems, like Windows. +An abandoned attempt to add support for this exists in [PR +22093](https://github.com/curl/curl/pull/22093). # TLS @@ -497,20 +472,6 @@ By changing the order of TLS extensions provided in the TLS handshake, it is sometimes possible to circumvent TLS fingerprinting by servers. The TLS extension order is of course not the only way to fingerprint a client. -## Consider OCSP stapling by default - -Treat a negative response a reason for aborting the connection. Since OCSP -stapling is presumed to get used much less in the future when Let's Encrypt -drops the OCSP support, the benefit of this might however be limited. - -[curl issue 15483](https://github.com/curl/curl/issues/15483) - -## Provide callback for cert verification - -OpenSSL supports a callback for customized verification of the peer -certificate, but this does not seem to be exposed in the libcurl APIs. Could -it be? There is so much that could be done if it were. - ## Less memory massaging with Schannel The Schannel backend does a lot of custom memory management we would rather @@ -590,6 +551,10 @@ The feature matrix at https://curl.se/libcurl/c/tls-options.html shows which features are supported by which TLS backends, and thus also where there are feature gaps. +## ECH for QUIC + +curl's support for ECH is currently limited to TCP only. + # Proxy ## Retry SOCKS handshake on address type not supported @@ -687,7 +652,7 @@ until [curl pull request 6021](https://github.com/curl/curl/pull/6021) brought the functionality with the libssh2 backend. Presumably, this support can/could be added for the libssh backend as well. -## SFTP with `SCP://` +## SFTP with `scp://` OpenSSH 9 switched their `scp` tool to speak SFTP under the hood. Going forward it might be worth having curl or libcurl attempt SFTP if SCP fails to @@ -737,10 +702,10 @@ RFC 6266 documents how UTF-8 names can be passed to a client in the [curl issue 1888](https://github.com/curl/curl/issues/1888) -## Option to make `-Z` merge lined based outputs on stdout +## Option to make `-Z` merge line-based outputs on stdout -When a user requests multiple lined based files using `-Z` and sends them to -stdout, curl does not *merge* and send complete lines fine but may send +When a user requests multiple line-based files using `-Z` and sends them to +stdout, curl does not *merge* and send complete lines but may send partial lines from several sources. [curl issue 5175](https://github.com/curl/curl/issues/5175) @@ -775,7 +740,7 @@ backed up from those that are either not ready or have not changed. Downloads in progress are neither ready to be backed up, nor should they be opened by a different process. Only after a download has been completed it is -sensible to include it in any integer snapshot or backup of the system. +sensible to include it in any incremental snapshot or backup of the system. See [curl issue 3354](https://github.com/curl/curl/issues/3354) @@ -791,7 +756,7 @@ done, and thus maintain its connection pool, DNS cache and more. Consider a command line option that can make curl do multiple serial requests while acknowledging server specified [rate -limits](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/). +limits](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers/). See [curl issue 5406](https://github.com/curl/curl/issues/5406) @@ -837,7 +802,7 @@ one, which then could make curl decide to rather retry the transfer on that URL only instead of the original operation to the original URL. Perhaps extra emphasized if the original transfer is a large POST that -redirects to a separate GET, and that GET is what gets the 529 +redirects to a separate GET, and that GET is what gets the 429 See [curl issue 5462](https://github.com/curl/curl/issues/5462) @@ -998,8 +963,6 @@ See [curl issue 4477](https://github.com/curl/curl/issues/4477) The rate-limiting logic is done in the PERFORMING state in multi.c but MQTT is not (yet) implemented to use that. -## Support MQTTS - ## Handle network blocks Running test suite with `CURL_DBG_SOCK_WBLOCK=90 ./runtests.pl -a mqtt` makes diff --git a/docs/TheArtOfHttpScripting.md b/docs/TheArtOfHttpScripting.md index 7f300f070395..541cb4c2ff71 100644 --- a/docs/TheArtOfHttpScripting.md +++ b/docs/TheArtOfHttpScripting.md @@ -591,7 +591,7 @@ Failing the verification causes curl to deny the connection. You must then use [`--insecure`](https://curl.se/docs/manpage.html#-k) (`-k`) in case you want to tell curl to ignore that the server cannot be verified. -More about server certificate verification and ca cert bundles can be read in +More about server certificate verification and CA cert bundles can be read in the [`SSLCERTS` document](https://curl.se/docs/sslcerts.html). At times you may end up with your own CA cert store and then you can tell diff --git a/docs/URL-SYNTAX.md b/docs/URL-SYNTAX.md index 45b6f5ab454c..219e84ee932c 100644 --- a/docs/URL-SYNTAX.md +++ b/docs/URL-SYNTAX.md @@ -11,8 +11,8 @@ SPDX-License-Identifier: curl The official "URL syntax" is primarily defined in these two different specifications: -- [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) (although URL is called - "URI" in there) +- [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) (although URL is + called "URI" in there) - [The WHATWG URL Specification](https://url.spec.whatwg.org/) RFC 3986 is the earlier one, and curl has always tried to adhere to that one @@ -151,10 +151,9 @@ schemes: ## Userinfo -The userinfo field can be used to set username and password for -authentication purposes in this transfer. The use of this field is discouraged -since it often means passing around the password in plain text and is thus a -security risk. +The userinfo field can be used to set username and password for authentication +purposes in this transfer. The use of this field is discouraged since it often +means passing around the password in plain text and is thus a security risk. URLs for IMAP, POP3 and SMTP also support *login options* as part of the userinfo field. They are provided as a semicolon after the password and then @@ -176,6 +175,40 @@ brackets). For example: https://[2001:1890:1112:1::20]/ +libcurl rejects hostnames with more than one trailing dot. + +### Numerical IPv4 addresses + +libcurl parses and normalizes everything that appears to be a numerical IPv4 +address. Including octal and hexadecimal formats and using one, two, three or +four number groups. + +This normalizing is done so that curl can properly get documents from HTTP +servers (with the correctly formatted address in the `Host:` header), so that +IP based filtering for things like the `NO_PROXY` environment variable has a +higher chance of working correctly, to increase the chances that two URLs can +be compared and to allow users to extract and visualize the address in a readable +way and to make sure libcurl works identically across different name resolver +libraries and function calls. + +For a hostname that is only an IPv4 address with a trailing dot, the trailing +dot is removed in the normalizing process. + +### Numerical IPv6 addresses + +libcurl allows a zone id to be provided with a numerical IPv6 address, +separated with a percent character (`%`). The percent character may also be +percent-encoded as `%25`. Like this: + + http://[fe80::1%25eth0]/ + + http://[fe80::1%eth0]/ + +### `IPvFuture` + +RFC 3986 documents a numerical IP address format called `IPvFuture`. libcurl +does not recognize this format. Using it causes parse errors. + ### "localhost" Starting in curl 7.77.0, curl uses loopback IP addresses for the name @@ -212,6 +245,14 @@ DICT 2628, FTP 21, FTPS 990, GOPHER 70, GOPHERS 70, HTTP 80, HTTPS 443, IMAP 143, IMAPS 993, LDAP 389, LDAPS 636, MQTT 1883, POP3 110, POP3S 995, RTSP 554, SCP 22, SFTP 22, SMB 445, SMBS 445, SMTP 25, SMTPS 465, TELNET 23, TFTP 69 +## Path + +By default, libcurl removes sequences of `/./` and `/../` from the path as per +RFC 3986. + +libcurl might also normalize percent-encoded sequences to use uppercase +hexadecimal letters. + # Scheme specific behaviors ## FTP @@ -228,7 +269,7 @@ value of the ASCII code for the slash). ## FILE -When a `FILE://` URL is accessed on Windows systems, it can be crafted in a +When a `file://` URL is accessed on Windows systems, it can be crafted in a way so that Windows attempts to connect to a (remote) machine when curl wants to read or write such a path. diff --git a/docs/VERIFY.md b/docs/VERIFY.md index de2c8b112e24..c18b65a6612c 100644 --- a/docs/VERIFY.md +++ b/docs/VERIFY.md @@ -36,10 +36,24 @@ script that generates a new curl release from source code and then compares this newly generated release tarball with the tarball file you downloaded from curl.se. +For full verification, invoke the script inside an up-to-date curl source code +git repository. Without a git repository present, it does a lighter check by +rebuilding the release using the files in the tarball. + +Note: full verification mode checks out the release tag in your repository. +Run it in a clean working tree (no local changes) or a dedicated clone. + Invoke it like this: + git clone https://github.com/curl/curl + cd curl + mv [download-dir]/curl-8.19.0.tar.xz . ./scripts/verify-release curl-8.19.0.tar.xz +A successful check ends up with a final output similar to: + + curl-8.19.0.tar.xz: OK + By verifying the release tarballs, you verify that Daniel does not infect the release on purpose or involuntarily because of anything malicious running in his setup. @@ -65,15 +79,21 @@ gain trust is to verify and review our testing procedures. - we have a ceiling for complexity in functions to keep them easy to follow, read and understand (failing to do so causes errors) -- we review all pull requests before merging, both with humans and with bots. We - link back commits to their origin pull requests in commit messages. +- we review all pull requests before merging, both with humans and with bots. + We link back commits to their origin pull requests in commit messages. - we ban use of "binary blobs" in git to not provide means for malicious actors to bundle encrypted payloads (trying to include a blob causes errors) +- every single file in the git repository has a clear copyright and license + statement. Complete knowledge and tracking of provenience. + - we actively avoid base64 encoded chunks as they too could function as ways to obfuscate malicious contents +- we forbid and prevent git force push on the master branch. History cannot be + rewritten. + - we ban most uses of UTF-8 in code and documentation to avoid easily mixed up Unicode characters that look like other characters. (adding Unicode characters causes errors) @@ -92,6 +112,10 @@ gain trust is to verify and review our testing procedures. every commit and every PR. We do not merge commits that have unexplained test failures. +- we run all tests as "torture tests", where each test case is rerun to have + every invoked fallible function call fail once each, to make sure curl + never leaks memory or crashes due to this. + - we build curl in CI with the most picky compiler options enabled and we never allow compiler warnings to linger. We always use `-Werror` that converts warnings to errors and fail the builds. @@ -100,9 +124,9 @@ gain trust is to verify and review our testing procedures. find and reduce the risk for memory problems, undefined behavior and similar -- we run all tests as "torture tests", where each test case is rerun to have - every invoked fallible function call fail once each, to make sure curl - never leaks memory or crashes due to this. +- we keep running static code analyzers on the code, both traditional ones + (clang-tidy, CodeSonar, Coverity) but also new generation AI powered ones + like Zeropath and Codex Security. - we run fuzzing on curl: non-stop as part of Google's OSS-Fuzz project, but also briefly as part of the CI setup for every commit and PR @@ -114,6 +138,14 @@ gain trust is to verify and review our testing procedures. - we run `zizmor` and other code analyzer tools on the CI job config scripts to reduce the risk of us running or using insecure CI jobs. +- we do reproducible releases to allow anyone to verify that the contents is + untainted + +- we digitally sign releases, git tags and git commits + +- there is a git backup on [codeberg](https://codeberg.org/curl/) for enhanced + resilience to infrastructure disturbance + - we are committed to always fix reported vulnerabilities in the following release. Security problems never linger around once they have been reported. @@ -121,11 +153,16 @@ gain trust is to verify and review our testing procedures. - we document everything and every detail about all curl vulnerabilities ever reported +- our code has been audited several times by external security experts, and + the few issues that have been detected in those were immediately addressed + +- Strong two-factor authentication on GitHub is mandatory for all committers + - our commitment to never breaking ABI or API allows all users to easily upgrade to new releases. This enables users to run recent security-fixed versions instead of legacy insecure versions. -- our code has been audited several times by external security experts, and - the few issues that have been detected in those were immediately addressed - -- Two-factor authentication on GitHub is mandatory for all committers +- we have a vulnerability disclosure program that allows researchers to submit + suspected vulnerabilities in a private and secure fashion, so that we can + work on fixing curl and announcing the flaw in a responsible manner to + minimize risks for users. diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 0aebbff49731..c5db67b75881 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -68,13 +68,15 @@ dates. The tool was called `httpget` before 2.0, `urlget` before 4.0 then `curl` since 4.0. `libcurl` and `curl` are always released in sync, using the same version numbers. -- 8.21.0: pending -- 8.20.0: April 29, 2026 -- 8.19.0: March 11, 2026 -- 8.18.0: January 7, 2026 -- 8.17.0: November 5, 2025 -- 8.16.0: September 10, 2025 -- 8.15.0: July 16, 2025 +- 8.23.0: pending +- 8.22.0: September 2, 2026 +- 8.21.0: June 24 2026 +- 8.20.0: April 29 2026 +- 8.19.0: March 11 2026 +- 8.18.0: January 7 2026 +- 8.17.0: November 5 2025 +- 8.16.0: September 10 2025 +- 8.15.0: July 16 2025 - 8.14.1: June 4 2025 - 8.14.0: May 28 2025 - 8.13.0: April 2 2025 @@ -343,3 +345,14 @@ same version numbers. - 0.3: February 1 1997 - 0.2: December 17 1996 - 0.1: November 11 1996 + +## Rock-solid curl + +The long-term support releases of curl, called [Rock-solid +curl](https://rock-solid.curl.dev/), are managed separately and are for paying +customers only. They are made as releases branches off previous standard curl +releases, and use version numbers that were never used by the freely available +version. + +- Rock-solid curl 8.18.1: February 25, 2026 +- Rock-solid curl 8.18.2: September 2, 2026 diff --git a/docs/VULN-DISCLOSURE-POLICY.md b/docs/VULN-DISCLOSURE-POLICY.md index abc7ef2c0a96..2ae863d10516 100644 --- a/docs/VULN-DISCLOSURE-POLICY.md +++ b/docs/VULN-DISCLOSURE-POLICY.md @@ -36,6 +36,17 @@ announcement. [HackerOne](https://hackerone.com/curl). Issues filed there reach a handful of selected and trusted people. +- When communicating in the curl project, please explain your issues or + improvements briefly and clearly in your own human voice. Do not lazily + paste massive, AI-generated explanations; as a contributor doing this + infrequently, it is your responsibility to invest a few extra minutes into + making your message digestible. The maintainers review submissions + constantly, and clear writing reduces their daily burden and friction. + +- The curl project cannot handle vulnerability reports sent to us over email. + We lose track of the reports. We cannot easily disclose them. Please do not + send us reports over email. + - Messages that do not relate to the reporting or managing of an undisclosed security vulnerability in curl or libcurl are ignored and no further action is required. @@ -146,7 +157,7 @@ made public. # Severity levels The curl project's security team rates security problems using four severity -levels depending how serious we consider the problem to be. We use **Low**, +levels depending on how serious we consider the problem to be. We use **Low**, **Medium**, **High** and **Critical**. We refrain from using numerical scoring of vulnerabilities. @@ -172,6 +183,10 @@ trigger. Due to timing, platform requirements or the fact that options or protocols involved are rare etc. [Past example](https://curl.se/docs/CVE-2022-43552.html) +Issues that are likely to be detected by basic testing are likely to not be +considered more severe than **Low**. Users that do not test cannot be expected +to have secure setups to begin with. + ## Medium This is a security problem that is less hard than **Low** to exploit or @@ -218,9 +233,11 @@ problem. There are already several benign and likely reasons for transfers to stall and never end, so applications that cannot deal with never-ending transfers already need to have counter-measures established. -Well known attacks, like [Slowloris](https://en.wikipedia.org/wiki/Slowloris_(cyber_attack)), that send partial -requests are usually not considered a flaw. If the problem avoids the regular counter-measures when it causes a never- -ending transfer, it might be a security problem. +Well-known attacks, like +[Slowloris](https://en.wikipedia.org/wiki/Slowloris_(cyber_attack)), that send +partial requests are usually not considered a flaw. If the problem bypasses +the regular counter-measures and it causes a never-ending transfer, it might +be a security problem. ## Not practically possible @@ -248,12 +265,20 @@ already do much worse harm and the problem is not really in curl. ## Debug & Experiments Vulnerabilities in features which are off by default (in the build) and -documented as experimental, or exist only in debug mode, are not eligible for a -reward and we do not consider them security problems. +documented as experimental, or exist only in debug mode, are not considered +security problems. The same applies to scripts and software which are not installed by default through the make install rule. +## Test code + +curl has an extensive test suite with lots of code written specifically to +exercise and verify curl, libcurl and specific internal functions. The test +code and its associated test servers are *not* intended for production use. +They are not secure, you should not assume otherwise and must not report about +security problems in those. + ## URL inconsistencies URL parser inconsistencies between browsers and curl are expected and are not @@ -338,7 +363,7 @@ A *legacy dependency* is here defined as: - there are modern versions of equivalent or better functionality offered and in common use -## weak algorithms required for functionality +## Weak algorithms required for functionality curl supports several algorithms that are considered weak, like DES and MD5. These algorithms are still not curl security vulnerabilities or security @@ -363,6 +388,17 @@ For example, a user might pass in a username that looks like `Mr[CR][LF]Smith`. It may cause some minor havoc in the protocol handling, depending on what protocol is used. +## Non-released code + +Only curl releases are ever considered *secure*. Between releases, we are +under development and then we may have code present in the git repository that +is insecure, but without those flaws being considered as vulnerabilities. +Another reason we strongly suggest you only use curl release versions in +production. + +Unreleased code may also contain fixes to problems that were present in the +most recent release. + # curl major incident response Vulnerability disclosure manages the full life cycle of a vulnerability @@ -413,7 +449,8 @@ roles: * **incident lead** - Coordinates technical efforts * **communication lead** - Single point of public contact -It is likely that our [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) occupies +It is likely that our +[BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) occupies one of these roles, though this plan does not depend on it. A declaration may also contain more detailed information but as we honor @@ -422,8 +459,8 @@ contain a brief notification that a **major incident** is occurring. ## Major incident ongoing -During the incident - all press, media, legal or commercial entities should contact -communication leader (security@curl.se). +During the incident - all press, media, legal or commercial entities should +contact communication lead (security@curl.se). Existing **curl-security** team internal communication channels are used for all internal communication. @@ -431,9 +468,9 @@ for all internal communication. Existing vulnerability disclosure process are followed for any embargoes and fixes. -Where possible, public communication are provided: -* regular communication from communication leader (for example daily update) -* asynchronous communication from incident leader +Where possible, public communications are provided: +* regular communication from communication lead (for example daily update) +* asynchronous communication from incident lead * Delivered to the aforementioned curl communication channels. diff --git a/docs/cmdline-opts/.gitignore b/docs/cmdline-opts/.gitignore index 8d42e2c53d51..4af021b28799 100644 --- a/docs/cmdline-opts/.gitignore +++ b/docs/cmdline-opts/.gitignore @@ -3,3 +3,5 @@ # SPDX-License-Identifier: curl curl.txt +asciipage.tmp.* +manpage.tmp.* diff --git a/docs/cmdline-opts/MANPAGE.md b/docs/cmdline-opts/MANPAGE.md index 1e4facd954cf..972fc9a51bb2 100644 --- a/docs/cmdline-opts/MANPAGE.md +++ b/docs/cmdline-opts/MANPAGE.md @@ -114,12 +114,12 @@ During rendering, the generator expands them as follows: ## Generate -`managen mainpage [list of markdown option file names]` +`managen mainpage [list of markdown option filenames]` This command outputs a single huge nroff file, meant to become `curl.1`. The full curl man page. -`managen ascii [list of markdown option file names]` +`managen ascii [list of markdown option filenames]` This command outputs a single text file, meant to become `curl.txt`. The full curl man page in text format, used to build `tool_hugehelp.c`. diff --git a/docs/cmdline-opts/Makefile.am b/docs/cmdline-opts/Makefile.am index 019dd57b8a56..7c2604b64796 100644 --- a/docs/cmdline-opts/Makefile.am +++ b/docs/cmdline-opts/Makefile.am @@ -37,11 +37,11 @@ GN_0 = @echo " GENERATE" $@; GN_1 = GN_ = $(GN_0) -MANAGEN=$(top_srcdir)/scripts/managen -MAXLINE=$(top_srcdir)/scripts/maxline +MANAGEN = $(top_srcdir)/scripts/managen +MAXLINE = $(top_srcdir)/scripts/maxline # Maximum number of columns accepted in the ASCII version of the man page -INCDIR=$(top_srcdir)/include +INCDIR = $(top_srcdir)/include if BUILD_DOCS CLEANFILES = $(MANPAGE) $(ASCIIPAGE) diff --git a/docs/cmdline-opts/Makefile.inc b/docs/cmdline-opts/Makefile.inc index f7236af1b127..c6ffc06a5ea7 100644 --- a/docs/cmdline-opts/Makefile.inc +++ b/docs/cmdline-opts/Makefile.inc @@ -134,6 +134,10 @@ DPAGES = \ http2.md \ http3.md \ http3-only.md \ + httpsig-algo.md \ + httpsig-headers.md \ + httpsig-key.md \ + httpsig-keyid.md \ ignore-content-length.md \ insecure.md \ interface.md \ @@ -212,6 +216,7 @@ DPAGES = \ proxy-digest.md \ proxy-header.md \ proxy-http2.md \ + proxy-http3.md \ proxy-insecure.md \ proxy-key-type.md \ proxy-key.md \ diff --git a/docs/cmdline-opts/_ENVIRONMENT.md b/docs/cmdline-opts/_ENVIRONMENT.md index 1ac85fb12841..a3c13ae0c00d 100644 --- a/docs/cmdline-opts/_ENVIRONMENT.md +++ b/docs/cmdline-opts/_ENVIRONMENT.md @@ -3,7 +3,8 @@ # ENVIRONMENT The environment variables can be specified in lower case or upper case. The lower case version has precedence. `http_proxy` is an exception as it is only -available in lower case. +available in lower case. (Note that some systems, like Windows, do not +differentiate between environment variables using different case.) Using an environment variable to set the proxy has the same effect as using the --proxy option. diff --git a/docs/cmdline-opts/_GLOBBING.md b/docs/cmdline-opts/_GLOBBING.md index 37c8d430693d..b801adb4d1f9 100644 --- a/docs/cmdline-opts/_GLOBBING.md +++ b/docs/cmdline-opts/_GLOBBING.md @@ -39,4 +39,17 @@ probably have to put the full URL within double quotes to avoid the shell from interfering with it. This also goes for other characters treated special, like for example '&', '?' and '*'. +The separate globbing components can be referenced in the --output option to +allow pieces to be reused in the target filename. + +Starting in curl 8.21.0, the separate globbing parts can be named and +referenced by their names. The case sensitive alphanumeric name is set +enclosed within angle brackets after the opening character. Examples: + + https://fun.example/{one,two,three}.jpg + + ftp://ftp.example.com/file[1-100].txt + +Setting the same glob name twice is an error. + Switch off globbing with --globoff. diff --git a/docs/cmdline-opts/_PROTOCOLS.md b/docs/cmdline-opts/_PROTOCOLS.md index 831b944d24b7..6b1918a9b227 100644 --- a/docs/cmdline-opts/_PROTOCOLS.md +++ b/docs/cmdline-opts/_PROTOCOLS.md @@ -6,7 +6,7 @@ particular build may not support them all. ## DICT Lets you lookup words using online dictionaries. ## FILE -Read or write local files. curl does not support accessing file:// URL +Read or write local files. curl does not support accessing `file://` URL remotely, but when running on Microsoft Windows using the native UNC approach works. Only absolute paths. ## FTP(S) diff --git a/docs/cmdline-opts/_PROXYPREFIX.md b/docs/cmdline-opts/_PROXYPREFIX.md index 297b56c4b64c..0c106306d878 100644 --- a/docs/cmdline-opts/_PROXYPREFIX.md +++ b/docs/cmdline-opts/_PROXYPREFIX.md @@ -1,22 +1,22 @@ # PROXY PROTOCOL PREFIXES -The proxy string may be specified with a protocol:// prefix to specify +The proxy string may be specified with a `protocol://` prefix to specify alternative proxy protocols. (Added in 7.21.7) If no protocol is specified in the proxy string or if the string does not match a supported one, the proxy is treated as an HTTP proxy. The supported proxy protocol prefixes are as follows: -## http:// +## `http://` Makes it use it as an HTTP proxy. The default if no scheme prefix is used. -## https:// +## `https://` Makes it treated as an **HTTPS** proxy. -## socks4:// +## `socks4://` Makes it the equivalent of --socks4 -## socks4a:// +## `socks4a://` Makes it the equivalent of --socks4a -## socks5:// +## `socks5://` Makes it the equivalent of --socks5 -## socks5h:// +## `socks5h://` Makes it the equivalent of --socks5-hostname diff --git a/docs/cmdline-opts/_URL.md b/docs/cmdline-opts/_URL.md index 288b9d0aa713..a68b005893eb 100644 --- a/docs/cmdline-opts/_URL.md +++ b/docs/cmdline-opts/_URL.md @@ -4,7 +4,7 @@ The URL syntax is protocol-dependent. You can find a detailed description in RFC 3986. -If you provide a URL without a leading **protocol://** scheme, curl guesses +If you provide a URL without a leading `protocol://` scheme, curl guesses what protocol you want. It then defaults to HTTP but assumes others based on often-used hostname prefixes. For example, for hostnames starting with `ftp.` curl assumes you want FTP. @@ -15,14 +15,10 @@ specify command line options and URLs mixed and in any order on the command line. curl attempts to reuse connections when doing multiple transfers, so that -getting many files from the same server do not use multiple connects and setup -handshakes. This improves speed. Connection reuse can only be done for URLs -specified for a single command line invocation and cannot be performed between -separate curl runs. +getting many files from the same server does not use multiple connects and +setup handshakes. This improves speed. Connection reuse can only be done for +URLs specified for a single command line invocation and cannot be performed +between separate curl runs. -Provide an IPv6 zone id in the URL with an escaped percentage sign. Like in - - http://[fe80::3%25eth0]/ - -Everything provided on the command line that is not a command line option or -its argument, curl assumes is a URL and treats it as such. +curl assumes everything provided on the command line that is not a command +line option or its argument is a URL and treats it as such. diff --git a/docs/cmdline-opts/abstract-unix-socket.md b/docs/cmdline-opts/abstract-unix-socket.md index b1b6100e1611..40ecce5cf699 100644 --- a/docs/cmdline-opts/abstract-unix-socket.md +++ b/docs/cmdline-opts/abstract-unix-socket.md @@ -6,7 +6,7 @@ Arg: Help: Connect via abstract Unix domain socket Added: 7.53.0 Protocols: HTTP -Category: connection +Category: connection http Multi: single See-also: - unix-socket diff --git a/docs/cmdline-opts/basic.md b/docs/cmdline-opts/basic.md index 1c372fbc7138..4285e88a42b9 100644 --- a/docs/cmdline-opts/basic.md +++ b/docs/cmdline-opts/basic.md @@ -4,7 +4,7 @@ SPDX-License-Identifier: curl Long: basic Help: HTTP Basic Authentication Protocols: HTTP -Category: auth +Category: auth http Added: 7.10.6 Multi: boolean See-also: diff --git a/docs/cmdline-opts/ca-native.md b/docs/cmdline-opts/ca-native.md index 4a887df558a6..67fdf8c3acef 100644 --- a/docs/cmdline-opts/ca-native.md +++ b/docs/cmdline-opts/ca-native.md @@ -24,7 +24,7 @@ Use the operating system's native CA store for certificate verification. This option is independent of other CA certificate locations set at run time or build time. Those locations are searched in addition to the native CA store. -This option works with OpenSSL and its forks (LibreSSL, BoringSSL, etc) on +This option works with OpenSSL and its forks (BoringSSL, LibreSSL, etc) on Windows (Added in 7.71.0) and on Apple OS when libcurl is built with Apple SecTrust enabled. (Added in 8.17.0) diff --git a/docs/cmdline-opts/compressed-ssh.md b/docs/cmdline-opts/compressed-ssh.md index 07d3981b484e..2c1fda9bdc4e 100644 --- a/docs/cmdline-opts/compressed-ssh.md +++ b/docs/cmdline-opts/compressed-ssh.md @@ -5,7 +5,7 @@ Long: compressed-ssh Help: Enable SSH compression Protocols: SCP SFTP Added: 7.56.0 -Category: scp ssh +Category: scp sftp ssh Multi: boolean See-also: - compressed diff --git a/docs/cmdline-opts/cookie.md b/docs/cmdline-opts/cookie.md index 30288fbcbad4..95b0a8c3c81f 100644 --- a/docs/cmdline-opts/cookie.md +++ b/docs/cmdline-opts/cookie.md @@ -42,15 +42,16 @@ If the filename is an empty string ("") and is the only cookie input, curl activates the cookie engine without any cookies. The file format of the file to read cookies from should be plain HTTP headers -(Set-Cookie style) or the Netscape/Mozilla cookie file format. +(Set-Cookie style) or the Netscape/Mozilla cookie file format. We discourage +the use of the HTTP header style. The file specified with --cookie is only used as input. No cookies are written to that file. To store cookies, use the --cookie-jar option. -If you use the Set-Cookie file format and do not specify a domain then the -cookie is not sent since the domain never matches. To address this, set a -domain in Set-Cookie line (doing that includes subdomains) or preferably: use -the Netscape format. +If you read cookies from a plain HTTP headers file, make sure each +`Set-Cookie` line specifies a `Domain` attribute. Without an explicit domain, +the cookie cannot be reliably matched to a target host and may be applied in +unexpected ways. We suggest using the Netscape file format instead. Users often want to both read cookies from a file and write updated cookies back to a file, so using both --cookie and --cookie-jar in the same command diff --git a/docs/cmdline-opts/data.md b/docs/cmdline-opts/data.md index d95b02395369..6faaad628dde 100644 --- a/docs/cmdline-opts/data.md +++ b/docs/cmdline-opts/data.md @@ -4,16 +4,17 @@ SPDX-License-Identifier: curl Long: data Short: d Arg: -Help: HTTP POST data +Help: Post data Protocols: HTTP MQTT Mutexed: form head upload-file -Category: important http post upload +Category: important http post upload mqtt Added: 4.0 Multi: append See-also: - data-binary - data-urlencode - data-raw + - form Example: - -d "name=curl" $URL - -d "name=curl" -d "tool=cmdline" $URL @@ -22,10 +23,14 @@ Example: # `--data` -Send the specified data in a POST request to the HTTP server, in the same way -that a browser does when a user has filled in an HTML form and presses the -submit button. This option makes curl pass the data to the server using the -content-type application/x-www-form-urlencoded. Compared to --form. +Send the specified data to a server. + +For HTTP(S), this is done with the POST method in the same way that a browser +does when a user has filled in an HTML form and presses the submit button. +This option makes curl pass the data to the server using the content-type +application/x-www-form-urlencoded. + +For MQTT, the data is sent as a PUBLISH. --data-raw is almost the same but does not have a special interpretation of the @ character. To post data purely binary, you should instead use the diff --git a/docs/cmdline-opts/follow.md b/docs/cmdline-opts/follow.md index 096324f1654b..b34e30c693d0 100644 --- a/docs/cmdline-opts/follow.md +++ b/docs/cmdline-opts/follow.md @@ -24,7 +24,7 @@ set with --request when following redirects as the HTTP specification says. The method string set with --request is used in subsequent requests for the status codes 307 or 308, but may be reset to GET for 301, 302 and 303. -This is subtly different than --location, as that option always set the custom +This is subtly different than --location, as that option always sets the custom method in all subsequent requests independent of response code. Restrict which protocols a redirect is accepted to follow with --proto-redir. diff --git a/docs/cmdline-opts/form-escape.md b/docs/cmdline-opts/form-escape.md index 7cf1cb7403db..d9f4ad4aa16e 100644 --- a/docs/cmdline-opts/form-escape.md +++ b/docs/cmdline-opts/form-escape.md @@ -5,7 +5,7 @@ Long: form-escape Help: Escape form fields using backslash Protocols: HTTP IMAP SMTP Added: 7.81.0 -Category: http upload post +Category: http upload post imap smtp Multi: single See-also: - form diff --git a/docs/cmdline-opts/ftp-ssl-control.md b/docs/cmdline-opts/ftp-ssl-control.md index a68359a7b635..fa85de24f187 100644 --- a/docs/cmdline-opts/ftp-ssl-control.md +++ b/docs/cmdline-opts/ftp-ssl-control.md @@ -18,3 +18,5 @@ Example: Require SSL/TLS for the FTP login, clear for transfer. Allows secure authentication, but non-encrypted data transfers for efficiency. Fails the transfer if the server does not support SSL/TLS. + +If set, this option overrides --ssl. diff --git a/docs/cmdline-opts/hostpubsha256.md b/docs/cmdline-opts/hostpubsha256.md index e695a10cb588..a92dbe5d7cb9 100644 --- a/docs/cmdline-opts/hostpubsha256.md +++ b/docs/cmdline-opts/hostpubsha256.md @@ -18,6 +18,3 @@ Example: Pass a string containing a Base64-encoded SHA256 hash of the remote host's public key. curl refuses the connection with the host unless the hashes match. - -This feature requires libcurl to be built with libssh2 and does not work with -other SSH backends. diff --git a/docs/cmdline-opts/hsts.md b/docs/cmdline-opts/hsts.md index 0f6673cc4ef1..7653c65cd5d9 100644 --- a/docs/cmdline-opts/hsts.md +++ b/docs/cmdline-opts/hsts.md @@ -18,11 +18,13 @@ Example: Enable HSTS for the transfer. If the filename points to an existing HSTS cache file, that is used. After a completed transfer, the cache is saved to the -filename again if it has been modified. +filename again if it has been modified. If you run multiple curl invokes at +the same time using the same HSTS cache file, they might interfere with each +other in possibly undesired ways. -If curl is told to use HTTP:// for a transfer involving a hostname that exists -in the HSTS cache, it upgrades the transfer to use HTTPS. Each HSTS cache -entry has an individual lifetime after which the upgrade is no longer +If curl is told to use `http://` for a transfer involving a hostname that +exists in the HSTS cache, it upgrades the transfer to use HTTPS. Each HSTS +cache entry has an individual lifetime after which the upgrade is no longer performed. Specify a "" filename (zero length) to avoid loading/saving and make curl diff --git a/docs/cmdline-opts/http1.1.md b/docs/cmdline-opts/http1.1.md index 14e5c74702aa..3d241e5c5944 100644 --- a/docs/cmdline-opts/http1.1.md +++ b/docs/cmdline-opts/http1.1.md @@ -18,4 +18,4 @@ Example: # `--http1.1` -Use HTTP version 1.1. This is the default with HTTP:// URLs. +Use HTTP version 1.1. This is the default with `http://` URLs. diff --git a/docs/cmdline-opts/httpsig-algo.md b/docs/cmdline-opts/httpsig-algo.md new file mode 100644 index 000000000000..c97814738ed2 --- /dev/null +++ b/docs/cmdline-opts/httpsig-algo.md @@ -0,0 +1,36 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: httpsig-algo +Protocols: HTTP +Arg: +Help: Algorithm for HTTP Message Signatures +Category: auth http +Added: 8.22.0 +Multi: single +Experimental: yes +See-also: + - httpsig-key + - httpsig-keyid + - httpsig-headers +Example: + - --httpsig-key key.hex --httpsig-keyid "my-key" $URL + - --httpsig-algo hmac-sha256 --httpsig-key secret.hex --httpsig-keyid "shared" $URL +--- + +# `--httpsig-algo` + +Sign outgoing HTTP requests using RFC 9421 HTTP Message Signatures. + +This option specifies which signing algorithm to use. Supported values are +**ed25519** and **hmac-sha256**. If not specified, **ed25519** is used. Any +other value causes curl to exit with an error. + +HTTP Message Signatures are enabled when any of --httpsig-algo, +--httpsig-key, --httpsig-keyid or --httpsig-headers is given. When enabled, +--httpsig-key and --httpsig-keyid are required. Without any of these options +no signing is performed. + +By default, the signed components are `method`, `authority`, `path`, and +`query` (when a query string is present). Use --httpsig-headers to override +the set of components included in the signature. diff --git a/docs/cmdline-opts/httpsig-headers.md b/docs/cmdline-opts/httpsig-headers.md new file mode 100644 index 000000000000..7b590c98540f --- /dev/null +++ b/docs/cmdline-opts/httpsig-headers.md @@ -0,0 +1,45 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: httpsig-headers +Protocols: HTTP +Arg: +Help: Components to sign for HTTP Message Signatures +Category: auth http +Added: 8.22.0 +Multi: single +Experimental: yes +See-also: + - httpsig-algo + - httpsig-key + - httpsig-keyid +Example: + - --httpsig-algo ed25519 --httpsig-key key.hex --httpsig-keyid "my-key" --httpsig-headers "method authority content-type:" $URL +--- + +# `--httpsig-headers` + +Space-separated list of components to include in the RFC 9421 HTTP Message +Signature. Derived components are given as bare names: `method`, `authority`, +`path`, and `query`. HTTP header fields are given with a trailing colon, for +example `content-type:` and `content-digest:`. + +If not specified, the default set is `method authority path` (plus `query` +when a query string is present in the URL). + +## Signing request headers + +Header components are taken from `-H` / `--header` options only. Headers curl +adds by default (such as `User-Agent`) are not signed unless you set them +explicitly, for example: + + curl --httpsig-algo ed25519 \ + --httpsig-key k.hex \ + --httpsig-keyid mykey \ + -H "User-Agent: MyApp/1.0" \ + --httpsig-headers \ + "method authority path user-agent:" \ + $URL + +Each component may appear only once. Duplicate identifiers in +`--httpsig-headers` cause curl to exit with an error. diff --git a/docs/cmdline-opts/httpsig-key.md b/docs/cmdline-opts/httpsig-key.md new file mode 100644 index 000000000000..c0a04051485f --- /dev/null +++ b/docs/cmdline-opts/httpsig-key.md @@ -0,0 +1,38 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: httpsig-key +Protocols: HTTP +Arg: +Help: Key for HTTP Message Signatures +Category: auth http +Added: 8.22.0 +Multi: single +Experimental: yes +See-also: + - httpsig-algo + - httpsig-keyid +Example: + - --httpsig-algo ed25519 --httpsig-key @key.hex --httpsig-keyid "my-key" $URL + - --httpsig-key 123a56fb72197633bc --httpsig-keyid "my-key" $URL +--- + +# `--httpsig-key` + +The key to use for RFC 9421 HTTP Message Signatures. Provide it as-is, or as +`@filename`. If the argument starts with an `@`, the rest is treated as a file +name for the key. + +The key is formatted as a series of hexadecimal digits in a single line. For +**ed25519**, this is the 32-byte private seed (64 hex characters). For +**hmac-sha256**, this is the shared secret. PEM files are not supported. + +## Generating Ed25519 keys + +With OpenSSL 3: + + openssl genpkey -algorithm ED25519 -out k.pem + openssl pkey -in k.pem -outform RAW -out k.raw + xxd -p -c 64 k.raw | tr -d '\n' > k.hex + +Use `@k.hex` with `--httpsig-key`. diff --git a/docs/cmdline-opts/httpsig-keyid.md b/docs/cmdline-opts/httpsig-keyid.md new file mode 100644 index 000000000000..2dc305fcfbd5 --- /dev/null +++ b/docs/cmdline-opts/httpsig-keyid.md @@ -0,0 +1,23 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: httpsig-keyid +Protocols: HTTP +Arg: +Help: Key identifier for HTTP Message Signatures +Category: auth http +Added: 8.22.0 +Multi: single +Experimental: yes +See-also: + - httpsig-algo + - httpsig-key +Example: + - --httpsig-algo ed25519 --httpsig-key key.hex --httpsig-keyid "my-key" $URL +--- + +# `--httpsig-keyid` + +The key identifier to include in the `Signature-Input` header when using RFC +9421 HTTP Message Signatures. This value appears as the `keyid` parameter and +allows the server to look up the correct verification key. diff --git a/docs/cmdline-opts/key.md b/docs/cmdline-opts/key.md index cc4bc73fa5b3..c0d6f804c412 100644 --- a/docs/cmdline-opts/key.md +++ b/docs/cmdline-opts/key.md @@ -5,7 +5,7 @@ Long: key Arg: Protocols: TLS SCP SFTP Help: Private key filename -Category: tls ssh +Category: tls sftp scp ssh Added: 7.9.3 Multi: single See-also: diff --git a/docs/cmdline-opts/knownhosts.md b/docs/cmdline-opts/knownhosts.md index 4b6386dd24ec..cb99fb910ab4 100644 --- a/docs/cmdline-opts/knownhosts.md +++ b/docs/cmdline-opts/knownhosts.md @@ -5,7 +5,7 @@ Long: knownhosts Arg: Protocols: SCP SFTP Help: Specify knownhosts path -Category: ssh +Category: sftp scp ssh Added: 8.17.0 Multi: single See-also: diff --git a/docs/cmdline-opts/libcurl.md b/docs/cmdline-opts/libcurl.md index e37e5aa0fadd..7f1b64a0656b 100644 --- a/docs/cmdline-opts/libcurl.md +++ b/docs/cmdline-opts/libcurl.md @@ -19,3 +19,6 @@ Example: Append this option to any ordinary curl command line, and you get libcurl-using C source code written to the file that does the equivalent of what your command-line operation does. + +The source code output should be considered example code and is not production +ready. You must double-check that the code actually does what you want it do. diff --git a/docs/cmdline-opts/max-filesize.md b/docs/cmdline-opts/max-filesize.md index 02b2293c56b2..a3adcdebf474 100644 --- a/docs/cmdline-opts/max-filesize.md +++ b/docs/cmdline-opts/max-filesize.md @@ -5,7 +5,7 @@ Long: max-filesize Arg: Help: Maximum file size to download Protocols: FTP HTTP MQTT -Category: connection +Category: connection ftp http mqtt Added: 7.10.8 Multi: single See-also: diff --git a/docs/cmdline-opts/oauth2-bearer.md b/docs/cmdline-opts/oauth2-bearer.md index b66477fc70c3..cf260e1b51c7 100644 --- a/docs/cmdline-opts/oauth2-bearer.md +++ b/docs/cmdline-opts/oauth2-bearer.md @@ -5,7 +5,7 @@ Long: oauth2-bearer Help: OAuth 2 Bearer Token Arg: Protocols: IMAP LDAP POP3 SMTP HTTP -Category: auth imap pop3 smtp ldap +Category: auth imap pop3 smtp ldap http Added: 7.33.0 Multi: single See-also: diff --git a/docs/cmdline-opts/output.md b/docs/cmdline-opts/output.md index 0c4f7f9facdd..b2d196d038da 100644 --- a/docs/cmdline-opts/output.md +++ b/docs/cmdline-opts/output.md @@ -23,10 +23,10 @@ Example: # `--output` -Write output to the given file instead of stdout. If you are using globbing to -fetch multiple documents, you should quote the URL and you can use `#` -followed by a number in the filename. That variable is then replaced with the -current string for the URL being fetched. Like in: +Write output to the given file instead of stdout. If you are using globbing in +the URL to fetch multiple documents, you should quote the URL and you can use +`#` followed by a number in the filename. That variable gets replaced with the +current glob text. Like in: curl "http://{one,two}.example.com" -o "file_#1.txt" @@ -69,3 +69,22 @@ override curl's internal binary output in terminal prevention: Note that the binary output may be caused by the response being compressed, in which case you may want to use the --compressed option. + +Since curl 8.21.0, the separate globbing parts can be named and referenced by +their names. The case sensitive alphanumeric name is set enclosed within angle +brackets after the opening character. Examples: + + curl "https://fun.example/{one,two}.jpg" -o "save-#" + + curl "ftp://ftp.example/file[1-100].txt" \ + -o "save-#.txt" + +Referencing a named glob that is not set, causes an error. + +Since curl 8.21.0, you can use parts of the upload filename when it uses +globbing by setting a glob name and referencing it the same way you reference +named URL globs. For example, if you upload three files to a single fixed HTTP +URL and want to save the corresponding responses in separate files: + + curl -T 'file{1,2,3}' \ + https://upload.example/ -o 'response-#' diff --git a/docs/cmdline-opts/pass.md b/docs/cmdline-opts/pass.md index 79c2f8738a13..39b60ebfd93c 100644 --- a/docs/cmdline-opts/pass.md +++ b/docs/cmdline-opts/pass.md @@ -5,7 +5,7 @@ Long: pass Arg: Help: Passphrase for the private key Protocols: TLS SCP SFTP -Category: ssh tls auth +Category: tls sftp scp ssh auth Added: 7.9.3 Multi: single See-also: diff --git a/docs/cmdline-opts/preproxy.md b/docs/cmdline-opts/preproxy.md index 87d94a9604fc..a108d9f09485 100644 --- a/docs/cmdline-opts/preproxy.md +++ b/docs/cmdline-opts/preproxy.md @@ -20,9 +20,9 @@ Use the specified SOCKS proxy before connecting to an HTTP or HTTPS --proxy. In such a case curl first connects to the SOCKS proxy and then connects (through SOCKS) to the HTTP or HTTPS proxy. Hence pre proxy. -The pre proxy string should be specified with a protocol:// prefix to specify -alternative proxy protocols. Use socks4://, socks4a://, socks5:// or -socks5h:// to request the specific SOCKS version to be used. No protocol +The pre proxy string should be specified with a `protocol://` prefix to specify +alternative proxy protocols. Use `socks4://`, `socks4a://`, `socks5://` or +`socks5h://` to request the specific SOCKS version to be used. No protocol specified makes curl default to SOCKS4. If the port number is not specified in the proxy string, it is assumed to be diff --git a/docs/cmdline-opts/proto-default.md b/docs/cmdline-opts/proto-default.md index 209e5cdc83da..903fac73a5e5 100644 --- a/docs/cmdline-opts/proto-default.md +++ b/docs/cmdline-opts/proto-default.md @@ -16,7 +16,8 @@ Example: # `--proto-default` -Use *protocol* for any provided URL missing a scheme. +Use *protocol* for any provided URL missing a scheme. The case-insensitive +name should be given without any `://` suffix. An unknown or unsupported protocol causes error *CURLE_UNSUPPORTED_PROTOCOL*. @@ -24,3 +25,6 @@ This option does not change the default proxy protocol (http). Without this option set, curl guesses protocol based on the hostname, see --url for details. + +The default protocol cannot be set to `ipfs` or `ipns`. Those schemes need to +be used explicitly in the URL. diff --git a/docs/cmdline-opts/proto.md b/docs/cmdline-opts/proto.md index cf288d456575..85d814a9b23d 100644 --- a/docs/cmdline-opts/proto.md +++ b/docs/cmdline-opts/proto.md @@ -18,7 +18,7 @@ Example: Limit what protocols to allow for transfers. Protocols are evaluated left to right, are comma separated, and are each a protocol name or 'all', optionally -prefixed by zero or more modifiers. Available modifiers are: +prefixed by a modifier. Available modifiers are: ## + Permit this protocol in addition to protocols already permitted (this is diff --git a/docs/cmdline-opts/proxy-header.md b/docs/cmdline-opts/proxy-header.md index 459eb462f683..3cc179657b04 100644 --- a/docs/cmdline-opts/proxy-header.md +++ b/docs/cmdline-opts/proxy-header.md @@ -6,7 +6,7 @@ Arg:

Help: Pass custom header(s) to proxy Protocols: HTTP Added: 7.37.0 -Category: proxy +Category: proxy http Multi: append See-also: - proxy diff --git a/docs/cmdline-opts/proxy-http2.md b/docs/cmdline-opts/proxy-http2.md index ca6a091f328e..a38da9e87ed8 100644 --- a/docs/cmdline-opts/proxy-http2.md +++ b/docs/cmdline-opts/proxy-http2.md @@ -5,7 +5,7 @@ Long: proxy-http2 Tags: Versions HTTP/2 Protocols: HTTP Added: 8.1.0 -Mutexed: +Mutexed: proxy-http3 Requires: HTTP/2 Help: Use HTTP/2 with HTTPS proxy Category: http proxy @@ -22,3 +22,5 @@ Negotiate HTTP/2 with an HTTPS proxy. The proxy might still only offer HTTP/1 and then curl sticks to using that version. This has no effect for any other kinds of proxies. + +This option is mutually exclusive with `--proxy-http3`. diff --git a/docs/cmdline-opts/proxy-http3.md b/docs/cmdline-opts/proxy-http3.md new file mode 100644 index 000000000000..6533b980b6cc --- /dev/null +++ b/docs/cmdline-opts/proxy-http3.md @@ -0,0 +1,31 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Long: proxy-http3 +Tags: Versions HTTP/3 +Protocols: HTTP +Added: 8.21.0 +Mutexed: proxy-http2 +Requires: HTTP/3 +Help: Use HTTP/3 with HTTPS proxy +Category: http proxy +Multi: boolean +See-also: + - proxy + - proxy-http2 +Example: + - --proxy-http3 -x proxy $URL +--- + +# `--proxy-http3` + +Negotiate HTTP/3 with an HTTPS proxy. +Fails to perform the transfer if the given proxy does not support HTTP/3. + +This has no effect for any other kinds of proxies. + +This option is mutually exclusive with `--proxy-http2`. + +This feature is experimental and requires a build with HTTP/3 proxy support +enabled. For autotools builds, use `--enable-proxy-http3`. For CMake builds, +use `-DUSE_PROXY_HTTP3=ON`. diff --git a/docs/cmdline-opts/proxy-tlsauthtype.md b/docs/cmdline-opts/proxy-tlsauthtype.md index 84becc149e99..24fa449fd03e 100644 --- a/docs/cmdline-opts/proxy-tlsauthtype.md +++ b/docs/cmdline-opts/proxy-tlsauthtype.md @@ -17,6 +17,8 @@ Example: # `--proxy-tlsauthtype` +Deprecated option. This has no functionality since 8.22.0. + Set TLS authentication type with HTTPS proxy. The only supported option is `SRP`, for TLS-SRP (RFC 5054). This option works only if the underlying libcurl is built with TLS-SRP support. diff --git a/docs/cmdline-opts/proxy-tlspassword.md b/docs/cmdline-opts/proxy-tlspassword.md index 63c252156627..5acc5e31a907 100644 --- a/docs/cmdline-opts/proxy-tlspassword.md +++ b/docs/cmdline-opts/proxy-tlspassword.md @@ -16,6 +16,8 @@ Example: # `--proxy-tlspassword` +Deprecated option. This has no functionality since 8.22.0. + Set password to use with the TLS authentication method specified with --proxy-tlsauthtype when using HTTPS proxy. Requires that --proxy-tlsuser is set. diff --git a/docs/cmdline-opts/proxy-tlsuser.md b/docs/cmdline-opts/proxy-tlsuser.md index 610a2169b885..549ab398157c 100644 --- a/docs/cmdline-opts/proxy-tlsuser.md +++ b/docs/cmdline-opts/proxy-tlsuser.md @@ -16,6 +16,8 @@ Example: # `--proxy-tlsuser` +Deprecated option. This has no functionality since 8.22.0. + Set username for use for HTTPS proxy with the TLS authentication method specified with --proxy-tlsauthtype. Requires that --proxy-tlspassword also is set. diff --git a/docs/cmdline-opts/proxy.md b/docs/cmdline-opts/proxy.md index 6cd456169d34..881f62d6a1a8 100644 --- a/docs/cmdline-opts/proxy.md +++ b/docs/cmdline-opts/proxy.md @@ -19,15 +19,15 @@ Example: Use the specified proxy. -The proxy string can be specified with a protocol:// prefix. No protocol -specified or http:// it is treated as an HTTP proxy. Use socks4://, -socks4a://, socks5:// or socks5h:// to request a specific SOCKS version to be -used. (Added in 7.21.7) +The proxy string can be specified with a `protocol://` prefix. No protocol +specified or http:// it is treated as an HTTP proxy. Use `socks4://`, +`socks4a://`, `socks5://` or `socks5h://` to request a specific SOCKS version +to be used. (Added in 7.21.7) Unix domain sockets are supported for socks proxy. Set localhost for the host part. e.g. socks5h://localhost/path/to/socket.sock -HTTPS proxy support works with the https:// protocol prefix for OpenSSL and +HTTPS proxy support works with the `https://` protocol prefix for OpenSSL and GnuTLS (added in 7.52.0). It also works for mbedTLS, Rustls, Schannel and wolfSSL (added in 7.87.0). @@ -50,7 +50,7 @@ by curl. This allows you to pass in special characters such as @ by using %40 or pass in a colon with %3a. The proxy host can be specified the same way as the proxy environment -variables, including the protocol prefix (http://) and the embedded user + +variables, including the protocol prefix (`http://`) and the embedded user + password. When a proxy is used, the active FTP mode as set with --ftp-port, cannot be diff --git a/docs/cmdline-opts/remote-header-name.md b/docs/cmdline-opts/remote-header-name.md index 52ae98b01cff..5faddaa79ad6 100644 --- a/docs/cmdline-opts/remote-header-name.md +++ b/docs/cmdline-opts/remote-header-name.md @@ -5,7 +5,7 @@ Long: remote-header-name Short: J Protocols: HTTP Help: Use the header-provided filename -Category: output +Category: output http Added: 7.20.0 Multi: boolean See-also: diff --git a/docs/cmdline-opts/sasl-authzid.md b/docs/cmdline-opts/sasl-authzid.md index 4e92a2054152..4b1338866b4f 100644 --- a/docs/cmdline-opts/sasl-authzid.md +++ b/docs/cmdline-opts/sasl-authzid.md @@ -6,7 +6,7 @@ Arg: Help: Identity for SASL PLAIN authentication Protocols: LDAP IMAP POP3 SMTP Added: 7.66.0 -Category: auth +Category: auth imap smtp pop3 ldap Multi: single See-also: - login-options diff --git a/docs/cmdline-opts/sasl-ir.md b/docs/cmdline-opts/sasl-ir.md index 206bf29317a8..49f21c1b5b24 100644 --- a/docs/cmdline-opts/sasl-ir.md +++ b/docs/cmdline-opts/sasl-ir.md @@ -5,7 +5,7 @@ Long: sasl-ir Help: Initial response in SASL authentication Protocols: LDAP IMAP POP3 SMTP Added: 7.31.0 -Category: auth +Category: auth imap pop3 ldap smtp Multi: boolean See-also: - sasl-authzid diff --git a/docs/cmdline-opts/show-headers.md b/docs/cmdline-opts/show-headers.md index d733784ac22d..7e181246be86 100644 --- a/docs/cmdline-opts/show-headers.md +++ b/docs/cmdline-opts/show-headers.md @@ -5,7 +5,7 @@ Long: show-headers Short: i Help: Show response headers in output Protocols: HTTP FTP -Category: important verbose output +Category: important verbose output http ftp Added: 4.8 Multi: boolean See-also: @@ -24,6 +24,12 @@ non-HTTP protocols, the "headers" are other server communication. This option makes the response headers get saved in the same stream/output as the data. --dump-header exists to save headers in a separate stream. +When HTTP headers are output to a tty, curl may use escape codes to make the +header field names appear in bold and URLs in `Location:` headers be +especially marked as such. Disable the use of terminal escape codes with +--no-styled-output. (This means using the --styled-output option with a +`--no-` prefix to disable it.) + To view the request headers, consider the --verbose option. Prior to 7.75.0 curl did not print the headers if --fail was used in diff --git a/docs/cmdline-opts/silent.md b/docs/cmdline-opts/silent.md index 2498ca56f4fb..ac7dc5abed11 100644 --- a/docs/cmdline-opts/silent.md +++ b/docs/cmdline-opts/silent.md @@ -17,8 +17,8 @@ Example: # `--silent` -Silent or quiet mode. Do not show progress meter, warning messages or error -messages. Makes curl mute. It still outputs the data you ask for, potentially +Silent or quiet mode. Do not show progress meter, note messages, warning +messages or error messages. Makes curl mute. It still outputs the data you ask for, potentially even to the terminal/stdout unless you redirect it. Use --show-error in addition to this option to disable progress meter but diff --git a/docs/cmdline-opts/socks4.md b/docs/cmdline-opts/socks4.md index 59ec172b8d86..9d1c5671c939 100644 --- a/docs/cmdline-opts/socks4.md +++ b/docs/cmdline-opts/socks4.md @@ -30,7 +30,7 @@ This option overrides any previous use of --proxy, as they are mutually exclusive. This option is superfluous since you can specify a socks4 proxy with --proxy -using a socks4:// protocol prefix. (Added in 7.21.7) +using a `socks4://` protocol prefix. (Added in 7.21.7) --preproxy can be used to specify a SOCKS proxy at the same time proxy is used with an HTTP/HTTPS proxy (added in 7.52.0). In such a case, curl first diff --git a/docs/cmdline-opts/socks4a.md b/docs/cmdline-opts/socks4a.md index 9e451cf7b063..695e44cb2b39 100644 --- a/docs/cmdline-opts/socks4a.md +++ b/docs/cmdline-opts/socks4a.md @@ -29,7 +29,7 @@ This option overrides any previous use of --proxy, as they are mutually exclusive. This option is superfluous since you can specify a socks4a proxy with --proxy -using a socks4a:// protocol prefix. (Added in 7.21.7) +using a `socks4a://` protocol prefix. (Added in 7.21.7) --preproxy can be used to specify a SOCKS proxy at the same time --proxy is used with an HTTP/HTTPS proxy (added in 7.52.0). In such a case, curl first diff --git a/docs/cmdline-opts/socks5-hostname.md b/docs/cmdline-opts/socks5-hostname.md index b558248a78b9..f8ee9fe13797 100644 --- a/docs/cmdline-opts/socks5-hostname.md +++ b/docs/cmdline-opts/socks5-hostname.md @@ -28,7 +28,7 @@ This option overrides any previous use of --proxy, as they are mutually exclusive. This option is superfluous since you can specify a socks5 hostname proxy with ---proxy using a socks5h:// protocol prefix. (Added in 7.21.7) +--proxy using a `socks5h://` protocol prefix. (Added in 7.21.7) --preproxy can be used to specify a SOCKS proxy at the same time --proxy is used with an HTTP/HTTPS proxy (added in 7.52.0). In such a case, curl first diff --git a/docs/cmdline-opts/socks5.md b/docs/cmdline-opts/socks5.md index 3aa65b33adda..b70e88f6fa2a 100644 --- a/docs/cmdline-opts/socks5.md +++ b/docs/cmdline-opts/socks5.md @@ -29,7 +29,7 @@ This option overrides any previous use of --proxy, as they are mutually exclusive. This option is superfluous since you can specify a socks5 proxy with --proxy -using a socks5:// protocol prefix. (Added in 7.21.7) +using a `socks5://` protocol prefix. (Added in 7.21.7) --preproxy can be used to specify a SOCKS proxy at the same time --proxy is used with an HTTP/HTTPS proxy (added in 7.52.0). In such a case, curl first diff --git a/docs/cmdline-opts/ssl-reqd.md b/docs/cmdline-opts/ssl-reqd.md index f21c145f5993..c48209676c7d 100644 --- a/docs/cmdline-opts/ssl-reqd.md +++ b/docs/cmdline-opts/ssl-reqd.md @@ -5,7 +5,7 @@ Long: ssl-reqd Help: Require SSL/TLS Protocols: FTP IMAP POP3 SMTP LDAP Added: 7.20.0 -Category: tls imap pop3 smtp ldap +Category: tls imap pop3 smtp ldap ftp Multi: boolean See-also: - ssl diff --git a/docs/cmdline-opts/ssl.md b/docs/cmdline-opts/ssl.md index 5951d0199123..9461cda4c72d 100644 --- a/docs/cmdline-opts/ssl.md +++ b/docs/cmdline-opts/ssl.md @@ -5,7 +5,7 @@ Long: ssl Help: Try enabling TLS Protocols: FTP IMAP POP3 SMTP LDAP Added: 7.20.0 -Category: tls imap pop3 smtp ldap +Category: tls imap pop3 smtp ldap ftp Multi: boolean See-also: - ssl-reqd @@ -30,5 +30,7 @@ OpenLDAP backend and ignored by the generic ldap backend. Please note that a server may close the connection if the negotiation fails. +If set, this option overrides --ftp-ssl-control. + This option was formerly known as --ftp-ssl (added in 7.11.0). That option name can still be used but might be removed in a future version. diff --git a/docs/cmdline-opts/tls-earlydata.md b/docs/cmdline-opts/tls-earlydata.md index 8e344758be5e..22a7abd3c376 100644 --- a/docs/cmdline-opts/tls-earlydata.md +++ b/docs/cmdline-opts/tls-earlydata.md @@ -20,8 +20,8 @@ Example: Enable the use of TLSv1.3 early data, also known as '0RTT' where possible. This has security implications for the requests sent that way. -This option can be used when curl is built to use GnuTLS, wolfSSL, quictls and -OpenSSL as a TLS provider (but not BoringSSL, AWS-LC, or Rustls). +This option can be used when curl is built to use GnuTLS, OpenSSL, quictls and +wolfSSL as a TLS provider (but not AWS-LC, BoringSSL, or Rustls). If a server supports this TLSv1.3 feature, and to what extent, is announced as part of the TLS "session" sent back to curl. Until curl has seen such diff --git a/docs/cmdline-opts/tlsauthtype.md b/docs/cmdline-opts/tlsauthtype.md index 10eccb29adc4..ce8acd52d271 100644 --- a/docs/cmdline-opts/tlsauthtype.md +++ b/docs/cmdline-opts/tlsauthtype.md @@ -16,6 +16,8 @@ Example: # `--tlsauthtype` +Deprecated option. This has no functionality since 8.22.0. + Set TLS authentication type. Currently, the only supported option is `SRP`, for TLS-SRP (RFC 5054). If --tlsuser and --tlspassword are specified but --tlsauthtype is not, then this option defaults to `SRP`. This option works diff --git a/docs/cmdline-opts/tlspassword.md b/docs/cmdline-opts/tlspassword.md index 6a1bb23a1523..1fd46288187b 100644 --- a/docs/cmdline-opts/tlspassword.md +++ b/docs/cmdline-opts/tlspassword.md @@ -16,6 +16,8 @@ Example: # `--tlspassword` +Deprecated option. This has no functionality since 8.22.0. + Set password to use with the TLS authentication method specified with --tlsauthtype. Requires that --tlsuser is set. diff --git a/docs/cmdline-opts/tlsuser.md b/docs/cmdline-opts/tlsuser.md index 7f4636fa0a2e..568f3f4917bd 100644 --- a/docs/cmdline-opts/tlsuser.md +++ b/docs/cmdline-opts/tlsuser.md @@ -16,6 +16,8 @@ Example: # `--tlsuser` +Deprecated option. This has no functionality since 8.22.0. + Set username for use with the TLS authentication method specified with --tlsauthtype. Requires that --tlspassword also is set. diff --git a/docs/cmdline-opts/unix-socket.md b/docs/cmdline-opts/unix-socket.md index 34cc714f79df..d448ad07721b 100644 --- a/docs/cmdline-opts/unix-socket.md +++ b/docs/cmdline-opts/unix-socket.md @@ -6,7 +6,7 @@ Arg: Help: Connect through this Unix domain socket Added: 7.40.0 Protocols: HTTP -Category: connection +Category: connection http Multi: single See-also: - abstract-unix-socket diff --git a/docs/cmdline-opts/upload-file.md b/docs/cmdline-opts/upload-file.md index 5a2842e58ad8..030fe96771c9 100644 --- a/docs/cmdline-opts/upload-file.md +++ b/docs/cmdline-opts/upload-file.md @@ -5,7 +5,7 @@ Long: upload-file Short: T Arg: Help: Transfer local FILE to destination -Category: important upload +Category: important upload imap Added: 4.0 Multi: per-URL See-also: @@ -26,13 +26,13 @@ Upload the specified local file to the remote URL. If there is no file part in the specified URL, curl appends the local file name to the end of the URL before the operation starts. You must use a -trailing slash (/) on the last directory to prove to curl that there is no +trailing slash (`/`) on the last directory to prove to curl that there is no filename or curl thinks that your last directory name is the remote filename to use. When putting the local filename at the end of the URL, curl ignores what is on -the left side of any slash (/) or backslash (\\) used in the filename and only -appends what is on the right side of the rightmost such character. +the left side of any slash (`/`) or backslash (`\\`) used in the filename and +only appends what is on the right side of the rightmost such character. Use the filename `-` (a single dash) to use stdin instead of a given file. Alternately, the filename `.` (a single period) may be specified instead of @@ -45,9 +45,19 @@ You can specify one --upload-file for each URL on the command line. Each --upload-file + URL pair specifies what to upload and to where. curl also supports globbing of the --upload-file argument, meaning that you can upload multiple files to a single URL by using the same URL globbing style supported -in the URL. +in the URL. Example: -When uploading to an SMTP server: the uploaded data is assumed to be RFC 5322 -formatted. It has to feature the necessary set of headers and mail body -formatted correctly by the user as curl does not transcode nor encode it -further in any way. + curl --upload-file 'file{1,2,3}' ftp://ftp.example/ + +Since curl 8.21.0, you can use parts of the upload filename when it uses +globbing by setting a glob name and referencing that in the same way you +reference named URL globs. For example, if you upload three files to a single +fixed HTTP URL and want to save the corresponding responses in separate files: + + curl -T 'file{1,2,3}' \ + https://upload.example/ -o 'response-#' + +When uploading to an SMTP server (aka "sending email"): the uploaded data is +assumed to be RFC 5322 formatted. It has to feature the necessary set of +headers and mail body formatted correctly by the user as curl does not +transcode nor encode it further in any way. diff --git a/docs/cmdline-opts/upload-flags.md b/docs/cmdline-opts/upload-flags.md index 6c014d0895f3..c4321880224e 100644 --- a/docs/cmdline-opts/upload-flags.md +++ b/docs/cmdline-opts/upload-flags.md @@ -5,7 +5,7 @@ Long: upload-flags Arg: Help: IMAP upload behavior Protocols: IMAP -Category: curl output +Category: curl output imap upload Added: 8.13.0 Multi: single See-also: diff --git a/docs/cmdline-opts/user-agent.md b/docs/cmdline-opts/user-agent.md index a24bd28449ad..d81b65d34fa4 100644 --- a/docs/cmdline-opts/user-agent.md +++ b/docs/cmdline-opts/user-agent.md @@ -19,8 +19,8 @@ Example: # `--user-agent` Specify the User-Agent string to send to the HTTP server. To encode blanks in -the string, surround the string with single quote marks. This header can also -be set with the --header or the --proxy-header options. +the string, surround the string with single or double quote marks. This header +can also be set with the --header or the --proxy-header options. If you give an empty argument to --user-agent (""), it removes the header completely from the request. If you prefer a blank header, you can set it to a diff --git a/docs/cmdline-opts/version.md b/docs/cmdline-opts/version.md index 35e9cf237af6..f17181a029c4 100644 --- a/docs/cmdline-opts/version.md +++ b/docs/cmdline-opts/version.md @@ -121,9 +121,6 @@ This build supports TLS session export/import, like with the --ssl-sessions. ## `SSPI` SSPI is supported. -## `TLS-SRP` -SRP (Secure Remote Password) authentication is supported for TLS. - ## `Unicode` Unicode support on Windows. diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index df0d3d5c84f1..5cf9d91f8704 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -202,7 +202,8 @@ The total amount of bytes that were downloaded. This is the size of the body/data that was transferred, excluding headers. ## `size_header` -The total amount of bytes of the downloaded headers. +The total amount of bytes of the downloaded headers, as represented in +HTTP/1-style header format. ## `size_request` The total amount of bytes that were sent in the HTTP request. diff --git a/docs/cmdline-opts/xattr.md b/docs/cmdline-opts/xattr.md index 380cdbdfd35d..893394c9d540 100644 --- a/docs/cmdline-opts/xattr.md +++ b/docs/cmdline-opts/xattr.md @@ -20,6 +20,11 @@ Store metadata in the extended file attributes. When saving output to a file, tell curl to store file metadata in extended file attributes. Currently, `curl` is stored in the `creator` attribute, -the URL is stored in the `xdg.origin.url` attribute and, for HTTP, the content -type is stored in the `mime_type` attribute. If the file system does not -support extended attributes, a warning is issued. +the URL is stored in the `xdg.origin.url` attribute, for HTTP, the content +type is stored in the `mime_type` attribute, and if set, the referrer URL in +`user.xdg.referrer.url`. If the file system does not support extended +attributes, a warning is issued. + +Since curl 8.22.0 this option is also supported on Windows, where it creates +an Alternate Data Stream named `Zone.Identifier`. It contains an INI formatted +`ZoneTransfer` section, with values: `HostUrl`, `ReferrerUrl` (if set). diff --git a/docs/examples/10-at-a-time.c b/docs/examples/10-at-a-time.c index f04702f59b8b..34572e438b3e 100644 --- a/docs/examples/10-at-a-time.c +++ b/docs/examples/10-at-a-time.c @@ -30,7 +30,7 @@ #include -static const char *urls[] = { +static const char * const urls[] = { "https://01.example/", "https://02.example/", "https://03.example/", @@ -134,14 +134,14 @@ int main(void) const char *url; CURL *curl = msg->easy_handle; curl_easy_getinfo(curl, CURLINFO_PRIVATE, &url); - fprintf(stderr, "R: %d - %s <%s>\n", - msg->data.result, curl_easy_strerror(msg->data.result), url); + fprintf(stderr, "R: %d - %s <%s>\n", (int)msg->data.result, + curl_easy_strerror(msg->data.result), url); curl_multi_remove_handle(multi, curl); curl_easy_cleanup(curl); left--; } else { - fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg); + fprintf(stderr, "E: CURLMsg (%d)\n", (int)msg->msg); } if(transfers < NUM_URLS) add_transfer(multi, transfers++, &left); diff --git a/docs/examples/adddocsref.pl b/docs/examples/adddocsref.pl index cbc48c040716..2f99c8544908 100755 --- a/docs/examples/adddocsref.pl +++ b/docs/examples/adddocsref.pl @@ -30,11 +30,11 @@ use File::Copy; -my $docroot="https://curl.se/libcurl/c"; +my $docroot = "https://curl.se/libcurl/c"; for my $f (@ARGV) { - open(NEW, ">$f.new"); - open(F, "<$f"); + open(NEW, ">", "$f.new"); + open(F, "<", $f); while() { my $l = $_; if($l =~ /\/* $docroot/) { diff --git a/docs/examples/anyauthput.c b/docs/examples/anyauthput.c index 9b08bbe41ba2..a28ee2c69437 100644 --- a/docs/examples/anyauthput.c +++ b/docs/examples/anyauthput.c @@ -88,7 +88,7 @@ static size_t read_cb(char *ptr, size_t size, size_t nmemb, void *stream) return nread; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURL *curl; CURLcode result; @@ -109,7 +109,7 @@ int main(int argc, const char **argv) if(!fp) return 2; - if(fstat(fileno(fp), &file_info) != 0) { + if(fstat(fileno(fp), &file_info)) { fclose(fp); return 1; /* cannot continue */ } diff --git a/docs/examples/block_ip.c b/docs/examples/block_ip.c index 675490701bfd..69ffb251953d 100644 --- a/docs/examples/block_ip.c +++ b/docs/examples/block_ip.c @@ -150,6 +150,7 @@ static struct ip *ip_list_append(struct ip *list, const char *data) ip->maskbits = 128; #endif + /* !checksrc! disable BANNEDFUNC 1 */ if(inet_pton(ip->family, ip->str, &ip->netaddr) != 1) { free(ip->str); free(ip); @@ -264,6 +265,7 @@ static curl_socket_t opensocket(void *clientp, curlsocktype purpose, if(ip && filter->type == CONNECTION_FILTER_BLACKLIST) { if(filter->verbose) { char buf[128] = { 0 }; + /* !checksrc! disable BANNEDFUNC 1 */ inet_ntop(address->family, cinaddr, buf, sizeof(buf)); fprintf(stderr, "* Rejecting IP %s due to blacklist entry %s.\n", buf, ip->str); @@ -273,6 +275,7 @@ static curl_socket_t opensocket(void *clientp, curlsocktype purpose, else if(!ip && filter->type == CONNECTION_FILTER_WHITELIST) { if(filter->verbose) { char buf[128] = { 0 }; + /* !checksrc! disable BANNEDFUNC 1 */ inet_ntop(address->family, cinaddr, buf, sizeof(buf)); fprintf(stderr, "* Rejecting IP %s due to missing whitelist entry.\n", buf); diff --git a/docs/examples/cacertinmem.c b/docs/examples/cacertinmem.c index 8ede167c2819..2e43a783f5dc 100644 --- a/docs/examples/cacertinmem.c +++ b/docs/examples/cacertinmem.c @@ -155,7 +155,7 @@ int main(void) curl_easy_setopt(curl, CURLOPT_CAINFO, NULL); curl_easy_setopt(curl, CURLOPT_CAPATH, NULL); - /* first try: retrieve page without ca certificates -> should fail + /* first try: retrieve page without CA certificates -> should fail * unless libcurl was built --with-ca-fallback enabled at build-time */ result = curl_easy_perform(curl); @@ -166,10 +166,10 @@ int main(void) /* use a fresh connection (optional) this option seriously impacts * performance of multiple transfers but it is necessary order to - * demonstrate this example. recall that the ssl ctx callback is only + * demonstrate this example. recall that the SSL ctx callback is only * called _before_ an SSL connection is established, therefore it does not * affect existing verified SSL connections already in the connection - * cache associated with this handle. normally you would set the ssl ctx + * cache associated with this handle. normally you would set the SSL ctx * function before making any transfers, and not use this option. */ curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1L); diff --git a/docs/examples/chkspeed.c b/docs/examples/chkspeed.c index 4274eb0c9051..7010acc4aeca 100644 --- a/docs/examples/chkspeed.c +++ b/docs/examples/chkspeed.c @@ -105,7 +105,7 @@ int main(int argc, const char *argv[]) case 'm': case 'M': if(argv[0][2] == '=') { - int m = atoi((*argv) + 3); + int m = atoi(*argv + 3); switch(m) { case 1: url = URL_1M; diff --git a/docs/examples/cookie_interface.c b/docs/examples/cookie_interface.c index aa3ea1d013ff..698044deaf5b 100644 --- a/docs/examples/cookie_interface.c +++ b/docs/examples/cookie_interface.c @@ -117,8 +117,7 @@ int main(void) /* HTTP-header style cookie. If you use the Set-Cookie format and do not specify a domain then the cookie is sent for any domain and is not modified, likely not what you intended. For more information refer to - the CURLOPT_COOKIELIST documentation. - */ + the CURLOPT_COOKIELIST documentation. */ snprintf(nline, sizeof(nline), "Set-Cookie: OLD_PREF=3d141414bf4209321; " "expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.example.com"); diff --git a/docs/examples/crawler.c b/docs/examples/crawler.c index 04d816320435..595375614eb2 100644 --- a/docs/examples/crawler.c +++ b/docs/examples/crawler.c @@ -47,7 +47,7 @@ static int max_total = 20000; static int max_requests = 500; static size_t max_link_per_page = 5; static int follow_relative_links = 0; -static const char *start_page = "https://www.reuters.com/"; +static const char start_page[] = "https://www.reuters.com/"; static int pending_interrupt = 0; static void sighandler(int dummy) @@ -173,7 +173,7 @@ static size_t follow_links(CURLM *multi, struct memory *mem, const char *url) static int is_html(const char *ctype) { - return ctype != NULL && strlen(ctype) > 10 && strstr(ctype, "text/html"); + return ctype && strlen(ctype) > 10 && strstr(ctype, "text/html"); } int main(void) diff --git a/docs/examples/ephiperfifo.c b/docs/examples/ephiperfifo.c index 62c3b2c674c8..de86690ef42f 100644 --- a/docs/examples/ephiperfifo.c +++ b/docs/examples/ephiperfifo.c @@ -42,7 +42,7 @@ * curl_multi "hiper" API. * * Thus, you can try a single URL: - * % echo http://www.yahoo.com > hiper.fifo + * % echo http://www.example.com > hiper.fifo * * Or a whole bunch of them: * % cat my-url-list > hiper.fifo @@ -303,7 +303,9 @@ static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) { struct GlobalInfo *g = (struct GlobalInfo *)cbp; struct SockInfo *fdp = (struct SockInfo *)sockp; - const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE" }; + static const char * const whatstr[] = { + "none", "IN", "OUT", "INOUT", "REMOVE" + }; fprintf(MSG_OUT, "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]); if(what == CURL_POLL_REMOVE) { @@ -407,12 +409,12 @@ static void fifo_cb(struct GlobalInfo *g, int revents) static int init_fifo(struct GlobalInfo *g) { struct stat st; - static const char *fifo = "hiper.fifo"; + static const char fifo[] = "hiper.fifo"; curl_socket_t sockfd; struct epoll_event epev; fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo); - if(lstat(fifo, &st) == 0) { + if(!lstat(fifo, &st)) { if((st.st_mode & S_IFMT) == S_IFREG) { errno = EEXIST; perror("lstat"); diff --git a/docs/examples/evhiperfifo.c b/docs/examples/evhiperfifo.c index e4fdf7d08929..32f92f9415c5 100644 --- a/docs/examples/evhiperfifo.c +++ b/docs/examples/evhiperfifo.c @@ -45,7 +45,7 @@ * curl_multi "hiper" API. * * Thus, you can try a single URL: - * % echo http://www.yahoo.com > hiper.fifo + * % echo http://www.example.com > hiper.fifo * * Or a whole bunch of them: * % cat my-url-list > hiper.fifo @@ -173,7 +173,7 @@ static void timer_cb(EV_P_ struct ev_timer *w, int revents) CURLMcode mresult; struct GlobalInfo *g; - printf("%s w %p revents %i\n", __PRETTY_FUNCTION__, (void *)w, revents); + printf("%s w %p revents %d\n", __PRETTY_FUNCTION__, (void *)w, revents); g = (struct GlobalInfo *)w->data; @@ -207,7 +207,7 @@ static void event_cb(EV_P_ struct ev_io *w, int revents) int action = ((revents & EV_READ) ? CURL_POLL_IN : 0) | ((revents & EV_WRITE) ? CURL_POLL_OUT : 0); - printf("%s w %p revents %i\n", __PRETTY_FUNCTION__, (void *)w, revents); + printf("%s w %p revents %d\n", __PRETTY_FUNCTION__, (void *)w, revents); g = (struct GlobalInfo *)w->data; mresult = curl_multi_socket_action(g->multi, w->fd, action, @@ -267,9 +267,11 @@ static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) { struct GlobalInfo *g = (struct GlobalInfo *)cbp; struct SockInfo *fdp = (struct SockInfo *)sockp; - const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE" }; + static const char * const whatstr[] = { + "none", "IN", "OUT", "INOUT", "REMOVE" + }; - printf("%s e %p s %i what %i cbp %p sockp %p\n", + printf("%s e %p s %d what %d cbp %p sockp %p\n", __PRETTY_FUNCTION__, e, s, what, cbp, sockp); fprintf(MSG_OUT, "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]); @@ -378,11 +380,11 @@ static void fifo_cb(EV_P_ struct ev_io *w, int revents) static int init_fifo(struct GlobalInfo *g) { struct stat st; - static const char *fifo = "hiper.fifo"; + static const char fifo[] = "hiper.fifo"; curl_socket_t sockfd; fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo); - if(lstat(fifo, &st) == 0) { + if(!lstat(fifo, &st)) { if((st.st_mode & S_IFMT) == S_IFREG) { errno = EEXIST; perror("lstat"); diff --git a/docs/examples/externalsocket.c b/docs/examples/externalsocket.c index 1d74ade723b7..b07f48b44724 100644 --- a/docs/examples/externalsocket.c +++ b/docs/examples/externalsocket.c @@ -164,7 +164,7 @@ int main(void) close(sockfd); if(result != CURLE_OK) { - printf("libcurl error: %d\n", result); + printf("libcurl error: %d\n", (int)result); return 4; } } diff --git a/docs/examples/fileupload.c b/docs/examples/fileupload.c index f444da9b9e58..0c2b3a57af5a 100644 --- a/docs/examples/fileupload.c +++ b/docs/examples/fileupload.c @@ -64,7 +64,7 @@ int main(void) } /* to get the file size */ - if(fstat(fileno(fd), &file_info) != 0) { + if(fstat(fileno(fd), &file_info)) { fclose(fd); curl_global_cleanup(); return 1; /* cannot continue */ diff --git a/docs/examples/ftp-delete.c b/docs/examples/ftp-delete.c index 5e49e5f8f7b6..c05f2f7a5457 100644 --- a/docs/examples/ftp-delete.c +++ b/docs/examples/ftp-delete.c @@ -74,7 +74,7 @@ int main(void) if(result != CURLE_OK) { /* we failed */ - fprintf(stderr, "curl told us %d\n", result); + fprintf(stderr, "curl told us %d\n", (int)result); } } diff --git a/docs/examples/ftp-wildcard.c b/docs/examples/ftp-wildcard.c index abde048e9d8d..d7627aab9887 100644 --- a/docs/examples/ftp-wildcard.c +++ b/docs/examples/ftp-wildcard.c @@ -97,7 +97,7 @@ static size_t write_cb(char *buff, size_t size, size_t nmemb, void *cb_data) return written; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { /* curl easy handle */ CURL *curl; diff --git a/docs/examples/ftpget.c b/docs/examples/ftpget.c index 973049e78ba3..eec6c86348fe 100644 --- a/docs/examples/ftpget.c +++ b/docs/examples/ftpget.c @@ -87,7 +87,7 @@ int main(void) if(result != CURLE_OK) { /* we failed */ - fprintf(stderr, "curl told us %d\n", result); + fprintf(stderr, "curl told us %d\n", (int)result); } } diff --git a/docs/examples/ftpgetinfo.c b/docs/examples/ftpgetinfo.c index b943f48d0285..d35cc47b5667 100644 --- a/docs/examples/ftpgetinfo.c +++ b/docs/examples/ftpgetinfo.c @@ -90,7 +90,7 @@ int main(void) } else { /* we failed */ - fprintf(stderr, "curl told us %d\n", result); + fprintf(stderr, "curl told us %d\n", (int)result); } /* always cleanup */ diff --git a/docs/examples/ftpsget.c b/docs/examples/ftpsget.c index abe1d40fda17..44d8665325d0 100644 --- a/docs/examples/ftpsget.c +++ b/docs/examples/ftpsget.c @@ -69,7 +69,7 @@ int main(void) if(curl) { /* * You better replace the URL with one that works! Note that we use an - * FTP:// URL with standard explicit FTPS. You can also do FTPS:// URLs if + * ftp:// URL with standard explicit FTPS. You can also do ftps:// URLs if * you want to do the rarer kind of transfers: implicit. */ curl_easy_setopt(curl, CURLOPT_URL, @@ -92,7 +92,7 @@ int main(void) if(result != CURLE_OK) { /* we failed */ - fprintf(stderr, "curl told us %d\n", result); + fprintf(stderr, "curl told us %d\n", (int)result); } } diff --git a/docs/examples/ftpupload.c b/docs/examples/ftpupload.c index 415bafe68ee2..07546f313e90 100644 --- a/docs/examples/ftpupload.c +++ b/docs/examples/ftpupload.c @@ -96,7 +96,7 @@ int main(void) } /* to get the file size */ - if(fstat(fileno(hd_src), &file_info) != 0) { + if(fstat(fileno(hd_src), &file_info)) { fclose(hd_src); return 1; /* cannot continue */ } diff --git a/docs/examples/ftpuploadfrommem.c b/docs/examples/ftpuploadfrommem.c index db06edcd9f17..59834cab1e1b 100644 --- a/docs/examples/ftpuploadfrommem.c +++ b/docs/examples/ftpuploadfrommem.c @@ -78,7 +78,7 @@ int main(void) upload.sizeleft = strlen(data); /* In Windows, this inits the Winsock stuff */ - result = curl_global_init(CURL_GLOBAL_DEFAULT); + result = curl_global_init(CURL_GLOBAL_ALL); /* Check for errors */ if(result != CURLE_OK) { fprintf(stderr, "curl_global_init() failed: %s\n", diff --git a/docs/examples/getinmemory.c b/docs/examples/getinmemory.c index 036953b50c6b..85b8c9ceee55 100644 --- a/docs/examples/getinmemory.c +++ b/docs/examples/getinmemory.c @@ -50,7 +50,7 @@ static size_t write_cb(char *contents, size_t size, size_t nmemb, void *userp) } mem->memory = ptr; - memcpy(&(mem->memory[mem->size]), contents, realsize); + memcpy(&mem->memory[mem->size], contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; diff --git a/docs/examples/ghiper.c b/docs/examples/ghiper.c index 31db2bc9f9d1..79516ccd82c5 100644 --- a/docs/examples/ghiper.c +++ b/docs/examples/ghiper.c @@ -42,7 +42,7 @@ * curl_multi "hiper" API. * * Thus, you can try a single URL: - * % echo http://www.yahoo.com > hiper.fifo + * % echo http://www.example.com > hiper.fifo * * Or a whole bunch of them: * % cat my-url-list > hiper.fifo @@ -258,7 +258,9 @@ static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) { struct GlobalInfo *g = (struct GlobalInfo *)cbp; struct SockInfo *fdp = (struct SockInfo *)sockp; - static const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE" }; + static const char * const whatstr[] = { + "none", "IN", "OUT", "INOUT", "REMOVE" + }; MSG_OUT("socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]); if(what == CURL_POLL_REMOVE) { @@ -395,11 +397,11 @@ static gboolean fifo_cb(GIOChannel *ch, GIOCondition condition, gpointer data) int init_fifo(void) { + static const char fifo[] = "hiper.fifo"; struct stat st; - const char *fifo = "hiper.fifo"; int socket; - if(lstat(fifo, &st) == 0) { + if(!lstat(fifo, &st)) { if((st.st_mode & S_IFMT) == S_IFREG) { errno = EEXIST; perror("lstat"); diff --git a/docs/examples/hiperfifo.c b/docs/examples/hiperfifo.c index 2b0ae0fdb964..cc27799012cd 100644 --- a/docs/examples/hiperfifo.c +++ b/docs/examples/hiperfifo.c @@ -42,7 +42,7 @@ * curl_multi "hiper" API. * * Thus, you can try a single URL: - * % echo http://www.yahoo.com > hiper.fifo + * % echo http://www.example.com > hiper.fifo * * Or a whole bunch of them: * % cat my-url-list > hiper.fifo @@ -270,7 +270,9 @@ static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) { struct GlobalInfo *g = (struct GlobalInfo *)cbp; struct SockInfo *fdp = (struct SockInfo *)sockp; - const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE" }; + static const char * const whatstr[] = { + "none", "IN", "OUT", "INOUT", "REMOVE" + }; fprintf(MSG_OUT, "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]); if(what == CURL_POLL_REMOVE) { @@ -376,14 +378,14 @@ static void fifo_cb(int fd, short event, void *arg) } /* Create a named pipe and tell libevent to monitor it */ -static const char *fifo = "hiper.fifo"; +static const char fifo[] = "hiper.fifo"; static int init_fifo(struct GlobalInfo *g) { struct stat st; curl_socket_t sockfd; fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo); - if(lstat(fifo, &st) == 0) { + if(!lstat(fifo, &st)) { if((st.st_mode & S_IFMT) == S_IFREG) { errno = EEXIST; perror("lstat"); diff --git a/docs/examples/htmltidy.c b/docs/examples/htmltidy.c index 2e2def59c3c4..8a510f49f4cc 100644 --- a/docs/examples/htmltidy.c +++ b/docs/examples/htmltidy.c @@ -74,7 +74,7 @@ static void dumpNode(TidyDoc doc, TidyNode tnod, int indent) } } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURL *curl; char curl_errbuf[CURL_ERROR_SIZE]; diff --git a/docs/examples/htmltitle.cpp b/docs/examples/htmltitle.cpp index 7986b94640a9..c1130ce522e6 100644 --- a/docs/examples/htmltitle.cpp +++ b/docs/examples/htmltitle.cpp @@ -24,14 +24,14 @@ /* * Get a webpage, extract the title with libxml. * - - Written by Lars Nilsson - - GNU C++ compile command line suggestion (edit paths accordingly): - - g++ -Wall -I/opt/curl/include -I/opt/libxml/include/libxml2 htmltitle.cpp \ - -o htmltitle -L/opt/curl/lib -L/opt/libxml/lib -lcurl -lxml2 -*/ + * + * Written by Lars Nilsson + * + * GNU C++ compile command line suggestion (edit paths accordingly): + * + * g++ -Wall -I/opt/curl/include -I/opt/libxml/include/libxml2 htmltitle.cpp \ + * -o htmltitle -L/opt/curl/lib -L/opt/libxml/lib -lcurl -lxml2 + */ #include #include #include @@ -73,7 +73,7 @@ static std::string buffer; static size_t writer(char *data, size_t size, size_t nmemb, std::string *writerData) { - if(writerData == NULL) + if(!writerData) return 0; writerData->append(data, size * nmemb); diff --git a/docs/examples/http2-download.c b/docs/examples/http2-download.c index 80c3365bb255..d46c4caead76 100644 --- a/docs/examples/http2-download.c +++ b/docs/examples/http2-download.c @@ -176,7 +176,7 @@ static int setup(struct transfer *t, int num) /* HTTP/2 please */ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); -#if (CURLPIPE_MULTIPLEX > 0) +#if CURLPIPE_MULTIPLEX > 0 /* wait for pipe connection to confirm */ curl_easy_setopt(curl, CURLOPT_PIPEWAIT, 1L); #endif @@ -187,7 +187,7 @@ static int setup(struct transfer *t, int num) /* * Download many transfers over HTTP/2, using the same connection! */ -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURLcode result; struct transfer *trans; diff --git a/docs/examples/http2-pushinmemory.c b/docs/examples/http2-pushinmemory.c index 09a8a8c67130..ba3db5b4b9e9 100644 --- a/docs/examples/http2-pushinmemory.c +++ b/docs/examples/http2-pushinmemory.c @@ -48,7 +48,7 @@ static size_t write_cb(char *contents, size_t size, size_t nmemb, void *userp) } mem->memory = ptr; - memcpy(&(mem->memory[mem->size]), contents, realsize); + memcpy(&mem->memory[mem->size], contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; diff --git a/docs/examples/http2-serverpush.c b/docs/examples/http2-serverpush.c index e54675ceac16..0d97c1e64a85 100644 --- a/docs/examples/http2-serverpush.c +++ b/docs/examples/http2-serverpush.c @@ -38,7 +38,7 @@ #include #ifndef CURLPIPE_MULTIPLEX -#error "too old libcurl, cannot do HTTP/2 server push!" +#error "too old libcurl, cannot do HTTP/2 server push" #endif #if defined(_MSC_VER) && (_MSC_VER < 1900) diff --git a/docs/examples/http2-upload.c b/docs/examples/http2-upload.c index aeac13ca2b22..9acd7198f1c4 100644 --- a/docs/examples/http2-upload.c +++ b/docs/examples/http2-upload.c @@ -67,20 +67,18 @@ #endif #ifdef _MSC_VER -#define gettimeofday(a, b) my_gettimeofday(a, b) -static int my_gettimeofday(struct timeval *tp, void *tzp) +static int gettimeofday(struct timeval *tp, void *tzp) { (void)tzp; if(tp) { -/* Offset between 1601-01-01 and 1970-01-01 in 100 nanosec units */ -#define WIN32_FT_OFFSET 116444736000000000 union { CURL_TYPEOF_CURL_OFF_T ns100; /* time since 1 Jan 1601 in 100ns units */ FILETIME ft; - } _now; - GetSystemTimeAsFileTime(&_now.ft); - tp->tv_usec = (long)((_now.ns100 / 10) % 1000000); - tp->tv_sec = (long)((_now.ns100 - WIN32_FT_OFFSET) / 10000000); + } now; + GetSystemTimeAsFileTime(&now.ft); + tp->tv_usec = (long)((now.ns100 / 10) % 1000000); + /* subtract offset between 1601-01-01 and 1970-01-01 in 100ns units */ + tp->tv_sec = (long)((now.ns100 - 116444736000000000) / 10000000); } return 0; } @@ -234,7 +232,7 @@ static int setup(struct input *t, int num, const char *upload) return 1; } - if(fstat(fileno(t->in), &file_info) != 0) { + if(fstat(fileno(t->in), &file_info)) { fprintf(stderr, "error: could not stat file %s: %s\n", upload, strerror(errno)); fclose(t->out); @@ -275,7 +273,7 @@ static int setup(struct input *t, int num, const char *upload) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); -#if (CURLPIPE_MULTIPLEX > 0) +#if CURLPIPE_MULTIPLEX > 0 /* wait for pipe connection to confirm */ curl_easy_setopt(curl, CURLOPT_PIPEWAIT, 1L); #endif @@ -286,7 +284,7 @@ static int setup(struct input *t, int num, const char *upload) /* * Upload all files over HTTP/2, using the same physical connection! */ -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURLcode result; struct input *trans; diff --git a/docs/examples/httpput-postfields.c b/docs/examples/httpput-postfields.c index caa0acfae51f..d34f387fdb78 100644 --- a/docs/examples/httpput-postfields.c +++ b/docs/examples/httpput-postfields.c @@ -40,14 +40,14 @@ static const char olivertwist[] = "all events; the item of mortality whose name is prefixed"; /* ... to the head of this chapter. String cut off to stick within the C90 - 509 byte limit. */ + 509-byte limit. */ /* * This example shows an HTTP PUT operation that sends a fixed buffer with * CURLOPT_POSTFIELDS to the URL given as an argument. */ -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURL *curl; CURLcode result; diff --git a/docs/examples/httpput.c b/docs/examples/httpput.c index 977d31ccaf29..90cb81b7de22 100644 --- a/docs/examples/httpput.c +++ b/docs/examples/httpput.c @@ -73,7 +73,7 @@ static size_t read_cb(char *ptr, size_t size, size_t nmemb, void *stream) return retcode; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURL *curl; CURLcode result; @@ -96,7 +96,7 @@ int main(int argc, const char **argv) return 2; /* get the file size of the local file */ - if(fstat(fileno(hd_src), &file_info) != 0) { + if(fstat(fileno(hd_src), &file_info)) { fclose(hd_src); return 1; /* cannot continue */ } diff --git a/docs/examples/imap-append.c b/docs/examples/imap-append.c index 77cf2bc02a39..c530bbb9c814 100644 --- a/docs/examples/imap-append.c +++ b/docs/examples/imap-append.c @@ -40,7 +40,7 @@ #define TO "" #define CC "" -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO "\r\n" "From: " FROM "(Example User)\r\n" @@ -111,7 +111,7 @@ int main(void) curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); - filesize = strlen(payload_text); + filesize = sizeof(payload_text) - 1; if(filesize <= LONG_MAX) infilesize = (long)filesize; curl_easy_setopt(curl, CURLOPT_INFILESIZE, infilesize); diff --git a/docs/examples/log_failed_transfers.c b/docs/examples/log_failed_transfers.c index 3279703176db..19e2a232e84a 100644 --- a/docs/examples/log_failed_transfers.c +++ b/docs/examples/log_failed_transfers.c @@ -289,7 +289,7 @@ int main(void) failed = 0; } else { - mem_addf(&t->log, "Transfer failed: (%d) %s\n", result, + mem_addf(&t->log, "Transfer failed: (%d) %s\n", (int)result, (errbuf[0] ? errbuf : curl_easy_strerror(result))); fprintf(stderr, "%s", t->log.recent); failed = 1; @@ -305,7 +305,7 @@ int main(void) if(failed) { FILE *fp = fopen(t->logfile, "wb"); - if(fp && t->log.len == fwrite(t->log.buf, 1, t->log.len, fp)) + if(fp && fwrite(t->log.buf, 1, t->log.len, fp) == t->log.len) fprintf(stderr, "Transfer log written to %s\n", t->logfile); else { fprintf(stderr, "Failed to write transfer log to %s: %s\n", diff --git a/docs/examples/maxconnects.c b/docs/examples/maxconnects.c index 7ef29828eab9..b52f1fcc3f54 100644 --- a/docs/examples/maxconnects.c +++ b/docs/examples/maxconnects.c @@ -39,7 +39,7 @@ int main(void) curl = curl_easy_init(); if(curl) { - const char *urls[] = { + static const char * const urls[] = { "https://example.com/", "https://curl.se/", "https://www.example/", diff --git a/docs/examples/multi-app.c b/docs/examples/multi-app.c index 4a1f3b1f979a..56509736e878 100644 --- a/docs/examples/multi-app.c +++ b/docs/examples/multi-app.c @@ -99,10 +99,12 @@ int main(void) switch(idx) { case HTTP_HANDLE: - printf("HTTP transfer completed with status %d\n", msg->data.result); + printf("HTTP transfer completed with status %d\n", + (int)msg->data.result); break; case FTP_HANDLE: - printf("FTP transfer completed with status %d\n", msg->data.result); + printf("FTP transfer completed with status %d\n", + (int)msg->data.result); break; } } diff --git a/docs/examples/multi-event.c b/docs/examples/multi-event.c index 4c52cbe3b3ad..d10c93d3e066 100644 --- a/docs/examples/multi-event.c +++ b/docs/examples/multi-event.c @@ -99,9 +99,7 @@ static void curl_perform(int fd, short event, void *arg) static struct curl_context *create_curl_context(curl_socket_t sockfd) { - struct curl_context *context; - - context = (struct curl_context *)malloc(sizeof(*context)); + struct curl_context *context = malloc(sizeof(*context)); context->sockfd = sockfd; @@ -207,13 +205,13 @@ static int handle_socket(CURL *curl, curl_socket_t s, int action, void *userp, } break; default: - abort(); + return -1; /* unknown */ } return 0; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURLcode result; diff --git a/docs/examples/multi-legacy.c b/docs/examples/multi-legacy.c index a0580c6712e2..bd23866d6f8c 100644 --- a/docs/examples/multi-legacy.c +++ b/docs/examples/multi-legacy.c @@ -177,10 +177,12 @@ int main(void) switch(idx) { case HTTP_HANDLE: - printf("HTTP transfer completed with status %d\n", msg->data.result); + printf("HTTP transfer completed with status %d\n", + (int)msg->data.result); break; case FTP_HANDLE: - printf("FTP transfer completed with status %d\n", msg->data.result); + printf("FTP transfer completed with status %d\n", + (int)msg->data.result); break; } } diff --git a/docs/examples/multi-uv.c b/docs/examples/multi-uv.c index 8d6227fc7e78..49155864936b 100644 --- a/docs/examples/multi-uv.c +++ b/docs/examples/multi-uv.c @@ -58,9 +58,7 @@ struct curl_context { static struct curl_context *create_curl_context(curl_socket_t sockfd, struct datauv *uv) { - struct curl_context *context; - - context = (struct curl_context *)malloc(sizeof(*context)); + struct curl_context *context = malloc(sizeof(*context)); context->sockfd = sockfd; context->uv = uv; @@ -221,13 +219,13 @@ static int cb_socket(CURL *curl, curl_socket_t s, int action, } break; default: - abort(); + return -1; /* unknown */ } return 0; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { CURLcode result; struct datauv uv = { 0 }; diff --git a/docs/examples/post-callback.c b/docs/examples/post-callback.c index 729642cbce6a..aaed0aaa063c 100644 --- a/docs/examples/post-callback.c +++ b/docs/examples/post-callback.c @@ -76,7 +76,7 @@ int main(void) wt.sizeleft = strlen(data); /* In Windows, this inits the Winsock stuff */ - result = curl_global_init(CURL_GLOBAL_DEFAULT); + result = curl_global_init(CURL_GLOBAL_ALL); /* Check for errors */ if(result != CURLE_OK) { fprintf(stderr, "curl_global_init() failed: %s\n", @@ -102,13 +102,11 @@ int main(void) /* get verbose debug output please */ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - /* - If you use POST to an HTTP 1.1 server, you can send data without knowing - the size before starting the POST if you use chunked encoding. You - enable this by adding a header like "Transfer-Encoding: chunked" with - CURLOPT_HTTPHEADER. With HTTP 1.0 or without chunked transfer, you must - specify the size in the request. - */ + /* If you use POST to an HTTP 1.1 server, you can send data without knowing + the size before starting the POST if you use chunked encoding. You + enable this by adding a header like "Transfer-Encoding: chunked" with + CURLOPT_HTTPHEADER. With HTTP 1.0 or without chunked transfer, you must + specify the size in the request. */ #ifdef USE_CHUNKED { struct curl_slist *chunk = NULL; diff --git a/docs/examples/postinmemory.c b/docs/examples/postinmemory.c index c4d9abcc1148..698e90163f1a 100644 --- a/docs/examples/postinmemory.c +++ b/docs/examples/postinmemory.c @@ -49,7 +49,7 @@ static size_t write_cb(char *contents, size_t size, size_t nmemb, void *userp) } mem->memory = ptr; - memcpy(&(mem->memory[mem->size]), contents, realsize); + memcpy(&mem->memory[mem->size], contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; @@ -61,7 +61,7 @@ int main(void) CURL *curl; CURLcode result; struct MemoryStruct chunk; - static const char *postthis = "Field=1&Field=2&Field=3"; + static const char postthis[] = "Field=1&Field=2&Field=3"; result = curl_global_init(CURL_GLOBAL_ALL); if(result != CURLE_OK) @@ -87,7 +87,7 @@ int main(void) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis); /* if we do not provide POSTFIELDSIZE, libcurl calls strlen() by itself */ - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(postthis) - 1); /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); diff --git a/docs/examples/sendrecv.c b/docs/examples/sendrecv.c index a8d06970288d..a3539adb3ccc 100644 --- a/docs/examples/sendrecv.c +++ b/docs/examples/sendrecv.c @@ -77,8 +77,8 @@ int main(void) { CURL *curl; /* Minimalistic http request */ - const char *request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"; - size_t request_len = strlen(request); + static const char request[] = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"; + static const size_t request_len = sizeof(request) - 1; CURLcode result = curl_global_init(CURL_GLOBAL_ALL); if(result != CURLE_OK) @@ -89,8 +89,7 @@ int main(void) supports "natively", you are doing it wrong and you should stop. This example uses HTTP only to show how to use this API, it does not - suggest that writing an application doing this is sensible. - */ + suggest that writing an application doing this is sensible. */ curl = curl_easy_init(); if(curl) { diff --git a/docs/examples/sepheaders.c b/docs/examples/sepheaders.c index c9fee6c2ba1d..011eae1aea94 100644 --- a/docs/examples/sepheaders.c +++ b/docs/examples/sepheaders.c @@ -53,9 +53,9 @@ int main(void) /* init the curl session */ curl = curl_easy_init(); if(curl) { - static const char *headerfilename = "head.out"; + static const char headerfilename[] = "head.out"; FILE *headerfile; - static const char *bodyfilename = "body.out"; + static const char bodyfilename[] = "body.out"; FILE *bodyfile; /* set URL to get */ diff --git a/docs/examples/sftpget.c b/docs/examples/sftpget.c index 53bc81ea6dad..a3da2d672191 100644 --- a/docs/examples/sftpget.c +++ b/docs/examples/sftpget.c @@ -103,7 +103,7 @@ int main(void) if(result != CURLE_OK) { /* we failed */ - fprintf(stderr, "curl told us %d\n", result); + fprintf(stderr, "curl told us %d\n", (int)result); } } diff --git a/docs/examples/sftpuploadresume.c b/docs/examples/sftpuploadresume.c index 153883640bea..e5d14a845945 100644 --- a/docs/examples/sftpuploadresume.c +++ b/docs/examples/sftpuploadresume.c @@ -134,8 +134,8 @@ int main(void) curl = curl_easy_init(); if(curl) { - const char *remote = "sftp://user:pass@example.com/path/filename"; - const char *filename = "filename"; + static const char remote[] = "sftp://user:pass@example.com/path/filename"; + static const char filename[] = "filename"; if(!sftpResumeUpload(curl, remote, filename)) { printf("resumed upload using curl %s failed\n", curl_version()); diff --git a/docs/examples/simplepost.c b/docs/examples/simplepost.c index d2540151ce63..fb85677c3128 100644 --- a/docs/examples/simplepost.c +++ b/docs/examples/simplepost.c @@ -32,7 +32,7 @@ int main(void) { - static const char *postthis = "moo mooo moo moo"; + static const char postthis[] = "moo mooo moo moo"; CURL *curl; @@ -46,7 +46,7 @@ int main(void) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis); /* if we do not provide POSTFIELDSIZE, libcurl calls strlen() by itself */ - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(postthis) - 1); /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); diff --git a/docs/examples/simplessl.c b/docs/examples/simplessl.c index f1c07a4ae239..45db65a05d01 100644 --- a/docs/examples/simplessl.c +++ b/docs/examples/simplessl.c @@ -22,7 +22,7 @@ * ***************************************************************************/ /* - * Shows HTTPS usage with client certs and optional ssl engine use. + * Shows HTTPS usage with client certs and optional SSL engine use. * */ #ifdef _MSC_VER @@ -46,7 +46,7 @@ 4. if you do not use a crypto engine: 4.1. set pKeyName to the filename of your client key 4.2. if the format of the key file is DER, set pKeyType to "DER" -*/ + */ int main(void) { @@ -55,9 +55,9 @@ int main(void) FILE *headerfile; const char *pPassphrase = NULL; - static const char *pCertFile = "testcert.pem"; - static const char *pCACertFile = "cacert.pem"; - static const char *pHeaderFile = "dumpit"; + static const char pCertFile[] = "testcert.pem"; + static const char pCACertFile[] = "cacert.pem"; + static const char pHeaderFile[] = "dumpit"; const char *pKeyName; const char *pKeyType; diff --git a/docs/examples/smooth-gtk-thread.c b/docs/examples/smooth-gtk-thread.c index 06eea1ff0e64..cca3be738bc9 100644 --- a/docs/examples/smooth-gtk-thread.c +++ b/docs/examples/smooth-gtk-thread.c @@ -125,7 +125,7 @@ static void *create_thread(void *progress_bar) pthread_t tid[NUMT]; int i; - /* Make sure I do not create more threads than urls. */ + /* Make sure I do not create more threads than URLs. */ for(i = 0; i < NUMT && i < num_urls; i++) { int error = pthread_create(&tid[i], NULL, /* default attributes please */ @@ -165,7 +165,7 @@ static gboolean cb_delete(GtkWidget *window, gpointer data) return FALSE; } -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { GtkWidget *top_window, *outside_frame, *inside_frame, *progress_bar; @@ -210,7 +210,7 @@ int main(int argc, const char **argv) g_signal_connect(G_OBJECT(top_window), "delete-event", G_CALLBACK(cb_delete), NULL); - if(!g_thread_create(&create_thread, progress_bar, FALSE, NULL) != 0) + if(!g_thread_create(&create_thread, progress_bar, FALSE, NULL)) g_warning("cannot create the thread"); gtk_main(); diff --git a/docs/examples/smtp-authzid.c b/docs/examples/smtp-authzid.c index fe91ba5e634a..0260452990c3 100644 --- a/docs/examples/smtp-authzid.c +++ b/docs/examples/smtp-authzid.c @@ -48,7 +48,7 @@ #define SENDER_MAIL "Kurt " SENDER_ADDR #define TO_MAIL "A Receiver " TO_ADDR -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO_MAIL "\r\n" "From: " FROM_MAIL "\r\n" diff --git a/docs/examples/smtp-mail.c b/docs/examples/smtp-mail.c index b1590fd3af75..618123ab5e2d 100644 --- a/docs/examples/smtp-mail.c +++ b/docs/examples/smtp-mail.c @@ -45,7 +45,7 @@ #define TO_MAIL "A Receiver " TO_ADDR #define CC_MAIL "John CC Smith " CC_ADDR -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO_MAIL "\r\n" "From: " FROM_MAIL "\r\n" diff --git a/docs/examples/smtp-mime.c b/docs/examples/smtp-mime.c index 0ddba4ebb736..4d54532559f6 100644 --- a/docs/examples/smtp-mime.c +++ b/docs/examples/smtp-mime.c @@ -41,7 +41,7 @@ #define TO "" #define CC "" -static const char *headers_text[] = { +static const char * const headers_text[] = { "Date: Tue, 22 Aug 2017 14:08:43 +0100", "To: " TO, "From: " FROM " (Example User)", @@ -82,7 +82,7 @@ int main(void) curl_mime *mime; curl_mime *alt; curl_mimepart *part; - const char **cpp; + const char * const *cpp; /* This is the URL for your mailserver */ curl_easy_setopt(curl, CURLOPT_URL, "smtp://mail.example.com"); diff --git a/docs/examples/smtp-multi.c b/docs/examples/smtp-multi.c index 86545a1ef3c6..9d76a57fe717 100644 --- a/docs/examples/smtp-multi.c +++ b/docs/examples/smtp-multi.c @@ -38,7 +38,7 @@ #define TO_MAIL "" #define CC_MAIL "" -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO_MAIL "\r\n" "From: " FROM_MAIL "\r\n" diff --git a/docs/examples/smtp-ssl.c b/docs/examples/smtp-ssl.c index 9cac9ecd5267..172f66b8dac7 100644 --- a/docs/examples/smtp-ssl.c +++ b/docs/examples/smtp-ssl.c @@ -42,7 +42,7 @@ #define TO_MAIL "" #define CC_MAIL "" -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO_MAIL "\r\n" "From: " FROM_MAIL "\r\n" diff --git a/docs/examples/smtp-tls.c b/docs/examples/smtp-tls.c index da452318a6e2..c3c992717be6 100644 --- a/docs/examples/smtp-tls.c +++ b/docs/examples/smtp-tls.c @@ -42,7 +42,7 @@ #define TO_MAIL "" #define CC_MAIL "" -static const char *payload_text = +static const char payload_text[] = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: " TO_MAIL "\r\n" "From: " FROM_MAIL "\r\n" diff --git a/docs/examples/sslbackend.c b/docs/examples/sslbackend.c index e10eaaa21754..bbb1ea409479 100644 --- a/docs/examples/sslbackend.c +++ b/docs/examples/sslbackend.c @@ -22,7 +22,7 @@ * ***************************************************************************/ /* - * Shows HTTPS usage with client certs and optional ssl engine use. + * Shows HTTPS usage with client certs and optional SSL engine use. * */ #include @@ -40,7 +40,7 @@ * **** This example only works with libcurl 7.56.0 and later! **** */ -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { const char *name = argc > 1 ? argv[1] : "openssl"; CURLsslset result; @@ -49,12 +49,11 @@ int main(int argc, const char **argv) const curl_ssl_backend **list; int i; - result = curl_global_sslset(CURLSSLBACKEND_NONE, NULL, &list); - assert(result == CURLSSLSET_UNKNOWN_BACKEND); + (void)curl_global_sslset(CURLSSLBACKEND_NONE, NULL, &list); for(i = 0; list[i]; i++) printf("SSL backend #%d: '%s' (ID: %d)\n", - i, list[i]->name, list[i]->id); + i, list[i]->name, (int)list[i]->id); return 0; } @@ -71,8 +70,6 @@ int main(int argc, const char **argv) return 1; } - assert(result == CURLSSLSET_OK); - printf("Version with SSL backend '%s':\n\n\t%s\n", name, curl_version()); return 0; diff --git a/docs/examples/synctime.c b/docs/examples/synctime.c index ed10f9d3b816..8e9adf1f3f85 100644 --- a/docs/examples/synctime.c +++ b/docs/examples/synctime.c @@ -99,10 +99,10 @@ static int AutoSyncTime; static SYSTEMTIME SYSTime; static SYSTEMTIME LOCALTime; -static const char *DayStr[] = { +static const char * const DayStr[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -static const char *MthStr[] = { +static const char * const MthStr[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; @@ -149,7 +149,7 @@ static size_t SyncTime_CURL_WriteHeader(void *ptr, size_t size, size_t nmemb, int i; SYSTime.wMilliseconds = 500; /* adjust to midpoint, 0.5 sec */ for(i = 0; i < 12; i++) { - if(strcmp(MthStr[i], TmpStr2) == 0) { + if(!strcmp(MthStr[i], TmpStr2)) { SYSTime.wMonth = (WORD)(i + 1); break; } @@ -232,26 +232,26 @@ int main(int argc, const char *argv[]) if(argc > 1) { int OptionIndex = 1; while(OptionIndex < argc) { - if(strncmp(argv[OptionIndex], "--server=", 9) == 0) + if(!strncmp(argv[OptionIndex], "--server=", 9)) snprintf(conf.timeserver, sizeof(conf.timeserver) - 1, "%s", &argv[OptionIndex][9]); - if(strcmp(argv[OptionIndex], "--showall") == 0) + if(!strcmp(argv[OptionIndex], "--showall")) ShowAllHeader = 1; - if(strcmp(argv[OptionIndex], "--synctime") == 0) + if(!strcmp(argv[OptionIndex], "--synctime")) AutoSyncTime = 1; - if(strncmp(argv[OptionIndex], "--proxy-user=", 13) == 0) + if(!strncmp(argv[OptionIndex], "--proxy-user=", 13)) snprintf(conf.proxy_user, sizeof(conf.proxy_user) - 1, "%s", &argv[OptionIndex][13]); - if(strncmp(argv[OptionIndex], "--proxy=", 8) == 0) + if(!strncmp(argv[OptionIndex], "--proxy=", 8)) snprintf(conf.http_proxy, sizeof(conf.http_proxy) - 1, "%s", &argv[OptionIndex][8]); - if((strcmp(argv[OptionIndex], "--help") == 0) || - (strcmp(argv[OptionIndex], "/?") == 0)) { + if(!strcmp(argv[OptionIndex], "--help") || + !strcmp(argv[OptionIndex], "/?")) { showUsage(); return 0; } @@ -300,7 +300,8 @@ int main(int argc, const char *argv[]) /* Get current system time and local time */ GetSystemTime(&SYSTime); GetLocalTime(&LOCALTime); - snprintf(timeBuf, 60, "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", + snprintf(timeBuf, sizeof(timeBuf) - 1, + "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", DayStr[LOCALTime.wDayOfWeek], LOCALTime.wDay, MthStr[LOCALTime.wMonth - 1], LOCALTime.wYear, LOCALTime.wHour, LOCALTime.wMinute, LOCALTime.wSecond, LOCALTime.wMilliseconds); @@ -314,7 +315,8 @@ int main(int argc, const char *argv[]) #if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) GetLocalTime(&LOCALTime); - snprintf(timeBuf, 60, "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", + snprintf(timeBuf, sizeof(timeBuf) - 1, + "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", DayStr[LOCALTime.wDayOfWeek], LOCALTime.wDay, MthStr[LOCALTime.wMonth - 1], LOCALTime.wYear, LOCALTime.wHour, LOCALTime.wMinute, LOCALTime.wSecond, LOCALTime.wMilliseconds); @@ -329,7 +331,8 @@ int main(int argc, const char *argv[]) else { /* Successfully re-adjusted computer clock */ GetLocalTime(&LOCALTime); - snprintf(timeBuf, 60, "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", + snprintf(timeBuf, sizeof(timeBuf) - 1, + "%s, %02d %s %04d %02d:%02d:%02d.%03d, ", DayStr[LOCALTime.wDayOfWeek], LOCALTime.wDay, MthStr[LOCALTime.wMonth - 1], LOCALTime.wYear, LOCALTime.wHour, LOCALTime.wMinute, LOCALTime.wSecond, diff --git a/docs/examples/threaded.c b/docs/examples/threaded.c index 54b315bc57b7..b858c1a3f828 100644 --- a/docs/examples/threaded.c +++ b/docs/examples/threaded.c @@ -73,11 +73,9 @@ static void *pull_one_url(void *p) return NULL; } -/* - int pthread_create(pthread_t *new_thread_ID, +/* int pthread_create(pthread_t *new_thread_ID, const pthread_attr_t *attr, - void * (*start_func)(void *), void *arg); -*/ + void * (*start_func)(void *), void *arg); */ int main(void) { diff --git a/docs/examples/url2file.c b/docs/examples/url2file.c index ce8805a40e5f..1216575638c0 100644 --- a/docs/examples/url2file.c +++ b/docs/examples/url2file.c @@ -44,7 +44,7 @@ static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) int main(int argc, const char *argv[]) { - static const char *pagefilename = "page.out"; + static const char pagefilename[] = "page.out"; CURLcode result; CURL *curl; diff --git a/docs/examples/version-check.pl b/docs/examples/version-check.pl index e6ad784fc811..680bbb358b02 100755 --- a/docs/examples/version-check.pl +++ b/docs/examples/version-check.pl @@ -41,21 +41,21 @@ use strict; use warnings; -open(S, "<../libcurl/symbols-in-versions") || die; +open(S, "<", '../libcurl/symbols-in-versions') or die; my %doc; my %rem; while() { if(/(^CURL[^ \n]*) *(.*)/) { - my ($sym, $rest)=($1, $2); - my @a=split(/ +/, $rest); + my ($sym, $rest) = ($1, $2); + my @a = split(/ +/, $rest); - $doc{$sym}=$a[0]; # when it was introduced + $doc{$sym} = $a[0]; # when it was introduced if($a[2]) { # this symbol is documented to have been present the last time # in this release - $rem{$sym}=$a[2]; + $rem{$sym} = $a[2]; } } } @@ -63,14 +63,14 @@ close(S); sub age { - my ($ver)=@_; + my ($ver) = @_; - my @s=split(/\./, $ver); + my @s = split(/\./, $ver); return $s[0]*10000+$s[1]*100+($s[2] || 0); } my %used; -open(C, "<$ARGV[0]") || die; +open(C, "<", $ARGV[0]) or die; while() { if(/\W(CURL[_A-Z0-9v]+)\W/) { diff --git a/docs/examples/websocket-updown.c b/docs/examples/websocket-updown.c index 3000be059497..495c4b71669c 100644 --- a/docs/examples/websocket-updown.c +++ b/docs/examples/websocket-updown.c @@ -69,7 +69,7 @@ static size_t read_cb(char *buf, size_t nitems, size_t buflen, void *p) result = curl_ws_start_frame(ctx->curl, CURLWS_TEXT, (curl_off_t)ctx->blen); if(result != CURLE_OK) { - fprintf(stderr, "error starting frame: %d\n", result); + fprintf(stderr, "error starting frame: %d\n", (int)result); return CURL_READFUNC_ABORT; } } @@ -88,7 +88,7 @@ int main(int argc, const char *argv[]) { CURL *curl; struct read_ctx rctx; - const char *payload = "Hello, friend!"; + static const char payload[] = "Hello, friend!"; CURLcode result = curl_global_init(CURL_GLOBAL_ALL); if(result != CURLE_OK) @@ -108,7 +108,7 @@ int main(int argc, const char *argv[]) curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb); /* tell curl that we want to send the payload */ rctx.curl = curl; - rctx.blen = strlen(payload); + rctx.blen = sizeof(payload) - 1; memcpy(rctx.buf, payload, rctx.blen); curl_easy_setopt(curl, CURLOPT_READDATA, &rctx); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); diff --git a/docs/examples/websocket.c b/docs/examples/websocket.c index ec445d88ed2d..7ae299f4ed6b 100644 --- a/docs/examples/websocket.c +++ b/docs/examples/websocket.c @@ -30,7 +30,7 @@ #ifdef _WIN32 #include #include -#define sleep(s) Sleep((DWORD)(s * 1000)) +#define sleep(s) Sleep((DWORD)((s) * 1000)) #else #include #endif @@ -94,7 +94,7 @@ static CURLcode recv_pong(CURL *curl, const char *expected_payload) else { /* some other frame arrived. */ fprintf(stderr, "ws: received frame of %u bytes rflags %x\n", - (unsigned int)rlen, meta->flags); + (unsigned int)rlen, (unsigned int)meta->flags); goto retry; } } diff --git a/docs/internals/BUFQ.md b/docs/internals/BUFQ.md index 9d926537d66a..a977fcc8bdc1 100644 --- a/docs/internals/BUFQ.md +++ b/docs/internals/BUFQ.md @@ -15,26 +15,31 @@ Its basic read/write functions have a similar signature and return code handling as many internal curl read and write ones. ```c -ssize_t Curl_bufq_write(struct bufq *q, const unsigned char *buf, size_t len, CURLcode *err); - -- returns the length written into `q` or -1 on error. -- writing to a full `q` returns -1 and set *err to CURLE_AGAIN - -ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err); +CURLcode Curl_bufq_write(struct bufq *q, + const uint8_t *buf, size_t len, + size_t *pnwritten); +``` -- returns the length read from `q` or -1 on error. -- reading from an empty `q` returns -1 and set *err to CURLE_AGAIN +- sets `pnwritten` to the length written into `q` or -1 on error. +- writing to a full `q` sets `pnwritten` to -1 and returns CURLE_AGAIN ``` +CURLcode Curl_bufq_read(struct bufq *q, uint8_t *buf, size_t len, + size_t *pnread); +``` + +- sets `pnread` to the length read from `q` or -1 on error. +- reading from an empty `q` sets `pnread` to -1 and returns CURLE_AGAIN To pass data into a `bufq` without an extra copy, read callbacks can be used. ```c -typedef ssize_t Curl_bufq_reader(void *reader_ctx, unsigned char *buf, size_t len, - CURLcode *err); +typedef CURLcode Curl_bufq_reader(void *reader_ctx, + uint8_t *buf, size_t len, + size_t *pnread); -ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader, void *reader_ctx, - CURLcode *err); +CURLcode Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader, + void *reader_ctx, size_t *pnread); ``` `Curl_bufq_slurp()` invokes the given `reader` callback, passing it its own @@ -46,11 +51,12 @@ once or only read in a maximum amount of bytes. The analog mechanism for write out buffer data is: ```c -typedef ssize_t Curl_bufq_writer(void *writer_ctx, const unsigned char *buf, size_t len, - CURLcode *err); +typedef CURLcode Curl_bufq_writer(void *writer_ctx, + const uint8_t *buf, size_t len, + size_t *pwritten); -ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, void *writer_ctx, - CURLcode *err); +CURLcode Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, + void *writer_ctx, size_t *pwritten); ``` `Curl_bufq_pass()` invokes the `writer`, passing its internal memory and @@ -61,7 +67,8 @@ remove the amount that `writer` reports. It is possible to get access to the memory of data stored in a `bufq` with: ```c -bool Curl_bufq_peek(const struct bufq *q, const unsigned char **pbuf, size_t *plen); +bool Curl_bufq_peek(struct bufq *q, + const uint8_t **pbuf, size_t *plen); ``` On returning TRUE, `pbuf` points to internal memory with `plen` bytes that one @@ -156,9 +163,11 @@ A `struct bufc_pool` may be used to create chunks for a `bufq` and keep spare ones around. It is initialized and used via: ```c -void Curl_bufcp_init(struct bufc_pool *pool, size_t chunk_size, size_t spare_max); +void Curl_bufcp_init(struct bufc_pool *pool, + size_t chunk_size, size_t spare_max); -void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool, size_t max_chunks, int opts); +void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool, + size_t max_chunks, int opts); ``` The pool gets the size and the mount of spares to keep. The `bufq` gets the diff --git a/docs/internals/CHECKSRC.md b/docs/internals/CHECKSRC.md index ea6f260cf13b..4719dd6ba6f5 100644 --- a/docs/internals/CHECKSRC.md +++ b/docs/internals/CHECKSRC.md @@ -40,21 +40,21 @@ warnings are: code style mandates the assignment to be done outside of it. - `ASTERISKNOSPACE`: A pointer was declared like `char* name` instead of the - more appropriate `char *name` style. The asterisk should sit next to the - name. + more appropriate `char *name` style. The asterisk should sit next to the + name. - `ASTERISKSPACE`: A pointer was declared like `char * name` instead of the - more appropriate `char *name` style. The asterisk should sit right next to - the name without a space in between. + more appropriate `char *name` style. The asterisk should sit right next to + the name without a space in between. - `BADCOMMAND`: There is a bad `checksrc` instruction in the code. See the - **Ignore certain warnings** section below for details. + **Ignore certain warnings** section below for details. - `BANNEDFUNC`: A banned function was used. The functions sprintf, vsprintf, - strcat, strncat, gets are **never** allowed in curl source code. + strcat, strncat, gets are **never** allowed in curl source code. - `BRACEELSE`: '} else' on the same line. The else is supposed to be on the - following line. + following line. - `BRACEPOS`: wrong position for an open brace (`{`). @@ -80,8 +80,8 @@ warnings are: string, use it - `INDENTATION`: detected a wrong start column for code. Note that this - warning only checks some specific places and can certainly miss many bad - indentations. + warning only checks some specific places and can certainly miss many bad + indentations. - `LONGLINE`: A line is longer than 79 columns. @@ -99,7 +99,7 @@ warnings are: - `PARENBRACE`: `){` was used without sufficient space in between. - `RETURNNOSPACE`: `return` was used without space between the keyword and the - following value. + following value. - `SEMINOSPACE`: There was no space (or newline) following a semicolon. @@ -107,7 +107,7 @@ warnings are: `sizeof(int)` style. - `SNPRINTF` - Found use of `snprintf()`. Since we use an internal replacement - with a different return code etc, we prefer `curl_msnprintf()`. + with a different return code etc, we prefer `curl_msnprintf()`. - `SPACEAFTERPAREN`: there was a space after open parenthesis, `( text`. @@ -116,7 +116,7 @@ warnings are: - `SPACEBEFORECOMMA`: there was a space before a comma, `one , two`. - `SPACEBEFOREPAREN`: there was a space before an open parenthesis, `if (`, - where one was not expected + where one was not expected - `SPACESEMICOLON`: there was a space before semicolon, ` ;`. @@ -127,7 +127,11 @@ warnings are: - `TYPEDEFSTRUCT`: we frown upon (most) typedefed structs - `UNUSEDIGNORE`: a `checksrc` inlined warning ignore was asked for but not - used, that is an ignore that should be removed or changed to get used. + used, that is an ignore that should be removed or changed to get used. + +- `USESAFEFREE`: there was a `curlx_free(var)` call made right before assigning + NULL to `var`. We prefer replacing that with `curlx_safefree()`, which is + doing these two operations in a single call. ### Extended warnings @@ -140,7 +144,7 @@ so: `enable ` Currently these are the extended warnings which can be enabled: - `COPYRIGHTYEAR`: the current changeset has not updated the copyright year in - the source file + the source file - `STRERROR`: use of banned function strerror() diff --git a/docs/internals/CODE_STYLE.md b/docs/internals/CODE_STYLE.md index 31c333af8229..95e532f35aee 100644 --- a/docs/internals/CODE_STYLE.md +++ b/docs/internals/CODE_STYLE.md @@ -104,7 +104,7 @@ if(!x) For functions the opening brace should be on a separate line: ```c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { return 1; } @@ -208,6 +208,17 @@ complement = ~bits; empty = (!*string) ? TRUE : FALSE; ``` +## No space following typecasts + +As far as possible, we write code to avoid typecasts. When we do use them, We +write typecasts "glued" to the following expression, with no space after the +closing parenthesis: + +```c +int value = (int)foobar; +char *ptr = (char *)random_func(); +``` + ## No parentheses for return values We use the 'return' statement without extra parentheses around the value: @@ -348,10 +359,12 @@ This is the full list of functions generally banned. _wfopen _wfreopen _wopen + abort accept accept4 access aprintf + assert atoi atol calloc @@ -370,6 +383,8 @@ This is the full list of functions generally banned. getaddrinfo gets gmtime + inet_ntop + inet_pton llseek LoadLibrary LoadLibraryA diff --git a/docs/internals/CONNECTION-FILTERS.md b/docs/internals/CONNECTION-FILTERS.md index 619ca0e3407d..1a817a15672a 100644 --- a/docs/internals/CONNECTION-FILTERS.md +++ b/docs/internals/CONNECTION-FILTERS.md @@ -156,9 +156,9 @@ The currently existing filter types (curl 8.5.0) are: `accept()`ed in a `listen()` * `SSL`: filter that applies TLS en-/decryption and handshake. Manages the underlying TLS backend implementation. -* `HTTP-PROXY`, `H1-PROXY`, `H2-PROXY`: the first manages the connection to an - HTTP proxy server and uses the other depending on which ALPN protocol has - been negotiated. +* `HTTP-PROXY`, `H1-PROXY`, `H2-PROXY`, `H3-PROXY`: the first manages the + connection to an HTTP proxy server and uses the other depending on which + ALPN protocol has been negotiated. * `SOCKS-PROXY`: filter for the various SOCKS proxy protocol variations * `HAPROXY`: filter for the protocol of the same name, providing client IP information to a server. @@ -166,7 +166,7 @@ The currently existing filter types (curl 8.5.0) are: connection * `HTTP/3`: filter for handling multiplexed transfers over an HTTP/3+QUIC connection -* `HAPPY-EYEBALLS`: meta filter that implements IPv4/IPv6 "happy eyeballing". +* `HAPPY-EYEBALLS`: meta filter that implements IPv4/IPv6 "happy eyeballs". It creates up to 2 sub-filters that race each other for a connection. * `SETUP`: meta filter that manages the creation of sub-filter chains for a specific transport (e.g. TCP or QUIC). @@ -220,6 +220,37 @@ as an `SSL` flagged filter is seen first. `conn3` is also encrypted as the Similar checks can determine if a connection is multiplexed or not. +## Adding CONNECT-UDP support +HTTP/3 on top of HTTP/1.1 (MASQUE CONNECT-UDP): +``` +conn --> HTTP/3 --> CAPSULE --> HTTP-PROXY --> H1-PROXY --> SSL --> HAPPY-EYEBALLS --> TCP +``` + +HTTP/3 on top of HTTP/2 (MASQUE CONNECT-UDP): +``` +conn --> HTTP/3 --> CAPSULE --> HTTP-PROXY --> H2-PROXY --> SSL --> HAPPY-EYEBALLS --> TCP +``` + +The CAPSULE filter handles RFC 9297 capsule protocol encapsulation and +decapsulation of UDP datagrams. It is inserted automatically when the +HTTP-PROXY filter completes a successful CONNECT-UDP tunnel. + +## Adding H3-PROXY support +HTTP/1.1 on top of HTTP/3 (CONNECT over QUIC): +``` +conn --> HTTP/1.1 --> SSL --> HTTP-PROXY --> H3-PROXY --> HAPPY-EYEBALLS --> UDP +``` + +HTTP/2 on top of HTTP/3 (CONNECT over QUIC): +``` +conn --> HTTP/2 --> SSL --> HTTP-PROXY --> H3-PROXY --> HAPPY-EYEBALLS --> UDP +``` + +HTTP/3 on top of HTTP/3 (MASQUE CONNECT-UDP over QUIC): +``` +conn --> HTTP/3 --> CAPSULE --> HTTP-PROXY --> H3-PROXY --> HAPPY-EYEBALLS --> UDP +``` + ## Filter Tracing Filters may make use of special trace macros like `CURL_TRC_CF(data, cf, msg, diff --git a/docs/internals/CREDENTIALS.md b/docs/internals/CREDENTIALS.md new file mode 100644 index 000000000000..5f4aa97885ab --- /dev/null +++ b/docs/internals/CREDENTIALS.md @@ -0,0 +1,75 @@ + + +# curl `creds` + +Authorization credentials are kept in `struct Curl_creds`. This contains: + +* `user`: the username, maybe the empty string +* `passwd`: the password, maybe the empty string +* `sasl_authzid`: the SASL `authz` value, maybe the empty string +* `oauth_bearer`: the OAUTH bearer token, maybe the empty string +* `source`: where the credentials come from +* `refcount`: a reference counter to link/unlink `creds` + +With reference counting, `creds` can be linked in several places. + +Two `creds` are the same if all values are equal apart from `source` +and `refcount`. The comparison of strings is done via `Curl_timestrcmp()` +to prevent side channel attacks. + +## `creds` locations + +Credentials are kept in three places: + +* `data->state.creds`: the credentials to use for the transfer in talking + to the `origin` (see PEERS) +* `conn->creds`: the credentials tied to a connection (more below) +* `conn->*_proxy.creds`: credentials used to talk to the `conn->*_proxy.peer` + +### `data->state.creds` + +This `creds` instance is created when the transfer starts looking for a +suitable connection. For an `easy_perform()` this may happen several times +if, for example, http redirects are followed. + +When an `easy_perform()` starts, the transfer's `data->state.initial_origin` +peer is cleared. When creating the connection, `data->state.origin` is +calculated (e.g. who the request talks to). If `data->state.initial_origin` +is not set, the first `data->state.origin` is linked there. +Now `libcurl` knows where +the transfer initially talked to on all possible subsequent requests. + +Credential information from `CURLOPT_*` settings is only applicable for the +initial origin. Any followup request going to another origin must not +use it. Therefore `data->state.creds` is *only* created from `CURLOPT_*` +when current origin and initial origin match. + +Without credentials from `CURLOPT_*`, the URL is inspected for user and +password and `netrc` is consulted as well (when built in). + +### `conn->creds` + +Once `data->state.creds` is known, the connection credentials are +determined. For protocols that tie authorization to everything sent +on a connection (protocols without flag `PROTOPT_CREDSPERREQUEST`), +`conn->creds` is linked to `data->state.creds`. Only connections +carrying the same credentials may be reused. + +Protocols with flag `PROTOPT_CREDSPERREQUEST` leave `conn->creds` empty, +as connections for such protocols may be reused with different +credentials. + +That being said, there are authentication schemes like `NTLM` and +`NEGOTIATE` that tie credentials to a connection. Those do set `conn->creds` +once they start to operate, preventing connection reuse from then on +for transfers with different credentials. + +### `conn->*_proxy.creds` + +Those are set during connection setup from the `CURLOPT_*` values. They +do not require any "initial origin" handling as the origin of a proxy +does not change for a transfer. diff --git a/docs/internals/DYNBUF.md b/docs/internals/DYNBUF.md index 1ae7131f977e..d28b02809dc9 100644 --- a/docs/internals/DYNBUF.md +++ b/docs/internals/DYNBUF.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: curl This is the internal module for creating and handling "dynamic buffers". This means buffers that can be appended to, dynamically and grow to adapt. -There is always a terminating zero put at the end of the dynamic buffer. +There is always a null-terminator put at the end of the dynamic buffer. The `struct dynbuf` is used to hold data for each instance of a dynamic buffer. The members of that struct **MUST NOT** be accessed or modified @@ -120,8 +120,8 @@ trusted or used anymore after the next buffer manipulation call. size_t curlx_dyn_len(const struct dynbuf *s); ``` -Returns the length of the buffer in bytes. Does not include the terminating -zero byte. +Returns the length of the buffer in bytes. Does not include the +null-terminator byte. ## `curlx_dyn_setlen` diff --git a/docs/internals/MULTI-EV.md b/docs/internals/MULTI-EV.md index f5d2fa831c57..cc899faf3624 100644 --- a/docs/internals/MULTI-EV.md +++ b/docs/internals/MULTI-EV.md @@ -55,11 +55,11 @@ compared to the *previous* pollset. If relevant changes are detected, * a socket was also in the previous one, but IN/OUT flags changed * a socket in the previous one is no longer part of the current -`multi_ev.c` keeps a `struct mev_sh_entry` for each sockets in a hash +`multi_ev.c` keeps a `struct mev_sh_entry` for each socket in a hash with the socket as key. It tracks in each entry which transfers are -interested in this particular socket. How many transfer want to read -and/or write and what the summarized `POLLIN/POLLOUT` action, that -had been reported to `multi->socket_cb` was. +interested in this particular socket, how many transfers want to read +and/or write and the summarized `POLLIN/POLLOUT` action reported to +`multi->socket_cb`. This is necessary as a socket may be in use by several transfers at the same time (think HTTP/2 on the same connection). When a transfer diff --git a/docs/internals/PEERS.md b/docs/internals/PEERS.md new file mode 100644 index 000000000000..28d002a92685 --- /dev/null +++ b/docs/internals/PEERS.md @@ -0,0 +1,130 @@ + + +# curl peers + +A `peer` in curl internals is represented by a `struct Curl_peer`. It has the following members: + +* `scheme`: a `struct Curl_scheme` of the URL schemes known to curl +* `user_hostname`: the hostname as supplied by the user/application +* `hostname`: a *normalized* version of `user_hostname` +* `port`: the network port +* `ipv6`: if `hostname` is an IPv6 address +* `unix_socket`: if `hostname` is a path to a `unix domain socket` +* `user_ipv6zone`: user supplied IPv6 zone name or `NULL` +* `ipv6scope_id`: IPv6 address scope or 0 +* `abstract`: (if `unix_socket`) if the socket is abstract + +A peer, in short, is a communication endpoint. + +## peers and transfers + +The peer a transfer, e.g. easy handle, works against is determined at the +start of each request. It is kept in `data->state.origin`. For the first +request done in a `curl_easy_perform()` or equivalent, this origin is +linked to `data->state.initial_origin`. This allows checks if properties +of `data->set.*` should apply to a request or not. + +`data->state.origin` is relevant for cookie processing, signing requests +and other request/response based processing. + +## peers and connections + +A network connection always goes *somewhere*. That *somewhere* is called +the `origin` of the connection (e.g. the source of responses/downloads). +It is kept in `conn->origin` and is always present in a connection. + +The `origin` is *logical* endpoint a connection talks to. In most +configurations it is the same as `data->state.origin` (see proxies below). + +For most connections, the `origin` is connected to *directly*. It +can be directed to another peer, however. + +### `connect-to` + +With the command line option `--connect-to` or the `libcurl` option +`CURLOPT_CONNECT_TO`, a connection can be told to make the network connection +to another endpoint *while keeping the `origin` unchanged*. + +This other endpoint is also a peer and is available as `conn->via_peer`. +This may be a peer for a different hostname and port or it may be a +`unix domain socket`. + +### proxies + +When a connection uses a proxy, the endpoint for contacting the proxy server +is also represented as a peer and is kept at `conn->socks_proxy.peer` and/or +`conn->http_proxy.peer`. `SOCKS` proxies always come first, so a connection +might connect as: + +``` +1. curl -------------------------------------------> conn->origin +2. curl -------------------------------------------> conn->via_peer (acting as conn->origin) +3. curl --> socks_proxy.peer ----------------------> conn->via_peer/origin +4. curl -----------------------> http_proxy.peer --> conn->via_peer/origin +5. curl --> socks_proxy.peer --> http_proxy.peer --> conn->via_peer/origin +``` + +A `conn->(socks|http)_proxy.peer` is only ever present when the proxy +is in use and `NULL` otherwise. + +SOCKS proxies are always used for tunneling, either to the origin or +the HTTP proxy. They operate in a connection filter. + +HTTP proxies can operate in two modes: tunneling or forwarding. When tunneling, +they also operate in a connection filter. In forwarding mode however, they +become the `origin` the connection talks to. + +Therefore, connections that talk to a forwarding HTTP proxy have `conn->origin` +set to `conn->http_proxy.peer` and `conn->bits.origin_is_proxy` is set. + +The connection filter `SETUP`, that assembles the filters for a connection, +figures out which peer to pass to which filter in order to make it all work. +The individual filters get passed a specific peer and do not need be concerned +with the whole chain. + +For example, IP connection goes to `origin`(1), `via_peer`(2), +`socks_proxy.peer`(3+5), `http_proxy.peer`(4) and that is the peer that gets +passed to the `DNS` and `HAPPY-EYEBALLS` filters. + +### TLS + +TLS filters' task is to verify the peer they talk to (unless that is +switched off). They either talk to the `conn->origin` or the +`conn->http_proxy.peer` (`SOCKS` does not have TLS). The `conn->via_peer` is +irrelevant. A `via_peer` endpoint needs to present a certificate matching +`conn->origin` or the connect must fail. + +### `unix domain socket`s + +Peers that represent a `unix domain socket` may be used in two places: + +1. `via_peer`: curl can connect to an `origin` server via `unix domain socket`s. + This disables any proxy settings a transfer might carry. +2. `socks_proxy.peer`: a `SOCKS` proxy may be contacted over a `unix domain + socket`. + +It is not supported to contact an http proxy over `unix domain socket`s. + +## peers and credentials + +There have been several vulnerabilities by leaking credentials in requests +where they should not appear. In future work we plan to tie credentials to +`peers` and use them only when their `peer` still matches the current +connection use. + +## peers internals + +A `struct Curl_peer` is allocated with space of the `user_hostname`. +Only when the user supplied value needs conversions (removing `[]` or +IDN encoding) is `hostname` an extra allocation. This keeps the number +of allocations the same as before. + +A `Curl_peer` is not expected to be modified after it has been created. +However, each `Curl_peer` has a reference counter. Code needs to use +`Curl_peer_link()` and `Curl_peer_unlink()` to keep/release references. +This makes it safe and cheap to keep references to peers in connections +and filters. diff --git a/docs/internals/SPLAY.md b/docs/internals/SPLAY.md index 29cf3858a66a..4a9e49940c5e 100644 --- a/docs/internals/SPLAY.md +++ b/docs/internals/SPLAY.md @@ -17,7 +17,7 @@ it automatically rebalances itself in each operation. ## libcurl use -libcurl adds fixed timeout expiry timestamps to the splay tree, and is meant +libcurl adds fixed timeout expiry `timediff_t` to the splay tree, and is meant to scale up to holding a huge amount of pending timeouts with decent performance. @@ -26,19 +26,25 @@ The splay tree is used to: 1. figure out the next timeout expiry value closest in time 2. iterate over timeouts that already have expired -This splay tree rebalances itself based on the time value. +This splay tree rebalances itself based on the timeout value. -Each node in the splay tree points to a `struct Curl_easy`. Each `Curl_easy` -struct is represented only once in the tree. To still allow each easy handle -to have a large number of timeouts per handle, each handle has a sorted linked -list of pending timeouts. Only the handle's timeout that is closest to expire +Each node in the splay tree carries a `uint32_t id`. This is set to the +`mid`, the unique transfer identifier in a multi handle. Each transfer +is added only once in the tree. To still allow each transfer +to have a large number of timeouts per handle, each handle has a sorted list +of pending timeouts. Only the handle's timeout that is closest to expire is the timestamp used for the splay tree node. When a specific easy handle's timeout expires, the node gets removed from the -splay tree and from the handle's linked list of timeouts. The next timeout for +splay tree and from the handle's list of timeouts. The next timeout for that handle is then first in line and becomes the new timeout value as the node is re-added to the splay. +To convert the `timediff_t` is the splay to the correct timestamps, the +multi handle carries a "base timestamp", set once when created, and the +timeout values are calculated relative to that. This works for about +500,000 years of continued operation of a multi handle. + ## `Curl_splay` ~~~c @@ -50,15 +56,16 @@ Rearranges the tree `t` after the provide time `i`. ## `Curl_splayinsert` ~~~c -struct Curl_tree *Curl_splayinsert(struct curltime key, +struct Curl_tree *Curl_splayinsert(timediff_t key, struct Curl_tree *t, - struct Curl_tree *node); + struct Curl_tree *node, + uint32_t id); ~~~ This function inserts a new `node` in the tree, using the given `key` -timestamp. The `node` struct has a field called `->payload` that can be set to -point to anything. libcurl sets this to the `struct Curl_easy` handle that is -associated with the timeout value set in `key`. +timeout. The `node` struct has a field called `->id` which is set to +the passed `id`. libcurl sets this to the transfer's `mid`, the unique +identifier in a multi handle. The splay insert function does not allocate any memory, it assumes the caller has that arranged. @@ -68,12 +75,12 @@ It returns a pointer to the new tree root. ## `Curl_splaygetbest` ~~~c -struct Curl_tree *Curl_splaygetbest(struct curltime key, +struct Curl_tree *Curl_splaygetbest(timediff_t key, struct Curl_tree *tree, struct Curl_tree **removed); ~~~ -If there is a node in the `tree` that has a time value that is less than the +If there is a node in the `tree` that has a timeout that is less than the provided `key`, this function removes that node from the tree and provides it in the `*removed` pointer (or NULL if there was no match). @@ -95,17 +102,16 @@ Note that a clean tree without any nodes present implies a NULL pointer. ## `Curl_splayset` ~~~c -void Curl_splayset(struct Curl_tree *node, void *payload); +void Curl_splayset(struct Curl_tree *node, uint32_t id); ~~~ -Set a custom pointer to be stored in the splay node. This pointer is not used +Sets the `id` in the splay node. This value is not used by the splay code itself and can be retrieved again with `Curl_splayget`. ## `Curl_splayget` ~~~c -void *Curl_splayget(struct Curl_tree *node); +uint32_t Curl_splayget(struct Curl_tree *node); ~~~ -Get the custom pointer from the splay node that was previously set with -`Curl_splayset`. If no pointer was set before, it returns NULL. +Get the `id` from the splay node that was previously set. diff --git a/docs/internals/THRDPOOL-AND-QUEUE.md b/docs/internals/THRDPOOL-AND-QUEUE.md index e55f8a2be313..65a2f70dc544 100644 --- a/docs/internals/THRDPOOL-AND-QUEUE.md +++ b/docs/internals/THRDPOOL-AND-QUEUE.md @@ -57,12 +57,12 @@ A thread pool can be destroyed via `Curl_thrdpool_destroy(pool, join)` where ### Safety -The thread pool operates use a mutex and condition variables to manage +The thread pool operates using a mutex and condition variables to manage concurrency. All interactions and callback invocation are done under the pool's mutex lock, *except* the "process" callback which is invoked unlocked. -To avoid deadlocks, no callback must invoked other pool functions. Also, +To avoid deadlocks, no callback must invoke other pool functions. Also, any call of pool functions may result in callback invocations. The "work items", once "taken" by the pool, should not be referenced @@ -102,7 +102,7 @@ the queue. Calling `Curl_thrdq_recv()` delivers processed items back. ### Safety -The thread queue operates use a mutex and condition variables to manage +The thread queue operates using a mutex and condition variables to manage concurrency. All interactions and callback invocation are done under the queue's mutex lock, *except* the "process" callback which is invoked unlocked. diff --git a/docs/internals/TIME-KEEPING.md b/docs/internals/TIME-KEEPING.md index b43daae0be61..4d99d63ce89d 100644 --- a/docs/internals/TIME-KEEPING.md +++ b/docs/internals/TIME-KEEPING.md @@ -25,7 +25,7 @@ this was mostly not noticeable. On slow machines or in CI, this led to rare and annoying test failures. (Especially when we added assertions that the reported "timeline" of a -transfer was in the correct order: *queue -> nameloopup -> connect -> +transfer was in the correct order: *queue -> namelookup -> connect -> appconnect ->...*.) ## Revised Approach diff --git a/docs/internals/TLS-SESSIONS.md b/docs/internals/TLS-SESSIONS.md index b108fbfcfbe2..fdca13ab1187 100644 --- a/docs/internals/TLS-SESSIONS.md +++ b/docs/internals/TLS-SESSIONS.md @@ -55,9 +55,7 @@ Examples: Different configurations produce different keys which is what curl needs when handling SSL session tickets. -One important thing: peer keys do not contain confidential information. If you -configure a client certificate or SRP authentication with username/password, -these are not part of the peer key. +One important thing: peer keys do not contain confidential information. Peer keys carry the hostnames you use curl for. They *do* leak the privacy of your communication. We recommend to *not* persist peer keys for this reason. @@ -76,10 +74,9 @@ its own peer_key and calls into the cache. The cache then looks for a ticket with exactly this peer_key. Peer keys between proxy SSL filters and SSL filters talking through a tunnel differ, as they talk to different peers. -If the connection filter wants to use a client certificate or SRP -authentication, the cache checks those as well. If the cache peer carries -client cert or SRP auth, the connection filter must have those with the same -values (and vice versa). +If the connection filter wants to use a client certificate, the cache checks +those as well. If the cache peer carries client certs, the connection filter +must have those with the same values (and vice versa). On a match, the connection filter gets the session ticket and feeds that to the TLS implementation which, on accepting it, tries to resume it for a @@ -119,7 +116,7 @@ concurrent connections do not reuse the same ticket. #### Privacy and Security -As mentioned above, ssl peer keys are not intended for storage in a file +As mentioned above, SSL peer keys are not intended for storage in a file system. They clearly show which hosts the user talked to. This is not only privacy relevant, but also has security implications as an attacker might find worthy targets among your peer keys. diff --git a/docs/libcurl/curl_easy_duphandle.md b/docs/libcurl/curl_easy_duphandle.md index 5ea9c4fba018..0de33496cacd 100644 --- a/docs/libcurl/curl_easy_duphandle.md +++ b/docs/libcurl/curl_easy_duphandle.md @@ -42,7 +42,9 @@ SSL sessions and no cookies. It also does not inherit any share object states or options (created as if CURLOPT_SHARE(3) was set to NULL). If the source handle has HSTS or alt-svc enabled, the duplicate gets data read -data from the main filename to populate the cache. +from the main filename to populate the cache. For HSTS, any entries learned at +runtime (E.g. `Strict-Transport-Security` response headers) are also copied to +the duplicate handle. In multi-threaded programs, this function must be called in a synchronous way, the input handle may not be in use when cloned. diff --git a/docs/libcurl/curl_easy_nextheader.md b/docs/libcurl/curl_easy_nextheader.md index 42314996b939..1a07f1fe31d8 100644 --- a/docs/libcurl/curl_easy_nextheader.md +++ b/docs/libcurl/curl_easy_nextheader.md @@ -77,6 +77,8 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { + unsigned int origin; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); curl_easy_perform(curl); @@ -87,7 +89,7 @@ int main(void) } /* extract the normal headers + 1xx + trailers from the last request */ - unsigned int origin = CURLH_HEADER | CURLH_1XX | CURLH_TRAILER; + origin = CURLH_HEADER | CURLH_1XX | CURLH_TRAILER; while((h = curl_easy_nextheader(curl, origin, -1, prev))) { printf("%s: %s\n", h->name, h->value); prev = h; diff --git a/docs/libcurl/curl_easy_option_by_name.md b/docs/libcurl/curl_easy_option_by_name.md index 12b460630792..8ba6ce0d27d1 100644 --- a/docs/libcurl/curl_easy_option_by_name.md +++ b/docs/libcurl/curl_easy_option_by_name.md @@ -43,7 +43,7 @@ int main(void) { const struct curl_easyoption *opt = curl_easy_option_by_name("URL"); if(opt) { - printf("This option wants CURLoption %x\n", (int)opt->id); + printf("This option wants CURLoption %x\n", (unsigned int)opt->id); } } ~~~ diff --git a/docs/libcurl/curl_easy_pause.md b/docs/libcurl/curl_easy_pause.md index 31e1cffe77c0..f0c9230da5fd 100644 --- a/docs/libcurl/curl_easy_pause.md +++ b/docs/libcurl/curl_easy_pause.md @@ -28,8 +28,8 @@ CURLcode curl_easy_pause(CURL *handle, int action); Using this function, you can explicitly mark a running connection to get paused, and you can unpause a connection that was previously paused. Unlike -most other libcurl functions, curl_easy_pause(3) can be used from within -callbacks. +most other libcurl functions, curl_easy_pause(3) can be used from within all +callbacks except the socket callback set with CURLMOPT_SOCKETFUNCTION(3). A connection can be paused by using this function or by letting the read or the write callbacks return the proper return code (*CURL_READFUNC_PAUSE* and @@ -80,19 +80,17 @@ Convenience define that unpauses both directions. # LIMITATIONS The pausing of transfers does not work with protocols that work without -network connectivity, like FILE://. Trying to pause such a transfer, in any +network connectivity, like `file://`. Trying to pause such a transfer, in any direction, might cause problems or error. # MULTIPLEXED -When a connection is used multiplexed, like for HTTP/2, and one of the -transfers over the connection is paused and the others continue flowing, -libcurl might end up buffering contents for the paused transfer. It has to do -this because it needs to drain the socket for the other transfers and the -already announced window size for the paused transfer allows the server to -continue sending data up to that window size amount. By default, libcurl -announces a 32 megabyte window size, which thus can make libcurl end up -buffering 32 megabyte of data for a paused stream. +On multiplexed connections (HTTP/2 or HTTP/3), pausing an individual stream +while others remain active forces libcurl to buffer up to 10 MB of data for +the paused transfer. Because libcurl must continuously drain the shared socket +to sustain active streams, and the default flow-control window allows the +server to send up to 10 MB before halting, libcurl is forced to buffer the +incoming bytes in memory. When such a paused stream is unpaused again, any buffered data is delivered first. diff --git a/docs/libcurl/curl_easy_setopt.md b/docs/libcurl/curl_easy_setopt.md index 13d966f3c319..23eda3520333 100644 --- a/docs/libcurl/curl_easy_setopt.md +++ b/docs/libcurl/curl_easy_setopt.md @@ -50,6 +50,9 @@ any way reset between transfers, so if you want subsequent transfers with different options, you must change them between the transfers. You can optionally reset all options back to internal default with curl_easy_reset(3). +Changing options with curl_easy_setopt(3) while a transfer is still in +progress may cause undefined and undesired behavior. + The order in which the options are set does not matter. # STRINGS @@ -69,7 +72,7 @@ Passing in "creative octets" like newlines where they are not expected might trigger unexpected results. Before version 7.17.0, strings were not copied. Instead the user was forced -keep them available until libcurl no longer needed them. +to keep them available until libcurl no longer needed them. # OPTIONS @@ -471,6 +474,22 @@ See CURLOPT_HTTPPOST(3) Tunnel through the HTTP proxy. CURLOPT_HTTPPROXYTUNNEL(3) +## CURLOPT_HTTPSIG_ALGORITHM + +RFC 9421 HTTP Message Signatures algorithm. See CURLOPT_HTTPSIG_ALGORITHM(3) + +## CURLOPT_HTTPSIG_HEADERS + +Components to sign for HTTP Message Signatures. See CURLOPT_HTTPSIG_HEADERS(3) + +## CURLOPT_HTTPSIG_KEY + +Hex-encoded key for HTTP Message Signatures. See CURLOPT_HTTPSIG_KEY(3) + +## CURLOPT_HTTPSIG_KEYID + +Key identifier for HTTP Message Signatures. See CURLOPT_HTTPSIG_KEYID(3) + ## CURLOPT_HTTP_CONTENT_DECODING Disable Content decoding. See CURLOPT_HTTP_CONTENT_DECODING(3) @@ -864,15 +883,18 @@ Proxy TLS 1.3 cipher suites to use. See CURLOPT_PROXY_TLS13_CIPHERS(3) ## CURLOPT_PROXY_TLSAUTH_PASSWORD -Proxy TLS authentication password. See CURLOPT_PROXY_TLSAUTH_PASSWORD(3) +**Deprecated option**. Proxy TLS authentication password. See +CURLOPT_PROXY_TLSAUTH_PASSWORD(3) ## CURLOPT_PROXY_TLSAUTH_TYPE -Proxy TLS authentication methods. See CURLOPT_PROXY_TLSAUTH_TYPE(3) +**Deprecated option**. Proxy TLS authentication methods. See +CURLOPT_PROXY_TLSAUTH_TYPE(3) ## CURLOPT_PROXY_TLSAUTH_USERNAME -Proxy TLS authentication username. See CURLOPT_PROXY_TLSAUTH_USERNAME(3) +**Deprecated option**. Proxy TLS authentication username. See +CURLOPT_PROXY_TLSAUTH_USERNAME(3) ## CURLOPT_PROXY_TRANSFER_MODE @@ -1162,11 +1184,12 @@ Redirect stderr to another stream. See CURLOPT_STDERR(3) ## CURLOPT_STREAM_DEPENDS -This HTTP/2 stream depends on another. See CURLOPT_STREAM_DEPENDS(3) +**Deprecated option** This HTTP/2 stream depends on another. See +CURLOPT_STREAM_DEPENDS(3) ## CURLOPT_STREAM_DEPENDS_E -This HTTP/2 stream depends on another exclusively. See +**Deprecated option** This HTTP/2 stream depends on another exclusively. See CURLOPT_STREAM_DEPENDS_E(3) ## CURLOPT_STREAM_WEIGHT @@ -1240,15 +1263,16 @@ TLS 1.3 cipher suites to use. See CURLOPT_TLS13_CIPHERS(3) ## CURLOPT_TLSAUTH_PASSWORD -TLS authentication password. See CURLOPT_TLSAUTH_PASSWORD(3) +**Deprecated option**. TLS authentication password. See CURLOPT_TLSAUTH_PASSWORD(3) ## CURLOPT_TLSAUTH_TYPE -TLS authentication methods. See CURLOPT_TLSAUTH_TYPE(3) +**Deprecated option**. TLS authentication methods. See CURLOPT_TLSAUTH_TYPE(3) ## CURLOPT_TLSAUTH_USERNAME -TLS authentication username. See CURLOPT_TLSAUTH_USERNAME(3) +**Deprecated option**. TLS authentication username. See +CURLOPT_TLSAUTH_USERNAME(3) ## CURLOPT_TRAILERDATA diff --git a/docs/libcurl/curl_easy_ssls_export.md b/docs/libcurl/curl_easy_ssls_export.md index fdefa408e524..ce0363acea3e 100644 --- a/docs/libcurl/curl_easy_ssls_export.md +++ b/docs/libcurl/curl_easy_ssls_export.md @@ -60,8 +60,7 @@ persisted: either **session_key** or **shamc** and always **sdata**. All other parameters are informative, e.g. allow the callback to act only on specific session tickets. -Note that SSL sessions that involve a client certificate or SRP -username/password are not exported. +Note that SSL sessions that involve a client certificate are not exported. # Export Function Parameter @@ -118,17 +117,17 @@ The maximum amount of bytes the server supports to receive in early data # EXAMPLE ~~~c -CURLcode my_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) +static CURLcode my_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) { /* persist sdata */ return CURLE_OK; @@ -149,8 +148,7 @@ int main(void) if(curl) { curl_easy_setopt(curl, CURLOPT_SHARE, share); - /* run a transfer, all TLS sessions received will be added - * to the share. */ + /* run a transfer, all TLS sessions received are added to the share. */ curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); curl_easy_perform(curl); diff --git a/docs/libcurl/curl_easy_ssls_import.md b/docs/libcurl/curl_easy_ssls_import.md index 178c10654c73..3e43972f7053 100644 --- a/docs/libcurl/curl_easy_ssls_import.md +++ b/docs/libcurl/curl_easy_ssls_import.md @@ -49,6 +49,8 @@ already expired is silently discarded. # EXAMPLE ~~~c +extern unsigned char *shmac, *sdata; + int main(void) { CURLSHcode sh; @@ -62,7 +64,6 @@ int main(void) curl = curl_easy_init(); if(curl) { - extern unsigned char *shmac, *sdata; size_t hlen = 4, slen = 5; curl_easy_setopt(curl, CURLOPT_SHARE, share); diff --git a/docs/libcurl/curl_formadd.md b/docs/libcurl/curl_formadd.md index 94e6c269bdcc..354957bfbec6 100644 --- a/docs/libcurl/curl_formadd.md +++ b/docs/libcurl/curl_formadd.md @@ -112,6 +112,12 @@ If you pass a 0 (zero) for this option, libcurl calls strlen() on the contents to figure out the size. If you really want to send a zero byte content then you must make sure strlen() on the data pointer returns zero. +## CURLFORM_NAMELENGTH + +followed by a long giving the length of the name. Pass this option to set +the length of *CURLFORM_COPYNAME* and *CURLFORM_PTRNAME* strings, if they are +not null-terminated. + ## CURLFORM_FILECONTENT followed by a filename, causes that file to be read and its contents used @@ -207,7 +213,7 @@ See example below. ~~~c #include /* for strlen */ -static const char record[]="data in a buffer"; +static const char record[] = "data in a buffer"; int main(void) { @@ -216,10 +222,10 @@ int main(void) struct curl_httppost *post = NULL; struct curl_httppost *last = NULL; char namebuffer[] = "name buffer"; - long namelength = strlen(namebuffer); + size_t namelength = strlen(namebuffer); char buffer[] = "test buffer"; char htmlbuffer[] = "test buffer"; - long htmlbufferlength = strlen(htmlbuffer); + size_t htmlbufferlength = strlen(htmlbuffer); struct curl_forms forms[3]; char file1[] = "my-face.jpg"; char file2[] = "your-face.jpg"; @@ -244,12 +250,12 @@ int main(void) /* Add ptrname/ptrcontent section */ curl_formadd(&post, &last, CURLFORM_PTRNAME, namebuffer, CURLFORM_PTRCONTENTS, buffer, CURLFORM_NAMELENGTH, - namelength, CURLFORM_END); + (long)namelength, CURLFORM_END); /* Add name/ptrcontent/contenttype section */ curl_formadd(&post, &last, CURLFORM_COPYNAME, "html_code_with_hole", CURLFORM_PTRCONTENTS, htmlbuffer, - CURLFORM_CONTENTSLENGTH, htmlbufferlength, + CURLFORM_CONTENTSLENGTH, (long)htmlbufferlength, CURLFORM_CONTENTTYPE, "text/html", CURLFORM_END); /* Add simple file section */ @@ -271,14 +277,14 @@ int main(void) forms[0].value = file1; forms[1].option = CURLFORM_FILE; forms[1].value = file2; - forms[2].option = CURLFORM_END; + forms[2].option = CURLFORM_END; /* Add a buffer to upload */ curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", CURLFORM_BUFFER, "data", CURLFORM_BUFFERPTR, record, - CURLFORM_BUFFERLENGTH, sizeof(record), + CURLFORM_BUFFERLENGTH, (long)sizeof(record), CURLFORM_END); /* no option needed for the end marker */ diff --git a/docs/libcurl/curl_formget.md b/docs/libcurl/curl_formget.md index 0da0150a90a5..a6afea5a8208 100644 --- a/docs/libcurl/curl_formget.md +++ b/docs/libcurl/curl_formget.md @@ -51,13 +51,14 @@ This, because first then does libcurl known which actual read callback to use. # EXAMPLE ~~~c -size_t print_httppost_callback(void *arg, const char *buf, size_t len) +static size_t print_httppost_callback(void *arg, const char *buf, size_t len) { fwrite(buf, len, 1, stdout); *((size_t *)arg) += len; return len; } +size_t print_httppost(struct curl_httppost *post); size_t print_httppost(struct curl_httppost *post) { size_t total_size = 0; diff --git a/docs/libcurl/curl_global_sslset.md b/docs/libcurl/curl_global_sslset.md index 8ef0ca99923b..fa80a8cc411b 100644 --- a/docs/libcurl/curl_global_sslset.md +++ b/docs/libcurl/curl_global_sslset.md @@ -40,7 +40,7 @@ specified, the *name* is ignored. If neither *id* nor *name* are specified, the function fails with **CURLSSLSET_UNKNOWN_BACKEND** and set the *avail* pointer to the -NULL-terminated list of available backends. The available backends are those +null-terminated list of available backends. The available backends are those that this particular build of libcurl supports. Since libcurl 7.60.0, the *avail* pointer is always set to the list of @@ -50,7 +50,7 @@ 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. In this case, you may call the +null-terminated list of available SSL backends. In this case, you may call the function again to try to select a different backend. The SSL backend can be set only once. If it has already been set, a subsequent @@ -70,11 +70,11 @@ SSL backend names (case-insensitive): GnuTLS, mbedTLS, OpenSSL, Rustls, Schannel, wolfSSL The name "OpenSSL" is used for all versions of OpenSSL and its associated -forks/flavors in this function. OpenSSL, BoringSSL, LibreSSL, quictls and -AmiSSL are all supported by libcurl, but in the eyes of curl_global_sslset(3) -they are all called "OpenSSL". They all mostly provide the same API. -curl_version_info(3) can return more specific info about the exact OpenSSL -flavor and version number in use. +forks/flavors in this function. AmiSSL, AWS-LC, BoringSSL, LibreSSL, OpenSSL +and quictls are all supported by libcurl, but in the eyes of +curl_global_sslset(3) they are all called "OpenSSL". They all mostly provide +the same API. curl_version_info(3) can return more specific info about the +exact OpenSSL flavor and version number in use. # struct @@ -109,16 +109,16 @@ typedef enum { ~~~c int main(void) { + const curl_ssl_backend **list; int i; /* choose a specific backend */ curl_global_sslset(CURLSSLBACKEND_WOLFSSL, NULL, NULL); /* list the available ones */ - const curl_ssl_backend **list; curl_global_sslset(CURLSSLBACKEND_NONE, NULL, &list); for(i = 0; list[i]; i++) - printf("SSL backend #%d: '%s' (ID: %d)\n", + printf("SSL backend #%d: '%s' (ID: %u)\n", i, list[i]->name, list[i]->id); } ~~~ diff --git a/docs/libcurl/curl_global_trace.md b/docs/libcurl/curl_global_trace.md index f78f2ca0b06f..e4cf8e05a564 100644 --- a/docs/libcurl/curl_global_trace.md +++ b/docs/libcurl/curl_global_trace.md @@ -101,6 +101,11 @@ trace. Tracing of DNS operations to resolve hostnames and HTTPS records. +## `doh` + +Tracing of DoH operations (DNS over HTTPS) to resolve hostnames and +HTTPS records. + ## `lib-ids` Adds transfer and connection identifiers as prefix to every call to @@ -116,10 +121,6 @@ connection. The command line tool `curl`uses the same format for its transfers but have no own way to identify in trace output which transfer a trace event is connected to. -## `doh` - -Former name for DNS-over-HTTP operations. Now an alias for `dns`. - ## `multi` Traces multi operations managing transfers' state changes and sockets poll diff --git a/docs/libcurl/curl_mime_data_cb.md b/docs/libcurl/curl_mime_data_cb.md index 199e4f7037d0..d5965c86c769 100644 --- a/docs/libcurl/curl_mime_data_cb.md +++ b/docs/libcurl/curl_mime_data_cb.md @@ -110,7 +110,7 @@ source to avoid data duplication. In this case, original data must be retained until after the transfer terminates. ~~~c #include /* for memcpy */ -char hugedata[512000]; +static char hugedata[512000]; struct ctl { char *buffer; @@ -118,7 +118,8 @@ struct ctl { curl_off_t position; }; -size_t read_callback(char *buffer, size_t size, size_t nitems, void *arg) +static size_t read_callback(char *buffer, size_t size, size_t nitems, + void *arg) { struct ctl *p = (struct ctl *)arg; size_t sz = (size_t)(p->size - p->position); @@ -132,7 +133,7 @@ size_t read_callback(char *buffer, size_t size, size_t nitems, void *arg) return sz; } -int seek_callback(void *arg, curl_off_t offset, int origin) +static int seek_callback(void *arg, curl_off_t offset, int origin) { struct ctl *p = (struct ctl *) arg; @@ -143,6 +144,8 @@ int seek_callback(void *arg, curl_off_t offset, int origin) case SEEK_CUR: offset += p->position; break; + default: + break; } if(offset < 0) diff --git a/docs/libcurl/curl_mime_subparts.md b/docs/libcurl/curl_mime_subparts.md index 3a8c18bd22bc..f705a276e3d2 100644 --- a/docs/libcurl/curl_mime_subparts.md +++ b/docs/libcurl/curl_mime_subparts.md @@ -48,8 +48,8 @@ setting *subparts* to NULL. ~~~c -static char *inline_html = "example"; -static char *inline_text = "once upon the time"; +static const char *inline_html = "example"; +static const char *inline_text = "once upon the time"; int main(void) { diff --git a/docs/libcurl/curl_mprintf.md b/docs/libcurl/curl_mprintf.md index 72ee0a1f0229..a5a6c9a2c5b6 100644 --- a/docs/libcurl/curl_mprintf.md +++ b/docs/libcurl/curl_mprintf.md @@ -54,7 +54,7 @@ write output to stdout, the standard output stream; **curl_mfprintf()** and **curl_mvsnprintf()** write to the character string **buffer**. The functions **curl_msnprintf()** and **curl_mvsnprintf()** write at most -*maxlength* bytes (including the terminating null byte ('0')) to +*maxlength* bytes (including the null-terminator byte ('0')) to *buffer*. The functions **curl_mvprintf()**, **curl_mvfprintf()**, @@ -246,10 +246,10 @@ is written. The *const char ** argument is expected to be a pointer to an array of character type (pointer to a string). Characters from the array are written up -to (but not including) a terminating null byte. If a precision is specified, +to (but not including) a null-terminator byte. If a precision is specified, no more than the number specified are written. If a precision is given, no null byte need be present; if the precision is not specified, or is greater -than the size of the array, the array must contain a terminating null byte. +than the size of the array, the array must contain a null-terminator byte. ## p @@ -269,7 +269,7 @@ A '%' symbol is written. No argument is converted. # EXAMPLE ~~~c -const char *name = "John"; +static const char *name = "John"; int main(void) { diff --git a/docs/libcurl/curl_multi_assign.md b/docs/libcurl/curl_multi_assign.md index 279965f53436..7d1eaac4ed3d 100644 --- a/docs/libcurl/curl_multi_assign.md +++ b/docs/libcurl/curl_multi_assign.md @@ -7,6 +7,7 @@ Source: libcurl See-also: - curl_multi_setopt (3) - curl_multi_socket_action (3) + - CURLMOPT_SOCKETFUNCTION (3) Protocol: - All Added-in: 7.15.5 @@ -31,11 +32,12 @@ This function creates an association in the multi handle between the given socket and a private pointer of the application. This is designed for curl_multi_socket_action(3) uses. -When set, the *sockptr* pointer is passed to all future socket callbacks -for the specific *sockfd* socket. +When set, the *sockptr* pointer is passed to all future socket callbacks for +the specific *sockfd* socket, until the socket stops being monitored +(CURL_POLL_REMOVE is sent to the CURLMOPT_SOCKETFUNCTION(3) callback). -If the given *sockfd* is not already in use by libcurl, this function -returns an error. +If the given *sockfd* is not already in use by libcurl, this function returns +an error. libcurl only keeps one single pointer associated with a socket, so calling this function several times for the same socket makes the last set pointer get @@ -51,11 +53,11 @@ It is acceptable to call this function from your multi callback functions. int main(void) { CURLM *multi = curl_multi_init(); - int private = 123; + int private_data = 123; curl_socket_t fd = 0; /* file descriptor to associate our data with */ /* make our struct pointer associated with socket fd */ - CURLMcode mresult = curl_multi_assign(multi, fd, &private); + CURLMcode mresult = curl_multi_assign(multi, fd, &private_data); if(mresult) printf("error: %s\n", curl_multi_strerror(mresult)); } diff --git a/docs/libcurl/curl_multi_get_handles.md b/docs/libcurl/curl_multi_get_handles.md index 56da272b8abf..ad472970c9fb 100644 --- a/docs/libcurl/curl_multi_get_handles.md +++ b/docs/libcurl/curl_multi_get_handles.md @@ -56,11 +56,13 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { + CURL **list; + /* add the transfer */ curl_multi_add_handle(multi, curl); /* extract all added handles */ - CURL **list = curl_multi_get_handles(multi); + list = curl_multi_get_handles(multi); if(list) { int i; diff --git a/docs/libcurl/curl_multi_socket_action.md b/docs/libcurl/curl_multi_socket_action.md index 44a06406c3e5..4d690fd97d26 100644 --- a/docs/libcurl/curl_multi_socket_action.md +++ b/docs/libcurl/curl_multi_socket_action.md @@ -12,7 +12,7 @@ See-also: - the hiperfifo.c example Protocol: - All -Added-in: 7.15.4 +Added-in: 7.16.3 --- # NAME @@ -92,7 +92,7 @@ to kickstart everything. To get one or more callbacks called. 7. Wait for activity on any of libcurl's sockets, use the timeout value your callback has been told. -8, When activity is detected, call curl_multi_socket_action() for the +8. When activity is detected, call curl_multi_socket_action() for the socket(s) that got action. If no activity is detected and the timeout expires, call curl_multi_socket_action(3) with *CURL_SOCKET_TIMEOUT*. @@ -103,7 +103,7 @@ call curl_multi_socket_action(3) with *CURL_SOCKET_TIMEOUT*. ~~~c int main(void) { - /* the event-library gets told when there activity on the socket 'fd', + /* the event-library gets told when there is activity on the socket 'fd', which we translate to a call to curl_multi_socket_action() */ int running = 0; int fd = 3; /* the descriptor that had action */ diff --git a/docs/libcurl/curl_multi_wakeup.md b/docs/libcurl/curl_multi_wakeup.md index d95f9eb6b874..97cb4b2b9abf 100644 --- a/docs/libcurl/curl_multi_wakeup.md +++ b/docs/libcurl/curl_multi_wakeup.md @@ -46,7 +46,7 @@ This function has no effect on curl_multi_wait(3) calls. ~~~c extern int time_to_die(void); extern int set_something_to_signal_thread_1_to_exit(void); -extern int decide_to_stop_thread1(); +extern int decide_to_stop_thread1(void); int main(void) { diff --git a/docs/libcurl/curl_pushheader_bynum.md b/docs/libcurl/curl_pushheader_bynum.md index 429710f00127..256098e03032 100644 --- a/docs/libcurl/curl_pushheader_bynum.md +++ b/docs/libcurl/curl_pushheader_bynum.md @@ -48,7 +48,7 @@ static int push_cb(CURL *parent, struct curl_pushheaders *headers, void *clientp) { - int i = 0; + size_t i = 0; char *field; do { field = curl_pushheader_bynum(headers, i); diff --git a/docs/libcurl/curl_strequal.md b/docs/libcurl/curl_strequal.md index e7270d874e14..1c29a8770387 100644 --- a/docs/libcurl/curl_strequal.md +++ b/docs/libcurl/curl_strequal.md @@ -44,9 +44,9 @@ string comparison functions. This function works on all platforms. # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - const char *name = "compare"; + static const char *name = "compare"; if(curl_strequal(name, argv[1])) printf("Name and input matches\n"); } diff --git a/docs/libcurl/curl_strnequal.md b/docs/libcurl/curl_strnequal.md index db42bcdf8e21..e6ce1eb3e7d1 100644 --- a/docs/libcurl/curl_strnequal.md +++ b/docs/libcurl/curl_strnequal.md @@ -47,9 +47,9 @@ string comparison functions. This function works on all platforms. # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - const char *name = "compare"; + static const char *name = "compare"; if(curl_strnequal(name, argv[1], 5)) printf("Name and input matches in the 5 first bytes\n"); } diff --git a/docs/libcurl/curl_url_set.md b/docs/libcurl/curl_url_set.md index 2fa31123f15e..35ed109fcd53 100644 --- a/docs/libcurl/curl_url_set.md +++ b/docs/libcurl/curl_url_set.md @@ -224,6 +224,10 @@ host part (normally the only mandatory part of the authority), but libcurl cannot know whether this is permitted for custom schemes. Specifying the flag permits empty authority sections, similar to how file scheme is handled. +This option also makes libcurl accept and parse URLs with known schemes even +when they have no hostnames. URLs such as `https://` and `ftp://` otherwise +cause parse errors by default. + ## CURLU_PATH_AS_IS When set for **CURLUPART_URL**, this skips the normalization of the diff --git a/docs/libcurl/curl_version_info.md b/docs/libcurl/curl_version_info.md index fd589a834cc3..6b1d61121b38 100644 --- a/docs/libcurl/curl_version_info.md +++ b/docs/libcurl/curl_version_info.md @@ -232,6 +232,13 @@ HTTP/3 and QUIC support are built-in (Added in 7.66.0) libcurl was built with support for HTTPS-proxy. +## `HTTPSIG` + +*features* mask bit: non-existent + +libcurl was built with support for RFC 9421 HTTP Message Signatures (Added in +8.22.0) + ## `HTTPSRR` *features* mask bit: non-existent @@ -298,6 +305,13 @@ supports HTTP NTLM libcurl was built with support for NTLM delegation to a winbind helper. This feature was removed from curl in 8.8.0. +## `proxy-HTTP3` + +*features* mask bit: non-existent + +libcurl was built with EXPERIMENTAL support for HTTP/3 proxy tunneling +(Added in 8.21.0) + ## `PSL` *features* mask bit: CURL_VERSION_PSL diff --git a/docs/libcurl/curl_ws_meta.md b/docs/libcurl/curl_ws_meta.md index 978520afd8d0..6fd44ac6bfda 100644 --- a/docs/libcurl/curl_ws_meta.md +++ b/docs/libcurl/curl_ws_meta.md @@ -29,9 +29,9 @@ const struct curl_ws_frame *curl_ws_meta(CURL *curl); # DESCRIPTION -When the write callback (CURLOPT_WRITEFUNCTION(3)) is invoked on -received WebSocket traffic, curl_ws_meta(3) can be called from within -the callback to provide additional information about the current frame. +When the write callback (CURLOPT_WRITEFUNCTION(3)) is invoked on received +WebSocket traffic, curl_ws_meta(3) can be called from within the callback to +provide additional information about the current frame. This function only works from within the callback, and only when receiving WebSocket data. @@ -41,6 +41,14 @@ what transfer the question is about, but as there is no such pointer provided to the callback by libcurl itself, applications that want to use curl_ws_meta(3) need to pass it on to the callback on its own. +WebSocket messages are split into *frames*. A WebSocket message can be made up +of an arbitrary number of frames. Each WebSocket frame payload can be up to +2^63-1 bytes. When libcurl delivers WebSocket data, it splits each frame into +*chunks*; each chunk is therefore part of a frame, or at most a full frame. + +Each callback delivers data for a single chunk that is then part of a single +frame. + # struct curl_ws_frame ~~~c @@ -64,13 +72,15 @@ See the list below. ## `offset` -When this chunk is a continuation of frame data already delivered, this is -the offset into the final frame data where this piece belongs to. +When this chunk is a continuation of frame data already delivered in a +previous callback, this is the offset into the complete frame payload where +this chunk's data belongs. ## `bytesleft` -If this is not a complete fragment, the *bytesleft* field informs about how -many additional bytes are expected to arrive before this fragment is complete. +When this is not a complete frame nor the last chunk for a frame, the +*bytesleft* field informs about how many additional bytes are expected to +arrive before this frame is complete. ## `len` @@ -118,12 +128,12 @@ libcurl does not verify that the payload is valid UTF-8. Can only occur in conjunction with CURLWS_TEXT or CURLWS_BINARY. -This is not the final fragment of the message, it implies that there is -another fragment coming as part of the same message. The application must -reassemble the fragments to receive the complete message. +This is not the final *frame* of the message, it implies that there is another +*frame* coming as part of the same message. The application must reassemble +the frames to receive the complete message. -Only a single fragmented message can be transmitted at a time, but it may -be interrupted by CURLWS_CLOSE, CURLWS_PING or CURLWS_PONG frames. +Only a single multi-frame message can be transmitted at a time, but it may be +interrupted by CURLWS_CLOSE, CURLWS_PING or CURLWS_PONG frames. # %PROTOCOLS% @@ -142,7 +152,7 @@ static size_t writecb(char *buffer, size_t size, size_t nitems, void *p) struct customdata *c = (struct customdata *)p; const struct curl_ws_frame *m = curl_ws_meta(c->easy); - printf("flags: %x\n", m->flags); + printf("flags: %x\n", (unsigned int)m->flags); return 0; } diff --git a/docs/libcurl/curl_ws_send.md b/docs/libcurl/curl_ws_send.md index dc6e927ca848..74d562e86184 100644 --- a/docs/libcurl/curl_ws_send.md +++ b/docs/libcurl/curl_ws_send.md @@ -86,7 +86,7 @@ calls. int main(void) { - const char *buffer = "PAYLOAD"; + static const char *buffer = "PAYLOAD"; size_t offset = 0; CURLcode result = CURLE_OK; CURL *curl = curl_easy_init(); diff --git a/docs/libcurl/curl_ws_start_frame.md b/docs/libcurl/curl_ws_start_frame.md index e02487759c80..9fcbae027454 100644 --- a/docs/libcurl/curl_ws_start_frame.md +++ b/docs/libcurl/curl_ws_start_frame.md @@ -66,7 +66,7 @@ Supports all flags documented in curl_ws_meta(3). struct read_ctx { CURL *easy; - char *message; + const char *message; size_t msg_len; size_t nsent; }; @@ -83,7 +83,7 @@ static size_t readcb(char *buf, size_t nitems, size_t buflen, void *p) result = curl_ws_start_frame(ctx->easy, CURLWS_TEXT, (curl_off_t)ctx->msg_len); if(result != CURLE_OK) { - fprintf(stderr, "error starting frame: %d\n", result); + fprintf(stderr, "error starting frame: %d\n", (int)result); return CURL_READFUNC_ABORT; } } diff --git a/docs/libcurl/libcurl-easy.md b/docs/libcurl/libcurl-easy.md index 782108835678..fdbc75582371 100644 --- a/docs/libcurl/libcurl-easy.md +++ b/docs/libcurl/libcurl-easy.md @@ -38,14 +38,14 @@ you see what libcurl is doing under the hood, which is useful when debugging for example. The curl_easy_setopt(3) man page has a full index of the over 300 available options. -If you at any point would like to blank all previously set options for a -single easy handle, you can call curl_easy_reset(3) and you can also make a +If you at any point would like to factory-reset all previously set options for +a single easy handle, you can call curl_easy_reset(3). You can also make a clone of an easy handle (with all its set options) using curl_easy_duphandle(3). -When all is setup, you tell libcurl to perform the transfer using -curl_easy_perform(3). It performs the entire transfer operation and does not -return until it is done (successfully or not). +When all necessary options have been set on the handle, you tell libcurl to +perform the transfer with curl_easy_perform(3). It performs the entire +transfer operation and does not return until it is done (successfully or not). After the transfer has been made, you can set new options and make another transfer, or if you are done, cleanup the session by calling diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 9fa9c069d1f9..7845f5c0f5d5 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -174,6 +174,11 @@ a multi handle is destroyed. This implicitly triggers for easy handles that are run via easy_perform. The value of the environment variable gives the shutdown timeout in milliseconds. +## `CURL_DBG_HE_AAAA_AWAIT_MS` + +Overrides the time delaying a connect for AAAA resolve results to arrive +before continuing with Happy Eyeballing. + ## `CURL_DBG_RESOLV_MAX_THREADS` Overrides the maximum number of threads for resolver. @@ -185,12 +190,23 @@ Makes ever threaded resolve experience an initial delay in milliseconds. ## `CURL_DBG_RESOLV_FAIL_DELAY` With a threaded resolver, delay each lookup by the given milliseconds -and give a negative answer. +and fail it, as if a transient resolver failure happened. + +## `CURL_DBG_RESOLV_FAIL_NEGATIVE` + +With a threaded resolver, make a lookup failing via +`CURL_DBG_RESOLV_FAIL_DELAY` count as an authoritative negative answer, +eligible for negative DNS caching, instead of a transient failure. ## `CURL_DBG_RESOLV_FAIL_IPV6` Make libcurl fail a resolve for IPv6 only. +## `CURL_DBG_THRDPOOL_FAIL_STARTS` + +Fail this many thread starts in a thread pool, as if the system +refused to spawn more threads. Read when the pool is created. + ## `CURL_QUICK_EXIT` Make `curl` use the quick exit option, even when built in debug mode. @@ -199,3 +215,12 @@ Make `curl` use the quick exit option, even when built in debug mode. When happy eyeballing for https: wait for the HTTPS-RR resolve answer to arrive before starting any connect attempt. + +## `CURL_DBG_NO_USE_SSL_ON_FIRST` + +When passing `--ssl-reqd`, clear it for the first URL in a curl command. +This allows testing of connection reuse in mixed `STARTTLS` needs. + +## `CURL_DBG_SUPPRESS_CONNECT_HDS` + +Existence of this variable suppresses the collection of CONNECT headers. diff --git a/docs/libcurl/libcurl-env.md b/docs/libcurl/libcurl-env.md index 6ef11ac9a3dd..5ce846df8aa6 100644 --- a/docs/libcurl/libcurl-env.md +++ b/docs/libcurl/libcurl-env.md @@ -32,8 +32,8 @@ uses the **ftp_proxy** variable. These proxy variables are also checked for in their uppercase versions, except the **http_proxy** one which is only used lowercase. Note also that some -systems actually have a case insensitive handling of environment variables and -then of course **HTTP_PROXY** still works. +systems (like Windows) have a case insensitive handling of environment +variables and then of course **HTTP_PROXY** still works. An exception exists for the WebSocket **ws** and **wss** URL schemes, where libcurl first checks **ws_proxy** or **wss_proxy** but if they are not set, it diff --git a/docs/libcurl/libcurl-errors.md b/docs/libcurl/libcurl-errors.md index 7ae1319dcf80..6f348eb5813f 100644 --- a/docs/libcurl/libcurl-errors.md +++ b/docs/libcurl/libcurl-errors.md @@ -219,7 +219,7 @@ file boundary. ## CURLE_FILE_COULDNT_READ_FILE (37) -A file given with FILE:// could not be opened. Most likely because the file +A file given with `file://` could not be opened. Most likely because the file path does not identify an existing file. Did you check file permissions? ## CURLE_LDAP_CANNOT_BIND (38) @@ -674,7 +674,7 @@ There is no zone id set in the URL. ## CURLUE_BAD_FILE_URL (19) -The file:// URL is invalid. +The `file://` URL is invalid. ## CURLUE_BAD_FRAGMENT (20) @@ -724,6 +724,11 @@ libcurl lacks IDN support. A value or data field is larger than allowed. +## CURLUE_BACKSLASH (32) + +Found a backslash character where a forward slash was expected. URL separators +are forward slashes (`/`), not backslashes (`\`). + # CURLHcode The header interface returns a *CURLHcode* to indicate when an error has diff --git a/docs/libcurl/libcurl-multi.md b/docs/libcurl/libcurl-multi.md index b400f61a03cc..da1331fae82c 100644 --- a/docs/libcurl/libcurl-multi.md +++ b/docs/libcurl/libcurl-multi.md @@ -175,8 +175,6 @@ A few areas in the code are still using blocking code, even when used from the multi interface. While we certainly want and intend for these to get fixed in the future, you should be aware of the following current restrictions: -~~~c - Name resolves unless the c-ares or threaded-resolver backends are used -- file:// transfers +- `file://` transfers - TELNET transfers -~~~ diff --git a/docs/libcurl/libcurl-security.md b/docs/libcurl/libcurl-security.md index cca0c31e8420..cefb386380b2 100644 --- a/docs/libcurl/libcurl-security.md +++ b/docs/libcurl/libcurl-security.md @@ -54,8 +54,8 @@ stored in a home directory that is NFS mounted or used on another network based file system, so the clear text password flies through your network every time anyone reads that file. -For applications that enable .netrc use, a user who manage to set the right -URL might then be possible to pass on passwords. +For applications that enable .netrc use, a user who manages to set the right +URL might then make it possible to pass on passwords. To avoid these problems, do not use .netrc files and never store passwords in plain text anywhere. @@ -267,16 +267,16 @@ of how the SCP protocol is designed. E.g. Applications must not allow unsanitized SCP: URLs to be passed in for downloads. -# file:// +# `file://` -By default curl and libcurl support file:// URLs. Such a URL is always an +By default curl and libcurl support `file://` URLs. Such a URL is always an access, or attempted access, to a local resource. If your application wants to avoid that, keep control of what URLs to use and/or prevent curl/libcurl from using the protocol. -By default, libcurl prohibits redirects to file:// URLs. +By default, libcurl prohibits redirects to `file://` URLs. -# Warning: file:// on Windows +# Warning: `file://` on Windows The Windows operating system tries automatically, and without any way for applications to disable it, to establish a connection to another host over the diff --git a/docs/libcurl/libcurl-thread.md b/docs/libcurl/libcurl-thread.md index 8ef893f07588..9b26224ce6e6 100644 --- a/docs/libcurl/libcurl-thread.md +++ b/docs/libcurl/libcurl-thread.md @@ -13,12 +13,12 @@ Added-in: n/a # NAME -libcurl-thread - libcurl thread safety +libcurl-thread - libcurl thread-safety # Multi-threading with libcurl libcurl is thread-safe but has no internal thread synchronization. You may have -to provide your own locking should you meet any of the thread safety exceptions +to provide your own locking should you meet any of the thread-safety exceptions below. # Handles diff --git a/docs/libcurl/libcurl-tutorial.md b/docs/libcurl/libcurl-tutorial.md index 704ee0356416..befcf7739968 100644 --- a/docs/libcurl/libcurl-tutorial.md +++ b/docs/libcurl/libcurl-tutorial.md @@ -373,7 +373,7 @@ them URL encoded, as %XX where XX is a two-digit hexadecimal number. libcurl also provides options to set various passwords. The username and password as shown embedded in the URL can instead get set with the CURLOPT_USERPWD(3) option. The argument passed to libcurl should be a -char * to a string in the format "user:password". In a manner like this: +`char *` to a string in the format `"user:password"`. In a manner like this: ~~~c curl_easy_setopt(handle, CURLOPT_USERPWD, "myname:thesecret"); @@ -391,7 +391,7 @@ CURLOPT_USERPWD(3) option, like this: There is a long time Unix "standard" way of storing FTP usernames and passwords, namely in the $HOME/.netrc file (on Windows, libcurl also checks the *%USERPROFILE% environment* variable if *%HOME%* is unset, and tries -"_netrc" as name). The file should be made private so that only the user may +`_netrc` as name). The file should be made private so that only the user may read it (see also the "Security Considerations" chapter), as it might contain the password in plain text. libcurl has the ability to use this file to figure out what set of username and password to use for a particular host. As an @@ -866,23 +866,23 @@ it defaults to assuming an HTTP proxy): libcurl automatically checks and uses a set of environment variables to know what proxies to use for certain protocols. The names of the variables are -following an old tradition and are built up as "[protocol]_proxy" (note the -lower casing). Which makes the variable 'http_proxy' checked for a name of a +following an old tradition and are built up as `[protocol]_proxy` (note the +lower casing). Which makes the variable `http_proxy` checked for a name of a proxy to use when the input URL is HTTP. Following the same rule, the variable -named 'ftp_proxy' is checked for FTP URLs. Again, the proxies are always HTTP +named `ftp_proxy` is checked for FTP URLs. Again, the proxies are always HTTP proxies, the different names of the variables allow different HTTP proxies to be used. The proxy environment variable contents should be in the format -"[protocol://][user:password@]machine[:port]". Where the protocol:// part +`[protocol://][user:password@]machine[:port]`. Where the `protocol://` part specifies which type of proxy it is, and the optional port number specifies on which port the proxy operates. If not specified, the internal default port number is used and that is most likely not the one you would like it to be. -There are two special environment variables. 'all_proxy' is what sets proxy -for any URL in case the protocol specific variable was not set, and 'no_proxy' +There are two special environment variables. `all_proxy` is what sets proxy +for any URL in case the protocol specific variable was not set, and `no_proxy` defines a list of hosts that should not use a proxy even though a variable may -say so. If 'no_proxy' is a plain asterisk ("*") it matches all hosts. +say so. If `no_proxy` is a plain asterisk (`*`) it matches all hosts. To explicitly disable libcurl's checking for and using the proxy environment variables, set the proxy name to "" - an empty string - with @@ -1420,7 +1420,7 @@ to figure out success on each individual transfer. # SSL, Certificates and Other Tricks -[ seeding, passwords, keys, certificates, ENGINE, ca certs ] +[ seeding, passwords, keys, certificates, ENGINE, CA certs ] # Sharing Data Between Easy Handles diff --git a/docs/libcurl/libcurl-url.md b/docs/libcurl/libcurl-url.md index b39d0304d5c6..bc936fcb4d0a 100644 --- a/docs/libcurl/libcurl-url.md +++ b/docs/libcurl/libcurl-url.md @@ -108,8 +108,8 @@ with it. # SET PARTS -A user set individual URL parts, either after having parsed a full URL or -instead of parsing such. +A user can set individual URL parts, either after having parsed a full URL or +instead of parsing one. ~~~c rc = curl_url_set(urlp, CURLUPART_FRAGMENT, "anchor", 0); diff --git a/docs/libcurl/libcurl-ws.md b/docs/libcurl/libcurl-ws.md index 147740157fee..cce13abb243f 100644 --- a/docs/libcurl/libcurl-ws.md +++ b/docs/libcurl/libcurl-ws.md @@ -39,7 +39,7 @@ WebSocket is a TCP-like message-based communication protocol done over HTTP, specified in RFC 6455. To initiate a WebSocket session with libcurl, setup an easy handle to use a -URL with a "WS://" or "WSS://" scheme. "WS" is for cleartext communication +URL with a `ws://` or `wss://` scheme. "WS" is for cleartext communication over HTTP and "WSS" is for doing WebSocket securely over HTTPS. A WebSocket request is done as an HTTP/1 GET request with an "Upgrade diff --git a/docs/libcurl/libcurl.md b/docs/libcurl/libcurl.md index 96f10656eed9..da61d4c5ea51 100644 --- a/docs/libcurl/libcurl.md +++ b/docs/libcurl/libcurl.md @@ -174,7 +174,7 @@ to select the active SSL backend. The global constant functions are thread-safe since libcurl 7.84.0 if curl_version_info(3) has the CURL_VERSION_THREADSAFE feature bit set -(most platforms). Read libcurl-thread(3) for thread safety guidelines. +(most platforms). Read libcurl-thread(3) for thread-safety guidelines. If the global constant functions are *not thread-safe*, then you must not call them when any other thread in the program is running. It diff --git a/docs/libcurl/mksymbolsmanpage.pl b/docs/libcurl/mksymbolsmanpage.pl index c70e99495758..9db028e7c4fc 100755 --- a/docs/libcurl/mksymbolsmanpage.pl +++ b/docs/libcurl/mksymbolsmanpage.pl @@ -70,7 +70,7 @@ ; sub nameref { - my ($n)=@_; + my ($n) = @_; if($n =~ /^CURLOPT_/) { if($n eq "CURLOPT_RTSPHEADER") { $n = "CURLOPT_HTTPHEADER"; @@ -289,7 +289,7 @@ sub nameref { while() { if($_ =~ /^(CURL[A-Z0-9_.]*) *(.*)/i) { - my ($symbol, $rest)=($1,$2); + my ($symbol, $rest) = ($1, $2); my ($intro, $dep, $rem); if($rest =~ s/^([0-9.]*) *//) { $intro = $1; diff --git a/docs/libcurl/opts/CURLINFO_CAINFO.md b/docs/libcurl/opts/CURLINFO_CAINFO.md index 23502d22966a..626abca19e3f 100644 --- a/docs/libcurl/opts/CURLINFO_CAINFO.md +++ b/docs/libcurl/opts/CURLINFO_CAINFO.md @@ -53,7 +53,7 @@ int main(void) char *cainfo = NULL; curl_easy_getinfo(curl, CURLINFO_CAINFO, &cainfo); if(cainfo) { - printf("default ca info path: %s\n", cainfo); + printf("default CA info path: %s\n", cainfo); } curl_easy_cleanup(curl); } diff --git a/docs/libcurl/opts/CURLINFO_CAPATH.md b/docs/libcurl/opts/CURLINFO_CAPATH.md index c58930e0e9bc..603634da8a4d 100644 --- a/docs/libcurl/opts/CURLINFO_CAPATH.md +++ b/docs/libcurl/opts/CURLINFO_CAPATH.md @@ -56,7 +56,7 @@ int main(void) char *capath = NULL; curl_easy_getinfo(curl, CURLINFO_CAPATH, &capath); if(capath) { - printf("default ca path: %s\n", capath); + printf("default CA path: %s\n", capath); } curl_easy_cleanup(curl); } diff --git a/docs/libcurl/opts/CURLINFO_CERTINFO.md b/docs/libcurl/opts/CURLINFO_CERTINFO.md index 0882cbccd181..4d95ad7509b3 100644 --- a/docs/libcurl/opts/CURLINFO_CERTINFO.md +++ b/docs/libcurl/opts/CURLINFO_CERTINFO.md @@ -33,7 +33,7 @@ CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_CERTINFO, # DESCRIPTION -Pass a pointer to a *struct curl_certinfo ** and it is set to point to a +Pass a pointer to a `struct curl_certinfo *` and it is set to point to a struct that holds info about the server's certificate chain, assuming you had CURLOPT_CERTINFO(3) enabled when the request was made. diff --git a/docs/libcurl/opts/CURLINFO_CONTENT_LENGTH_UPLOAD_T.md b/docs/libcurl/opts/CURLINFO_CONTENT_LENGTH_UPLOAD_T.md index 219481f43df1..7b5d05852155 100644 --- a/docs/libcurl/opts/CURLINFO_CONTENT_LENGTH_UPLOAD_T.md +++ b/docs/libcurl/opts/CURLINFO_CONTENT_LENGTH_UPLOAD_T.md @@ -31,6 +31,11 @@ CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_CONTENT_LENGTH_UPLOAD_T, Pass a pointer to a *curl_off_t* to receive the specified size of the upload. Stores -1 if the size is not known. +This is the size set by the client prior to the transfer start. The expected +upload amount. Compare this with CURLINFO_SIZE_UPLOAD_T(3), which is the +amount of data that was actually uploaded in the end. In many cases those two +numbers are identical. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLINFO_HEADER_SIZE.md b/docs/libcurl/opts/CURLINFO_HEADER_SIZE.md index c27856809ee5..f0ce241e3f00 100644 --- a/docs/libcurl/opts/CURLINFO_HEADER_SIZE.md +++ b/docs/libcurl/opts/CURLINFO_HEADER_SIZE.md @@ -29,11 +29,15 @@ CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_HEADER_SIZE, long *sizep); # DESCRIPTION Pass a pointer to a long to receive the total size of all the headers -received. Measured in number of bytes. +received, represented in HTTP/1-style header format. Measured in number of +bytes. The total includes the size of any received headers suppressed by CURLOPT_SUPPRESS_CONNECT_HEADERS(3). +The number of bytes transferred over the wire (or to the TLS backend) is +different when using HTTP/2 or greater. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLINFO_HTTPAUTH_AVAIL.md b/docs/libcurl/opts/CURLINFO_HTTPAUTH_AVAIL.md index 2a65509ede58..b9d18805c3af 100644 --- a/docs/libcurl/opts/CURLINFO_HTTPAUTH_AVAIL.md +++ b/docs/libcurl/opts/CURLINFO_HTTPAUTH_AVAIL.md @@ -55,10 +55,10 @@ int main(void) printf("No auth available, perhaps no 401?\n"); else { printf("%s%s%s%s\n", - auth & CURLAUTH_BASIC ? "Basic " : "", - auth & CURLAUTH_DIGEST ? "Digest " : "", - auth & CURLAUTH_NEGOTIATE ? "Negotiate " : "", - auth % CURLAUTH_NTLM ? "NTLM " : ""); + (unsigned long)auth & CURLAUTH_BASIC ? "Basic " : "", + (unsigned long)auth & CURLAUTH_DIGEST ? "Digest " : "", + (unsigned long)auth & CURLAUTH_NEGOTIATE ? "Negotiate " : "", + (unsigned long)auth & CURLAUTH_NTLM ? "NTLM " : ""); } } } diff --git a/docs/libcurl/opts/CURLINFO_PROXYAUTH_AVAIL.md b/docs/libcurl/opts/CURLINFO_PROXYAUTH_AVAIL.md index c5110ef3771b..7ae437d18d57 100644 --- a/docs/libcurl/opts/CURLINFO_PROXYAUTH_AVAIL.md +++ b/docs/libcurl/opts/CURLINFO_PROXYAUTH_AVAIL.md @@ -56,10 +56,10 @@ int main(void) printf("No proxy auth available, perhaps no 407?\n"); else { printf("%s%s%s%s\n", - auth & CURLAUTH_BASIC ? "Basic " : "", - auth & CURLAUTH_DIGEST ? "Digest " : "", - auth & CURLAUTH_NEGOTIATE ? "Negotiate " : "", - auth % CURLAUTH_NTLM ? "NTLM " : ""); + (unsigned long)auth & CURLAUTH_BASIC ? "Basic " : "", + (unsigned long)auth & CURLAUTH_DIGEST ? "Digest " : "", + (unsigned long)auth & CURLAUTH_NEGOTIATE ? "Negotiate " : "", + (unsigned long)auth & CURLAUTH_NTLM ? "NTLM " : ""); } } } diff --git a/docs/libcurl/opts/CURLINFO_TLS_SSL_PTR.md b/docs/libcurl/opts/CURLINFO_TLS_SSL_PTR.md index c79b5e7ebd17..f5c979b57177 100644 --- a/docs/libcurl/opts/CURLINFO_TLS_SSL_PTR.md +++ b/docs/libcurl/opts/CURLINFO_TLS_SSL_PTR.md @@ -127,7 +127,8 @@ https://github.com/curl/curl/issues/685 #include #include -CURL *curl; +static CURL *curl; + static size_t wf(char *ptr, size_t size, size_t nmemb, void *stream) { const struct curl_tlssessioninfo *info = NULL; @@ -140,9 +141,9 @@ static size_t wf(char *ptr, size_t size, size_t nmemb, void *stream) return size * nmemb; } -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - CURLcode result; + CURLcode result = CURLE_OK; curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); @@ -150,7 +151,7 @@ int main(int argc, char **argv) result = curl_easy_perform(curl); curl_easy_cleanup(curl); } - return result; + return (int)result; } ~~~ diff --git a/docs/libcurl/opts/CURLMOPT_MAXCONNECTS.md b/docs/libcurl/opts/CURLMOPT_MAXCONNECTS.md index b02029b59fcd..5973fa8c55cd 100644 --- a/docs/libcurl/opts/CURLMOPT_MAXCONNECTS.md +++ b/docs/libcurl/opts/CURLMOPT_MAXCONNECTS.md @@ -28,14 +28,19 @@ CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_MAXCONNECTS, long max); Pass a long indicating the **max**, the maximum amount of connections that libcurl may keep alive in its connection cache after use. By default libcurl -enlarges the size for each added easy handle to make it fit 4 times the number +enlarges the size for each added easy handle to make it fit twice the number of added easy handles. By setting this option, you prevent the cache size from growing beyond the limit set by you. -When the cache is full, curl closes the oldest connection present in the cache -to prevent the number of connections from increasing. +When the cache is full on a set limit, curl closes the oldest connection +present in the cache to prevent the number of connections from increasing. + +When the cache is full on the default limit, curl closes the oldest connection +present in the cache only if it has not been used for at least a second. This +is done because the number of added transfer can vary greatly, depending on +how the application uses them. This option is for the multi handle's use only, when using the easy interface you should instead use the CURLOPT_MAXCONNECTS(3) option. diff --git a/docs/libcurl/opts/CURLMOPT_PIPELINING_SERVER_BL.md b/docs/libcurl/opts/CURLMOPT_PIPELINING_SERVER_BL.md index 6d3764ef90ce..c57020b01ec3 100644 --- a/docs/libcurl/opts/CURLMOPT_PIPELINING_SERVER_BL.md +++ b/docs/libcurl/opts/CURLMOPT_PIPELINING_SERVER_BL.md @@ -49,12 +49,13 @@ NULL, which means that there is no block list. # EXAMPLE ~~~c -static char *server_block_list[] = +static const char *server_block_list[] = { "Microsoft-IIS/6.0", "nginx/0.8.54", NULL }; + int main(void) { CURLM *m = curl_multi_init(); diff --git a/docs/libcurl/opts/CURLMOPT_PIPELINING_SITE_BL.md b/docs/libcurl/opts/CURLMOPT_PIPELINING_SITE_BL.md index 19fd0fdba919..b9d27cb3394e 100644 --- a/docs/libcurl/opts/CURLMOPT_PIPELINING_SITE_BL.md +++ b/docs/libcurl/opts/CURLMOPT_PIPELINING_SITE_BL.md @@ -44,7 +44,7 @@ NULL, which means that there is no block list. # EXAMPLE ~~~c -static char *site_block_list[] = +static const char *site_block_list[] = { "www.haxx.se", "www.example.com:1234", diff --git a/docs/libcurl/opts/CURLMOPT_PUSHDATA.md b/docs/libcurl/opts/CURLMOPT_PUSHDATA.md index f3383d3ece5c..4204edcecf68 100644 --- a/docs/libcurl/opts/CURLMOPT_PUSHDATA.md +++ b/docs/libcurl/opts/CURLMOPT_PUSHDATA.md @@ -44,11 +44,11 @@ NULL #include /* only allow pushes for filenames starting with "push-" */ -int push_callback(CURL *parent, - CURL *easy, - size_t num_headers, - struct curl_pushheaders *headers, - void *clientp) +static int push_callback(CURL *parent, + CURL *easy, + size_t num_headers, + struct curl_pushheaders *headers, + void *clientp) { char *headp; int *transfers = (int *)clientp; diff --git a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md index 4bead2b94fdd..1336133cde0b 100644 --- a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md @@ -105,11 +105,11 @@ NULL, no callback #include /* only allow pushes for filenames starting with "push-" */ -int push_callback(CURL *parent, - CURL *easy, - size_t num_headers, - struct curl_pushheaders *headers, - void *clientp) +static int push_callback(CURL *parent, + CURL *easy, + size_t num_headers, + struct curl_pushheaders *headers, + void *clientp) { char *headp; int *transfers = (int *)clientp; diff --git a/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md b/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md index 411b856e9328..fb67c01a60a9 100644 --- a/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md @@ -7,6 +7,7 @@ Source: libcurl See-also: - CURLMOPT_SOCKETDATA (3) - CURLMOPT_TIMERFUNCTION (3) + - CURLOPT_CLOSESOCKETFUNCTION (3) - curl_multi_socket_action (3) Protocol: - All @@ -22,13 +23,14 @@ CURLMOPT_SOCKETFUNCTION - callback informed about what to wait for ~~~c #include -int socket_callback(CURL *easy, /* easy handle */ - curl_socket_t s, /* socket */ - int what, /* describes the socket */ - void *clientp, /* private callback pointer */ - void *socketp); /* private socket pointer */ +int socket_callback(CURL *easy, + curl_socket_t socket, + int what, + void *clientp, + void *socketp); -CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_SOCKETFUNCTION, socket_callback); +CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_SOCKETFUNCTION, + socket_callback); ~~~ # DESCRIPTION @@ -36,12 +38,12 @@ CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_SOCKETFUNCTION, socket_callb Pass a pointer to your callback function, which should match the prototype shown above. -When the curl_multi_socket_action(3) function is called, it uses this -callback to inform the application about updates in the socket (file -descriptor) status by doing none, one, or multiple calls to the -**socket_callback**. The callback function gets status updates with changes -since the previous time the callback was called. If the given callback pointer -is set to NULL, no callback is called. +When the curl_multi_socket_action(3) function is called, it uses this callback +to inform the application about updates in the socket (file descriptor) status +by doing none, one, or multiple calls to the **socket_callback**. The callback +function gets status updates with changes since the previous time the callback +was called. If the given callback pointer is set to NULL, no callback is +called. libcurl then expects the application to monitor the sockets for the specific activities and tell libcurl again when something happens on one of them. Tell @@ -52,18 +54,23 @@ This may even happen after all transfers are done and is *likely* to happen *during* a call to curl_multi_cleanup(3) when cached connections are shut down. +libcurl may use a number of internal file descriptors for name resolving, +Happy Eyeballs racing, internal communication and more, in addition to the +main sockets used for network transfers. All of those file descriptors might +get passed to this callback as "sockets". + # CALLBACK ARGUMENTS -*easy* identifies the specific transfer for which this update is related. +**easy** identifies the specific transfer for which this update is related. Since this callback manages a whole multi handle, an application should not make assumptions about which particular handle that is passed here. It might even be an internal easy handle that the application did not add itself. -*s* is the specific socket this function invocation concerns. If the -**what** argument is not CURL_POLL_REMOVE then it holds information about -what activity on this socket the application is supposed to -monitor. Subsequent calls to this callback might update the **what** bits -for a socket that is already monitored. +**socket** is the specific socket this function invocation concerns. If the +**what** argument is not CURL_POLL_REMOVE then it holds information about what +activity on this socket the application is supposed to monitor. Subsequent +calls to this callback might update the **what** bits for a socket that is +already monitored. The socket callback should return 0 on success, and -1 on error. If this callback returns error, **all** transfers currently in progress in this @@ -73,8 +80,8 @@ multi handle are aborted and made to fail. **socketp** is set with curl_multi_assign(3) or NULL. -The **what** parameter informs the callback on the status of the given -socket. It can hold one of these values: +The **what** parameter informs the callback on the status of the given socket. +It can hold one of these values: ## CURL_POLL_IN @@ -91,9 +98,17 @@ writable. ## CURL_POLL_REMOVE -The specified socket/file descriptor is no longer used by libcurl for any +The specified socket or file descriptor is no longer used by libcurl for any active transfer. It might soon be added again. +When a socket is given a CURL_POLL_REMOVE value, it might be because libcurl +is going to close it, but it might also mean that it does not need any more +monitoring for the moment. An application cannot assume either. The same +socket might appear soon in a call asking for monitoring again. + +A socket that is removed like this loses its assigned pointer as set with +curl_multi_assign(3). + # DEFAULT NULL (no callback) diff --git a/docs/libcurl/opts/CURLOPT_CAINFO_BLOB.md b/docs/libcurl/opts/CURLOPT_CAINFO_BLOB.md index ffced772b4d8..0b243da5a76d 100644 --- a/docs/libcurl/opts/CURLOPT_CAINFO_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_CAINFO_BLOB.md @@ -58,17 +58,18 @@ NULL # EXAMPLE ~~~c +#include /* for uintptr_t */ #include int main(void) { - char *strpem = "PEMDATA"; /* strpem must point to a PEM string */ + static const char *strpem = "PEMDATA"; /* must point to a PEM string */ CURL *curl = curl_easy_init(); if(curl) { CURLcode result; struct curl_blob blob; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); - blob.data = strpem; + blob.data = (void *)(uintptr_t)(const void *)strpem; /* strip const */ blob.len = strlen(strpem); blob.flags = CURL_BLOB_COPY; curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); diff --git a/docs/libcurl/opts/CURLOPT_CHUNK_BGN_FUNCTION.md b/docs/libcurl/opts/CURLOPT_CHUNK_BGN_FUNCTION.md index 838b2e5a5f07..b632e1a3a759 100644 --- a/docs/libcurl/opts/CURLOPT_CHUNK_BGN_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_CHUNK_BGN_FUNCTION.md @@ -37,7 +37,8 @@ struct curl_fileinfo { char *perm; char *user; char *group; - char *target; /* pointer to the target filename of a symlink */ + char *target; /* pointer to the target filename of a symlink, only + available for CURLFILETYPE_SYMLINK */ } strings; unsigned int flags; @@ -131,7 +132,7 @@ static long file_is_coming(struct curl_fileinfo *finfo, return CURL_CHUNK_BGN_FUNC_OK; } -int main() +int main(void) { /* data for callback */ struct callback_data callback_info; diff --git a/docs/libcurl/opts/CURLOPT_CHUNK_DATA.md b/docs/libcurl/opts/CURLOPT_CHUNK_DATA.md index fb1877e49174..e571b7c4962e 100644 --- a/docs/libcurl/opts/CURLOPT_CHUNK_DATA.md +++ b/docs/libcurl/opts/CURLOPT_CHUNK_DATA.md @@ -81,7 +81,7 @@ static long file_is_coming(struct curl_fileinfo *finfo, return CURL_CHUNK_BGN_FUNC_OK; } -int main() +int main(void) { /* data for callback */ struct callback_data callback_info; diff --git a/docs/libcurl/opts/CURLOPT_CHUNK_END_FUNCTION.md b/docs/libcurl/opts/CURLOPT_CHUNK_END_FUNCTION.md index 82bf9ad5baa6..3f365941bfb5 100644 --- a/docs/libcurl/opts/CURLOPT_CHUNK_END_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_CHUNK_END_FUNCTION.md @@ -63,7 +63,7 @@ static long file_is_downloaded(void *ptr) return CURL_CHUNK_END_FUNC_OK; } -int main() +int main(void) { /* data for callback */ struct callback_data callback_info; diff --git a/docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md b/docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md index 0dc9bb4c6115..42a58e25a505 100644 --- a/docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md @@ -7,6 +7,7 @@ Source: libcurl See-also: - CURLOPT_CLOSESOCKETDATA (3) - CURLOPT_OPENSOCKETFUNCTION (3) + - CURLMOPT_SOCKETFUNCTION (3) Protocol: - All Added-in: 7.21.7 @@ -47,6 +48,18 @@ after the easy handle has been cleaned up. The callback and data is inherited by a new connection and that connection may live longer than the transfer itself in the multi/share handle's connection cache. +# NOTES ON CONNECTION REUSE + +The close socket callback is invoked when libcurl closes a socket it owns. +When using the multi interface, the callback and +CURLOPT_CLOSESOCKETDATA(3) are copied from the *first* easy handle that +creates the socket used for a connection; changing this option on a subsequent +easy handle that reuses the same connection has no effect for that connection. +The callback is stored with the connection because the connection and its +associated socket may outlive the easy handle that created it, so that libcurl +can still invoke it when the socket is closed even after that handle has been +cleaned up. + # DEFAULT Use the standard socket close function. diff --git a/docs/libcurl/opts/CURLOPT_COOKIE.md b/docs/libcurl/opts/CURLOPT_COOKIE.md index 4c47c1fdafa5..1e363cf7ea7b 100644 --- a/docs/libcurl/opts/CURLOPT_COOKIE.md +++ b/docs/libcurl/opts/CURLOPT_COOKIE.md @@ -43,12 +43,12 @@ multiple requests are done due to authentication, followed redirections or similar, they all get this cookie passed on. The cookies set by this option are separate from the internal cookie storage -held by the cookie engine and they are not be modified by it. If you enable +held by the cookie engine and they are not modified by it. If you enable the cookie engine and either you have imported a cookie of the same name (e.g. 'foo') or the server has set one, it has no effect on the cookies you set here. A request to the server sends both the 'foo' held by the cookie engine and the 'foo' held by this option. To set a cookie that is instead held by the -cookie engine and can be modified by the server use CURLOPT_COOKIELIST(3). +cookie engine and can be modified by the server, use CURLOPT_COOKIELIST(3). Since this custom cookie is appended to the Cookie: header in addition to any cookies set by the cookie engine, there is a risk that the header ends up too diff --git a/docs/libcurl/opts/CURLOPT_COOKIEFILE.md b/docs/libcurl/opts/CURLOPT_COOKIEFILE.md index bf2d374464ca..ccccba5fe325 100644 --- a/docs/libcurl/opts/CURLOPT_COOKIEFILE.md +++ b/docs/libcurl/opts/CURLOPT_COOKIEFILE.md @@ -39,14 +39,13 @@ By passing the empty string ("") to this option, you enable the cookie engine without reading any initial cookies. If you tell libcurl the filename is "-" (a single minus sign), libcurl instead reads from stdin. -This option only **reads** cookies. To make libcurl write cookies to file, -see CURLOPT_COOKIEJAR(3). - -If you read cookies from a plain HTTP headers file and it does not specify a -domain in the Set-Cookie line, then the cookie is not sent since the cookie -domain cannot match the target URL's. To address this, set a domain in -Set-Cookie line (doing that includes subdomains) or preferably: use the -Netscape format. +This option only **reads** cookies. To make libcurl write cookies to file, see +CURLOPT_COOKIEJAR(3). + +If you read cookies from a plain HTTP headers file, make sure each +`Set-Cookie` line specifies a `Domain` attribute. Without an explicit domain, +libcurl cannot reliably associate the cookie with a host and it may be applied +in unexpected ways. We suggest using the Netscape file format instead. The application does not have to keep the string around after setting this option. diff --git a/docs/libcurl/opts/CURLOPT_COOKIELIST.md b/docs/libcurl/opts/CURLOPT_COOKIELIST.md index ae7d650db37b..e55699b49809 100644 --- a/docs/libcurl/opts/CURLOPT_COOKIELIST.md +++ b/docs/libcurl/opts/CURLOPT_COOKIELIST.md @@ -47,6 +47,18 @@ future transfers to that server, likely not what you intended. To address these issues set a domain in `Set-Cookie` (doing that includes subdomains) or much better: use the Netscape file format. +Cookies added through this API bypass automatic Public Suffix List (PSL) +checking because the handle's internal PSL engine has not yet been initialized +when the call is made. Under normal transfer operations, PSL validation +prevents cookies from being set on broad or shared domains - such as `.com`, +`.co.uk`, or `.github.io` - which would otherwise create security +vulnerabilities by allowing unrelated subdomains to access sensitive cookie +data. Because the library skips this safety check during manual cookie +insertion, the caller assumes full responsibility for domain validation. +Applications using this interface must independently verify that the target +domain attribute represents a valid host and does not match a public suffix +before injecting the cookie into the handle. + Additionally, there are commands available that perform actions if you pass in these exact strings: @@ -64,7 +76,9 @@ writes all known cookies to the file specified by CURLOPT_COOKIEJAR(3) ## `RELOAD` -loads all cookies from the files specified by CURLOPT_COOKIEFILE(3) +loads all cookies from the files specified by CURLOPT_COOKIEFILE(3). If +CURLOPT_COOKIESESSION(3) is enabled before this reload, it is applied to this +load operation as well and all session cookies are discarded. # DEFAULT @@ -100,17 +114,15 @@ int main(void) before a transfer is performed. Cookies in the list that have the same hostname, path and name as in my_cookie are skipped. That is because libcurl has already imported my_cookie and it is considered a "live" - cookie. A live cookie is not replaced by one read from a file. - */ + cookie. A live cookie is not replaced by one read from a file. */ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt"); /* import */ /* Cookies are exported after curl_easy_cleanup is called. The server may have added, deleted or modified cookies by then. The cookies that - were skipped on import are not exported. - */ + were skipped on import are not exported. */ curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt"); /* export */ - result = curl_easy_perform(curl); /* cookies imported from cookies.txt */ + result = curl_easy_perform(curl); /* cookies imported from cookies.txt */ curl_easy_cleanup(curl); /* cookies exported to cookies.txt */ } diff --git a/docs/libcurl/opts/CURLOPT_COPYPOSTFIELDS.md b/docs/libcurl/opts/CURLOPT_COPYPOSTFIELDS.md index d7613f124760..96269eb4c850 100644 --- a/docs/libcurl/opts/CURLOPT_COPYPOSTFIELDS.md +++ b/docs/libcurl/opts/CURLOPT_COPYPOSTFIELDS.md @@ -63,7 +63,7 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { CURLcode result; - char local_buffer[1024]="data to send"; + char local_buffer[1024] = "data to send"; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); /* size of the data to copy from the buffer and send in the request */ diff --git a/docs/libcurl/opts/CURLOPT_DEBUGFUNCTION.md b/docs/libcurl/opts/CURLOPT_DEBUGFUNCTION.md index b1d0dd872c46..e66a470f6ada 100644 --- a/docs/libcurl/opts/CURLOPT_DEBUGFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_DEBUGFUNCTION.md @@ -119,10 +119,10 @@ static void dump(const char *text, unsigned int width = 0x10; fprintf(stream, "%s, %lu bytes (0x%lx)\n", - text, (long)size, (long)size); + text, (unsigned long)size, (unsigned long)size); for(i = 0; i < size; i += width) { - fprintf(stream, "%4.4lx: ", (long)i); + fprintf(stream, "%4.4lx: ", (unsigned long)i); /* show hex to the left */ for(c = 0; c < width; c++) { @@ -134,7 +134,8 @@ static void dump(const char *text, /* show data on the right */ for(c = 0; (c < width) && (i + c < size); c++) { - char x = (ptr[i + c] >= 0x20 && ptr[i + c] < 0x80) ? ptr[i + c] : '.'; + char x = (ptr[i + c] >= 0x20 && ptr[i + c] < 0x80) + ? (char)ptr[i + c] : '.'; fputc(x, stream); } diff --git a/docs/libcurl/opts/CURLOPT_DISALLOW_USERNAME_IN_URL.md b/docs/libcurl/opts/CURLOPT_DISALLOW_USERNAME_IN_URL.md index 7435f9866840..e896b8b685ba 100644 --- a/docs/libcurl/opts/CURLOPT_DISALLOW_USERNAME_IN_URL.md +++ b/docs/libcurl/opts/CURLOPT_DISALLOW_USERNAME_IN_URL.md @@ -29,12 +29,14 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DISALLOW_USERNAME_IN_URL, # DESCRIPTION -A long parameter set to 1 tells the library to not allow URLs that include a -username. +A long parameter set to 1 tells the library to not allow URLs set with +CURLOPT_URL(3) that include a username. This is the equivalent to the *CURLU_DISALLOW_USER* flag for the curl_url_set(3) function. +Note that this option does not affect URLs set with CURLOPT_CURLU(3). + # DEFAULT 0 (disabled) diff --git a/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md b/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md index 5a9b7bbb9af8..893852d3ead9 100644 --- a/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md +++ b/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.md @@ -53,7 +53,9 @@ libcurl prunes entries from the DNS cache if it exceeds 30,000 entries no matter which timeout value is used. (Added in version 8.1.0) Since curl 8.16.0, failed name resolves are stored in the DNS cache for half -the set timeout period. +the set timeout period. Since curl 8.22.0, this only happens when the +resolver answered that the name does not exist, not on transient or local +resolver failures. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_DOH_URL.md b/docs/libcurl/opts/CURLOPT_DOH_URL.md index 1c463f13caff..696903c6baeb 100644 --- a/docs/libcurl/opts/CURLOPT_DOH_URL.md +++ b/docs/libcurl/opts/CURLOPT_DOH_URL.md @@ -47,6 +47,8 @@ option. Using this option multiple times makes the last set string override the previous ones. Set it to NULL to disable its use again. +DoH lookups do not inherit proxy options from its parent transfer. + # INHERIT OPTIONS DoH lookups use SSL and some SSL settings from your transfer are inherited, @@ -97,6 +99,6 @@ curl_easy_setopt(3) returns a CURLcode indicating success or error. CURLE_OK (0) means everything was OK, non-zero means an error occurred, see libcurl-errors(3). -Note that curl_easy_setopt(3) does immediately parse the given string so when -given a bad DoH URL, libcurl might not detect the problem until it later tries -to resolve a name with it. +Note that curl_easy_setopt(3) does not immediately parse the given string so +when given a bad DoH URL, libcurl might not detect the problem until it later +tries to resolve a name with it. diff --git a/docs/libcurl/opts/CURLOPT_ECH.md b/docs/libcurl/opts/CURLOPT_ECH.md index e409d58cece4..660102c4ce43 100644 --- a/docs/libcurl/opts/CURLOPT_ECH.md +++ b/docs/libcurl/opts/CURLOPT_ECH.md @@ -29,14 +29,10 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ECH, char *config); # DESCRIPTION -ECH is only compatible with TLSv1.3. - -This experimental feature requires a special build of OpenSSL, as ECH is not -yet supported in OpenSSL releases. In contrast ECH is supported by the latest -BoringSSL, wolfSSL and Rustls-ffi releases. +This feature is **experimental** and may change before it is considered +stable. We advise against using it in production. -There is also a known issue with using wolfSSL which does not support ECH when -the HelloRetryRequest mechanism is used. +ECH is only compatible with TLSv1.3. Pass a string that specifies configuration details for ECH. In all cases, if ECH is attempted, it may fail for various reasons. The keywords supported are: @@ -92,8 +88,8 @@ int main(void) { CURL *curl = curl_easy_init(); - const char *config = \ - "ecl:AED+DQA87wAgACB/RuzUCsW3uBbSFI7mzD63TUXpI8sGDTnFTbFCDpa+" \ + static const char *config = + "ecl:AED+DQA87wAgACB/RuzUCsW3uBbSFI7mzD63TUXpI8sGDTnFTbFCDpa+" "CAAEAAEAAQANY292ZXIuZGVmby5pZQAA"; if(curl) { CURLcode result; diff --git a/docs/libcurl/opts/CURLOPT_ERRORBUFFER.md b/docs/libcurl/opts/CURLOPT_ERRORBUFFER.md index 6ba194aabb92..ff5ee86c0044 100644 --- a/docs/libcurl/opts/CURLOPT_ERRORBUFFER.md +++ b/docs/libcurl/opts/CURLOPT_ERRORBUFFER.md @@ -82,12 +82,11 @@ int main(void) result = curl_easy_perform(curl); /* if the request did not complete correctly, show the error - information. if no detailed error information was written to errbuf - show the more generic information from curl_easy_strerror instead. - */ + information. if no detailed error information was written to errbuf + show the more generic information from curl_easy_strerror instead. */ if(result != CURLE_OK) { size_t len = strlen(errbuf); - fprintf(stderr, "\nlibcurl: (%d) ", result); + fprintf(stderr, "\nlibcurl: (%d) ", (int)result); if(len) fprintf(stderr, "%s%s", errbuf, ((errbuf[len - 1] != '\n') ? "\n" : "")); diff --git a/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.md b/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.md index 77ef0e06f33f..1cfb53de6012 100644 --- a/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.md +++ b/docs/libcurl/opts/CURLOPT_HAPROXYPROTOCOL.md @@ -33,6 +33,10 @@ send this header. This option is primarily useful when sending test requests to a service that expects this header. +Note that the HAProxy protocol message is only is sent over a freshly setup +connection. A subsequent transfer that reuses a previous connection does not +send it again. + Most applications do not need this option. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_HEADERDATA.md b/docs/libcurl/opts/CURLOPT_HEADERDATA.md index 7df0f0d1e6ef..39770989b91f 100644 --- a/docs/libcurl/opts/CURLOPT_HEADERDATA.md +++ b/docs/libcurl/opts/CURLOPT_HEADERDATA.md @@ -51,7 +51,7 @@ NULL ~~~c struct my_info { int shoesize; - char *secret; + const char *secret; }; static size_t header_callback(char *buffer, size_t size, diff --git a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md index e89ec887f21b..107e9e524c0b 100644 --- a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md @@ -92,10 +92,11 @@ curl_easy_header(3). # LIMITATIONS -libcurl does not unfold HTTP "folded headers" (deprecated since RFC 7230). A -folded header is a header that continues on a subsequent line and starts with -a whitespace. Such folds are passed to the header callback as separate ones, -although strictly they are continuations of the previous lines. +For legacy HTTP/1 "folded headers" (deprecated since RFC 7230), libcurl +unfolds the lines before calling the header callback. A folded header is a +header that continues on a subsequent line and starts with whitespace. The +callback gets the full single header with one whitespace between the lines. +This unfolding is done since 8.18.0. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_HSTS.md b/docs/libcurl/opts/CURLOPT_HSTS.md index 2dc0c457d6cd..12efff50bc79 100644 --- a/docs/libcurl/opts/CURLOPT_HSTS.md +++ b/docs/libcurl/opts/CURLOPT_HSTS.md @@ -66,7 +66,7 @@ NULL, no filename # SECURITY CONCERNS -We strongly urge users to stick to `HTTPS://` URLs, which makes this option +We strongly urge users to stick to `https://` URLs, which makes this option unnecessary. libcurl cannot fully protect against attacks where an attacker has write diff --git a/docs/libcurl/opts/CURLOPT_HSTSREADDATA.md b/docs/libcurl/opts/CURLOPT_HSTSREADDATA.md index 0b5174d8e19a..21397c490944 100644 --- a/docs/libcurl/opts/CURLOPT_HSTSREADDATA.md +++ b/docs/libcurl/opts/CURLOPT_HSTSREADDATA.md @@ -58,14 +58,14 @@ static CURLSTScode hsts_cb(CURL *easy, struct curl_hstsentry *sts, int main(void) { CURL *curl = curl_easy_init(); - struct MyData this; + struct MyData my_data; if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "http://example.com"); /* pass pointer that gets passed in to the CURLOPT_HSTSREADFUNCTION callback */ - curl_easy_setopt(curl, CURLOPT_HSTSREADDATA, &this); + curl_easy_setopt(curl, CURLOPT_HSTSREADDATA, &my_data); /* set HSTS read callback */ curl_easy_setopt(curl, CURLOPT_HSTSREADFUNCTION, hsts_cb); diff --git a/docs/libcurl/opts/CURLOPT_HSTSWRITEDATA.md b/docs/libcurl/opts/CURLOPT_HSTSWRITEDATA.md index cc078539dd24..d84a810f2564 100644 --- a/docs/libcurl/opts/CURLOPT_HSTSWRITEDATA.md +++ b/docs/libcurl/opts/CURLOPT_HSTSWRITEDATA.md @@ -51,14 +51,14 @@ struct MyData { int main(void) { CURL *curl = curl_easy_init(); - struct MyData this; + struct MyData my_data; if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "http://example.com"); /* pass pointer that gets passed in to the CURLOPT_HSTSWRITEFUNCTION callback */ - curl_easy_setopt(curl, CURLOPT_HSTSWRITEDATA, &this); + curl_easy_setopt(curl, CURLOPT_HSTSWRITEDATA, &my_data); result = curl_easy_perform(curl); curl_easy_cleanup(curl); diff --git a/docs/libcurl/opts/CURLOPT_HTTPAUTH.md b/docs/libcurl/opts/CURLOPT_HTTPAUTH.md index e05c84183a76..6715ddc3e328 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPAUTH.md +++ b/docs/libcurl/opts/CURLOPT_HTTPAUTH.md @@ -37,6 +37,10 @@ extra network round-trip. Set the actual name and password with the CURLOPT_USERPWD(3) option or with the CURLOPT_USERNAME(3) and the CURLOPT_PASSWORD(3) options. +Custom `Authorization:` headers set with CURLOPT_HTTPHEADER(3) may interfere +with and cause unintended side-effects if combined with authentication set +with CURLOPT_HTTPAUTH(3). + For authentication with a proxy, see CURLOPT_PROXYAUTH(3). ## CURLAUTH_BASIC @@ -54,11 +58,8 @@ regular old-fashioned Basic method. ## CURLAUTH_DIGEST_IE -HTTP Digest authentication with an IE flavor. Digest authentication is defined -in RFC 2617 and is a more secure way to do authentication over public networks -than the regular old-fashioned Basic method. The IE flavor means that -libcurl uses a special "quirk" that IE is known to have used before version 7 -and that some servers require the client to use. +The IE-specific Digest authentication behavior is no longer supported. +This bit is kept for compatibility and is treated as CURLAUTH_DIGEST. ## CURLAUTH_BEARER @@ -121,6 +122,11 @@ single auth algorithm is acceptable. provides AWS V4 signature authentication on HTTPS header see CURLOPT_AWS_SIGV4(3). +## CURLAUTH_HTTPSIG + +provides RFC 9421 HTTP Message Signatures on outgoing requests, +see CURLOPT_HTTPSIG_ALGORITHM(3). + # DEFAULT CURLAUTH_BASIC @@ -159,6 +165,10 @@ CURLAUTH_BEARER was added in 7.61.0 CURLAUTH_AWS_SIGV4 was added in 7.74.0 +CURLAUTH_DIGEST_IE does nothing since 8.21.0 + +CURLAUTH_HTTPSIG was added in 8.22.0 + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_HTTPHEADER.md b/docs/libcurl/opts/CURLOPT_HTTPHEADER.md index 2c9ed039c77c..34a864adb11e 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPHEADER.md +++ b/docs/libcurl/opts/CURLOPT_HTTPHEADER.md @@ -102,6 +102,16 @@ Host: is not sent at all over the wire. Tells libcurl the upload is to be done using this chunked encoding instead of providing the Content-Length: field in the request. +## Authorization: + +Custom `Authorization:` headers may interfere with and cause unintended +side-effects when combined with authentication set with CURLOPT_HTTPAUTH(3). + +## Proxy-Authorization: + +Custom `Proxy-Authorization:` headers may interfere with and cause unintended +side-effects when combined with authentication set with CURLOPT_PROXYAUTH(3). + # SPECIFIC MIME HEADERS When used to build a MIME email for IMAP or SMTP, the following document-level diff --git a/docs/libcurl/opts/CURLOPT_HTTPPOST.md b/docs/libcurl/opts/CURLOPT_HTTPPOST.md index 8ae91095e4e0..4f07eacdcde7 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPPOST.md +++ b/docs/libcurl/opts/CURLOPT_HTTPPOST.md @@ -57,6 +57,7 @@ NULL ~~~c int main(void) { + CURL *curl; struct curl_httppost *formpost; struct curl_httppost *lastptr; @@ -82,7 +83,7 @@ int main(void) CURLFORM_COPYCONTENTS, "send", CURLFORM_END); - CURL *curl = curl_easy_init(); + curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_ALGORITHM.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_ALGORITHM.md new file mode 100644 index 000000000000..3fa4da378bd0 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_ALGORITHM.md @@ -0,0 +1,88 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLOPT_HTTPSIG_ALGORITHM +Section: 3 +Source: libcurl +See-also: + - CURLOPT_HTTPSIG_HEADERS (3) + - CURLOPT_HTTPSIG_KEY (3) + - CURLOPT_HTTPSIG_KEYID (3) + - CURLOPT_HTTPAUTH (3) +Protocol: + - HTTP +Added-in: 8.22.0 +--- + +# NAME + +CURLOPT_HTTPSIG_ALGORITHM - RFC 9421 HTTP Message Signatures algorithm + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_ALGORITHM, + long algorithm); +~~~ + +# DESCRIPTION + +This feature is **experimental** and may change before it is considered +stable. We advise against using it in production. + +Enable RFC 9421 HTTP Message Signatures on outgoing requests. Pass a long +set to one of the values below to select the signing algorithm. + +## CURLHTTPSIG_NONE (0) + +Disable HTTP Message Signatures. + +## CURLHTTPSIG_ED25519 (1) + +Sign with Ed25519 (RFC 8032). Requires a TLS backend with Ed25519 support. + +## CURLHTTPSIG_HMAC_SHA256 (2) + +Sign with HMAC-SHA256. + +## + +Setting this option to a non-zero value also sets CURLOPT_HTTPAUTH(3) to +CURLAUTH_HTTPSIG. The options CURLOPT_HTTPSIG_KEY(3) and +CURLOPT_HTTPSIG_KEYID(3) must also be set. + +# DEFAULT + +CURLHTTPSIG_NONE (0) + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + curl_easy_perform(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_easy_setopt(3) returns a CURLcode indicating success or error. + +CURLE_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_HEADERS.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_HEADERS.md new file mode 100644 index 000000000000..ef56d2f9bb65 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_HEADERS.md @@ -0,0 +1,107 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLOPT_HTTPSIG_HEADERS +Section: 3 +Source: libcurl +See-also: + - CURLOPT_HTTPSIG_ALGORITHM (3) + - CURLOPT_HTTPSIG_KEY (3) + - CURLOPT_HTTPSIG_KEYID (3) +Protocol: + - HTTP +Added-in: 8.22.0 +--- + +# NAME + +CURLOPT_HTTPSIG_HEADERS - components to sign for HTTP Message Signatures + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_HEADERS, + char *components); +~~~ + +# DESCRIPTION + +This feature is **experimental** and may change before it is considered +stable. We advise against using it in production. + +Pass a space-separated list of component identifiers to include in the +RFC 9421 HTTP Message Signature. + +Derived components are given as bare names: + +- **method** - the HTTP method (GET, POST, etc.) +- **authority** - the host and optional port +- **path** - the request path +- **query** - the query string including the leading `?` + +HTTP header fields are given with a trailing colon, for example `content-type:` +or `content-digest:`. This mirrors how a header looks and keeps a leading `@` +free for its usual curl meaning (read the value from a file). + +If this option is not set, the default components are **method**, **authority**, +**path** (plus **query** when a query string is present). + +## Signing request headers + +Header components are resolved from the list set with CURLOPT_HTTPHEADER(3) +only. Headers that libcurl adds later (such as the default `User-Agent`) are +**not** visible to the signer unless the application supplies them explicitly. + +To sign `User-Agent`, supply it via CURLOPT_HTTPHEADER(3) together with this +option before the transfer; see EXAMPLE. + +Each component identifier may appear at most once (RFC 9421 Section 2). +Listing the same component twice returns `CURLE_BAD_FUNCTION_ARGUMENT`. + +At most 16 components are accepted; supplying more returns +`CURLE_BAD_FUNCTION_ARGUMENT`. + +The application does not have to keep the string around after setting this +option. + +# DEFAULT + +NULL (uses the default component set) + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + struct curl_slist *headers = NULL; + + if(curl) { + headers = curl_slist_append(headers, "User-Agent: MyApp/1.0"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS, + "method authority path content-type: user-agent:"); + curl_easy_perform(curl); + curl_slist_free_all(headers); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_easy_setopt(3) returns a CURLcode indicating success or error. + +CURLE_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md new file mode 100644 index 000000000000..1f03de2c26e5 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md @@ -0,0 +1,89 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLOPT_HTTPSIG_KEY +Section: 3 +Source: libcurl +See-also: + - CURLOPT_HTTPSIG_ALGORITHM (3) + - CURLOPT_HTTPSIG_KEYID (3) +Protocol: + - HTTP +Added-in: 8.22.0 +--- + +# NAME + +CURLOPT_HTTPSIG_KEY - hex-encoded key for HTTP Message Signatures + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_KEY, char *key); +~~~ + +# DESCRIPTION + +This feature is **experimental** and may change before it is considered +stable. We advise against using it in production. + +Pass a null-terminated string containing the hex-encoded private key or +shared secret used for RFC 9421 HTTP Message Signatures. + +For **ed25519**, this is the 32-byte private seed (64 hex characters). For +**hmac-sha256**, this is the shared secret as hex; the decoded length is half +the number of hex digits, up to `CURL_MAX_INPUT_LENGTH / 2` bytes (the same +upper bound as other libcurl string options). + +PEM and other encodings are not supported; pass the raw key material as hex. + +## Generating Ed25519 keys + +With OpenSSL 3: + + openssl genpkey -algorithm ED25519 -out ed25519.pem + openssl pkey -in ed25519.pem -outform RAW | xxd -p -c 64 | tr -d '\n' > key.hex + +The `key.hex` file is one line of 64 hexadecimal digits. + +The application does not have to keep the string around after setting this +option. + +Using this option multiple times makes the last set string override the +previous ones. Set it to NULL to disable its use again. + +# DEFAULT + +NULL + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + curl_easy_perform(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_easy_setopt(3) returns a CURLcode indicating success or error. + +CURLE_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEYID.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEYID.md new file mode 100644 index 000000000000..43e0c087ea23 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEYID.md @@ -0,0 +1,75 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLOPT_HTTPSIG_KEYID +Section: 3 +Source: libcurl +See-also: + - CURLOPT_HTTPSIG_ALGORITHM (3) + - CURLOPT_HTTPSIG_KEY (3) +Protocol: + - HTTP +Added-in: 8.22.0 +--- + +# NAME + +CURLOPT_HTTPSIG_KEYID - key identifier for HTTP Message Signatures + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_KEYID, char *keyid); +~~~ + +# DESCRIPTION + +This feature is **experimental** and may change before it is considered +stable. We advise against using it in production. + +Pass a null-terminated string that identifies the key used for RFC 9421 HTTP +Message Signatures. This value is included as the `keyid` parameter in the +`Signature-Input` header, allowing the server to look up the corresponding +verification key. + +The application does not have to keep the string around after setting this +option. + +Using this option multiple times makes the last set string override the +previous ones. Set it to NULL to disable its use again. + +# DEFAULT + +NULL + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + curl_easy_perform(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_easy_setopt(3) returns a CURLcode indicating success or error. + +CURLE_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/CURLOPT_INTERLEAVEFUNCTION.md b/docs/libcurl/opts/CURLOPT_INTERLEAVEFUNCTION.md index 884c23fc898d..8cfe40406d01 100644 --- a/docs/libcurl/opts/CURLOPT_INTERLEAVEFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_INTERLEAVEFUNCTION.md @@ -38,7 +38,7 @@ interleaved RTP data. This function gets called for each $ block and therefore contains exactly one upper-layer protocol unit (e.g. one RTP packet). curl writes the interleaved header as well as the included data for each call. The first byte is always an ASCII dollar sign. The dollar sign is followed by a -one byte channel identifier and then a 2 byte integer length in network byte +one-byte channel identifier and then a 2-byte integer length in network byte order. See RFC 2326 Section 10.12 for more information on how RTP interleaving behaves. If unset or set to NULL, curl uses the default write function. diff --git a/docs/libcurl/opts/CURLOPT_KEYPASSWD.md b/docs/libcurl/opts/CURLOPT_KEYPASSWD.md index d97a94716436..38f5120cae8c 100644 --- a/docs/libcurl/opts/CURLOPT_KEYPASSWD.md +++ b/docs/libcurl/opts/CURLOPT_KEYPASSWD.md @@ -9,6 +9,8 @@ See-also: - CURLOPT_SSLKEY (3) Protocol: - TLS + - SFTP + - SCP TLS-backend: - OpenSSL - mbedTLS diff --git a/docs/libcurl/opts/CURLOPT_MAXFILESIZE.md b/docs/libcurl/opts/CURLOPT_MAXFILESIZE.md index 17c4c9f4e176..a1a0d8496ccd 100644 --- a/docs/libcurl/opts/CURLOPT_MAXFILESIZE.md +++ b/docs/libcurl/opts/CURLOPT_MAXFILESIZE.md @@ -32,17 +32,16 @@ value, the transfer is aborted and *CURLE_FILESIZE_EXCEEDED* is returned. Passing a zero *size* disables this, and passing a negative *size* yields a *CURLE_BAD_FUNCTION_ARGUMENT*. -The file size is not always known prior to the download start, and for such -transfers this option has no effect - even if the file transfer eventually -ends up being larger than this given limit. - If you want a limit above 2GB, use CURLOPT_MAXFILESIZE_LARGE(3). -Since 8.4.0, this option also stops ongoing transfers if they reach this -threshold. +If the size is known to be too big before the transfer starts, libcurl +aborts before starting the transfer. If it is instead found to be too big +while the transfer is in progress, libcurl aborts the transfer once the +received bytes exceed the limit. -Since 8.20.0, this option also stops ongoing transfers that would reach this -threshold due to automatic decompression using CURLOPT_ACCEPT_ENCODING(3). +Since 8.20.0, this option also aborts ongoing transfers once the +decompressed bytes exceed this threshold due to automatic decompression using +CURLOPT_ACCEPT_ENCODING(3). # DEFAULT @@ -68,6 +67,10 @@ int main(void) # %AVAILABILITY% +# HISTORY + +Before curl 8.4.0, the limit was not applied to transfers in progress. + # RETURN VALUE curl_easy_setopt(3) returns a CURLcode indicating success or error. diff --git a/docs/libcurl/opts/CURLOPT_MAXFILESIZE_LARGE.md b/docs/libcurl/opts/CURLOPT_MAXFILESIZE_LARGE.md index 791b25862f7e..929bae67f2c1 100644 --- a/docs/libcurl/opts/CURLOPT_MAXFILESIZE_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_MAXFILESIZE_LARGE.md @@ -29,18 +29,15 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_MAXFILESIZE_LARGE, # DESCRIPTION -Pass a curl_off_t as parameter. This specifies the maximum accepted *size* -(in bytes) of a file to download. If the file requested is found larger than -this value, the transfer is aborted and *CURLE_FILESIZE_EXCEEDED* is -returned. Passing a zero *size* disables this, and passing a negative *size* -yields a *CURLE_BAD_FUNCTION_ARGUMENT*. +Pass a curl_off_t as parameter. This specifies the maximum accepted *size* (in +bytes) of a file to download. If the file requested is found larger than this +value, the transfer is aborted and *CURLE_FILESIZE_EXCEEDED* is returned. +Passing a zero *size* disables this, and passing a negative *size* yields a +*CURLE_BAD_FUNCTION_ARGUMENT*. -The file size is not always known prior to the download start, and for such -transfers this option has no effect - even if the file transfer eventually -ends up being larger than this given limit. - -Since 8.4.0, this option also stops ongoing transfers if they reach this -threshold. +If the size is known to exceed the limit before the transfer starts, libcurl +aborts before starting the transfer. If the transfer instead exceeds the limit +while it is in progress, libcurl aborts it at that point. Since 8.20.0, this option also stops ongoing transfers that would reach this threshold due to automatic decompression using CURLOPT_ACCEPT_ENCODING(3). @@ -70,6 +67,10 @@ int main(void) # %AVAILABILITY% +# HISTORY + +Before curl 8.4.0, the limit was not applied to transfers in progress. + # RETURN VALUE curl_easy_setopt(3) returns a CURLcode indicating success or error. diff --git a/docs/libcurl/opts/CURLOPT_MAX_RECV_SPEED_LARGE.md b/docs/libcurl/opts/CURLOPT_MAX_RECV_SPEED_LARGE.md index 9a9117669d62..ee0c7e84bfc4 100644 --- a/docs/libcurl/opts/CURLOPT_MAX_RECV_SPEED_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_MAX_RECV_SPEED_LARGE.md @@ -38,7 +38,7 @@ the given threshold over a period time. If you set *maxspeed* to a value lower than CURLOPT_BUFFERSIZE(3), libcurl might download faster than the set limit initially. -This option does not affect transfer speeds done with FILE:// URLs. +This option does not affect transfer speeds done with `file://` URLs. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_MAX_SEND_SPEED_LARGE.md b/docs/libcurl/opts/CURLOPT_MAX_SEND_SPEED_LARGE.md index 34566ece0ded..d64d324afd78 100644 --- a/docs/libcurl/opts/CURLOPT_MAX_SEND_SPEED_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_MAX_SEND_SPEED_LARGE.md @@ -39,7 +39,7 @@ If you set *maxspeed* to a value lower than CURLOPT_UPLOAD_BUFFERSIZE(3), libcurl might "shoot over" the limit on its first send and still send off a full buffer. -This option does not affect transfer speeds done with FILE:// URLs. +This option does not affect transfer speeds done with `file://` URLs. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_NEW_DIRECTORY_PERMS.md b/docs/libcurl/opts/CURLOPT_NEW_DIRECTORY_PERMS.md index cb6731912c72..264ae897cd69 100644 --- a/docs/libcurl/opts/CURLOPT_NEW_DIRECTORY_PERMS.md +++ b/docs/libcurl/opts/CURLOPT_NEW_DIRECTORY_PERMS.md @@ -33,7 +33,7 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_NEW_DIRECTORY_PERMS, Pass a long as a parameter, containing the value of the permissions that is set on newly created directories on the remote server. The default value is *0755*, but any valid value can be used. The only protocols that can use -this are *sftp://*, *scp://*, and *file://*. +this are `sftp://`, `scp://`, and `file://`. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_NEW_FILE_PERMS.md b/docs/libcurl/opts/CURLOPT_NEW_FILE_PERMS.md index 4c9579c8264e..00766f1e7e44 100644 --- a/docs/libcurl/opts/CURLOPT_NEW_FILE_PERMS.md +++ b/docs/libcurl/opts/CURLOPT_NEW_FILE_PERMS.md @@ -31,7 +31,7 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_NEW_FILE_PERMS, Pass a long as a parameter, containing the value of the permissions that are set on newly created files on the remote server. The default value is *0644*. -The only protocols that can use this are *sftp://*, *scp://*, and *file://*. +The only protocols that can use this are `sftp://`, `scp://`, and `file://`. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_OPENSOCKETDATA.md b/docs/libcurl/opts/CURLOPT_OPENSOCKETDATA.md index 2bcdee3b6650..0d0b28fd88c5 100644 --- a/docs/libcurl/opts/CURLOPT_OPENSOCKETDATA.md +++ b/docs/libcurl/opts/CURLOPT_OPENSOCKETDATA.md @@ -59,12 +59,13 @@ static int sockopt_callback(void *clientp, curl_socket_t curlfd, return CURL_SOCKOPT_ALREADY_CONNECTED; } +extern int sockfd; /* the already connected one */ + int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; - extern int sockfd; /* the already connected one */ /* libcurl thinks that you connect to the host * and port that you specify in the URL option. */ diff --git a/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md b/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md index 568fb11b9402..4978d65f987f 100644 --- a/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md @@ -107,12 +107,13 @@ static int sockopt_callback(void *clientp, curl_socket_t curlfd, return CURL_SOCKOPT_ALREADY_CONNECTED; } +extern int sockfd; /* the already connected one */ + int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; - extern int sockfd; /* the already connected one */ /* libcurl thinks that you connect to the host * and port that you specify in the URL option. */ curl_easy_setopt(curl, CURLOPT_URL, "http://99.99.99.99:9999"); diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md index 0590143c906b..ed4011d57fa3 100644 --- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md +++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md @@ -36,9 +36,9 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PINNEDPUBLICKEY, # DESCRIPTION Pass a pointer to a null-terminated string as parameter. The string can be the -filename of your pinned public key. The file format expected is "PEM" or -"DER". The string can also be any number of base64 encoded sha256 hashes -preceded by "sha256//" and separated by ";" +filename of your pinned public key. The file format expected is `PEM` or +`DER`. The string can also be any number of base64 encoded sha256 hashes +preceded by `sha256//` and separated by `;`. When negotiating a TLS or SSL connection, the server sends a certificate indicating its identity. A public key is extracted from this certificate and @@ -53,6 +53,15 @@ On mismatch, *CURLE_SSL_PINNEDPUBKEYNOTMATCH* is returned. The application does not have to keep the string around after setting this option. +The pinned public key is used to verify the initial origin used in a transfer. +If the transfer is set to follow redirects to other origins, they are *not* +checked against this key. + +This option has no effect on LDAP connections when libcurl uses the legacy LDAP +backend. That backend manages TLS independently of curl's TLS layer. When +libcurl is built with USE_OPENLDAP, the OpenLDAP backend routes TLS through +curl's layer and this option is honored. + # DEFAULT NULL diff --git a/docs/libcurl/opts/CURLOPT_PORT.md b/docs/libcurl/opts/CURLOPT_PORT.md index 334f01b3d719..567d6a975a79 100644 --- a/docs/libcurl/opts/CURLOPT_PORT.md +++ b/docs/libcurl/opts/CURLOPT_PORT.md @@ -27,8 +27,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PORT, long number); # DESCRIPTION -We discourage using this option since its scope is not obvious and hard to -predict. Set the preferred port number in the URL instead. +We strongly discourage using this unreliable option since its scope is not +obvious and hard to predict. Set the preferred port number in the URL instead. This option sets *number* to be the remote port number to connect to, instead of the one specified in the URL or the default port for the used diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDS.md b/docs/libcurl/opts/CURLOPT_POSTFIELDS.md index 6258e53f209f..49d955966a3f 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDS.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDS.md @@ -87,7 +87,7 @@ int main(void) CURLcode result = CURLE_OK; CURL *curl = curl_easy_init(); if(curl) { - const char *data = "data to send"; + static const char *data = "data to send"; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); @@ -104,7 +104,7 @@ int main(void) /* send an application/json POST */ curl = curl_easy_init(); if(curl) { - const char *json = "{\"name\": \"daniel\"}"; + static const char *json = "{\"name\": \"daniel\"}"; struct curl_slist *slist1 = NULL; slist1 = curl_slist_append(slist1, "Content-Type: application/json"); slist1 = curl_slist_append(slist1, "Accept: application/json"); diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md index 4dbc324e2d68..d45121b53f1d 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md @@ -52,7 +52,7 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { CURLcode result; - const char *data = "data to send"; + static const char *data = "data to send"; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); diff --git a/docs/libcurl/opts/CURLOPT_POSTQUOTE.md b/docs/libcurl/opts/CURLOPT_POSTQUOTE.md index 55ef81aabc02..f53e20109cff 100644 --- a/docs/libcurl/opts/CURLOPT_POSTQUOTE.md +++ b/docs/libcurl/opts/CURLOPT_POSTQUOTE.md @@ -50,11 +50,12 @@ NULL ~~~c int main(void) { + CURL *curl; struct curl_slist *cmdlist = NULL; cmdlist = curl_slist_append(cmdlist, "RNFR source-name"); cmdlist = curl_slist_append(cmdlist, "RNTO new-name"); - CURL *curl = curl_easy_init(); + curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/foo.bin"); diff --git a/docs/libcurl/opts/CURLOPT_PREQUOTE.md b/docs/libcurl/opts/CURLOPT_PREQUOTE.md index 79cf9fee80e4..21ae822b5d71 100644 --- a/docs/libcurl/opts/CURLOPT_PREQUOTE.md +++ b/docs/libcurl/opts/CURLOPT_PREQUOTE.md @@ -52,10 +52,11 @@ NULL ~~~c int main(void) { + CURL *curl; struct curl_slist *cmdlist = NULL; cmdlist = curl_slist_append(cmdlist, "SYST"); - CURL *curl = curl_easy_init(); + curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/foo.bin"); diff --git a/docs/libcurl/opts/CURLOPT_PRE_PROXY.md b/docs/libcurl/opts/CURLOPT_PRE_PROXY.md index 3b447fa907a7..06d0f320f48d 100644 --- a/docs/libcurl/opts/CURLOPT_PRE_PROXY.md +++ b/docs/libcurl/opts/CURLOPT_PRE_PROXY.md @@ -39,11 +39,11 @@ A pre proxy is a SOCKS proxy that curl connects to before it connects to the HTTP(S) proxy specified in the CURLOPT_PROXY(3) option. The pre proxy can only be a SOCKS proxy. -The pre proxy string should be prefixed with [scheme]:// to specify which kind -of socks is used. Use socks4://, socks4a://, socks5:// or socks5h:// (the last -one to enable socks5 and asking the proxy to do the resolving, also known as -*CURLPROXY_SOCKS5_HOSTNAME* type) to request the specific SOCKS version to -be used. Otherwise SOCKS4 is used as default. +The pre proxy string should be prefixed with `[scheme]://` to specify which +kind of socks is used. Use `socks4://`, `socks4a://`, `socks5://` or +`socks5h://` (the last one to enable socks5 and asking the proxy to do the +resolving, also known as *CURLPROXY_SOCKS5_HOSTNAME* type) to request the +specific SOCKS version to be used. Otherwise SOCKS4 is used as default. Setting the pre proxy string to "" (an empty string) explicitly disables the use of a pre proxy. diff --git a/docs/libcurl/opts/CURLOPT_PRIVATE.md b/docs/libcurl/opts/CURLOPT_PRIVATE.md index e59016bf8c79..5b7443072c5a 100644 --- a/docs/libcurl/opts/CURLOPT_PRIVATE.md +++ b/docs/libcurl/opts/CURLOPT_PRIVATE.md @@ -41,25 +41,25 @@ NULL # EXAMPLE ~~~c -struct private { +struct private_data { void *custom; }; int main(void) { CURL *curl = curl_easy_init(); - struct private secrets; + struct private_data secrets; if(curl) { CURLcode result; - struct private *extracted; + struct private_data *extracted; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); - /* store a pointer to our private struct */ + /* store a pointer to our private_data struct */ curl_easy_setopt(curl, CURLOPT_PRIVATE, &secrets); result = curl_easy_perform(curl); - /* we can extract the private pointer again too */ + /* we can extract the private_data pointer again too */ curl_easy_getinfo(curl, CURLINFO_PRIVATE, &extracted); curl_easy_cleanup(curl); diff --git a/docs/libcurl/opts/CURLOPT_PROGRESSDATA.md b/docs/libcurl/opts/CURLOPT_PROGRESSDATA.md index 6222995e618b..918ae9fd891d 100644 --- a/docs/libcurl/opts/CURLOPT_PROGRESSDATA.md +++ b/docs/libcurl/opts/CURLOPT_PROGRESSDATA.md @@ -39,7 +39,7 @@ NULL ~~~c struct progress { - char *private; + char *private_data; size_t size; }; @@ -50,7 +50,7 @@ static int progress_callback(void *clientp, double ulnow) { struct progress *memory = clientp; - printf("private: %p\n", memory->private); + printf("private: %p\n", (void *)memory->private_data); /* use the values */ diff --git a/docs/libcurl/opts/CURLOPT_PROGRESSFUNCTION.md b/docs/libcurl/opts/CURLOPT_PROGRESSFUNCTION.md index d1d6ba3d3791..41fb91ae9ad8 100644 --- a/docs/libcurl/opts/CURLOPT_PROGRESSFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_PROGRESSFUNCTION.md @@ -85,7 +85,7 @@ NULL. libcurl has an internal progress meter. That is rarely wanted by users. ~~~c struct progress { - char *private; + char *private_data; size_t size; }; @@ -96,7 +96,7 @@ static int progress_callback(void *clientp, double ulnow) { struct progress *memory = clientp; - printf("private: %p\n", memory->private); + printf("private: %p\n", (void *)memory->private_data); /* use the values */ diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md index cdd02b5ed8d5..023773315998 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md @@ -82,7 +82,7 @@ All protocols built-in. # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { CURL *curl = curl_easy_init(); if(curl) { diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md index 02a78ae5d647..969bf88ab7dd 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md @@ -65,7 +65,7 @@ All protocols built-in # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { CURL *curl = curl_easy_init(); if(curl) { diff --git a/docs/libcurl/opts/CURLOPT_PROXY.md b/docs/libcurl/opts/CURLOPT_PROXY.md index 3f8cf6bf5ddd..072dfd48093e 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY.md +++ b/docs/libcurl/opts/CURLOPT_PROXY.md @@ -33,12 +33,12 @@ should be a char * to a null-terminated string holding the hostname or dotted numerical IP address. A numerical IPv6 address must be written within [brackets]. -To specify port number in this string, append :[port] to the end of the host +To specify port number in this string, append `:[port]` to the end of the host name. The proxy's port number may optionally (but discouraged) be specified with the separate option CURLOPT_PROXYPORT(3). If not specified, libcurl defaults to using port 1080 for proxies. -The proxy string may be prefixed with [scheme]:// to specify which kind of +The proxy string may be prefixed with `[scheme]://` to specify which kind of proxy is used. Using this option multiple times makes the last set string override the @@ -47,30 +47,34 @@ previous ones. Set it to NULL to disable its use again. The application does not have to keep the string around after setting this option. -## http:// +## `http://` HTTP Proxy. Default when no scheme or proxy type is specified. -## https:// +## `https://` HTTPS Proxy. (with OpenSSL, GnuTLS, mbedTLS, Rustls, Schannel or wolfSSL.) This uses HTTP/1 by default. Setting CURLOPT_PROXYTYPE(3) to **CURLPROXY_HTTPS2** allows libcurl to negotiate using HTTP/2 with proxy. +Setting CURLOPT_PROXYTYPE(3) to **CURLPROXY_HTTPS3** allows libcurl to +negotiate using HTTP/3 with proxy. This feature is experimental and requires +a build with HTTP/3 proxy support enabled. + ## socks4:// SOCKS4 Proxy. -## socks4a:// +## `socks4a://` SOCKS4a Proxy. Proxy resolves URL hostname. -## socks5:// +## `socks5://` SOCKS5 Proxy. -## socks5h:// +## `socks5h://` SOCKS5 Proxy. Proxy resolves URL hostname. @@ -114,7 +118,7 @@ CURLOPT_PROXYPASSWORD(3). libcurl respects the proxy environment variables named **http_proxy**, **ftp_proxy**, **sftp_proxy** etc. If set, libcurl uses the specified proxy -for that URL scheme. For an "FTP://" URL, the **ftp_proxy** is +for that URL scheme. For an `ftp://` URL, the **ftp_proxy** is considered. **all_proxy** is used if no protocol specific proxy was set. If **no_proxy** (or **NO_PROXY**) is set, it is the exact equivalent of diff --git a/docs/libcurl/opts/CURLOPT_PROXYAUTH.md b/docs/libcurl/opts/CURLOPT_PROXYAUTH.md index 459fd429c371..a13c992dfcbd 100644 --- a/docs/libcurl/opts/CURLOPT_PROXYAUTH.md +++ b/docs/libcurl/opts/CURLOPT_PROXYAUTH.md @@ -40,6 +40,10 @@ option. The bitmask can be constructed by the bits listed and described in the CURLOPT_HTTPAUTH(3) man page. +Custom `Proxy-Authorization:` headers set with CURLOPT_PROXYHEADER(3) may +interfere with and cause unintended side-effects if combined with +authentication set with CURLOPT_PROXYAUTH(3). + # DEFAULT CURLAUTH_BASIC diff --git a/docs/libcurl/opts/CURLOPT_PROXYTYPE.md b/docs/libcurl/opts/CURLOPT_PROXYTYPE.md index 6000d10b00bc..1dc1a1328a01 100644 --- a/docs/libcurl/opts/CURLOPT_PROXYTYPE.md +++ b/docs/libcurl/opts/CURLOPT_PROXYTYPE.md @@ -41,6 +41,12 @@ HTTPS Proxy using HTTP/1. (Added in 7.52.0 for OpenSSL and GnuTLS. Since HTTPS Proxy and attempt to speak HTTP/2 over it. (Added in 8.1.0) +## CURLPROXY_HTTPS3 + +HTTPS Proxy and attempt to speak HTTP/3 over it. (Added in 8.21.0) +This feature is experimental and requires a build with HTTP/3 proxy support +enabled. + ## CURLPROXY_HTTP_1_0 HTTP 1.0 Proxy. This is similar to CURLPROXY_HTTP except it uses HTTP/1.0 for diff --git a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md index 180a7b1808bf..0885ad96e0c9 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md @@ -66,8 +66,8 @@ NULL ~~~c -extern char *certificateData; /* point to the data */ -size_t filesize; /* size of the data */ +static char *certificateData; /* point to the data */ +static size_t filesize; /* size of the data */ int main(void) { diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSL_OPTIONS.md b/docs/libcurl/opts/CURLOPT_PROXY_SSL_OPTIONS.md index ab0e366b0d8c..0b491eeff653 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_SSL_OPTIONS.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSL_OPTIONS.md @@ -38,10 +38,10 @@ behaviors. Available bits: Tells libcurl to not attempt to use any workarounds for a security flaw in the SSL3 and TLS1.0 protocols. If this option is not used or this bit is set to 0, -the SSL layer libcurl uses may use a work-around for this flaw although it +the SSL layer libcurl uses may use a workaround for this flaw although it might cause interoperability problems with some (older) SSL implementations. -**WARNING:** avoiding this work-around lessens the security, and by setting +**WARNING:** avoiding this workaround lessens the security, and by setting this option to 1 you ask for exactly that. This option is only supported for Secure Transport and OpenSSL. @@ -58,7 +58,7 @@ Tells libcurl to not accept "partial" certificate chains, which it otherwise does by default. This option fails the certificate verification if the chain ends with an intermediate certificate and not with a root cert. -Works with OpenSSL and its forks (LibreSSL, BoringSSL, etc). (Added in 7.68.0) +Works with OpenSSL and its forks (BoringSSL, LibreSSL, etc). (Added in 7.68.0) Works with Schannel if the user specified certificates to verify the peer. (Added in 8.15.0) @@ -78,9 +78,9 @@ verification. This option is independent of other CA certificate locations set at run time or build time. Those locations are searched in addition to the native CA store. -Works with wolfSSL on Windows, Linux (Debian, Ubuntu, Gentoo, Fedora, RHEL), +Works with wolfSSL on Windows, Linux (Debian, Fedora, Gentoo, RHEL, Ubuntu), macOS, Android and iOS (added in 8.3.0); with GnuTLS (added in 8.5.0) and with -OpenSSL and its forks (LibreSSL, BoringSSL, etc) on Windows (Added in 7.71.0). +OpenSSL and its forks (BoringSSL, LibreSSL, etc) on Windows (Added in 7.71.0). ## CURLSSLOPT_AUTO_CLIENT_CERT diff --git a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_PASSWORD.md b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_PASSWORD.md index 17acca6761d6..5ef82f02d3a9 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_PASSWORD.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_PASSWORD.md @@ -47,6 +47,10 @@ previous ones. Set it to NULL to disable its use again. NULL +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_TYPE.md b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_TYPE.md index d1badf4be565..33b5075abd2c 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_TYPE.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_TYPE.md @@ -54,6 +54,10 @@ options. blank +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_USERNAME.md b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_USERNAME.md index c4db7aa81526..816a1063f7dc 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_USERNAME.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_TLSAUTH_USERNAME.md @@ -47,6 +47,10 @@ previous ones. Set it to NULL to disable its use again. NULL +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_QUOTE.md b/docs/libcurl/opts/CURLOPT_QUOTE.md index d4365f7b4185..020d5ed95ae3 100644 --- a/docs/libcurl/opts/CURLOPT_QUOTE.md +++ b/docs/libcurl/opts/CURLOPT_QUOTE.md @@ -141,11 +141,12 @@ NULL ~~~c int main(void) { + CURL *curl; struct curl_slist *cmdlist = NULL; cmdlist = curl_slist_append(cmdlist, "RNFR source-name"); cmdlist = curl_slist_append(cmdlist, "RNTO new-name"); - CURL *curl = curl_easy_init(); + curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/foo.bin"); diff --git a/docs/libcurl/opts/CURLOPT_READDATA.md b/docs/libcurl/opts/CURLOPT_READDATA.md index fb18753363dd..81055cd0748c 100644 --- a/docs/libcurl/opts/CURLOPT_READDATA.md +++ b/docs/libcurl/opts/CURLOPT_READDATA.md @@ -55,14 +55,14 @@ struct MyData { int main(void) { CURL *curl = curl_easy_init(); - struct MyData this; + struct MyData my_data; if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); /* pass pointer that gets passed in to the CURLOPT_READFUNCTION callback */ - curl_easy_setopt(curl, CURLOPT_READDATA, &this); + curl_easy_setopt(curl, CURLOPT_READDATA, &my_data); result = curl_easy_perform(curl); curl_easy_cleanup(curl); diff --git a/docs/libcurl/opts/CURLOPT_READFUNCTION.md b/docs/libcurl/opts/CURLOPT_READFUNCTION.md index 0046ded635c8..e0345e832ae3 100644 --- a/docs/libcurl/opts/CURLOPT_READFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_READFUNCTION.md @@ -84,7 +84,8 @@ fread(3) # EXAMPLE ~~~c -size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +static size_t read_callback(char *ptr, size_t size, size_t nmemb, + void *userdata) { FILE *readhere = (FILE *)userdata; curl_off_t nread; @@ -100,7 +101,7 @@ size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata) return retcode; } -int main(int argc, char **argv) +int main(int argc, char *argv[]) { FILE *file = fopen(argv[1], "rb"); CURLcode result; diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md index 1d860312c01c..0803d587865d 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md @@ -87,7 +87,7 @@ HTTP, HTTPS, FTP and FTPS # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { CURL *curl = curl_easy_init(); if(curl) { diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md index 8f3c0ef885d6..731b506319d9 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md @@ -71,7 +71,7 @@ HTTP, HTTPS, FTP and FTPS # EXAMPLE ~~~c -int main(int argc, char **argv) +int main(int argc, char *argv[]) { CURL *curl = curl_easy_init(); if(curl) { diff --git a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md index 39d6ae791307..061e8228f73d 100644 --- a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md +++ b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md @@ -56,7 +56,8 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { CURLcode result; - char *prev_id = "old"; /* previously retrieved RTSP session ID */ + static const char *prev_id = "old"; /* previously retrieved + RTSP session ID */ curl_easy_setopt(curl, CURLOPT_URL, "rtsp://example.com/"); curl_easy_setopt(curl, CURLOPT_RTSP_SESSION_ID, prev_id); result = curl_easy_perform(curl); diff --git a/docs/libcurl/opts/CURLOPT_SHARE.md b/docs/libcurl/opts/CURLOPT_SHARE.md index 2c6a496593fd..907e4f280519 100644 --- a/docs/libcurl/opts/CURLOPT_SHARE.md +++ b/docs/libcurl/opts/CURLOPT_SHARE.md @@ -43,6 +43,9 @@ if no share was used. Set this option to NULL again to stop using that share object. +Warning: adding a *share* and then setting it to NULL while the transfer +is ongoing is discouraged and may lead to undefined behavior. + # DEFAULT NULL diff --git a/docs/libcurl/opts/CURLOPT_SOCKOPTFUNCTION.md b/docs/libcurl/opts/CURLOPT_SOCKOPTFUNCTION.md index 2080aa9483c5..abc8d65f0cc2 100644 --- a/docs/libcurl/opts/CURLOPT_SOCKOPTFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_SOCKOPTFUNCTION.md @@ -73,6 +73,9 @@ then libcurl does no attempt to connect. This allows an application to pass in an already connected socket with CURLOPT_OPENSOCKETFUNCTION(3) and then have this function make libcurl not attempt to connect (again). +The *CURL_SOCKOPT_ALREADY_CONNECTED* feature does not work for HTTP/3 (QUIC) +connections. + # DEFAULT NULL diff --git a/docs/libcurl/opts/CURLOPT_SSH_HOSTKEYFUNCTION.md b/docs/libcurl/opts/CURLOPT_SSH_HOSTKEYFUNCTION.md index 09aa83c875f6..6ff15427a8fe 100644 --- a/docs/libcurl/opts/CURLOPT_SSH_HOSTKEYFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_SSH_HOSTKEYFUNCTION.md @@ -38,12 +38,15 @@ shown above. It overrides CURLOPT_SSH_KNOWNHOSTS(3). This callback gets called when the verification of the SSH host key is needed. -**key** is **keylen** bytes long and is the key to check. **keytype** -says what type it is, from the **CURLKHTYPE_*** series in the -**curl_khtype** enum. +**key** is **keylen** bytes long and is the key to check. **keytype** says +what type it is, from the **CURLKHTYPE_*** series in the **curl_khtype** enum. **clientp** is a custom pointer set with CURLOPT_SSH_HOSTKEYDATA(3). +This option is used to verify new SSH connections only. Once the connection +has been vetted by this callback it is deemed vetted and may be reused again +without invoking this callback again. + The callback must return one of the following return codes to tell libcurl how to act: @@ -68,15 +71,16 @@ struct mine { void *custom; }; -int hostkeycb(void *clientp, /* passed with CURLOPT_SSH_HOSTKEYDATA */ - int keytype, /* CURLKHTYPE */ - const char *key, /* host key to check */ - size_t keylen) /* length of the key */ +static int hostkeycb(void *clientp, /* passed with CURLOPT_SSH_HOSTKEYDATA */ + int keytype, /* CURLKHTYPE */ + const char *key, /* host key to check */ + size_t keylen) /* length of the key */ { /* 'clientp' points to the callback_data struct */ /* investigate the situation and return the correct value */ return CURLKHMATCH_OK; } + int main(void) { struct mine callback_data; diff --git a/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md index 43a6d9e708e5..fce7e58f04bf 100644 --- a/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md +++ b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md @@ -73,10 +73,6 @@ int main(void) } ~~~ -# NOTES - -Requires the libssh2 backend. - # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_SSH_PRIVATE_KEYFILE.md b/docs/libcurl/opts/CURLOPT_SSH_PRIVATE_KEYFILE.md index 2a1f79845dbf..efc627b8dcff 100644 --- a/docs/libcurl/opts/CURLOPT_SSH_PRIVATE_KEYFILE.md +++ b/docs/libcurl/opts/CURLOPT_SSH_PRIVATE_KEYFILE.md @@ -33,8 +33,7 @@ used, libcurl defaults to **$HOME/.ssh/id_rsa** or **$HOME/.ssh/id_dsa** if the HOME environment variable is set, and in the current directory if HOME is not set. -If the file is password-protected, set the password with -CURLOPT_KEYPASSWD(3). +If the file is password-protected, set the password with CURLOPT_KEYPASSWD(3). The SSH library derives the public key from this private key when possible. If the SSH library cannot derive the public key from the private one and no @@ -44,6 +43,11 @@ fails. The application does not have to keep the string around after setting this option. +This option is used to set up a new connection only. The private key is used +when libcurl establishes a new SSH connection; once that connection has been +successfully set up and verified, it is deemed vetted and may be reused by +libcurl even if this option is changed. + # DEFAULT As explained above diff --git a/docs/libcurl/opts/CURLOPT_SSH_PUBLIC_KEYFILE.md b/docs/libcurl/opts/CURLOPT_SSH_PUBLIC_KEYFILE.md index 161daf4ecf8a..5cd2fc5b8c11 100644 --- a/docs/libcurl/opts/CURLOPT_SSH_PUBLIC_KEYFILE.md +++ b/docs/libcurl/opts/CURLOPT_SSH_PUBLIC_KEYFILE.md @@ -40,6 +40,11 @@ no public one is provided, the transfer fails. The application does not have to keep the string around after setting this option. +This option is used to set up a new connection only. The public key is used +when libcurl establishes a new SSH connection; once that connection has been +successfully set up and verified, it is deemed vetted and may be reused by +libcurl even if this option is changed. + # DEFAULT NULL diff --git a/docs/libcurl/opts/CURLOPT_SSLVERSION.md b/docs/libcurl/opts/CURLOPT_SSLVERSION.md index 411da989efbf..cbe7a89c30c9 100644 --- a/docs/libcurl/opts/CURLOPT_SSLVERSION.md +++ b/docs/libcurl/opts/CURLOPT_SSLVERSION.md @@ -34,43 +34,43 @@ Pass a long as parameter to control which version range of SSL/TLS versions to use. The SSL and TLS versions have typically developed from the most insecure -version to be more and more secure in this order through history: SSL v2, -SSLv3, TLS v1.0, TLS v1.1, TLS v1.2 and the most recent TLS v1.3. +version to be more and more secure in this order through history: SSLv2, +SSLv3, TLSv1.0, TLSv1.1, TLSv1.2 and the most recent TLSv1.3. Use one of the available defines for this purpose. The available options are: ## CURL_SSLVERSION_DEFAULT The default acceptable version range. The minimum acceptable version is by -default TLS v1.2 since 8.16.0 (unless the TLS library has a stricter rule). +default TLSv1.2 since 8.16.0 (unless the TLS library has a stricter rule). ## CURL_SSLVERSION_TLSv1 -TLS v1.0 or later +TLSv1.0 or later ## CURL_SSLVERSION_SSLv2 -SSL v2 - refused +SSLv2 - refused ## CURL_SSLVERSION_SSLv3 -SSL v3 - refused +SSLv3 - refused ## CURL_SSLVERSION_TLSv1_0 -TLS v1.0 or later +TLSv1.0 or later ## CURL_SSLVERSION_TLSv1_1 -TLS v1.1 or later +TLSv1.1 or later ## CURL_SSLVERSION_TLSv1_2 -TLS v1.2 or later +TLSv1.2 or later ## CURL_SSLVERSION_TLSv1_3 -TLS v1.3 or later +TLSv1.3 or later ## @@ -82,24 +82,24 @@ with *one* of the CURL_SSLVERSION_MAX_ macros. The flag defines the maximum supported TLS version by libcurl, or the default value from the SSL library is used. libcurl uses a sensible default maximum, -which was TLS v1.2 up to before 7.61.0 and is TLS v1.3 since then - assuming +which was TLSv1.2 up to before 7.61.0 and is TLSv1.3 since then - assuming the TLS library support it. ## CURL_SSLVERSION_MAX_TLSv1_0 -The flag defines maximum supported TLS version as TLS v1.0. +The flag defines maximum supported TLS version as TLSv1.0. ## CURL_SSLVERSION_MAX_TLSv1_1 -The flag defines maximum supported TLS version as TLS v1.1. +The flag defines maximum supported TLS version as TLSv1.1. ## CURL_SSLVERSION_MAX_TLSv1_2 -The flag defines maximum supported TLS version as TLS v1.2. +The flag defines maximum supported TLS version as TLSv1.2. ## CURL_SSLVERSION_MAX_TLSv1_3 -The flag defines maximum supported TLS version as TLS v1.3. +The flag defines maximum supported TLS version as TLSv1.3. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_SSL_CTX_DATA.md b/docs/libcurl/opts/CURLOPT_SSL_CTX_DATA.md index a2132ee5b367..c6521622b9fd 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_CTX_DATA.md +++ b/docs/libcurl/opts/CURLOPT_SSL_CTX_DATA.md @@ -30,7 +30,7 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSL_CTX_DATA, void *pointer); # DESCRIPTION -Data *pointer* to pass to the ssl context callback set by the option +Data *pointer* to pass to the SSL context callback set by the option CURLOPT_SSL_CTX_FUNCTION(3), this is the pointer you get as third parameter. @@ -84,7 +84,7 @@ int main(void) CURL *curl; CURLcode result; /* CA cert in PEM format, replace the XXXs */ - char *mypem = + const char *mypem = "-----BEGIN CERTIFICATE-----\n" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" diff --git a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md index f0460e53577a..c834cc8bf523 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md @@ -144,7 +144,7 @@ int main(void) CURL *curl; CURLcode result; /* CA cert in PEM format, replace the XXXs */ - char *mypem = + const char *mypem = "-----BEGIN CERTIFICATE-----\n" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" diff --git a/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md b/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md index 2fdf8ee15f57..ccbb4895ef9b 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md +++ b/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md @@ -36,10 +36,10 @@ behaviors. Available bits: Tells libcurl to not attempt to use any workarounds for a security flaw in the SSL3 and TLS1.0 protocols. If this option is not used or this bit is set to 0, -the SSL layer libcurl uses may use a work-around for this flaw although it +the SSL layer libcurl uses may use a workaround for this flaw although it might cause interoperability problems with some (older) SSL implementations. -**WARNING:** avoiding this work-around lessens the security, and by setting +**WARNING:** avoiding this workaround lessens the security, and by setting this option to 1 you ask for exactly that. This option is only supported for Secure Transport and OpenSSL. @@ -56,7 +56,7 @@ Tells libcurl to not accept "partial" certificate chains, which it otherwise does by default. This option fails the certificate verification if the chain ends with an intermediate certificate and not with a root cert. -Works with OpenSSL and its forks (LibreSSL, BoringSSL, etc). (Added in 7.68.0) +Works with OpenSSL and its forks (BoringSSL, LibreSSL, etc). (Added in 7.68.0) Works with Schannel if the user specified certificates to verify the peer. (Added in 8.15.0) @@ -76,9 +76,9 @@ verification. This option is independent of other CA certificate locations set at run time or build time. Those locations are searched in addition to the native CA store. -Works with wolfSSL on Windows, Linux (Debian, Ubuntu, Gentoo, Fedora, RHEL), +Works with wolfSSL on Windows, Linux (Debian, Fedora, Gentoo, RHEL, Ubuntu), macOS, Android and iOS (added in 8.3.0); with GnuTLS (added in 8.5.0) and with -OpenSSL and its forks (LibreSSL, BoringSSL, etc) on Windows (Added in 7.71.0). +OpenSSL and its forks (BoringSSL, LibreSSL, etc) on Windows (Added in 7.71.0). This works with Rustls on Windows, macOS, Android and iOS. On Linux it is equivalent to using the Mozilla CA certificate bundle. When used with Rustls @@ -98,13 +98,12 @@ could be a privacy violation and unexpected. ## CURLSSLOPT_EARLYDATA Tell libcurl to try sending application data as TLS1.3 early data. This option -is supported for GnuTLS, wolfSSL, quictls and OpenSSL (but not BoringSSL -or AWS-LC). It works on TCP and QUIC connections using ngtcp2. +is supported for GnuTLS, OpenSSL, quictls and wolfSSL (but not AWS-LC or +BoringSSL). It works on TCP and QUIC connections using ngtcp2. This option works on a best effort basis, in cases when it was not possible to send early data the request is resent normally post-handshake. -This option does not work when using QUIC. -(Added in 8.11.0 for GnuTLS and 8.13.0 for wolfSSL, quictls and OpenSSL) +(Added in 8.11.0 for GnuTLS and 8.13.0 for OpenSSL, quictls and wolfSSL) # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_STDERR.md b/docs/libcurl/opts/CURLOPT_STDERR.md index 213eb9ef0e8c..9294fafa60a0 100644 --- a/docs/libcurl/opts/CURLOPT_STDERR.md +++ b/docs/libcurl/opts/CURLOPT_STDERR.md @@ -33,7 +33,7 @@ data. If you are using libcurl as a Windows DLL, this option causes an exception and a crash in the library since it cannot access a FILE * passed on from the -application. A work-around is to instead use CURLOPT_DEBUGFUNCTION(3). +application. A workaround is to instead use CURLOPT_DEBUGFUNCTION(3). # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.md b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.md index 5e3e177dce8f..e0e8ef0c53c3 100644 --- a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.md +++ b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.md @@ -44,6 +44,10 @@ an error. It must be another easy handle, and it also needs to be a handle of a transfer that is about to be sent over the same HTTP/2 connection for this option to have an actual effect. +Since version 8.21.0 setting this option no longer has an effect. HTTP/2 +stream dependencies were introduced in RFC 7540 and then later deprecated +in RFC 9113. + # DEFAULT NULL @@ -69,6 +73,10 @@ int main(void) } ~~~ +# DEPRECATED + +Deprecated since 8.21.0. + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.md b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.md index fe95ae8d7101..fe8f97f950d8 100644 --- a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.md +++ b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.md @@ -47,6 +47,10 @@ an error. It must be another easy handle, and it also needs to be a handle of a transfer that is about to be sent over the same HTTP/2 connection for this option to have an actual effect. +Since version 8.21.0 setting this option no longer has an effect. HTTP/2 +stream dependencies were introduced in RFC 7540 and then later deprecated +in RFC 9113. + # DEFAULT NULL @@ -72,6 +76,10 @@ int main(void) } ~~~ +# DEPRECATED + +Deprecated since 8.21.0. + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_TFTP_NO_OPTIONS.md b/docs/libcurl/opts/CURLOPT_TFTP_NO_OPTIONS.md index 43f3317c01a5..63b9444168cc 100644 --- a/docs/libcurl/opts/CURLOPT_TFTP_NO_OPTIONS.md +++ b/docs/libcurl/opts/CURLOPT_TFTP_NO_OPTIONS.md @@ -41,7 +41,7 @@ CURLOPT_TFTP_BLKSIZE(3) is ignored. # EXAMPLE ~~~c -size_t write_callback(char *ptr, size_t size, size_t nmemb, void *fp) +static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *fp) { return fwrite(ptr, size, nmemb, (FILE *)fp); } diff --git a/docs/libcurl/opts/CURLOPT_TLSAUTH_PASSWORD.md b/docs/libcurl/opts/CURLOPT_TLSAUTH_PASSWORD.md index 89dd79ebc946..fba7af2374de 100644 --- a/docs/libcurl/opts/CURLOPT_TLSAUTH_PASSWORD.md +++ b/docs/libcurl/opts/CURLOPT_TLSAUTH_PASSWORD.md @@ -30,6 +30,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TLSAUTH_PASSWORD, char *pwd); # DESCRIPTION +Deprecated option. It serves no purpose anymore. + Pass a char pointer as parameter, which should point to the null-terminated password to use for the TLS authentication method specified with the CURLOPT_TLSAUTH_TYPE(3) option. Requires that the CURLOPT_TLSAUTH_USERNAME(3) @@ -47,6 +49,10 @@ This feature relies on TLS-SRP which does not work with TLS 1.3. NULL +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_TLSAUTH_TYPE.md b/docs/libcurl/opts/CURLOPT_TLSAUTH_TYPE.md index 35ae6bf4674c..9b2bbe0bf54d 100644 --- a/docs/libcurl/opts/CURLOPT_TLSAUTH_TYPE.md +++ b/docs/libcurl/opts/CURLOPT_TLSAUTH_TYPE.md @@ -29,6 +29,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TLSAUTH_TYPE, char *type); # DESCRIPTION +Deprecated option. It serves no purpose anymore. + Pass a pointer to a null-terminated string as parameter. The string should be the method of the TLS authentication. Supported method is "SRP". @@ -51,6 +53,10 @@ TLS-SRP does not work with TLS 1.3. blank +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_TLSAUTH_USERNAME.md b/docs/libcurl/opts/CURLOPT_TLSAUTH_USERNAME.md index b1d352e80e95..01ea52d35e42 100644 --- a/docs/libcurl/opts/CURLOPT_TLSAUTH_USERNAME.md +++ b/docs/libcurl/opts/CURLOPT_TLSAUTH_USERNAME.md @@ -29,6 +29,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TLSAUTH_USERNAME, char *user); # DESCRIPTION +Deprecated option. It serves no purpose anymore. + Pass a char pointer as parameter, which should point to the null-terminated username to use for the TLS authentication method specified with the CURLOPT_TLSAUTH_TYPE(3) option. Requires that the CURLOPT_TLSAUTH_PASSWORD(3) @@ -46,6 +48,10 @@ This feature relies on TLS-SRP which does not work with TLS 1.3. NULL +# DEPRECATED + +This option was deprecated in 8.22.0. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.md b/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.md index 716967d88adf..3d9e2d096a6b 100644 --- a/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.md @@ -79,6 +79,7 @@ int main(void) CURL *curl = curl_easy_init(); if(curl) { CURLcode result; + struct curl_slist *headers; /* Set the URL of the request */ curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); @@ -89,8 +90,7 @@ int main(void) Let that function be read_cb */ curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb); - struct curl_slist *headers = NULL; - headers = curl_slist_append(headers, "Trailer: My-super-awesome-trailer"); + headers = curl_slist_append(NULL, "Trailer: My-super-awesome-trailer"); result = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); /* Set the trailers filling callback */ diff --git a/docs/libcurl/opts/CURLOPT_UNRESTRICTED_AUTH.md b/docs/libcurl/opts/CURLOPT_UNRESTRICTED_AUTH.md index 2dc15ca6b014..37a9fe56110d 100644 --- a/docs/libcurl/opts/CURLOPT_UNRESTRICTED_AUTH.md +++ b/docs/libcurl/opts/CURLOPT_UNRESTRICTED_AUTH.md @@ -36,12 +36,12 @@ when following locations, even when the host changes. This option is meaningful only when setting CURLOPT_FOLLOWLOCATION(3). Further, when this option is not used or set to **0L**, libcurl does not send -custom nor internally generated `Authentication:` or `Cookie:` headers on +custom nor internally generated `Authorization:` or `Cookie:` headers on requests done to other hosts than the one used for the initial URL. Another host means that one or more of hostname, protocol scheme or port number changed. -By default, libcurl only sends `Authentication:` or explicitly set `Cookie:` +By default, libcurl only sends `Authorization:` or explicitly set `Cookie:` headers to the initial host as given in the original URL, to avoid leaking username + password to other sites. diff --git a/docs/libcurl/opts/CURLOPT_URL.md b/docs/libcurl/opts/CURLOPT_URL.md index 2ad4739391d8..1790ce3288e9 100644 --- a/docs/libcurl/opts/CURLOPT_URL.md +++ b/docs/libcurl/opts/CURLOPT_URL.md @@ -44,7 +44,7 @@ libcurl does not validate the syntax or use the URL until the transfer is started. Even if you set a crazy value here, curl_easy_setopt(3) might still return *CURLE_OK*. -If the given URL is missing a scheme name (such as "http://" or "ftp://" etc) +If the given URL is missing a scheme name (such as `http://` or `ftp://` etc) then libcurl guesses based on the host. If the outermost subdomain name matches DICT, FTP, IMAP, LDAP, POP3 or SMTP then that protocol gets used, otherwise HTTP is used. Scheme guessing can be disabled by setting a default @@ -72,7 +72,7 @@ option. Using this option multiple times makes the last set string override the previous ones. Set it to NULL to disable its use again. Note however that -libcurl needs a URL set to be able to performed a transfer. +libcurl needs a URL set to be able to perform a transfer. The parser used for handling the URL set with CURLOPT_URL(3) is the same that curl_url_set(3) uses. @@ -111,7 +111,7 @@ are part of the regular URL format. The combination of a local host and a custom port number can allow external users to play tricks with your local services. -Accepting external URLs may also use other protocols than http:// or other +Accepting external URLs may also use other protocols than `http://` or other common ones. Restrict what accept with CURLOPT_PROTOCOLS_STR(3). User provided URLs can also be made to point to sites that redirect further on diff --git a/docs/libcurl/opts/CURLOPT_USERNAME.md b/docs/libcurl/opts/CURLOPT_USERNAME.md index faf703194904..b0c22c9316b9 100644 --- a/docs/libcurl/opts/CURLOPT_USERNAME.md +++ b/docs/libcurl/opts/CURLOPT_USERNAME.md @@ -32,17 +32,17 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_USERNAME, Pass a char pointer as parameter, which should be pointing to the null-terminated username to use for the transfer. -CURLOPT_USERNAME(3) sets the username to be used in protocol -authentication. You should not use this option together with the (older) -CURLOPT_USERPWD(3) option. +CURLOPT_USERNAME(3) sets the username to be used in protocol authentication. +You should not use this option together with the (older) CURLOPT_USERPWD(3) +option. When using Kerberos V5 authentication with a Windows based server, you should include the domain name in order for the server to successfully obtain a Kerberos Ticket. If you do not then the initial part of the authentication handshake may fail. -When using NTLM, the username can be specified without the domain name -should the server be part of a single domain and forest. +When using NTLM, the username can be specified without the domain name should +the server be part of a single domain and forest. To include the domain name use either Down-Level Logon Name or UPN (User Principal Name) formats. For example, **EXAMPLE\user** and @@ -57,9 +57,17 @@ CURLOPT_PASSWORD(3) and CURLOPT_LOGIN_OPTIONS(3) options. The application does not have to keep the string around after setting this option. +For some authentication methods (`Negotiate`, or `NTLM` when built to use +SSPI), setting it to a zero-length string (`""`) makes libcurl use an implied +*ambient* user decided by the environment. libcurl cannot identify this user +and may reuse an authenticated connection for a later transfer on the same +easy handle. If the ambient user changes while that connection remains +reusable, the later transfer can be authenticated as the previous user. +Applications must prevent such reuse when changing ambient users. + # DEFAULT -blank +NULL # %PROTOCOLS% diff --git a/docs/libcurl/opts/CURLOPT_WRITEFUNCTION.md b/docs/libcurl/opts/CURLOPT_WRITEFUNCTION.md index 407484debb27..792ed3d3b8ff 100644 --- a/docs/libcurl/opts/CURLOPT_WRITEFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_WRITEFUNCTION.md @@ -47,6 +47,11 @@ defined in the curl.h header file: *CURL_MAX_WRITE_SIZE* (the usual default is the write callback, you can get up to *CURL_MAX_HTTP_HEADER* bytes of header data passed into it. This usually means 100K. +The CURLOPT_WRITEFUNCTION(3) callback receives the final response payload. +When CURLOPT_FOLLOWLOCATION(3) is enabled, libcurl automatically handles +intermediate 3xx redirects, meaning their HTTP bodies are skipped and not +passed to this callback. + This function may be called with zero bytes data if the transferred file is empty. @@ -60,16 +65,13 @@ aborted and the libcurl function used returns *CURLE_WRITE_ERROR*. You can also abort the transfer by returning CURL_WRITEFUNC_ERROR (added in 7.87.0), which makes *CURLE_WRITE_ERROR* get returned. -If the callback function returns CURL_WRITEFUNC_PAUSE it pauses this -transfer. See curl_easy_pause(3) for further details. +If the callback function returns CURL_WRITEFUNC_PAUSE it pauses this transfer. +See curl_easy_pause(3) for further details. Set this option to NULL to get the internal default function used instead of your callback. The internal default function writes the data to the FILE * given with CURLOPT_WRITEDATA(3). -This option does not enable HSTS, you need to use CURLOPT_HSTS_CTRL(3) to -do that. - # DEFAULT fwrite(3) diff --git a/docs/libcurl/opts/CURLOPT_XFERINFODATA.md b/docs/libcurl/opts/CURLOPT_XFERINFODATA.md index 5099e0a46944..78fc0274257a 100644 --- a/docs/libcurl/opts/CURLOPT_XFERINFODATA.md +++ b/docs/libcurl/opts/CURLOPT_XFERINFODATA.md @@ -42,7 +42,7 @@ NULL ~~~c struct progress { - char *private; + char *private_data; size_t size; }; @@ -53,7 +53,7 @@ static int progress_cb(void *clientp, curl_off_t ulnow) { struct progress *memory = clientp; - printf("private ptr: %p\n", memory->private); + printf("private ptr: %p\n", (void *)memory->private_data); /* use the values */ return 0; /* all is good */ diff --git a/docs/libcurl/opts/CURLOPT_XFERINFOFUNCTION.md b/docs/libcurl/opts/CURLOPT_XFERINFOFUNCTION.md index 161be8fd4b3d..9c074b60f66c 100644 --- a/docs/libcurl/opts/CURLOPT_XFERINFOFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_XFERINFOFUNCTION.md @@ -81,7 +81,7 @@ NULL - use the internal progress meter. That is rarely wanted by users. ~~~c struct progress { - char *private; + char *private_data; size_t size; }; @@ -92,7 +92,7 @@ static int xferinfo_callback(void *clientp, curl_off_t ulnow) { struct progress *memory = clientp; - printf("my ptr: %p\n", memory->private); + printf("my ptr: %p\n", (void *)memory->private_data); /* use the values */ diff --git a/docs/libcurl/opts/CURLSHOPT_SHARE.md b/docs/libcurl/opts/CURLSHOPT_SHARE.md index 60e1405bb229..827ba77ab607 100644 --- a/docs/libcurl/opts/CURLSHOPT_SHARE.md +++ b/docs/libcurl/opts/CURLSHOPT_SHARE.md @@ -35,6 +35,9 @@ CURLSHOPT_SHARE(3) multiple times with different data arguments to have the share object share multiple types of data. Unset a type again by setting CURLSHOPT_UNSHARE(3). +Do not add types to a shared object that is being in use. Add them only +between transfers. + If any of the data is to be shared in multiple threads then mutex callbacks must be set as well. See CURLSHOPT_LOCKFUNC(3) and CURLSHOPT_UNLOCKFUNC(3). @@ -76,6 +79,11 @@ in different threads using a shared connection. Note that when you use the multi interface, all easy handles added to the same multi handle share the connection cache by default without using this option. +Connection limits set with CURLMOPT_MAX_HOST_CONNECTIONS(3) and +CURLMOPT_MAX_TOTAL_CONNECTIONS(3) also apply to transfers using a shared +connection cache. Each transfer applies the limits of the multi handle it +runs on to the shared cache. + ## CURL_LOCK_DATA_PSL The Public Suffix List stored in the share object is made available to all diff --git a/docs/libcurl/opts/CURLSHOPT_UNSHARE.md b/docs/libcurl/opts/CURLSHOPT_UNSHARE.md index 45ee27dec0f0..5ad0c60c7bd6 100644 --- a/docs/libcurl/opts/CURLSHOPT_UNSHARE.md +++ b/docs/libcurl/opts/CURLSHOPT_UNSHARE.md @@ -36,6 +36,9 @@ below. You can set CURLSHOPT_UNSHARE(3) multiple times with different data arguments to remove multiple types from the shared object. Add data to share again with CURLSHOPT_SHARE(3). +Do not remove types from a shared object that is being in use. Unshare them +only between transfers. + ## CURL_LOCK_DATA_COOKIE Cookie data is no longer shared across the easy handles using this shared diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index 1eb628b800a4..f0dc077a9423 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -227,6 +227,10 @@ man_MANS = \ CURLOPT_HTTPHEADER.3 \ CURLOPT_HTTPPOST.3 \ CURLOPT_HTTPPROXYTUNNEL.3 \ + CURLOPT_HTTPSIG_ALGORITHM.3 \ + CURLOPT_HTTPSIG_HEADERS.3 \ + CURLOPT_HTTPSIG_KEY.3 \ + CURLOPT_HTTPSIG_KEYID.3 \ CURLOPT_IGNORE_CONTENT_LENGTH.3 \ CURLOPT_INFILESIZE.3 \ CURLOPT_INFILESIZE_LARGE.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 0d775fa65594..65279d642659 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -151,8 +151,8 @@ CURL_TIMECOND_IFMODSINCE 7.9.7 CURL_TIMECOND_IFUNMODSINCE 7.9.7 CURL_TIMECOND_LASTMOD 7.9.7 CURL_TIMECOND_NONE 7.9.7 -CURL_TLSAUTH_NONE 7.21.4 -CURL_TLSAUTH_SRP 7.21.4 +CURL_TLSAUTH_NONE 7.21.4 8.22.0 +CURL_TLSAUTH_SRP 7.21.4 8.22.0 CURL_TRAILERFUNC_ABORT 7.64.0 CURL_TRAILERFUNC_OK 7.64.0 CURL_UPKEEP_INTERVAL_DEFAULT 7.62.0 @@ -184,7 +184,7 @@ CURL_VERSION_SPNEGO 7.10.8 CURL_VERSION_SSL 7.10 CURL_VERSION_SSPI 7.13.2 CURL_VERSION_THREADSAFE 7.84.0 -CURL_VERSION_TLSAUTH_SRP 7.21.4 +CURL_VERSION_TLSAUTH_SRP 7.21.4 8.22.0 CURL_VERSION_UNICODE 7.72.0 CURL_VERSION_UNIX_SOCKETS 7.40.0 CURL_VERSION_ZSTD 7.72.0 @@ -202,10 +202,11 @@ CURLALTSVC_READONLYFILE 7.64.1 CURLAUTH_ANY 7.10.6 CURLAUTH_ANYSAFE 7.10.6 CURLAUTH_AWS_SIGV4 7.75.0 +CURLAUTH_HTTPSIG 8.22.0 CURLAUTH_BASIC 7.10.6 CURLAUTH_BEARER 7.61.0 CURLAUTH_DIGEST 7.10.6 -CURLAUTH_DIGEST_IE 7.19.3 +CURLAUTH_DIGEST_IE 7.19.3 8.21.0 CURLAUTH_GSSAPI 7.55.0 CURLAUTH_GSSNEGOTIATE 7.10.6 7.38.0 CURLAUTH_NEGOTIATE 7.38.0 @@ -420,6 +421,9 @@ CURLHEADER_SEPARATE 7.37.0 CURLHEADER_UNIFIED 7.37.0 CURLHSTS_ENABLE 7.74.0 CURLHSTS_READONLYFILE 7.74.0 +CURLHTTPSIG_ED25519 8.22.0 +CURLHTTPSIG_HMAC_SHA256 8.22.0 +CURLHTTPSIG_NONE 8.22.0 CURLINFO_ACTIVESOCKET 7.45.0 CURLINFO_APPCONNECT_TIME 7.19.0 CURLINFO_APPCONNECT_TIME_T 7.61.0 @@ -697,6 +701,10 @@ CURLOPT_HTTPHEADER 7.1 CURLOPT_HTTPPOST 7.1 7.56.0 CURLOPT_HTTPPROXYTUNNEL 7.3 CURLOPT_HTTPREQUEST 7.1 - 7.15.5 +CURLOPT_HTTPSIG_ALGORITHM 8.22.0 +CURLOPT_HTTPSIG_HEADERS 8.22.0 +CURLOPT_HTTPSIG_KEY 8.22.0 +CURLOPT_HTTPSIG_KEYID 8.22.0 CURLOPT_IGNORE_CONTENT_LENGTH 7.14.1 CURLOPT_INFILE 7.1 7.9.7 CURLOPT_INFILESIZE 7.1 @@ -791,9 +799,9 @@ CURLOPT_PROXY_SSLKEY_BLOB 7.71.0 CURLOPT_PROXY_SSLKEYTYPE 7.52.0 CURLOPT_PROXY_SSLVERSION 7.52.0 CURLOPT_PROXY_TLS13_CIPHERS 7.61.0 -CURLOPT_PROXY_TLSAUTH_PASSWORD 7.52.0 -CURLOPT_PROXY_TLSAUTH_TYPE 7.52.0 -CURLOPT_PROXY_TLSAUTH_USERNAME 7.52.0 +CURLOPT_PROXY_TLSAUTH_PASSWORD 7.52.0 8.22.0 +CURLOPT_PROXY_TLSAUTH_TYPE 7.52.0 8.22.0 +CURLOPT_PROXY_TLSAUTH_USERNAME 7.52.0 8.22.0 CURLOPT_PROXY_TRANSFER_MODE 7.18.0 CURLOPT_PROXYAUTH 7.10.7 CURLOPT_PROXYHEADER 7.37.0 @@ -882,8 +890,8 @@ CURLOPT_SSLKEYPASSWD 7.9.3 7.17.0 CURLOPT_SSLKEYTYPE 7.9.3 CURLOPT_SSLVERSION 7.1 CURLOPT_STDERR 7.1 -CURLOPT_STREAM_DEPENDS 7.46.0 -CURLOPT_STREAM_DEPENDS_E 7.46.0 +CURLOPT_STREAM_DEPENDS 7.46.0 8.21.0 +CURLOPT_STREAM_DEPENDS_E 7.46.0 8.21.0 CURLOPT_STREAM_WEIGHT 7.46.0 CURLOPT_SUPPRESS_CONNECT_HEADERS 7.54.0 CURLOPT_TCP_FASTOPEN 7.49.0 @@ -901,9 +909,9 @@ CURLOPT_TIMEOUT_MS 7.16.2 CURLOPT_TIMEVALUE 7.1 CURLOPT_TIMEVALUE_LARGE 7.59.0 CURLOPT_TLS13_CIPHERS 7.61.0 -CURLOPT_TLSAUTH_PASSWORD 7.21.4 -CURLOPT_TLSAUTH_TYPE 7.21.4 -CURLOPT_TLSAUTH_USERNAME 7.21.4 +CURLOPT_TLSAUTH_PASSWORD 7.21.4 8.22.0 +CURLOPT_TLSAUTH_TYPE 7.21.4 8.22.0 +CURLOPT_TLSAUTH_USERNAME 7.21.4 8.22.0 CURLOPT_TRAILERDATA 7.64.0 CURLOPT_TRAILERFUNCTION 7.64.0 CURLOPT_TRANSFER_ENCODING 7.21.6 @@ -993,6 +1001,7 @@ CURLPROXY_HTTP 7.10 CURLPROXY_HTTP_1_0 7.19.4 CURLPROXY_HTTPS 7.52.0 CURLPROXY_HTTPS2 8.1.0 +CURLPROXY_HTTPS3 8.21.0 CURLPROXY_SOCKS4 7.10 CURLPROXY_SOCKS4A 7.18.0 CURLPROXY_SOCKS5 7.10 @@ -1104,6 +1113,7 @@ CURLU_PUNY2IDN 8.3.0 CURLU_PUNYCODE 7.88.0 CURLU_URLDECODE 7.62.0 CURLU_URLENCODE 7.62.0 +CURLUE_BACKSLASH 8.22.0 CURLUE_BAD_FILE_URL 7.81.0 CURLUE_BAD_FRAGMENT 7.81.0 CURLUE_BAD_HANDLE 7.62.0 diff --git a/docs/libcurl/symbols.pl b/docs/libcurl/symbols.pl index 4126fe54e1a6..1472d59bb188 100755 --- a/docs/libcurl/symbols.pl +++ b/docs/libcurl/symbols.pl @@ -47,10 +47,10 @@ use strict; use warnings; -open F, ") { if(/^(CURL[^ ]*)[ \t]*(.*)/) { - my ($sym, $vers)=($1, $2); + my ($sym, $vers) = ($1, $2); my $intr; my $rm; @@ -77,11 +77,11 @@ sub str2num { # is there removed info? if($vers =~ /([\d.]+)[ \t-]+([\d.-]+)[ \t]+([\d.]+)/) { - ($intr, $dep, $rm)=($1, $2, $3); + ($intr, $dep, $rm) = ($1, $2, $3); } # is it a dep-only line? elsif($vers =~ /([\d.]+)[ \t-]+([\d.]+)/) { - ($intr, $dep)=($1, $2); + ($intr, $dep) = ($1, $2); } else { $intr = $vers; diff --git a/docs/options-in-versions b/docs/options-in-versions index 95d84a4bfec2..595cb05caab5 100644 --- a/docs/options-in-versions +++ b/docs/options-in-versions @@ -98,6 +98,10 @@ --http2-prior-knowledge 7.49.0 --http3 7.66.0 --http3-only 7.88.0 +--httpsig-algo 8.22.0 +--httpsig-headers 8.22.0 +--httpsig-key 8.22.0 +--httpsig-keyid 8.22.0 --ignore-content-length 7.14.1 --ip-tos 8.9.0 --ipfs-gateway 8.4.0 @@ -177,6 +181,7 @@ --proxy-digest 7.12.0 --proxy-header 7.37.0 --proxy-http2 8.1.0 +--proxy-http3 8.21.0 --proxy-insecure 7.52.0 --proxy-key 7.52.0 --proxy-key-type 7.52.0 diff --git a/docs/runtests.md b/docs/runtests.md index 79066d8ffd0b..0b4b1432e5ad 100644 --- a/docs/runtests.md +++ b/docs/runtests.md @@ -164,11 +164,6 @@ CPU cores is a good figure to start with, or 1.3 times if Valgrind is in use, or 5 times for torture tests. Enabling parallel tests is not recommended in conjunction with the -g option. -## `-k` - -Keep output and log files in log/ after a test run, even if no error was -detected. Useful for debugging. - ## `-L \` Load and execute the specified file which should contain perl code. This @@ -265,6 +260,24 @@ script randomly discards entries to fail until the amount is **num**. The random seed initially set for this is fixed per month and can be set with *--seed*. +## `--subset=
/` + +Tell runtests to run a subset of the available tests. The test selection is +done first based on existing options. The list of selected tests is then +divided into a number of *parts*. The selected *section* specifier is the +set of tests this invoke runs. Note that both numbers must be provided and +*section* is zero indexed so it must be smaller than *parts*. + +Using this option you can for example split up an identical test run into +three separate invokes that combined run all the tests: + + ./runtests.pl --subset=0/3 + ./runtests.pl --subset=1/3 + ./runtests.pl --subset=2/3 + +This option works fine in combination with `-R` but consider also using +`--seed` for that. + ## `-t[num]` Selects a **torture** test for the given tests. This makes runtests.pl first diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index f3121fd3c9ad..cb4743b89153 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -200,8 +200,8 @@ Available substitute variables include: - `%SOCKSPORT` - Port number of the SOCKS4/5 server - `%SOCKSUNIXPATH` - Path to the Unix socket of the SOCKS server - `%SRCDIR` - Full path to the source dir -- `%SCP_PWD` - Current directory friendly for the SSH server for the scp:// protocol -- `%SFTP_PWD` - Current directory friendly for the SSH server for the sftp:// protocol +- `%SCP_PWD` - Current directory friendly for the SSH server for the `scp://` protocol +- `%SFTP_PWD` - Current directory friendly for the SSH server for the `sftp://` protocol - `%SSHKEYALGO` - SSH host and client key algorithm, e.g. `ssh-rsa` or `ssh-ed25519` - `%SSHPORT` - Port number of the SCP/SFTP server - `%SSHSRVMD5` - MD5 of SSH server's public key @@ -421,7 +421,7 @@ issue. Commands for the test DNS server. -- `A: [dotted ipv4 address]` - set IPv4 address to return +- `A: [dotted IPv4 address]` - set IPv4 address to return - `AAAA: [numerical IPv6 address]` - set IPv6 address to return, with or without `[]` @@ -445,8 +445,6 @@ What server(s) this test case requires/uses. Available servers: - `https` - `https-proxy` - `https-mtls` -- `httptls+srp` -- `httptls+srp-ipv6` - `http-unix` - `imap` - `mqtt` @@ -487,7 +485,7 @@ Features testable here are: - `brotli` - `c-ares` - c-ares is used for (all) name resolves - `CharConv` -- `codeset-utf8`. If the running codeset is UTF-8 capable. +- `codeset-utf8` - if the running codeset is UTF-8 capable. - `cookies` - `crypto` - `cygwin` @@ -509,13 +507,13 @@ Features testable here are: - `IPv6` - `Kerberos` - `Largefile` -- `large-time` (time_t is larger than 32-bit) -- `large-size` (size_t is larger than 32-bit) +- `large-time` - time_t is larger than 32-bit +- `large-size` - size_t is larger than 32-bit - `libssh2` - `libssh` -- `badlibssh` (libssh configuration incompatible with the test suite) +- `badlibssh` - libssh configuration incompatible with the test suite - `libz` -- `local-http`. The HTTP server runs on 127.0.0.1 +- `local-http` - the HTTP server runs on 127.0.0.1 - `manual` - `mbedtls` - `Mime` @@ -536,9 +534,11 @@ Features testable here are: - `SPNEGO` - `SSL` - `SSLpinning` +- `SSLS-EXPORT` - `SSPI` - `threaded-resolver` - `TLS-SRP` +- `torture` - if runtests is running in memory test mode - `TrackMemory` - `typecheck` - `threadsafe` @@ -589,7 +589,7 @@ command has been run. If the variable name has no assignment, no `=`, then that variable is deleted. -### `` +### `` Command line to run. If the command spans multiple lines, they are concatenated with a space added @@ -605,6 +605,9 @@ Set `option="no-output"` to prevent the test script to slap on the `--output` argument that directs the output to a file. The `--output` is also not added if the verify/stdout section is used. +Set `option="no-stdout"` to prevent the normal stdout redirect template to be +applied on the command line to run. + Set `option="force-output"` to make use of `--output` even when the test is otherwise written to verify stdout. diff --git a/docs/tests/HTTP.md b/docs/tests/HTTP.md index 88fb9a0c4b4e..79f3fac20054 100644 --- a/docs/tests/HTTP.md +++ b/docs/tests/HTTP.md @@ -62,6 +62,9 @@ Via curl's `configure` script you may specify: * `--with-test-nghttpx=` if you have nghttpx to use somewhere outside your `$PATH`. + * `--with-test-h2o=` if you have h2o to use somewhere + outside your `$PATH`. + * `--with-test-httpd=` if you have an Apache httpd installed somewhere else. On Debian/Ubuntu it otherwise looks into `/usr/bin` and `/usr/sbin` to find those. diff --git a/docs/tests/TEST-SUITE.md b/docs/tests/TEST-SUITE.md index dc7671e2e651..6856eb5f8a21 100644 --- a/docs/tests/TEST-SUITE.md +++ b/docs/tests/TEST-SUITE.md @@ -136,7 +136,7 @@ set to identify the IP address and port number of the DNS server to use. host information - curl built to use `getaddrinfo()` for resolving *and* is built with c-ares - 1.26.0 or later, gets a special work-around. In such builds, when the + 1.26.0 or later, gets a special workaround. In such builds, when the environment variable is set, curl instead invokes a getaddrinfo wrapper that emulates the function and acknowledges the DNS server environment variable. This way, the getaddrinfo-using code paths in curl are verified, @@ -188,6 +188,9 @@ that memory leaks do not occur even in those situations. It can help to compile curl with `CPPFLAGS=-DMEMDEBUG_LOG_SYNC` when using this option, to ensure that the memory log file is properly written even if curl crashes. +If a specific test takes a long time to run in memory test mode, you can +disable it individually by adding `!torture` to its `` section. + ### Debug If a test case fails, you can conveniently get the script to invoke the diff --git a/docs/wcurl.md b/docs/wcurl.md index f0ed42baf25d..4137e98b35a6 100644 --- a/docs/wcurl.md +++ b/docs/wcurl.md @@ -35,8 +35,8 @@ Call **wcurl** with a list of URLs you want to download and **wcurl** picks sane defaults. If you need anything more complex, you can provide any of curl's supported -parameters via the **--curl-options** option. Beware that you likely should be -using curl directly if your use case is not covered. +parameters via the **--curl-options** option. Beware that you likely should +be using curl directly if your use case is not covered. By default, **wcurl** does: diff --git a/include/curl/curl.h b/include/curl/curl.h index db2c53cf89f5..676e1daa733b 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -93,7 +93,9 @@ defined(__CYGWIN__) || defined(AMIGA) || defined(__NuttX__) || \ (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) || \ (defined(__MidnightBSD_version) && (__MidnightBSD_version < 100000)) || \ - defined(__sun__) || defined(__serenity__) || defined(__vxworks__) + defined(__sun__) || defined(__serenity__) || defined(__vxworks__) || \ + (defined(__linux__) && defined(_POSIX_C_SOURCE) && \ + _POSIX_C_SOURCE >= 200112L) #include #endif @@ -253,7 +255,7 @@ typedef int (*curl_xferinfo_callback)(void *clientp, #ifndef CURL_MAX_READ_SIZE /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */ -#define CURL_MAX_READ_SIZE (10*1024*1024) +#define CURL_MAX_READ_SIZE (10 * 1024 * 1024) #endif #ifndef CURL_MAX_WRITE_SIZE @@ -270,7 +272,7 @@ typedef int (*curl_xferinfo_callback)(void *clientp, /* The only reason to have a max limit for this is to avoid the risk of a bad server feeding libcurl with a never-ending header that causes reallocs infinitely */ -#define CURL_MAX_HTTP_HEADER (100*1024) +#define CURL_MAX_HTTP_HEADER (100 * 1024) #endif /* This is a magic return code for the write callback that, when returned, @@ -594,7 +596,7 @@ typedef enum { CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind that failed */ - CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialize ENGINE */ CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not accepted and we failed to login */ CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ @@ -633,7 +635,7 @@ typedef enum { match */ CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer - */ + */ CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from inside a callback */ CURLE_AUTH_ERROR, /* 94 - an authentication function returned an @@ -816,9 +818,11 @@ typedef CURLcode (*curl_unity_certverify_callback)(CURL *curl, #define CURLPROXY_SOCKS5_HOSTNAME 7L /* Use the SOCKS5 protocol but pass along the hostname rather than the IP address. added in 7.18.0 */ +#define CURLPROXY_HTTPS3 8L /* HTTPS and attempt HTTP/3 + added in 8.21.0 */ typedef enum { - CURLPROXY_LAST = 8 /* never use */ + CURLPROXY_LAST = 9 /* never use */ } curl_proxytype; /* this enum was added in 7.10 */ /* @@ -830,7 +834,7 @@ typedef enum { * CURLAUTH_NEGOTIATE - HTTP Negotiate (SPNEGO) authentication * CURLAUTH_GSSNEGOTIATE - Alias for CURLAUTH_NEGOTIATE (deprecated) * CURLAUTH_NTLM - HTTP NTLM authentication - * CURLAUTH_DIGEST_IE - HTTP Digest authentication with IE flavour + * CURLAUTH_DIGEST_IE - HTTP Digest authentication with IE flavor * CURLAUTH_NTLM_WB - HTTP NTLM authentication delegated to winbind helper * CURLAUTH_BEARER - HTTP Bearer token authentication * CURLAUTH_ONLY - Use together with a single other type to force no @@ -855,12 +859,20 @@ typedef enum { #endif #define CURLAUTH_BEARER (((unsigned long)1) << 6) #define CURLAUTH_AWS_SIGV4 (((unsigned long)1) << 7) +#define CURLAUTH_HTTPSIG (((unsigned long)1) << 8) #define CURLAUTH_ONLY (((unsigned long)1) << 31) -#define CURLAUTH_ANY ((~CURLAUTH_DIGEST_IE) & \ +#define CURLAUTH_ANY ((~(CURLAUTH_DIGEST_IE | \ + CURLAUTH_HTTPSIG)) & \ ((unsigned long)0xffffffff)) -#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE)) & \ +#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE | \ + CURLAUTH_HTTPSIG)) & \ ((unsigned long)0xffffffff)) +/* constants for CURLOPT_HTTPSIG_ALGORITHM */ +#define CURLHTTPSIG_NONE 0L +#define CURLHTTPSIG_ED25519 1L +#define CURLHTTPSIG_HMAC_SHA256 2L + /* all types supported by server */ #define CURLSSH_AUTH_ANY ((unsigned long)0xffffffff) #define CURLSSH_AUTH_NONE 0L /* none allowed, silly but complete */ @@ -916,16 +928,16 @@ enum curl_khmatch { }; typedef int - (*curl_sshkeycallback) (CURL *easy, /* easy handle */ - const struct curl_khkey *knownkey, /* known */ - const struct curl_khkey *foundkey, /* found */ - enum curl_khmatch, /* libcurl's view on the keys */ - void *clientp); /* custom pointer passed with */ - /* CURLOPT_SSH_KEYDATA */ + (*curl_sshkeycallback)(CURL *easy, /* easy handle */ + const struct curl_khkey *knownkey, /* known */ + const struct curl_khkey *foundkey, /* found */ + enum curl_khmatch, /* libcurl's view on the keys */ + void *clientp); /* custom pointer passed with */ + /* CURLOPT_SSH_KEYDATA */ typedef int - (*curl_sshhostkeycallback) (void *clientp,/* custom pointer passed */ - /* with CURLOPT_SSH_HOSTKEYDATA */ + (*curl_sshhostkeycallback)(void *clientp,/* custom pointer passed */ + /* with CURLOPT_SSH_HOSTKEYDATA */ int keytype, /* CURLKHTYPE */ const char *key, /* hostkey to check */ size_t keylen); /* length of the key */ @@ -946,7 +958,7 @@ typedef enum { /* - ALLOW_BEAST tells libcurl to allow the BEAST SSL vulnerability in the name of improving interoperability with older servers. Some SSL libraries - have introduced work-arounds for this flaw but those work-arounds sometimes + have introduced workarounds for this flaw but those workarounds sometimes make the SSL communication fail. To regain functionality with those broken servers, a user can this way allow the vulnerability back. */ #define CURLSSLOPT_ALLOW_BEAST (1L << 0) @@ -1376,7 +1388,7 @@ typedef enum { CURLOPTDEPRECATED(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63, 8.17.0, "removed"), - /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ + /* Set if we should verify the peer in SSL handshake, set 1 to verify. */ CURLOPT(CURLOPT_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 64), /* The CApath or CAfile used to validate the peer certificate @@ -1434,7 +1446,7 @@ typedef enum { */ CURLOPT(CURLOPT_HTTPGET, CURLOPTTYPE_LONG, 80), - /* Set if we should verify the Common name from the peer certificate in ssl + /* Set if we should verify the Common name from the peer certificate in SSL * handshake, set 1 to check existence, 2 to ensure that it matches the * provided hostname. */ CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81), @@ -1508,8 +1520,8 @@ typedef enum { CURLOPT(CURLOPT_SHARE, CURLOPTTYPE_OBJECTPOINT, 100), /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default), - CURLPROXY_HTTPS, CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and - CURLPROXY_SOCKS5. */ + CURLPROXY_HTTPS, CURLPROXY_HTTPS2, CURLPROXY_HTTPS3, CURLPROXY_SOCKS4, + CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */ CURLOPT(CURLOPT_PROXYTYPE, CURLOPTTYPE_VALUES, 101), /* Set the Accept-Encoding string. Use this to tell a server you would like @@ -1538,19 +1550,18 @@ typedef enum { Note that setting multiple bits may cause extra network round-trips. */ CURLOPT(CURLOPT_HTTPAUTH, CURLOPTTYPE_VALUES, 107), - /* Set the ssl context callback function, currently only for OpenSSL or + /* Set the SSL context callback function, currently only for OpenSSL or 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), - /* Set the userdata for the ssl context callback function's third + /* Set the userdata for the SSL context callback function's third argument */ CURLOPT(CURLOPT_SSL_CTX_DATA, CURLOPTTYPE_CBPOINT, 109), /* FTP Option that causes missing dirs to be created on the remote server. In 7.19.4 we introduced the convenience enums for this option using the - CURLFTP_CREATE_DIR prefix. - */ + CURLFTP_CREATE_DIR prefix. */ CURLOPT(CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPTTYPE_LONG, 110), /* Set this to a bitmask value to enable the particular authentications @@ -1601,7 +1612,7 @@ typedef enum { 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 - */ + */ CURLOPT(CURLOPT_USE_SSL, CURLOPTTYPE_VALUES, 119), /* The _LARGE version of the standard POSTFIELDSIZE option */ @@ -1627,7 +1638,7 @@ typedef enum { CURLFTPAUTH_DEFAULT - let libcurl decide CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL - */ + */ CURLOPT(CURLOPT_FTPSSLAUTH, CURLOPTTYPE_VALUES, 129), CURLOPTDEPRECATED(CURLOPT_IOCTLFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 130, @@ -1662,8 +1673,7 @@ typedef enum { CURLOPT(CURLOPT_LOCALPORT, CURLOPTTYPE_LONG, 139), /* Number of ports to try, including the first one set with LOCALPORT. - Thus, setting it to 1 makes no additional attempts but the first. - */ + Thus, setting it to 1 makes no additional attempts but the first. */ CURLOPT(CURLOPT_LOCALPORTRANGE, CURLOPTTYPE_LONG, 140), /* no transfer, set up connection and let application use the socket by @@ -1875,13 +1885,16 @@ typedef enum { CURLOPT(CURLOPT_RESOLVE, CURLOPTTYPE_SLISTPOINT, 203), /* Set a username for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204, + 8.22.0, "Support was removed"), /* Set a password for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205, + 8.22.0, "Support was removed"), /* Set authentication type for authenticated TLS */ - CURLOPT(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206), + CURLOPTDEPRECATED(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206, + 8.22.0, "Support was removed"), /* Set to 1 to enable the "TE:" header in HTTP requests to ask for compressed transfer-encoded responses. Set to 0 to disable the use of TE: @@ -1949,11 +1962,11 @@ typedef enum { /* Set authentication options directly */ CURLOPT(CURLOPT_LOGIN_OPTIONS, CURLOPTTYPE_STRINGPOINT, 224), - /* Enable/disable TLS NPN extension (http2 over ssl might fail without) */ + /* Enable/disable TLS NPN extension (http2 over SSL might fail without) */ CURLOPTDEPRECATED(CURLOPT_SSL_ENABLE_NPN, CURLOPTTYPE_LONG, 225, 7.86.0, "Has no function"), - /* Enable/disable TLS ALPN extension (http2 over ssl might fail without) */ + /* Enable/disable TLS ALPN extension (http2 over SSL might fail without) */ CURLOPT(CURLOPT_SSL_ENABLE_ALPN, CURLOPTTYPE_LONG, 226), /* Time to wait for a response to an HTTP request containing an @@ -1999,10 +2012,12 @@ typedef enum { CURLOPT(CURLOPT_STREAM_WEIGHT, CURLOPTTYPE_LONG, 239), /* Set stream dependency on another curl handle */ - CURLOPT(CURLOPT_STREAM_DEPENDS, CURLOPTTYPE_OBJECTPOINT, 240), + CURLOPTDEPRECATED(CURLOPT_STREAM_DEPENDS, CURLOPTTYPE_OBJECTPOINT, 240, + 8.21.0, "Has no function"), /* Set E-xclusive stream dependency on another curl handle */ - CURLOPT(CURLOPT_STREAM_DEPENDS_E, CURLOPTTYPE_OBJECTPOINT, 241), + CURLOPTDEPRECATED(CURLOPT_STREAM_DEPENDS_E, CURLOPTTYPE_OBJECTPOINT, 241, + 8.21.0, "Has no function"), /* Do not send any tftp option requests to the server */ CURLOPT(CURLOPT_TFTP_NO_OPTIONS, CURLOPTTYPE_LONG, 242), @@ -2026,11 +2041,11 @@ typedef enum { this option is used only if PROXY_SSL_VERIFYPEER is true */ CURLOPT(CURLOPT_PROXY_CAPATH, CURLOPTTYPE_STRINGPOINT, 247), - /* Set if we should verify the proxy in ssl handshake, + /* Set if we should verify the proxy in SSL handshake, set 1 to verify. */ CURLOPT(CURLOPT_PROXY_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 248), - /* Set if we should verify the Common name from the proxy certificate in ssl + /* Set if we should verify the Common name from the proxy certificate in SSL * handshake, set 1 to check existence, 2 to ensure that it matches * the provided hostname. */ CURLOPT(CURLOPT_PROXY_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 249), @@ -2040,13 +2055,16 @@ typedef enum { CURLOPT(CURLOPT_PROXY_SSLVERSION, CURLOPTTYPE_VALUES, 250), /* Set a username for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 251), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, + 251, 8.22.0, "Support was removed"), /* Set a password for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 252), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, + 252, 8.22.0, "Support was removed"), /* Set authentication type for authenticated TLS for proxy */ - CURLOPT(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253), + CURLOPTDEPRECATED(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253, + 8.22.0, "Support was removed"), /* name of the file keeping your private SSL-certificate for proxy */ CURLOPT(CURLOPT_PROXY_SSLCERT, CURLOPTTYPE_STRINGPOINT, 254), @@ -2272,6 +2290,18 @@ typedef enum { /* set TLS supported signature algorithms */ CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328), + /* RFC 9421 HTTP Message Signatures algorithm */ + CURLOPT(CURLOPT_HTTPSIG_ALGORITHM, CURLOPTTYPE_VALUES, 329), + + /* Hex-encoded key for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_KEY, CURLOPTTYPE_STRINGPOINT, 330), + + /* Key identifier for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_KEYID, CURLOPTTYPE_STRINGPOINT, 331), + + /* Space-separated list of components to sign for HTTP Message Signatures */ + CURLOPT(CURLOPT_HTTPSIG_HEADERS, CURLOPTTYPE_STRINGPOINT, 332), + #if UNITY_CERTVERIFY /* Unity extensions. Numbered from 900 to stay clear of upstream option numbers, so a curl update can never silently collide with these. */ @@ -2836,14 +2866,14 @@ struct curl_slist { * backend can also be specified via the name parameter (passing -1 as id). If * both id and name are specified, the name is ignored. If neither id nor * name are specified, the function fails with CURLSSLSET_UNKNOWN_BACKEND - * and set the "avail" pointer to the NULL-terminated list of available + * 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. + * 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 results in a CURLSSLSET_TOO_LATE. diff --git a/include/curl/curlver.h b/include/curl/curlver.h index 144f5fea17b3..98d10c4fc2b5 100644 --- a/include/curl/curlver.h +++ b/include/curl/curlver.h @@ -32,12 +32,12 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.20.0-DEV" +#define LIBCURL_VERSION "8.22.0-DEV" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 20 +#define LIBCURL_VERSION_MINOR 22 #define LIBCURL_VERSION_PATCH 0 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define always @@ -47,7 +47,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. Version 1.2 would appear as "0x010200" while 9.11.7 appears as "0x090b07". This 6-digit (24 bits) hexadecimal number does not show pre-release number, @@ -56,9 +56,8 @@ Note: This define is the full hex number and _does not_ use the CURL_VERSION_BITS() macro since curl's own configure script greps for it - and needs it to contain the full number. -*/ -#define LIBCURL_VERSION_NUM 0x081400 + and needs it to contain the full number. */ +#define LIBCURL_VERSION_NUM 0x081600 /* * This is the date and time when the full source package was created. The diff --git a/include/curl/multi.h b/include/curl/multi.h index 060b73eeec7f..8cef6e774e46 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -24,20 +24,20 @@ * ***************************************************************************/ /* - This is an "external" header file. Do not give away any internals here! - - GOALS - - o Enable a "pull" interface. The application that uses libcurl decides where - and when to ask libcurl to get/send data. - - o Enable multiple simultaneous transfers in the same thread without making it - complicated for the application. - - o Enable the application to select() on its own file descriptors and curl's - file descriptors simultaneous easily. - -*/ + * This is an "external" header file. Do not give away any internals here! + * + * GOALS + * + * o Enable a "pull" interface. The application that uses libcurl decides where + * and when to ask libcurl to get/send data. + * + * o Enable multiple simultaneous transfers in the same thread without making + * it complicated for the application. + * + * o Enable the application to select() on its own file descriptors and curl's + * file descriptors simultaneous easily. + * + */ /* * This header file should not really need to include "curl.h" since curl.h diff --git a/include/curl/system.h b/include/curl/system.h index c2dbab56e089..30216ea34c10 100644 --- a/include/curl/system.h +++ b/include/curl/system.h @@ -297,7 +297,7 @@ /* ===================================== */ #elif defined(_MSC_VER) -# if (_MSC_VER >= 1800) +# if _MSC_VER >= 1800 # include # define CURL_FORMAT_CURL_OFF_T PRId64 # define CURL_FORMAT_CURL_OFF_TU PRIu64 diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h index d600c73cdcbd..f2e4542772c5 100644 --- a/include/curl/typecheck-gcc.h +++ b/include/curl/typecheck-gcc.h @@ -496,6 +496,9 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_USERAGENT || \ (option) == CURLOPT_USERNAME || \ (option) == CURLOPT_AWS_SIGV4 || \ + (option) == CURLOPT_HTTPSIG_HEADERS || \ + (option) == CURLOPT_HTTPSIG_KEY || \ + (option) == CURLOPT_HTTPSIG_KEYID || \ (option) == CURLOPT_USERPWD || \ (option) == CURLOPT_XOAUTH2_BEARER || \ 0) @@ -605,9 +608,10 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, * == or whatsoever. */ -/* XXX: should evaluate to true if expr is a pointer */ +/* XXX: should evaluate to true if expr is a pointer or a char[] array */ #define curlcheck_any_ptr(expr) \ - (sizeof(expr) == sizeof(void *)) + (sizeof(expr) == sizeof(void *) || \ + __builtin_types_compatible_p(__typeof__(expr), char[])) /* evaluates to true if expr is NULL */ /* XXX: must not evaluate expr, so this check is not accurate */ @@ -674,7 +678,7 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t, (__builtin_types_compatible_p(__typeof__(expr), curl_off_t)) /* evaluates to true if expr is abuffer suitable for CURLOPT_ERRORBUFFER */ -/* XXX: also check size of an char[] array? */ +/* XXX: also check size of a char[] array? */ #define curlcheck_error_buffer(expr) \ (curlcheck_NULL(expr) || \ __builtin_types_compatible_p(__typeof__(expr), char *) || \ diff --git a/include/curl/urlapi.h b/include/curl/urlapi.h index b1f3a2316b7a..02553dfebfcb 100644 --- a/include/curl/urlapi.h +++ b/include/curl/urlapi.h @@ -64,6 +64,7 @@ typedef enum { CURLUE_BAD_USER, /* 29 */ CURLUE_LACKS_IDN, /* 30 */ CURLUE_TOO_LARGE, /* 31 */ + CURLUE_BACKSLASH, /* 32 */ CURLUE_LAST } CURLUcode; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index aae466c677bd..c202a6c6d752 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -110,8 +110,7 @@ if(SHARE_LIB_OBJECT) 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) + set_target_properties(${LIB_OBJECT} PROPERTIES POSITION_INDEPENDENT_CODE ON) set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAGS}") if(CURL_HIDES_PRIVATE_SYMBOLS) set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAG_SYMBOLS_HIDE}") @@ -252,8 +251,7 @@ if(BUILD_SHARED_LIBS) CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR CMAKE_SYSTEM_NAME STREQUAL "Haiku" OR CMAKE_SYSTEM_NAME STREQUAL "OHOS" OR # OpenHarmony - CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR - # FreeBSD comes with the a.out and ELF flavours but a.out was supported + # FreeBSD comes with the a.out and ELF flavors but a.out was supported # up to v3.x and ELF from v3.x. I cannot imagine someone running CMake # on those ancient systems. CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") diff --git a/lib/Makefile.am b/lib/Makefile.am index 8a2bd4e68e46..9a3189ce8186 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -26,11 +26,17 @@ AUTOMAKE_OPTIONS = foreign nostdinc # Get CSOURCES, HHEADERS, LIB_RCFILES variables include Makefile.inc -CMAKE_DIST = CMakeLists.txt curl_config-cmake.h.in - -EXTRA_DIST = config-mac.h config-os400.h config-riscos.h config-win32.h \ - curl_config.h.in $(LIB_RCFILES) libcurl.def $(CMAKE_DIST) Makefile.soname \ - optiontable.pl +EXTRA_DIST = \ + CMakeLists.txt \ + config-mac.h \ + config-os400.h \ + config-win32.h \ + curl_config-cmake.h.in \ + curl_config.h.in \ + libcurl.def \ + Makefile.soname \ + optiontable.pl \ + $(LIB_RCFILES) lib_LTLIBRARIES = libcurl.la diff --git a/lib/Makefile.inc b/lib/Makefile.inc index d762f72e42c9..c4e26849aa20 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -84,6 +84,26 @@ LIB_VAUTH_HFILES = \ vauth/digest.h \ vauth/vauth.h +LIB_VDNS_CFILES = \ + vdns/asyn-ares.c \ + vdns/asyn-base.c \ + vdns/asyn-thrdd.c \ + vdns/cf-dns.c \ + vdns/dnscache.c \ + vdns/doh.c \ + vdns/hostip.c \ + vdns/hostip4.c \ + vdns/hostip6.c \ + vdns/httpsrr.c + +LIB_VDNS_HFILES = \ + vdns/asyn.h \ + vdns/cf-dns.h \ + vdns/dnscache.h \ + vdns/doh.h \ + vdns/hostip.h \ + vdns/httpsrr.h + LIB_VTLS_CFILES = \ vtls/apple.c \ vtls/cipher_suite.c \ @@ -96,6 +116,7 @@ LIB_VTLS_CFILES = \ vtls/schannel.c \ vtls/schannel_verify.c \ vtls/vtls.c \ + vtls/vtls_config.c \ vtls/vtls_scache.c \ vtls/vtls_spack.c \ vtls/wolfssl.c \ @@ -113,23 +134,32 @@ LIB_VTLS_HFILES = \ vtls/schannel.h \ vtls/schannel_int.h \ vtls/vtls.h \ + vtls/vtls_config.h \ vtls/vtls_int.h \ vtls/vtls_scache.h \ vtls/vtls_spack.h \ vtls/wolfssl.h \ vtls/x509asn1.h -LIB_VQUIC_CFILES = \ - vquic/curl_ngtcp2.c \ - vquic/curl_quiche.c \ - vquic/vquic.c \ +LIB_VQUIC_CFILES = \ + vquic/capsule.c \ + vquic/cf-capsule.c \ + vquic/cf-ngtcp2.c \ + vquic/cf-ngtcp2-cmn.c \ + vquic/cf-ngtcp2-proxy.c \ + vquic/cf-quiche.c \ + vquic/vquic.c \ vquic/vquic-tls.c -LIB_VQUIC_HFILES = \ - vquic/curl_ngtcp2.h \ - vquic/curl_quiche.h \ - vquic/vquic.h \ - vquic/vquic_int.h \ +LIB_VQUIC_HFILES = \ + vquic/capsule.h \ + vquic/cf-capsule.h \ + vquic/cf-ngtcp2.h \ + vquic/cf-ngtcp2-cmn.h \ + vquic/cf-ngtcp2-proxy.h \ + vquic/cf-quiche.h \ + vquic/vquic.h \ + vquic/vquic_int.h \ vquic/vquic-tls.h LIB_VSSH_CFILES = \ @@ -144,23 +174,23 @@ LIB_VSSH_HFILES = \ LIB_CFILES = \ altsvc.c \ amigaos.c \ - asyn-ares.c \ - asyn-base.c \ - asyn-thrdd.c \ + api.c \ bufq.c \ bufref.c \ - cf-dns.c \ cf-h1-proxy.c \ cf-h2-proxy.c \ cf-haproxy.c \ cf-https-connect.c \ cf-ip-happy.c \ + cf-recvbuf.c \ + cf-setup.c \ cf-socket.c \ cfilters.c \ conncache.c \ connect.c \ content_encoding.c \ cookie.c \ + creds.c \ cshutdn.c \ curl_addrinfo.c \ curl_endian.c \ @@ -181,8 +211,6 @@ LIB_CFILES = \ cw-out.c \ cw-pause.c \ dict.c \ - dnscache.c \ - doh.c \ dynhds.c \ easy.c \ easygetopt.c \ @@ -200,20 +228,18 @@ LIB_CFILES = \ hash.c \ headers.c \ hmac.c \ - hostip.c \ - hostip4.c \ - hostip6.c \ hsts.c \ http.c \ http1.c \ http2.c \ http_aws_sigv4.c \ + http_httpsig.c \ + curl_ed25519.c \ http_chunks.c \ http_digest.c \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ - httpsrr.c \ idn.c \ if2ip.c \ imap.c \ @@ -230,13 +256,14 @@ LIB_CFILES = \ multi_ev.c \ multi_ntfy.c \ netrc.c \ - noproxy.c \ openldap.c \ parsedate.c \ + peer.c \ pingpong.c \ pop3.c \ progress.c \ protocol.c \ + proxy.c \ psl.c \ rand.c \ ratelimit.c \ @@ -265,6 +292,7 @@ LIB_CFILES = \ transfer.c \ uint-bset.c \ uint-hash.c \ + uint-hashset.c \ uint-spbset.c \ uint-table.c \ url.c \ @@ -275,16 +303,17 @@ LIB_CFILES = \ LIB_HFILES = \ altsvc.h \ amigaos.h \ + api.h \ arpa_telnet.h \ - asyn.h \ bufq.h \ bufref.h \ - cf-dns.h \ cf-h1-proxy.h \ cf-h2-proxy.h \ cf-haproxy.h \ cf-https-connect.h \ cf-ip-happy.h \ + cf-recvbuf.h \ + cf-setup.h \ cf-socket.h \ cfilters.h \ conncache.h \ @@ -292,6 +321,7 @@ LIB_HFILES = \ connect.h \ content_encoding.h \ cookie.h \ + creds.h \ curl_addrinfo.h \ curl_ctype.h \ curl_endian.h \ @@ -319,8 +349,6 @@ LIB_HFILES = \ cw-out.h \ cw-pause.h \ dict.h \ - dnscache.h \ - doh.h \ dynhds.h \ easy_lock.h \ easyif.h \ @@ -338,18 +366,18 @@ LIB_HFILES = \ gopher.h \ hash.h \ headers.h \ - hostip.h \ hsts.h \ http.h \ http1.h \ http2.h \ http_aws_sigv4.h \ + http_httpsig.h \ + curl_ed25519.h \ http_chunks.h \ http_digest.h \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ - httpsrr.h \ idn.h \ if2ip.h \ imap.h \ @@ -362,12 +390,13 @@ LIB_HFILES = \ multi_ntfy.h \ multiif.h \ netrc.h \ - noproxy.h \ parsedate.h \ + peer.h \ pingpong.h \ pop3.h \ progress.h \ protocol.h \ + proxy.h \ psl.h \ rand.h \ ratelimit.h \ @@ -397,6 +426,7 @@ LIB_HFILES = \ transfer.h \ uint-bset.h \ uint-hash.h \ + uint-hashset.h \ uint-spbset.h \ uint-table.h \ url.h \ @@ -406,7 +436,9 @@ LIB_HFILES = \ LIB_RCFILES = libcurl.rc -CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_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_CURLX_HFILES) +CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VDNS_CFILES) \ + $(LIB_VTLS_CFILES) $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) \ + $(LIB_CURLX_CFILES) +HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VDNS_HFILES) \ + $(LIB_VTLS_HFILES) $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) \ + $(LIB_CURLX_HFILES) diff --git a/lib/altsvc.c b/lib/altsvc.c index 81b5379ad42c..c57d3f6b40a5 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -78,6 +78,12 @@ const char *Curl_alpnid2str(enum alpnid id) } } +static enum alpnid Curl_str2alpnid(const struct Curl_str *cstr) +{ + return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr), + curlx_strlen(cstr)); +} + #define altsvc_free(x) curlx_free(x) static struct altsvc *altsvc_createid(const char *srchost, @@ -113,11 +119,11 @@ static struct altsvc *altsvc_createid(const char *srchost, return NULL; as->src.host = (char *)as + sizeof(struct altsvc); memcpy(as->src.host, srchost, hlen); - /* the null terminator is already there */ + /* the null-terminator is already there */ as->dst.host = (char *)as + sizeof(struct altsvc) + hlen + 1; memcpy(as->dst.host, dsthost, dlen); - /* the null terminator is already there */ + /* the null-terminator is already there */ as->src.alpnid = srcalpnid; as->dst.alpnid = dstalpnid; @@ -149,7 +155,7 @@ static struct altsvc *altsvc_create(struct Curl_str *srchost, static void altsvc_append(struct altsvcinfo *asi, struct altsvc *as) { while(Curl_llist_count(&asi->list) >= MAX_ALTSVC_ENTRIES) { - /* It's full. Remove the first entry in the list */ + /* It is full. Remove the first entry in the list */ struct Curl_llist_node *e = Curl_llist_head(&asi->list); struct altsvc *oldas = Curl_node_elem(e); Curl_node_remove(e); @@ -447,23 +453,125 @@ static bool hostcompare(const char *host, const char *check) /* altsvc_flush() removes all alternatives for this source origin from the list */ -static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, - const char *srchost, unsigned short srcport) +static void altsvc_flush(struct altsvcinfo *asi, + struct Curl_peer *origin, + enum alpnid origin_alpnid) { 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)) { + if((origin_alpnid == as->src.alpnid) && + (origin->port == as->src.port) && + hostcompare(origin->hostname, as->src.host)) { Curl_node_remove(e); altsvc_free(as); } } } +#define ALTSVC_MA 1 +#define ALTSVC_PERSIST 2 +static void altsvc_parse_params(const char **pp, + time_t *pmaxage, + bool *ppersist) +{ + curlx_str_passblanks(pp); + if(curlx_str_single(pp, ';')) + return; + + for(;;) { + struct Curl_str name; + struct Curl_str val; + curl_off_t num; + int keyword = 0; + + /* allow some extra whitespaces around name and value */ + if(curlx_str_until(pp, &name, MAX_ALTSVC_LINE, '=') || + curlx_str_single(pp, '=')) + break; /* skip further parameter parsing */ + + curlx_str_trimblanks(&name); + curlx_str_passblanks(pp); + if(**pp == '\"') { + if(curlx_str_quotedword(pp, &val, MAX_ALTSVC_LINE)) + break; + } + else { + if(curlx_str_cspn(pp, &val, ",;\r\n")) + break; + } + curlx_str_trimblanks(&val); + + if(curlx_str_casecompare(&name, "ma")) + keyword = ALTSVC_MA; + else if(curlx_str_casecompare(&name, "persist")) + keyword = ALTSVC_PERSIST; + + if(keyword) { + const char *vp = curlx_str(&val); + const char *vend = vp + curlx_strlen(&val); + if(curlx_str_number(&vp, &num, TIME_T_MAX)) + break; /* not a number, skip further parameter parsing */ + if(vp != vend) + break; /* not entirely a number, skip further parameter parsing */ + if(keyword == ALTSVC_MA) + *pmaxage = (time_t)num; + else if(num == 1) + *ppersist = TRUE; + } + + curlx_str_passblanks(pp); + if(curlx_str_single(pp, ';')) + break; /* no further parameters */ + } +} + +static bool altsvc_parse_dest(const char **pp, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_str *dsthost, + uint16_t *pdstport) +{ + curl_off_t port = 0; + + if(curlx_str_single(pp, '\"')) + return FALSE; + + /* quoted string, with hostname or just :port ? */ + if(curlx_str_single(pp, ':')) { /* is hostname:port ? */ + if(curlx_str_single(pp, '[')) { /* DNS hostname/IPv4 */ + if(curlx_str_until(pp, dsthost, MAX_ALTSVC_HOSTLEN, ':')) { + infof(data, "Bad alt-svc hostname, ignoring."); + return FALSE; + } + } + else { /* IPv6 hostname */ + if(curlx_str_until(pp, dsthost, MAX_IPADR_LEN, ']') || + curlx_str_single(pp, ']')) { + infof(data, "Bad alt-svc IPv6 hostname, ignoring."); + return FALSE; + } + } + if(curlx_str_single(pp, ':')) + return FALSE; /* not followed by ':' */ + } + else /* is only :port, hostname is effectively origin */ + curlx_str_assign(dsthost, origin->hostname, + strlen(origin->hostname)); + + if(curlx_str_number(pp, &port, 0xffff)) { + infof(data, "Unknown alt-svc port number, ignoring."); + return FALSE; + } + + *pdstport = (uint16_t)port; + if(curlx_str_single(pp, '\"')) + return FALSE; /* quoted string not ending here as expected */ + return TRUE; +} + /* * Curl_altsvc_parse() takes an incoming alt-svc response header and stores * the data correctly in the cache. @@ -477,161 +585,91 @@ static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, */ CURLcode Curl_altsvc_parse(struct Curl_easy *data, struct altsvcinfo *asi, const char *value, - enum alpnid srcalpnid, const char *srchost, - unsigned short srcport) + struct Curl_peer *origin, + enum alpnid origin_alpnid) { - const char *p = value; struct altsvc *as; - unsigned short dstport = srcport; /* the same by default */ size_t entries = 0; struct Curl_str alpn; + const char *p; DEBUGASSERT(asi); + DEBUGASSERT(origin); + /* RFC 7838, The "Alt-Svc" header field value is basically + * Alt-Svc: (clear|alpn="(host)?:port"\s*(;\s*parameter=value)*) + * This can be repeated, comma-separated. + * + * We parse "best effort", ignoring values we do not recognize. + */ - /* initial check for "clear" */ + /* Try to catch a standalone "clear" */ + p = value; if(!curlx_str_cspn(&p, &alpn, ";\n\r")) { 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); + altsvc_flush(asi, origin, origin_alpnid); return CURLE_OK; } } - p = value; + /* Not a standalone "clear", parse from start for alpn entries */ + for(p = value; *p;) { + time_t maxage = 24 * 3600; /* default is 24 hours */ + bool persist = FALSE; + enum alpnid dstalpnid; + struct Curl_str dsthost; + uint16_t dstport; - if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) - return CURLE_OK; /* strange line */ - - curlx_str_trimblanks(&alpn); - - do { - if(!curlx_str_single(&p, '=')) { - time_t maxage = 24 * 3600; /* default is 24 hours */ - bool persist = FALSE; - /* [protocol]="[host][:port], [protocol]="[host][:port]" */ - enum alpnid dstalpnid = Curl_str2alpnid(&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 { - /* IPv6 hostname */ - 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 - /* no destination name, use source host */ - curlx_str_assign(&dsthost, srchost, strlen(srchost)); + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + break; /* not another entry, leave */ + curlx_str_trimblanks(&alpn); + dstalpnid = Curl_str2alpnid(&alpn); - if(curlx_str_number(&p, &port, 0xffff)) { - infof(data, "Unknown alt-svc port number, ignoring."); - break; - } + if(curlx_str_single(&p, '=')) + break; - dstport = (unsigned short)port; - - if(curlx_str_single(&p, '\"')) - break; - - /* Handle the optional 'ma' and 'persist' flags. Unknown flags are - skipped. */ - curlx_str_passblanks(&p); - if(!curlx_str_single(&p, ';')) { - 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(&p, &name, 20, '=') || - curlx_str_single(&p, '=') || - curlx_str_cspn(&p, &val, ",;")) - 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; - } - else - break; - p = vp; /* point to the byte ending the value */ - curlx_str_passblanks(&p); - if(quoted && curlx_str_single(&p, '\"')) - break; - curlx_str_passblanks(&p); - 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, strlen(srchost), - curlx_str(&dsthost), - curlx_strlen(&dsthost), - srcalpnid, dstalpnid, - srcport, dstport); - if(as) { - 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; - altsvc_append(asi, as); - infof(data, "Added alt-svc: %.*s:%d over %s", - (int)curlx_strlen(&dsthost), curlx_str(&dsthost), - dstport, Curl_alpnid2str(dstalpnid)); - } - else - return CURLE_OUT_OF_MEMORY; - } - } - else - break; + /* Parse altsvc hostname:port */ + if(!altsvc_parse_dest(&p, data, origin, &dsthost, &dstport)) + break; - /* after the double quote there can be a comma if there is another - string or a semicolon if no more */ - if(curlx_str_single(&p, ',')) - break; + /* Parse optional parameters */ + altsvc_parse_params(&p, &maxage, &persist); - /* comma means another alternative is present */ - if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) - break; - curlx_str_trimblanks(&alpn); + if(dstalpnid) { /* this is a known ALPN id, e.g. not ALPN_none */ + if(!entries++) + /* Flush cached alternatives for this source origin, if any - when + this is the first entry of the line. */ + altsvc_flush(asi, origin, origin_alpnid); + + as = altsvc_createid(origin->hostname, strlen(origin->hostname), + curlx_str(&dsthost), + curlx_strlen(&dsthost), + origin_alpnid, dstalpnid, + origin->port, dstport); + if(as) { + 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; + altsvc_append(asi, as); + infof(data, "Added alt-svc: %.*s:%u over %s", + (int)curlx_strlen(&dsthost), curlx_str(&dsthost), + dstport, Curl_alpnid2str(dstalpnid)); + } + else + return CURLE_OUT_OF_MEMORY; } - else + + /* When this is followed by a comma, we expect another entry */ + if(curlx_str_single(&p, ',')) break; - } while(1); + } return CURLE_OK; } @@ -640,38 +678,42 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, * Return TRUE on a match */ bool Curl_altsvc_lookup(struct altsvcinfo *asi, - enum alpnid srcalpnid, const char *srchost, - int srcport, + struct Curl_peer *origin, + enum alpnid origin_alpnid, struct altsvc **dstentry, const int versions, /* one or more bits */ bool *psame_destination) { - struct Curl_llist_node *e; - struct Curl_llist_node *n; - time_t now = time(NULL); DEBUGASSERT(asi); - DEBUGASSERT(srchost); + DEBUGASSERT(origin); DEBUGASSERT(dstentry); - *psame_destination = FALSE; - 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_node_remove(e); - altsvc_free(as); - continue; - } - if((as->src.alpnid == srcalpnid) && - hostcompare(srchost, as->src.host) && - (as->src.port == srcport) && - (versions & (int)as->dst.alpnid)) { - /* match */ - *dstentry = as; - *psame_destination = (srcport == as->dst.port) && - hostcompare(srchost, as->dst.host); - return TRUE; + + if(Curl_llist_count(&asi->list)) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; + time_t now = time(NULL); + + 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_node_remove(e); + altsvc_free(as); + continue; + } + if((origin_alpnid == as->src.alpnid) && + (versions & (int)as->dst.alpnid) && + (origin->port == as->src.port) && + hostcompare(origin->hostname, as->src.host)) { + /* match */ + *dstentry = as; + /* alt-svc on the same host+port or another one? */ + *psame_destination = (origin->port == as->dst.port) && + hostcompare(origin->hostname, as->dst.host); + return TRUE; + } } } return FALSE; diff --git a/lib/altsvc.h b/lib/altsvc.h index dc1740bce13e..cf9e21808ecf 100644 --- a/lib/altsvc.h +++ b/lib/altsvc.h @@ -28,6 +28,8 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) #include "llist.h" +struct Curl_peer; + /* the maximum number of alt-svc entries kept in a single cache */ #define MAX_ALTSVC_ENTRIES 5000 @@ -60,11 +62,11 @@ CURLcode Curl_altsvc_ctrl(struct Curl_easy *data, const long ctrl); void Curl_altsvc_cleanup(struct altsvcinfo **asi); CURLcode Curl_altsvc_parse(struct Curl_easy *data, struct altsvcinfo *asi, const char *value, - enum alpnid srcalpnid, const char *srchost, - unsigned short srcport); + struct Curl_peer *origin, + enum alpnid origin_alpnid); bool Curl_altsvc_lookup(struct altsvcinfo *asi, - enum alpnid srcalpnid, const char *srchost, - int srcport, + struct Curl_peer *origin, + enum alpnid origin_alpnid, struct altsvc **dstentry, const int versions, /* CURLALTSVC_H* bits */ bool *psame_destination); diff --git a/lib/amigaos.c b/lib/amigaos.c index e4f3bfb77c1c..3f615651838f 100644 --- a/lib/amigaos.c +++ b/lib/amigaos.c @@ -25,7 +25,7 @@ #ifdef __AMIGA__ -#include "hostip.h" +#include "vdns/hostip.h" #include "curl_addrinfo.h" #include "amigaos.h" diff --git a/lib/api.c b/lib/api.c new file mode 100644 index 000000000000..15e3789861ab --- /dev/null +++ b/lib/api.c @@ -0,0 +1,451 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "api.h" +#include "curl_threads.h" +#include "multiif.h" +#include "vtls/vtls_scache.h" + +struct Curl_eapi_fn_props { + Curl_eapi_fn fn; + uint8_t data_is_killed; /* easy handle is killed in call */ + uint8_t recurse; /* may be called when another call is in progress */ + uint8_t no_event_cb; /* may not be called during a multi event callback */ + uint8_t no_scache_lock; /* may not be called with easy's vtls_scache + locked by current thread */ +}; + +static const struct Curl_eapi_fn_props eapi_fn_props[CURL_EAPI_FN_LAST] = { + /* function kill rec !ev !scach */ + { CURL_EAPI_FN_easy_cleanup, 1, 0, 0, 0 }, + { CURL_EAPI_FN_easy_duphandle, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_getinfo, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_header, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_nextheader, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_pause, 0, 1, 1, 0 }, + { CURL_EAPI_FN_easy_perform_ev, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_perform, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_recv, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_reset, 0, 0, 0, 0 }, + { CURL_EAPI_FN_easy_send, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_setopt, 0, 1, 0, 0 }, + { CURL_EAPI_FN_easy_ssls_export, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_ssls_import, 0, 0, 0, 1 }, + { CURL_EAPI_FN_easy_upkeep, 0, 0, 0, 1 }, + { CURL_EAPI_FN_ws_recv, 0, 1, 0, 1 }, + { CURL_EAPI_FN_ws_send, 0, 1, 0, 1 }, + { CURL_EAPI_FN_ws_start_frame, 0, 1, 0, 0 }, +}; + +struct Curl_mapi_fn_props { + Curl_mapi_fn fn; + uint8_t multi_is_killed; /* multi handle is killed during call */ + uint8_t recurse; /* may be called when another call is in progress */ + uint8_t allow_ntfy_cb; /* may be called during a notify callback */ + uint8_t no_scache_lock; /* may not be called with multi's vtls_scache + locked by current thread */ +}; + +static const struct Curl_mapi_fn_props mapi_fn_props[CURL_MAPI_FN_LAST] = { + /* function kill rec ntfy !scach */ + { CURL_MAPI_FN_multi_add_handle, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_assign, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_cleanup, 1, 0, 0, 1 }, + { CURL_MAPI_FN_multi_fdset, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_get_handles, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_get_offt, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_info_read, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_notify_disable, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_notify_enable, 0, 1, 1, 0 }, + { CURL_MAPI_FN_multi_perform, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_poll, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_remove_handle, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_setopt, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_socket_action, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_socket_all, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_socket, 0, 0, 0, 1 }, + { CURL_MAPI_FN_multi_timeout, 0, 0, 1, 1 }, + { CURL_MAPI_FN_multi_wait, 0, 0, 1, 0 }, + { CURL_MAPI_FN_multi_waitfds, 0, 0, 1, 0 }, +}; + +struct Curl_cbapi_fn_props { + Curl_cbapi_fn fn; + uint8_t is_event_cb; /* is a multi event processing callback */ +}; + +static const struct Curl_cbapi_fn_props +cbapi_fn_props[CURL_CBAPI_FN_LAST - CURL_CBAPI_FN_START] = { + { CURL_CBAPI_FN_easy_chunk_bgn, 0 }, + { CURL_CBAPI_FN_easy_chunk_end, 0 }, + { CURL_CBAPI_FN_easy_closesocket, 0 }, + { CURL_CBAPI_FN_easy_cr_in_read, 0 }, + { CURL_CBAPI_FN_easy_cr_in_resume_from, 0 }, + { CURL_CBAPI_FN_easy_cw_out_cb, 0 }, + { CURL_CBAPI_FN_easy_fdebug, 0 }, + { CURL_CBAPI_FN_easy_fnmatch_data, 0 }, + { CURL_CBAPI_FN_easy_fopensocket, 0 }, + { CURL_CBAPI_FN_easy_fprereq, 0 }, + { CURL_CBAPI_FN_easy_fprogress, 0 }, + { CURL_CBAPI_FN_easy_fread_func, 0 }, + { CURL_CBAPI_FN_easy_fsockopt, 0 }, + { CURL_CBAPI_FN_easy_fsslctx, 0 }, + { CURL_CBAPI_FN_easy_fwrite_rtp, 0 }, + { CURL_CBAPI_FN_easy_fxferinfo, 0 }, + { CURL_CBAPI_FN_easy_ioctl_func, 0 }, + { CURL_CBAPI_FN_easy_resolver_start, 0 }, + { CURL_CBAPI_FN_easy_seek_func, 0 }, + { CURL_CBAPI_FN_easy_ssh_hostkeyfunc, 0 }, + { CURL_CBAPI_FN_easy_ssh_keyfunc, 0 }, + { CURL_CBAPI_FN_easy_trailer_callback, 0 }, + + { CURL_CBAPI_FN_multi_ntfy_cb, 0 }, + { CURL_CBAPI_FN_multi_push_cb, 0 }, + { CURL_CBAPI_FN_multi_socket_cb, 1 }, + { CURL_CBAPI_FN_multi_timer_cb, 1 }, +}; + +static bool eapi_in_event_cb(struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi; + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) { + uint16_t fn = multi->callstack.calls[i]; + if((fn < CURL_CBAPI_FN_LAST) && + cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb) + return TRUE; + } + } + } + return FALSE; +} + +static bool mapi_in_ntfy_cb(struct Curl_multi *multi) +{ + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] == CURL_CBAPI_FN_multi_ntfy_cb) + return TRUE; + } + } + return FALSE; +} + +bool Curl_api_multi_is_in_callback(struct Curl_multi *multi) +{ + if(multi && multi->callstack.count) { + size_t i; + for(i = 0; i < multi->callstack.count; ++i) { + if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) + return TRUE; + } + } + return FALSE; +} + +bool Curl_api_is_in_callback(struct Curl_easy *data) +{ + if(data && data->multi) { + return Curl_api_multi_is_in_callback(data->multi); + } + return FALSE; +} + +bool Curl_eapi_enter(struct Curl_eapi_guard *guard, + CURL *curl, + Curl_eapi_fn fn, + CURLcode *presult) +{ + struct Curl_easy *data = curl; + const struct Curl_eapi_fn_props *fn_props; + CURLcode result = CURLE_OK; + + guard->depth = 0; + + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_EASY_HANDLE(data)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + /* verify that is either not added to a multi handle OR has a + * GOOD multi handle that knows `data` for `data->mid`. */ + if(data->mid != UINT32_MAX) { + if(GOOD_MULTI_HANDLE(data->multi)) { + if(!Curl_multi_knows_easy(data->multi, data)) { + /* But multi does not know it, something is fishy, better deny call */ + DEBUGASSERT(0); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + } + else { + DEBUGASSERT(0); /* data needs to have a GOOD multi handle */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + } + else if(data->multi) { + DEBUGASSERT(0); /* data should not have a multi handle */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + /* verify that the call `fn` we're about to enter is known + * and check call properties to be admitting. */ + if(fn >= CURL_EAPI_FN_LAST) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + fn_props = &eapi_fn_props[fn]; + DEBUGASSERT(fn_props->fn == fn); + if(!fn_props->recurse) { + if(data->callstack.count) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, + data->callstack.calls[data->callstack.count-1])); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + if(data->multi && data->multi->callstack.count) { +#ifdef CURLVERBOSE + + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu with multi call to %u ongoing\n", + (uint16_t)fn, + data->multi->callstack.calls[data->multi->callstack.count-1])); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + } + + if(fn_props->no_event_cb && eapi_in_event_cb(data)) { + /* Not allowed to be invoked while an event cb is ongoing */ +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu while event callback ongoing\n", + (uint16_t)fn)); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + +#if defined(USE_SSL) && defined(USE_MUTEX) + if(fn_props->no_scache_lock && + Curl_ssl_scache_is_locked_by_current_thread(data)) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "EAPI guard: calling %hu while vtls_scache is locked by " + "current thread\n", (uint16_t)fn)); +#endif + result = CURLE_RECURSIVE_API_CALL; + goto out; + } +#endif + + /* all fine, add to data's callstack */ + if(data->callstack.count >= CURL_EAPI_MAX_RECURSION) { + result = CURLE_RECURSIVE_API_CALL; + goto out; + } + data->callstack.calls[data->callstack.count] = (uint16_t)fn; + ++data->callstack.count; + guard->depth = data->callstack.count; + guard->data = fn_props->data_is_killed ? NULL : data; + +out: + if(presult) + *presult = result; + return guard->depth > 0; +} + +void Curl_eapi_leave(struct Curl_eapi_guard *guard) +{ + if(guard->depth) { + /* guard->data is set when handle is supposed to stay alive during call */ + if(guard->data && GOOD_EASY_HANDLE(guard->data)) { + if(guard->depth > guard->data->callstack.count) { + DEBUGASSERT(0); /* something very wrong */ + } + else { + if(guard->depth < guard->data->callstack.count) { + DEBUGASSERT(0); /* someone forgot to clean up */ + } + /* reset to depth the guard was entered in */ + guard->data->callstack.count = (uint16_t)(guard->depth - 1); + } + } + } +} + +CURLHcode Curl_eapi_hcode(CURLcode result) +{ + switch(result) { + case CURLE_OK: + return CURLHE_OK; + case CURLE_BAD_FUNCTION_ARGUMENT: + return CURLHE_BAD_ARGUMENT; + case CURLE_OUT_OF_MEMORY: + return CURLHE_OUT_OF_MEMORY; + case CURLE_NOT_BUILT_IN: + return CURLHE_NOT_BUILT_IN; + default: + /* Unfortunately, we cannot convert RECURSIVE_API_CALL, + * but since the header API is reentrant, this should not happen. */ + return CURLHE_BAD_ARGUMENT; + } +} + +bool Curl_mapi_enter(struct Curl_mapi_guard *guard, + CURLM *m, + Curl_mapi_fn fn, + CURLMcode *pmresult) +{ + struct Curl_multi *multi = m; + const struct Curl_mapi_fn_props *fn_props; + CURLMcode mresult = CURLM_OK; + + guard->depth = 0; + + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_MULTI_HANDLE(multi)) { + mresult = CURLM_BAD_HANDLE; + goto out; + } + if(fn >= CURL_MAPI_FN_LAST) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } + fn_props = &mapi_fn_props[fn]; + DEBUGASSERT(fn_props->fn == fn); + if(fn_props->allow_ntfy_cb && mapi_in_ntfy_cb(multi)) { + /* explicitly allowed, even though normal recursion may not */ + } + else if(!fn_props->recurse && multi->callstack.count) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, + multi->callstack.calls[multi->callstack.count-1])); +#endif + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } + +#if defined(USE_SSL) && defined(USE_MUTEX) + if(fn_props->no_scache_lock && multi->ssl_scache && + Curl_ssl_scache_is_locked_by_current_thread(multi->admin)) { +#ifdef CURLVERBOSE + DEBUGF(curl_mfprintf(stderr, + "MAPI guard: calling %hu while its vtls_scache is locked by " + "current thread\n", (uint16_t)fn)); +#endif + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } +#endif + + /* all fine, add to data's callstack */ + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { + mresult = CURLM_RECURSIVE_API_CALL; + goto out; + } + multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + ++multi->callstack.count; + guard->depth = multi->callstack.count; + guard->multi = fn_props->multi_is_killed ? NULL : multi; + +out: + if(pmresult) + *pmresult = mresult; + return guard->depth > 0; +} + +void Curl_mapi_leave(struct Curl_mapi_guard *guard) +{ + if(guard->depth) { + /* guard->data is set when handle is supposed to stay alive during call */ + if(guard->multi && GOOD_MULTI_HANDLE(guard->multi)) { + if(guard->depth > guard->multi->callstack.count) { + DEBUGASSERT(0); /* something very wrong */ + } + else { + if(guard->depth < guard->multi->callstack.count) { + DEBUGASSERT(0); /* someone forgot to clean up */ + } + /* reset to depth the guard was entered in */ + guard->multi->callstack.count = (uint16_t)(guard->depth - 1); + } + } + } +} + +void Curl_cbapi_enter(struct Curl_mapi_guard *guard, + struct Curl_easy *data, + struct Curl_multi *multi, + Curl_cbapi_fn fn) +{ + guard->depth = 0; + + if(!multi) + multi = data ? data->multi : NULL; + /* if not multi is involved here, just leave */ + if(!multi) + return; + /* invalid callback specifier? */ + if((fn >= CURL_CBAPI_FN_LAST) || (fn < CURL_CBAPI_FN_START)) { + DEBUGASSERT(0); + return; + } + DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn); + if(multi->callstack.count) { + size_t i; + for(i = multi->callstack.count; i; --i) { + if(multi->callstack.calls[i - 1] == fn) { + /* recursive invocation of the same callback */ + DEBUGASSERT(0); + return; + } + } + } + + /* all fine, add to data's callstack */ + /* if multi callstack already at max depth, leave */ + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) + return; + multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + ++multi->callstack.count; + guard->depth = multi->callstack.count; + guard->multi = multi; +} + +void Curl_cbapi_leave(struct Curl_mapi_guard *guard) +{ + Curl_mapi_leave(guard); +} diff --git a/lib/api.h b/lib/api.h new file mode 100644 index 000000000000..337d686bb5b9 --- /dev/null +++ b/lib/api.h @@ -0,0 +1,212 @@ +#ifndef HEADER_CURL_API_H +#define HEADER_CURL_API_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 CURLEASY_MAGIC_NUMBER 0xc0dedbadU +#ifdef DEBUGBUILD +/* On a debug build, we want to fail hard on easy handles that + * are not NULL, but no longer have the MAGIC touch. This gives + * us early warning on things only discovered by valgrind otherwise. */ +#define GOOD_EASY_HANDLE(x) \ + (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) ? TRUE : \ + (DEBUGASSERT(!(x)), FALSE)) +#else +#define GOOD_EASY_HANDLE(x) \ + ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) +#endif + +#define CURLMULTI_MAGIC_NUMBER 0x000bab1e + +#ifdef DEBUGBUILD +/* On a debug build, we want to fail hard on multi handles that + * are not NULL, but no longer have the MAGIC touch. This gives + * us early warning on things only discovered by valgrind otherwise. */ +#define GOOD_MULTI_HANDLE(x) \ + (((x) && (x)->magic == CURLMULTI_MAGIC_NUMBER) ? TRUE : \ + (DEBUGASSERT(!(x)), FALSE)) +#else +#define GOOD_MULTI_HANDLE(x) \ + ((x) && (x)->magic == CURLMULTI_MAGIC_NUMBER) +#endif + +/* the API functions called on a CURL* */ +typedef enum { + CURL_EAPI_FN_easy_cleanup, + CURL_EAPI_FN_easy_duphandle, + CURL_EAPI_FN_easy_getinfo, + CURL_EAPI_FN_easy_header, + CURL_EAPI_FN_easy_nextheader, + CURL_EAPI_FN_easy_pause, + CURL_EAPI_FN_easy_perform_ev, + CURL_EAPI_FN_easy_perform, + CURL_EAPI_FN_easy_recv, + CURL_EAPI_FN_easy_reset, + CURL_EAPI_FN_easy_send, + CURL_EAPI_FN_easy_setopt, + CURL_EAPI_FN_easy_ssls_export, + CURL_EAPI_FN_easy_ssls_import, + CURL_EAPI_FN_easy_upkeep, + CURL_EAPI_FN_ws_recv, + CURL_EAPI_FN_ws_send, + CURL_EAPI_FN_ws_start_frame, + CURL_EAPI_FN_LAST +} Curl_eapi_fn; + +/* the API functions called on a CURLM* */ +typedef enum { + CURL_MAPI_FN_multi_add_handle, + CURL_MAPI_FN_multi_assign, + CURL_MAPI_FN_multi_cleanup, + CURL_MAPI_FN_multi_fdset, + CURL_MAPI_FN_multi_get_handles, + CURL_MAPI_FN_multi_get_offt, + CURL_MAPI_FN_multi_info_read, + CURL_MAPI_FN_multi_notify_disable, + CURL_MAPI_FN_multi_notify_enable, + CURL_MAPI_FN_multi_perform, + CURL_MAPI_FN_multi_poll, + CURL_MAPI_FN_multi_remove_handle, + CURL_MAPI_FN_multi_setopt, + CURL_MAPI_FN_multi_socket_action, + CURL_MAPI_FN_multi_socket_all, + CURL_MAPI_FN_multi_socket, + CURL_MAPI_FN_multi_timeout, + CURL_MAPI_FN_multi_wait, + CURL_MAPI_FN_multi_waitfds, + CURL_MAPI_FN_LAST +} Curl_mapi_fn; + +#define CURL_CBAPI_FN_START (16 * 1024) + +/* the callback functions */ +typedef enum { + CURL_CBAPI_FN_easy_chunk_bgn = CURL_CBAPI_FN_START, + CURL_CBAPI_FN_easy_chunk_end, + CURL_CBAPI_FN_easy_closesocket, + CURL_CBAPI_FN_easy_cr_in_read, + CURL_CBAPI_FN_easy_cr_in_resume_from, + CURL_CBAPI_FN_easy_cw_out_cb, + CURL_CBAPI_FN_easy_fdebug, + CURL_CBAPI_FN_easy_fnmatch_data, + CURL_CBAPI_FN_easy_fopensocket, + CURL_CBAPI_FN_easy_fprereq, + CURL_CBAPI_FN_easy_fprogress, + CURL_CBAPI_FN_easy_fread_func, + CURL_CBAPI_FN_easy_fsockopt, + CURL_CBAPI_FN_easy_fsslctx, + CURL_CBAPI_FN_easy_fwrite_rtp, + CURL_CBAPI_FN_easy_fxferinfo, + CURL_CBAPI_FN_easy_ioctl_func, + CURL_CBAPI_FN_easy_resolver_start, + CURL_CBAPI_FN_easy_seek_func, + CURL_CBAPI_FN_easy_ssh_hostkeyfunc, + CURL_CBAPI_FN_easy_ssh_keyfunc, + CURL_CBAPI_FN_easy_trailer_callback, + + CURL_CBAPI_FN_multi_ntfy_cb, + CURL_CBAPI_FN_multi_push_cb, + CURL_CBAPI_FN_multi_socket_cb, + CURL_CBAPI_FN_multi_timer_cb, + + CURL_CBAPI_FN_LAST +} Curl_cbapi_fn; + +/* EAPI */ + +#define CURL_EAPI_MAX_RECURSION 7 + +struct Curl_eapi_stack { + uint16_t count; + uint16_t calls[CURL_EAPI_MAX_RECURSION]; +}; + +struct Curl_eapi_guard { + struct Curl_easy *data; /* != NULL if handle stays */ + uint16_t depth; /* > 0 if this guard was entered */ +}; + +bool Curl_eapi_enter(struct Curl_eapi_guard *guard, + CURL *curl, + Curl_eapi_fn fn, + CURLcode *presult); +void Curl_eapi_leave(struct Curl_eapi_guard *guard); + +/* Convert an EAPI failure to a header API result */ +CURLHcode Curl_eapi_hcode(CURLcode result); + +/* Curl_eapi_enter() checks for curl being NULL, but windows compiler + * analyzers do not realize this. *sigh* */ +#define CURL_EAPI_ENTER(g, curl, fn, r) \ + Curl_eapi_enter((g), (curl), CURL_EAPI_FN_##fn, (r)) && (curl) +#define CURL_EAPI_LEAVE(g) \ + Curl_eapi_leave(g) + +/* MAPI */ + +#define CURL_MAPI_MAX_RECURSION 15 + +struct Curl_mapi_stack { + uint16_t count; + uint16_t calls[CURL_MAPI_MAX_RECURSION]; +}; + +struct Curl_mapi_guard { + struct Curl_multi *multi; /* != NULL if handle stays */ + uint16_t depth; /* > 0 if this guard was entered */ +}; + +bool Curl_mapi_enter(struct Curl_mapi_guard *guard, + CURLM *m, + Curl_mapi_fn fn, + CURLMcode *pmresult); +void Curl_mapi_leave(struct Curl_mapi_guard *guard); + +/* Curl_mapi_enter() checks for m being NULL, but windows compiler + * analyzers do not realize this. *sigh* */ +#define CURL_MAPI_ENTER(g, m, fn, r) \ + Curl_mapi_enter((g), (m), CURL_MAPI_FN_##fn, (r)) && (m) +#define CURL_MAPI_LEAVE(g) \ + Curl_mapi_leave(g) + +void Curl_cbapi_enter(struct Curl_mapi_guard *guard, + struct Curl_easy *data, + struct Curl_multi *multi, + Curl_cbapi_fn fn); +void Curl_cbapi_leave(struct Curl_mapi_guard *guard); + +#define CURL_CBAPI_START(g, d, fn) \ + Curl_cbapi_enter((g), (d), NULL, CURL_CBAPI_FN_##fn) +#define CURL_CBAPI_MULTI_START(g, m, fn) \ + Curl_cbapi_enter((g), NULL, (m), CURL_CBAPI_FN_##fn) +#define CURL_CBAPI_END(g) \ + Curl_cbapi_leave(g) +#define CURL_CBAPI_MULTI_END(g) \ + Curl_cbapi_leave(g) + +bool Curl_api_is_in_callback(struct Curl_easy *data); +bool Curl_api_multi_is_in_callback(struct Curl_multi *multi); + +#endif /* HEADER_CURL_API_H */ diff --git a/lib/arpa_telnet.h b/lib/arpa_telnet.h index b5faab419c26..826f937537c0 100644 --- a/lib/arpa_telnet.h +++ b/lib/arpa_telnet.h @@ -35,6 +35,7 @@ #define CURL_TELOPT_NAWS 31 /* Negotiate About Window Size */ #define CURL_TELOPT_XDISPLOC 35 /* X DISPlay LOCation */ #define CURL_TELOPT_NEW_ENVIRON 39 /* NEW ENVIRONment variables */ + #define CURL_NEW_ENV_VAR 0 #define CURL_NEW_ENV_VALUE 1 diff --git a/lib/bufq.h b/lib/bufq.h index da411b586d86..c53749d84e36 100644 --- a/lib/bufq.h +++ b/lib/bufq.h @@ -204,6 +204,7 @@ void Curl_bufq_skip(struct bufq *q, size_t amount); typedef CURLcode Curl_bufq_writer(void *writer_ctx, const uint8_t *buf, size_t len, size_t *pwritten); + /** * Passes the chunks in the buffer queue to the writer and returns * the amount of buf written. A writer may return -1 and CURLE_AGAIN diff --git a/lib/cf-dns.c b/lib/cf-dns.c deleted file mode 100644 index e763b8ed3849..000000000000 --- a/lib/cf-dns.c +++ /dev/null @@ -1,667 +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 "urldata.h" -#include "curl_addrinfo.h" -#include "cfilters.h" -#include "connect.h" -#include "dnscache.h" -#include "httpsrr.h" -#include "curl_trc.h" -#include "progress.h" -#include "url.h" -#include "cf-dns.h" - - -struct cf_dns_ctx { - struct Curl_dns_entry *dns; - CURLcode resolv_result; - uint32_t resolv_id; - uint16_t port; - uint8_t dns_queries; - uint8_t transport; - BIT(started); - BIT(announced); - BIT(abstract_unix_socket); - BIT(complete_resolve); - BIT(for_proxy); - char hostname[1]; -}; - -static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, - uint8_t dns_queries, - const char *hostname, - uint16_t port, uint8_t transport, - bool abstract_unix_socket, - bool for_proxy, - bool complete_resolve, - struct Curl_dns_entry *dns) -{ - struct cf_dns_ctx *ctx; - size_t hlen = strlen(hostname); - - ctx = curlx_calloc(1, sizeof(*ctx) + hlen); - if(!ctx) - return NULL; - - ctx->port = port; - ctx->dns_queries = dns_queries; - ctx->transport = transport; - ctx->abstract_unix_socket = abstract_unix_socket; - ctx->for_proxy = for_proxy; - ctx->complete_resolve = complete_resolve; - ctx->dns = Curl_dns_entry_link(data, dns); - ctx->started = !!ctx->dns; - if(hlen) - memcpy(ctx->hostname, hostname, hlen); - - CURL_TRC_DNS(data, "created DNS filter for %s:%u, transport=%x, queries=%x", - ctx->hostname, ctx->port, ctx->transport, ctx->dns_queries); - return ctx; -} - -static void cf_dns_ctx_destroy(struct Curl_easy *data, - struct cf_dns_ctx *ctx) -{ - if(ctx) { - Curl_dns_entry_unlink(data, &ctx->dns); - curlx_free(ctx); - } -} - -#ifdef CURLVERBOSE -static void cf_dns_report_addr(struct Curl_easy *data, - struct dynbuf *tmp, - const char *label, - int ai_family, - const struct Curl_addrinfo *ai) -{ - char buf[MAX_IPADR_LEN]; - const char *sep = ""; - CURLcode result; - - curlx_dyn_reset(tmp); - for(; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - Curl_printable_address(ai, buf, sizeof(buf)); - result = curlx_dyn_addf(tmp, "%s%s", sep, buf); - if(result) { - infof(data, "too many IP, cannot show"); - return; - } - sep = ", "; - } - } - - infof(data, "%s%s", label, - (curlx_dyn_len(tmp) ? curlx_dyn_ptr(tmp) : "(none)")); -} - -static void cf_dns_report(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_dns_entry *dns) -{ - struct cf_dns_ctx *ctx = cf->ctx; - struct dynbuf tmp; - - if(!Curl_trc_is_verbose(data) || - /* ignore no name or numerical IP addresses */ - !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname)) - return; - - switch(ctx->transport) { - case TRNSPRT_UNIX: -#ifdef USE_UNIX_SOCKETS - CURL_TRC_CF(data, cf, "resolved unix domain %s", - Curl_conn_get_unix_path(data->conn)); -#else - DEBUGASSERT(0); -#endif - break; - default: - curlx_dyn_init(&tmp, 1024); - infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); -#ifdef CURLRES_IPV6 - cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); -#endif - cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); -#ifdef USE_HTTPSRR - if(!dns->hinfo) - infof(data, "HTTPS-RR: -"); - else if(!Curl_httpsrr_applicable(data, dns->hinfo)) - infof(data, "HTTPS-RR: not applicable"); - else { - CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); - if(!result) - infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); - else - infof(data, "Error printing HTTPS-RR information"); - } -#endif - curlx_dyn_free(&tmp); - break; - } -} -#else -#define cf_dns_report(x, y, z) Curl_nop_stmt -#endif - -/************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ -static CURLcode cf_dns_start(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_dns_entry **pdns) -{ - struct cf_dns_ctx *ctx = cf->ctx; - timediff_t timeout_ms = Curl_timeleft_ms(data); - CURLcode result; - - *pdns = NULL; - -#ifdef USE_UNIX_SOCKETS - if(ctx->transport == TRNSPRT_UNIX) { - CURL_TRC_CF(data, cf, "resolve unix socket %s", ctx->hostname); - return Curl_resolv_unix(data, ctx->hostname, - (bool)cf->conn->bits.abstract_unix_socket, pdns); - } -#endif - - /* Resolve target host right on */ - CURL_TRC_CF(data, cf, "cf_dns_start host %s:%u", ctx->hostname, ctx->port); - if(Curl_is_ipv4addr(ctx->hostname)) - ctx->dns_queries |= CURL_DNSQ_A; -#ifdef USE_IPV6 - else if(Curl_is_ipaddr(ctx->hostname)) /* not ipv4, must be ipv6 then */ - ctx->dns_queries |= CURL_DNSQ_AAAA; -#endif - result = Curl_resolv(data, ctx->dns_queries, - ctx->hostname, ctx->port, ctx->transport, - (bool)ctx->for_proxy, timeout_ms, - &ctx->resolv_id, pdns); - DEBUGASSERT(!result || !*pdns); - if(!result) { /* resolved right away, either sync or from dnscache */ - DEBUGASSERT(*pdns); - return CURLE_OK; - } - else if(result == CURLE_AGAIN) { /* async resolv in progress */ - return CURLE_OK; - } - else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */ - failf(data, "Failed to resolve '%s' with timeout after %" - FMT_TIMEDIFF_T " ms", ctx->hostname, - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle)); - return CURLE_OPERATION_TIMEDOUT; - } - else { - DEBUGASSERT(result); - failf(data, "Could not resolve: %s", ctx->hostname); - return result; - } -} - -#define CURL_HEV3_RESOLVE_DELAY_MS 50 - -static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_dns_ctx *ctx = cf->ctx; - - if(ctx->resolv_result) - return TRUE; - else if(ctx->dns) - return TRUE; -#ifdef USE_CURL_ASYNC - else { - /* We want AAAA answer as we prefer ipv6. If a sub-filter desires - * HTTPS-RR, we check for that query as well. */ - uint8_t wanted_answers = CURL_DNSQ_AAAA; - if(Curl_conn_cf_wants_httpsrr(cf, data)) - wanted_answers |= CURL_DNSQ_HTTPS; - - /* Note: if a query was never started, it is considered to have - * an answer (e.g. a negative one). */ - if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers)) - return TRUE; - /* If the wanted answers are not available after a delay, - * we let the connect attempts start anyway. */ - return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >= - CURL_HEV3_RESOLVE_DELAY_MS; - } -#else - (void)data; - DEBUGASSERT(0); /* We should not come here */ - return FALSE; -#endif /* USE_CURL_ASYNC */ -} - -static CURLcode cf_dns_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - struct cf_dns_ctx *ctx = cf->ctx; - - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - - *done = FALSE; - if(!ctx->started) { - ctx->started = TRUE; - ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns); - } - - if(!ctx->dns && !ctx->resolv_result) { - ctx->resolv_result = - Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns); - } - - if(ctx->resolv_result) { - CURL_TRC_CF(data, cf, "error resolving: %d", ctx->resolv_result); - return ctx->resolv_result; - } - - if(ctx->dns && !ctx->announced) { - ctx->announced = TRUE; - if(cf->sockindex == FIRSTSOCKET) { - cf->conn->bits.dns_resolved = TRUE; - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - } - cf_dns_report(cf, data, ctx->dns); - } - - if(!cf_dns_ready_to_connect(cf, data)) { - return CURLE_OK; - } - - if(cf->next && !cf->next->connected) { - bool sub_done; - CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done); - if(result || !sub_done) - return result; - DEBUGASSERT(sub_done); - } - - /* sub filter chain is connected */ - CURL_TRC_CF(data, cf, "connected filter chain below"); - if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) { - /* This filter only connects when it has resolved everything. */ - CURL_TRC_CF(data, cf, "delay connect until resolve complete"); - return CURLE_OK; - } - *done = TRUE; - cf->connected = TRUE; - Curl_resolv_destroy(data, ctx->resolv_id); - return CURLE_OK; -} - -static void cf_dns_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_dns_ctx *ctx = cf->ctx; - - CURL_TRC_CF(data, cf, "destroy"); - cf_dns_ctx_destroy(data, ctx); -} - -static void cf_dns_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - cf->connected = FALSE; - if(cf->next) - cf->next->cft->do_close(cf->next, data); -} - -static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps) -{ -#ifdef USE_CURL_ASYNC - if(!cf->connected) - return Curl_resolv_pollset(data, ps); -#else - (void)cf; - (void)data; - (void)ps; -#endif - return CURLE_OK; -} - -static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2) -{ - struct cf_dns_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - (void)arg1; - (void)arg2; - switch(event) { - case CF_CTRL_DATA_DONE: - if(ctx->dns) { - /* Should only come here when the connect attempt failed and - * `data` is giving up on it. On a successful connect, we already - * unlinked the DNS entry. */ - Curl_dns_entry_unlink(data, &ctx->dns); - } - break; - default: - break; - } - return result; -} - -struct Curl_cftype Curl_cft_dns = { - "DNS", - CF_TYPE_SETUP, - CURL_LOG_LVL_NONE, - cf_dns_destroy, - cf_dns_connect, - cf_dns_close, - Curl_cf_def_shutdown, - cf_dns_adjust_pollset, - Curl_cf_def_data_pending, - Curl_cf_def_send, - Curl_cf_def_recv, - cf_dns_cntrl, - Curl_cf_def_conn_is_alive, - Curl_cf_def_conn_keep_alive, - Curl_cf_def_query, -}; - -static CURLcode cf_dns_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - uint8_t dns_queries, - const char *hostname, - uint16_t port, - uint8_t transport, - bool abstract_unix_socket, - bool for_proxy, - bool complete_resolve, - struct Curl_dns_entry *dns) -{ - struct Curl_cfilter *cf = NULL; - struct cf_dns_ctx *ctx; - CURLcode result = CURLE_OK; - - (void)data; - ctx = cf_dns_ctx_create(data, dns_queries, hostname, port, transport, - abstract_unix_socket, for_proxy, - complete_resolve, dns); - if(!ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - result = Curl_cf_create(&cf, &Curl_cft_dns, ctx); - -out: - *pcf = result ? NULL : cf; - if(result) - cf_dns_ctx_destroy(data, ctx); - return result; -} - -/* Create a "resolv" filter for the transfer's connection. Figures - * out the hostname/path and port where to connect to. */ -static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - uint8_t dns_queries, - uint8_t transport, - bool complete_resolve, - struct Curl_dns_entry *dns) -{ - struct connectdata *conn = data->conn; - const char *hostname = NULL; - uint16_t port = 0; - bool abstract_unix_socket = FALSE, for_proxy = FALSE; - -#ifdef USE_UNIX_SOCKETS - { - const char *unix_path = Curl_conn_get_unix_path(conn); - if(unix_path) { - DEBUGASSERT(transport == TRNSPRT_UNIX); - hostname = unix_path; - abstract_unix_socket = (bool)conn->bits.abstract_unix_socket; - } - } -#endif - -#ifndef CURL_DISABLE_PROXY - if(!hostname && conn->bits.proxy) { - for_proxy = TRUE; - hostname = conn->bits.socksproxy ? - conn->socks_proxy.host.name : conn->http_proxy.host.name; - port = conn->bits.socksproxy ? - conn->socks_proxy.port : conn->http_proxy.port; - } -#endif - if(!hostname) { - struct hostname *ehost; - ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; - /* If not connecting via a proxy, extract the port from the URL, if it is - * there, thus overriding any defaults that might have been set above. */ - hostname = ehost->name; - port = conn->bits.conn_to_port ? - conn->conn_to_port : (uint16_t)conn->remote_port; - } - - if(!hostname) { - DEBUGASSERT(0); - return CURLE_FAILED_INIT; - } - return cf_dns_create(pcf, data, dns_queries, - hostname, port, transport, - abstract_unix_socket, for_proxy, - complete_resolve, dns); -} - -/* Adds a "resolv" filter at the top of the connection's filter chain. - * For FIRSTSOCKET, the `dns` parameter may be NULL. The filter will - * figure out hostname and port to connect to and start the DNS resolve - * on the first connect attempt. - * For SECONDARYSOCKET, the `dns` parameter must be given. - */ -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - uint8_t dns_queries, - uint8_t transport, - struct Curl_dns_entry *dns) -{ - struct Curl_cfilter *cf = NULL; - CURLcode result; - - DEBUGASSERT(data); - if(sockindex == FIRSTSOCKET) - result = cf_dns_conn_create(&cf, data, dns_queries, transport, FALSE, dns); - else if(dns) { - result = cf_dns_create(&cf, data, dns_queries, - dns->hostname, dns->port, transport, - FALSE, FALSE, FALSE, dns); - } - else { - DEBUGASSERT(0); - result = CURLE_FAILED_INIT; - } - if(result) - goto out; - Curl_conn_cf_add(data, conn, sockindex, cf); -out: - return result; -} - -/* Insert a new "resolv" filter directly after `cf`. It will - * start a DNS resolve for the given hostnmae and port on the - * first connect attempt. - * See socks.c on how this is used to make a non-blocking DNS - * resolve during connect. - */ -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - const char *hostname, - uint16_t port, - uint8_t transport, - bool complete_resolve) -{ - struct Curl_cfilter *cf; - CURLcode result; - - result = cf_dns_create(&cf, data, dns_queries, - hostname, port, transport, - FALSE, FALSE, complete_resolve, NULL); - if(result) - return result; - - Curl_conn_cf_insert_after(cf_at, cf); - return CURLE_OK; -} - -/* Return the resolv result from the first "resolv" filter, starting - * the given filter `cf` downwards. - */ -static CURLcode cf_dns_result(struct Curl_cfilter *cf) -{ - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_dns) { - struct cf_dns_ctx *ctx = cf->ctx; - if(ctx->dns || ctx->resolv_result) - return ctx->resolv_result; - return CURLE_AGAIN; - } - } - return CURLE_FAILED_INIT; -} - -/* Return the result of the DNS resolution. Searches for a "resolv" - * filter from the top of the filter chain down. Returns - * - CURLE_AGAIN when not done yet - * - CURLE_OK when DNS was successfully resolved - * - CURLR_FAILED_INIT when no resolv filter was found - * - error returned by the DNS resolv - */ -CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex) -{ - return cf_dns_result(conn->cfilter[sockindex]); -} - -static const struct Curl_addrinfo *cf_dns_get_nth_ai( - struct Curl_cfilter *cf, - const struct Curl_addrinfo *ai, - int ai_family, unsigned int index) -{ - struct cf_dns_ctx *ctx = cf->ctx; - unsigned int i = 0; - - if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A)) - return NULL; -#ifdef USE_IPV6 - if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA)) - return NULL; -#endif - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -/* Return the addrinfo at `index` for the given `family` from the - * first "resolve" filter underneath `cf`. If the DNS resolving is - * not done yet or if no address for the family exists, returns NULL. - */ -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - int ai_family, - unsigned int index) -{ - (void)data; - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_dns) { - struct cf_dns_ctx *ctx = cf->ctx; - if(ctx->resolv_result) - return NULL; - else if(ctx->dns) - return cf_dns_get_nth_ai(cf, ctx->dns->addr, ai_family, index); - else - return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index); - } - } - return NULL; -} - -/* Return the addrinfo at `index` for the given `family` from the - * first "resolve" filter at the connection. If the DNS resolving is - * not done yet or if no address for the family exists, returns NULL. - */ -const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, - int sockindex, int ai_family, - unsigned int index) -{ - struct connectdata *conn = data->conn; - return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data, ai_family, index); -} - -#ifdef USE_HTTPSRR -/* Return the HTTPS-RR info from the first "resolve" filter at the - * connection. If the DNS resolving is not done yet or if there - * is no HTTPS-RR info, returns NULL. - */ -const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data, - int sockindex) -{ - struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_dns) { - struct cf_dns_ctx *ctx = cf->ctx; - if(ctx->dns) - return ctx->dns->hinfo; - else - return Curl_resolv_get_https(data, ctx->resolv_id); - } - } - return NULL; -} - -bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex) -{ - struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_dns) { - struct cf_dns_ctx *ctx = cf->ctx; - if(ctx->dns) - return TRUE; - else - return Curl_resolv_knows_https(data, ctx->resolv_id); - } - } - return FALSE; -} - -#endif /* USE_HTTPSRR */ diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 3c2c8374d14b..49b157bb3ad8 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -25,6 +25,8 @@ #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include #include "urldata.h" #include "curlx/dynbuf.h" #include "sendf.h" @@ -33,6 +35,7 @@ #include "http_proxy.h" #include "select.h" #include "progress.h" +#include "multiif.h" #include "cfilters.h" #include "cf-h1-proxy.h" #include "connect.h" @@ -40,7 +43,6 @@ #include "strcase.h" #include "curlx/strparse.h" - typedef enum { H1_TUNNEL_INIT, /* init/default/no tunnel state */ H1_TUNNEL_CONNECT, /* CONNECT request is being send */ @@ -52,11 +54,13 @@ typedef enum { /* struct for HTTP CONNECT tunneling */ struct h1_tunnel_state { + struct Curl_peer *dest; struct dynbuf rcvbuf; struct dynbuf request_data; size_t nsent; size_t headerlines; struct Curl_chunker ch; + int httpversion; enum keeponval { KEEPON_DONE, KEEPON_CONNECT, @@ -70,6 +74,12 @@ struct h1_tunnel_state { BIT(leading_unfold); }; +/* Persistent context for the H1-PROXY filter */ +struct cf_h1_proxy_ctx { + struct h1_tunnel_state *ts; + BIT(udp_tunnel); +}; + static bool tunnel_is_established(struct h1_tunnel_state *ts) { return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED); @@ -80,6 +90,12 @@ static bool tunnel_is_failed(struct h1_tunnel_state *ts) return ts && (ts->tunnel_state == H1_TUNNEL_FAILED); } +static bool h1_proxy_is_udp(struct Curl_cfilter *cf) +{ + struct cf_h1_proxy_ctx *pctx = cf->ctx; + return (pctx->udp_tunnel ? TRUE : FALSE); +} + static CURLcode tunnel_reinit(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts) @@ -95,6 +111,8 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, ts->close_connection = FALSE; ts->maybe_folded = FALSE; ts->leading_unfold = FALSE; + ts->nsent = 0; + ts->headerlines = 0; return CURLE_OK; } @@ -117,7 +135,7 @@ static CURLcode tunnel_init(struct Curl_cfilter *cf, 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); + Curl_httpchunk_init(data, &ts->ch, TRUE, TRUE); *pts = ts; return tunnel_reinit(cf, data, ts); @@ -156,7 +174,9 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, case H1_TUNNEL_ESTABLISHED: CURL_TRC_CF(data, cf, "new tunnel state 'established'"); - infof(data, "CONNECT phase completed"); + infof(data, "CONNECT%s phase completed for HTTP proxy", + h1_proxy_is_udp(cf) ? "-UDP" : ""); + data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; FALLTHROUGH(); @@ -172,23 +192,33 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, /* If a proxy-authorization header was used for the proxy, then we should make sure that it is not accidentally used for the document request after we have connected. Let's thus free and clear it here. */ - curlx_safefree(data->req.proxyuserpwd); + curlx_safefree(data->req.hd_proxy_auth); break; } } -static void tunnel_free(struct Curl_cfilter *cf, +static void tunnel_free(struct h1_tunnel_state *ts, struct Curl_easy *data) +{ + if(ts) { + Curl_peer_unlink(&ts->dest); + curlx_dyn_free(&ts->rcvbuf); + curlx_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); + curlx_free(ts); + } +} + +static void cf_tunnel_free(struct Curl_cfilter *cf, + struct Curl_easy *data) { if(cf) { - struct h1_tunnel_state *ts = cf->ctx; + struct cf_h1_proxy_ctx *pctx = cf->ctx; + struct h1_tunnel_state *ts = pctx ? pctx->ts : NULL; 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); - curlx_free(ts); - cf->ctx = NULL; + tunnel_free(ts, data); + pctx->ts = NULL; } } } @@ -206,20 +236,21 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, int http_minor; CURLcode result; + DEBUGASSERT(data); /* This only happens if we have looped here due to authentication reasons, and we do not really use the newly cloned URL here then. Free it. */ curlx_safefree(data->req.newurl); - result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1); + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ts->dest, + PROXY_HTTP_V1, + h1_proxy_is_udp(cf)); if(result) goto out; - infof(data, "Establish HTTP proxy tunnel to %s", req->authority); - 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; + http_minor = ts->httpversion % 10; result = Curl_h1_req_write_head(req, http_minor, &ts->request_data); if(!result) @@ -268,6 +299,92 @@ static CURLcode send_CONNECT(struct Curl_cfilter *cf, return result; } +static CURLcode on_resp_header_udp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + const char *header) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + + if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { + + bool proxy = (k->httpcode == 407); + char *auth = Curl_copy_header_value(header); + if(!auth) + return CURLE_OUT_OF_MEMORY; + + CURL_TRC_CF(data, cf, "CONNECT-UDP: fwd auth header '%s'", header); + result = Curl_http_input_auth(data, proxy, auth); + + curlx_free(auth); + + if(result) + return result; + } + else if(checkprefix("Content-Length:", header)) { + if(k->httpcode / 100 == 2 || k->httpcode == 101) { + infof(data, "Ignoring Content-Length in CONNECT-UDP %03d response", + k->httpcode); + } + else { + const char *p = header + CURL_CSTRLEN("Content-Length:"); + if(curlx_str_numblanks(&p, &ts->cl)) { + failf(data, "Unsupported Content-Length value"); + return CURLE_WEIRD_SERVER_REPLY; + } + } + } + else if(checkprefix("Transfer-Encoding:", header)) { + if(k->httpcode / 100 == 2 || k->httpcode == 101) { + infof(data, "Ignoring Transfer-Encoding in " + "CONNECT-UDP %03d response", k->httpcode); + } + else if(Curl_compareheader(header, + STRCONST("Transfer-Encoding:"), + STRCONST("chunked"))) { + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> " + "Transfer-Encoding: chunked"); + ts->chunked_encoding = TRUE; + /* reset our chunky engine */ + Curl_httpchunk_reset(data, &ts->ch, TRUE); + } + } + else if(checkprefix("Capsule-protocol:", header)) { + if(Curl_compareheader(header, + STRCONST("Capsule-protocol:"), + STRCONST("?1"))) { + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1"); + } + } + else if(Curl_compareheader(header, + STRCONST("Connection:"), STRCONST("close"))) { + ts->close_connection = TRUE; + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Connection: close"); + } + else if(Curl_compareheader(header, + STRCONST("Proxy-Connection:"), + STRCONST("close"))) { + ts->close_connection = TRUE; + CURL_TRC_CF(data, cf, + "CONNECT-UDP Response --> Proxy-Connection: close"); + } + else if(!strncmp(header, "HTTP/1.", 7) && + ((header[7] == '0') || (header[7] == '1')) && + (header[8] == ' ') && + ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && + !ISDIGIT(header[12])) { + /* store the HTTP code from the proxy */ + data->info.httpproxycode = k->httpcode = + ((header[9] - '0') * 100) + + ((header[10] - '0') * 10) + + (header[11] - '0'); + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> %d", k->httpcode); + } + return result; +} + static CURLcode on_resp_header(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts, @@ -302,7 +419,7 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, k->httpcode); } else { - const char *p = header + strlen("Content-Length:"); + const char *p = header + CURL_CSTRLEN("Content-Length:"); if(curlx_str_numblanks(&p, &ts->cl)) { failf(data, "Unsupported Content-Length value"); return CURLE_WEIRD_SERVER_REPLY; @@ -359,6 +476,12 @@ static CURLcode single_header(struct Curl_cfilter *cf, /* output debug if that is requested */ Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); + /* a CONNECT response line is handed to the client as a header, so it must + pass the same checks as a regular response header before delivery */ + result = Curl_verify_header(data, linep, line_len); + if(result) + return result; + /* send the header to the callback */ writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); @@ -406,7 +529,13 @@ static CURLcode single_header(struct Curl_cfilter *cf, return result; } - result = on_resp_header(cf, data, ts, linep); + if(h1_proxy_is_udp(cf)) { + result = on_resp_header_udp(cf, data, ts, linep); + } + else { + result = on_resp_header(cf, data, ts, linep); + } + if(result) return result; @@ -448,8 +577,15 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, } if(!nread) { + if(ts->maybe_folded) { + /* EOF right after LF: finalize the pending header line. */ + result = single_header(cf, data, ts); + if(result) + return result; + ts->maybe_folded = FALSE; + } if(data->set.proxyauth && data->state.authproxy.avail && - data->req.proxyuserpwd) { + data->req.hd_proxy_auth) { /* proxy auth was requested and there was proxy auth available, then deem this as "mere" proxy disconnect */ ts->close_connection = TRUE; @@ -539,12 +675,16 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, ts->maybe_folded = TRUE; } + if(result) + return result; } /* while there is buffer left and loop is requested */ if(error) result = CURLE_RECV_ERROR; *done = (ts->keepon == KEEPON_DONE); - if(!result && *done && data->info.httpproxycode / 100 != 2) { + if(!result && *done && + data->info.httpproxycode / 100 != 2 && + !(h1_proxy_is_udp(cf) && data->info.httpproxycode == 101)) { /* Deal with the possibly already received authenticate headers. 'newurl' is set to a new URL if we must loop. */ result = Curl_http_auth_act(data); @@ -597,7 +737,8 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "CONNECT receive"); result = recv_CONNECT_resp(cf, data, ts, &done); if(result) - CURL_TRC_CF(data, cf, "error receiving CONNECT response: %d", result); + CURL_TRC_CF(data, cf, "error receiving CONNECT response: %d", + (int)result); if(!result) result = Curl_pgrsUpdate(data); /* error or not complete yet. return for more multi-multi */ @@ -623,9 +764,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, */ CURL_TRC_CF(data, cf, "CONNECT need to close+open"); infof(data, "Connect me again please"); - Curl_conn_cf_close(cf, data); - result = Curl_conn_cf_connect(cf->next, data, &done); - goto out; + return CURLE_AGAIN; } else { /* staying on this connection, reset state */ @@ -641,17 +780,36 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, } while(data->req.newurl); 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. */ - curlx_safefree(data->req.newurl); - h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); - failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode); - return CURLE_COULDNT_CONNECT; + if(h1_proxy_is_udp(cf)) { + /* RFC 9298: Accept 101 Upgrade for HTTP/1.1 and + * 2xx responses for HTTP/2 and HTTP/3 proxies. */ + if(data->info.httpproxycode / 100 != 2 && + data->info.httpproxycode != 101) { + curlx_safefree(data->req.newurl); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + failf(data, "CONNECT-UDP tunnel failed, response %d", + data->req.httpcode); + return CURLE_COULDNT_CONNECT; + } + } + else { + if(data->info.httpproxycode / 100 != 2) { + /* a non-2xx response and we have no next URL to try. */ + curlx_safefree(data->req.newurl); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode); + return CURLE_COULDNT_CONNECT; + } } /* 2xx response, SUCCESS! */ + /* 101 Switching Protocol for CONNECT-UDP */ h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data); - infof(data, "CONNECT tunnel established, response %d", - data->info.httpproxycode); + if(h1_proxy_is_udp(cf)) + infof(data, "CONNECT-UDP tunnel established, response %d", + data->info.httpproxycode); + else + infof(data, "CONNECT tunnel established, response %d", + data->info.httpproxycode); result = CURLE_OK; out: @@ -665,7 +823,8 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, bool *done) { CURLcode result; - struct h1_tunnel_state *ts = cf->ctx; + struct cf_h1_proxy_ctx *pctx = cf->ctx; + struct h1_tunnel_state *ts = pctx->ts; if(cf->connected) { *done = TRUE; @@ -682,7 +841,7 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, result = tunnel_init(cf, data, &ts); if(result) return result; - cf->ctx = ts; + pctx->ts = ts; } /* We want "seamless" operations through HTTP proxy tunnel */ @@ -690,18 +849,17 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, result = H1_CONNECT(cf, data, ts); if(result) goto out; - curlx_safefree(data->req.proxyuserpwd); + curlx_safefree(data->req.hd_proxy_auth); out: - *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx); + *done = (result == CURLE_OK) && tunnel_is_established(pctx->ts); 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_pgrsReset(data); - - tunnel_free(cf, data); + cf_tunnel_free(cf, data); } return result; } @@ -710,7 +868,8 @@ static CURLcode cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, struct easy_pollset *ps) { - struct h1_tunnel_state *ts = cf->ctx; + struct cf_h1_proxy_ctx *pctx = cf->ctx; + struct h1_tunnel_state *ts = pctx->ts; CURLcode result = CURLE_OK; if(!cf->connected) { @@ -730,28 +889,52 @@ static CURLcode cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf, else result = Curl_pollset_set_out_only(data, ps, sock); } + else { + if(cf->next) + result = cf->next->cft->adjust_pollset(cf->next, data, ps); + } return result; } +static bool cf_h1_proxy_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { CURL_TRC_CF(data, cf, "destroy"); - tunnel_free(cf, data); + cf_tunnel_free(cf, data); + curlx_safefree(cf->ctx); } -static void cf_h1_proxy_close(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode cf_h1_proxy_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) { - 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); + struct cf_h1_proxy_ctx *pctx = cf->ctx; + struct h1_tunnel_state *ts = pctx ? pctx->ts : NULL; + switch(query) { + case CF_QUERY_HOST_PORT: + if(!ts || !ts->dest) + break; + *pres1 = (int)ts->dest->port; + *((const char **)pres2) = ts->dest->hostname; + return CURLE_OK; + case CF_QUERY_ALPN_NEGOTIATED: { + const char **palpn = pres2; + DEBUGASSERT(palpn); + *palpn = NULL; + 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_h1_proxy = { @@ -760,28 +943,62 @@ struct Curl_cftype Curl_cft_h1_proxy = { 0, cf_h1_proxy_destroy, cf_h1_proxy_connect, - cf_h1_proxy_close, Curl_cf_def_shutdown, cf_h1_proxy_adjust_pollset, - Curl_cf_def_data_pending, + cf_h1_proxy_data_pending, Curl_cf_def_send, Curl_cf_def_recv, Curl_cf_def_cntrl, Curl_cf_def_conn_is_alive, Curl_cf_def_conn_keep_alive, - Curl_cf_http_proxy_query, + cf_h1_proxy_query, }; CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *dest, + int httpversion, + bool udp_tunnel) { struct Curl_cfilter *cf; + struct cf_h1_proxy_ctx *pctx; + struct h1_tunnel_state *ts; CURLcode result; (void)data; - result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL); - if(!result) - Curl_conn_cf_insert_after(cf_at, cf); + if(!dest) + return CURLE_FAILED_INIT; + if((httpversion < 10) || (httpversion >= 20)) + return CURLE_FAILED_INIT; + + ts = curlx_calloc(1, sizeof(*ts)); + if(!ts) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_peer_link(&ts->dest, dest); + ts->httpversion = httpversion; + 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, TRUE); + + pctx = curlx_calloc(1, sizeof(*pctx)); + if(!pctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + pctx->udp_tunnel = udp_tunnel; + pctx->ts = ts; + result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, pctx); + if(result) { + curlx_free(pctx); + goto out; + } + ts = NULL; + Curl_conn_cf_insert_after(cf_at, cf); + +out: + tunnel_free(ts, data); return result; } diff --git a/lib/cf-h1-proxy.h b/lib/cf-h1-proxy.h index 6544ec58d026..3255bf79f240 100644 --- a/lib/cf-h1-proxy.h +++ b/lib/cf-h1-proxy.h @@ -27,8 +27,13 @@ #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) +struct Curl_peer; + CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *dest, + int httpversion, + bool udp_tunnel); extern struct Curl_cftype Curl_cft_h1_proxy; diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index 2f8cc41dd52b..fde9e1bc799a 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -76,30 +76,40 @@ struct tunnel_stream { BIT(reset); }; -static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, - struct tunnel_stream *ts) +static CURLcode tunnel_stream_init(struct tunnel_stream *ts, + struct Curl_peer *dest) { - const char *hostname; - uint16_t port; - bool ipv6_ip; - ts->state = H2_TUNNEL_INIT; ts->stream_id = -1; Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS); - Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); - /* host:port with IPv6 support */ - ts->authority = curl_maprintf("%s%s%s:%u", ipv6_ip ? "[" : "", hostname, - ipv6_ip ? "]" : "", port); + ts->authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", + dest->hostname, + dest->ipv6 ? "]" : "", + dest->port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } +static void tunnel_stream_reset(struct tunnel_stream *ts) +{ + Curl_http_resp_free(ts->resp); + ts->resp = NULL; + Curl_bufq_reset(&ts->recvbuf); + Curl_bufq_reset(&ts->sendbuf); + ts->stream_id = -1; + ts->error = 0; + ts->has_final_response = FALSE; + ts->closed = FALSE; + ts->reset = FALSE; + ts->state = H2_TUNNEL_INIT; +} + static void tunnel_stream_clear(struct tunnel_stream *ts) { Curl_http_resp_free(ts->resp); @@ -113,9 +123,11 @@ static void tunnel_stream_clear(struct tunnel_stream *ts) static void h2_tunnel_go_state(struct Curl_cfilter *cf, struct tunnel_stream *ts, h2_tunnel_state new_state, - struct Curl_easy *data) + struct Curl_easy *data, + bool udp_tunnel) { (void)cf; + (void)udp_tunnel; if(ts->state == new_state) return; @@ -131,7 +143,7 @@ static void h2_tunnel_go_state(struct Curl_cfilter *cf, switch(new_state) { case H2_TUNNEL_INIT: CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id); - tunnel_stream_clear(ts); + tunnel_stream_reset(ts); break; case H2_TUNNEL_CONNECT: @@ -147,7 +159,8 @@ static void h2_tunnel_go_state(struct Curl_cfilter *cf, case H2_TUNNEL_ESTABLISHED: CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'", ts->stream_id); - infof(data, "CONNECT phase completed"); + infof(data, "CONNECT%s phase completed for HTTP/2 proxy", + udp_tunnel ? "-UDP" : ""); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; FALLTHROUGH(); @@ -158,7 +171,7 @@ static void h2_tunnel_go_state(struct Curl_cfilter *cf, /* If a proxy-authorization header was used for the proxy, then we should make sure that it is not accidentally used for the document request after we have connected. Let's thus free and clear it here. */ - curlx_safefree(data->req.proxyuserpwd); + curlx_safefree(data->req.hd_proxy_auth); break; } } @@ -171,13 +184,15 @@ struct cf_h2_proxy_ctx { struct bufq inbufq; /* network receive buffer */ struct bufq outbufq; /* network send buffer */ + struct Curl_peer *dest; /* where to tunnel to */ struct tunnel_stream tunnel; /* our tunnel CONNECT stream */ int32_t goaway_error; - int32_t last_stream_id; + int32_t remote_max_sid; BIT(conn_closed); BIT(rcvd_goaway); BIT(sent_goaway); BIT(nw_out_blocked); + BIT(udp_tunnel); }; /* How to access `call_data` from a cf_h2 filter */ @@ -193,6 +208,7 @@ static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx) } Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); + Curl_peer_unlink(&ctx->dest); tunnel_stream_clear(&ctx->tunnel); memset(ctx, 0, sizeof(*ctx)); ctx->call_data = save; @@ -213,7 +229,8 @@ static void drain_tunnel(struct Curl_cfilter *cf, struct cf_h2_proxy_ctx *ctx = cf->ctx; (void)cf; if(!tunnel->closed && !tunnel->reset && - !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) + (!Curl_bufq_is_empty(&ctx->tunnel.sendbuf) || + !Curl_bufq_is_empty(&ctx->tunnel.recvbuf))) Curl_multi_mark_dirty(data); } @@ -228,7 +245,7 @@ static CURLcode proxy_h2_nw_out_writer(void *writer_ctx, CURLcode result; result = Curl_conn_cf_send(cf->next, data, buf, buflen, FALSE, pnwritten); CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %d, %zu", - buflen, result, *pnwritten); + buflen, (int)result, *pnwritten); return result; } return CURLE_FAILED_INIT; @@ -247,8 +264,7 @@ static int proxy_h2_client_new(struct Curl_cfilter *cf, return rc; /* We handle window updates ourself to enforce buffer limits */ nghttp2_option_set_no_auto_window_update(o, 1); -#if NGHTTP2_VERSION_NUM >= 0x013200 - /* with 1.50.0 */ +#if NGHTTP2_VERSION_NUM >= 0x013200 /* with 1.50.0 */ /* turn off RFC 9113 leading and trailing white spaces validation against HTTP field value. */ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); @@ -354,7 +370,7 @@ static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread); CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %d, %zu", - Curl_bufq_len(&ctx->inbufq), result, nread); + Curl_bufq_len(&ctx->inbufq), (int)result, nread); if(result) { if(result != CURLE_AGAIN) { failf(data, "Failed receiving HTTP2 proxy data"); @@ -363,6 +379,7 @@ static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, break; } else if(nread == 0) { + CURL_TRC_CF(data, cf, "server closed connection"); ctx->conn_closed = TRUE; break; } @@ -483,6 +500,11 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, break; case NGHTTP2_GOAWAY: ctx->rcvd_goaway = TRUE; + ctx->remote_max_sid = frame->goaway.last_stream_id; + if(data) { + infof(data, "received GOAWAY, error=%u, last_stream=%d", + frame->goaway.error_code, ctx->remote_max_sid); + } break; default: break; @@ -557,13 +579,13 @@ static int proxy_h2_on_header(nghttp2_session *session, return 0; } - if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && - memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) { + if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && + !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { int http_status; struct http_resp *resp; /* status: always comes first, we might get more than one response, - * link the previous ones for keepers */ + * discard previous, interim responses */ result = Curl_http_decode_status(&http_status, (const char *)value, valuelen); if(result) @@ -571,7 +593,8 @@ static int proxy_h2_on_header(nghttp2_session *session, result = Curl_http_resp_make(&resp, http_status, NULL); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - resp->prev = ctx->tunnel.resp; + if(ctx->tunnel.resp) + Curl_http_resp_free(ctx->tunnel.resp); ctx->tunnel.resp = resp; CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d", stream_id, ctx->tunnel.resp->status); @@ -750,15 +773,15 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf, CURLcode result; struct httpreq *req = NULL; - result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2); + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ctx->dest, + PROXY_HTTP_V2, + (bool)ctx->udp_tunnel); if(result) goto out; result = Curl_creader_set_null(data); if(result) goto out; - infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority); - result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req, NULL, ts, tunnel_send_callback, cf); if(result) { @@ -778,41 +801,30 @@ static CURLcode inspect_response(struct Curl_cfilter *cf, struct Curl_easy *data, struct tunnel_stream *ts) { - CURLcode result = CURLE_OK; - struct dynhds_entry *auth_reply = NULL; - (void)cf; - - DEBUGASSERT(ts->resp); - if(ts->resp->status / 100 == 2) { - infof(data, "CONNECT tunnel established, response %d", ts->resp->status); - h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data); - return CURLE_OK; - } - - if(ts->resp->status == 401) { - auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate"); - } - else if(ts->resp->status == 407) { - auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate"); - } + struct cf_h2_proxy_ctx *ctx = cf->ctx; + proxy_inspect_result res; + CURLcode result; - if(auth_reply) { - 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) { - /* Indicator that we should try again */ - curlx_safefree(data->req.newurl); - h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data); - return CURLE_OK; - } + result = Curl_http_proxy_inspect_tunnel_response( + cf, data, ts->resp, (bool)ctx->udp_tunnel, &res); + if(result) + return result; + switch(res) { + case PROXY_INSPECT_OK: + h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data, + (bool)ctx->udp_tunnel); + break; + case PROXY_INSPECT_FAILED: + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data, + (bool)ctx->udp_tunnel); + result = CURLE_COULDNT_CONNECT; + break; + case PROXY_INSPECT_AUTH_RETRY: + h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data, + (bool)ctx->udp_tunnel); + break; } - - /* Seems to have failed */ - return CURLE_COULDNT_CONNECT; + return result; } static CURLcode H2_CONNECT(struct Curl_cfilter *cf, @@ -824,6 +836,11 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, DEBUGASSERT(ts); DEBUGASSERT(ts->authority); + if(ctx->conn_closed) { + failf(data, "proxy closed connection"); + return CURLE_COULDNT_CONNECT; + } + do { switch(ts->state) { case H2_TUNNEL_INIT: @@ -832,7 +849,8 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, result = submit_CONNECT(cf, data, ts); if(result) goto out; - h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data, + (bool)ctx->udp_tunnel); FALLTHROUGH(); case H2_TUNNEL_CONNECT: @@ -841,12 +859,14 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, if(!result) result = proxy_h2_progress_egress(cf, data); if(result && result != CURLE_AGAIN) { - h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data, + (bool)ctx->udp_tunnel); break; } if(ts->has_final_response) { - h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data, + (bool)ctx->udp_tunnel); } else { result = CURLE_OK; @@ -875,7 +895,8 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, out: if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed) - h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data, + (bool)ctx->udp_tunnel); return result; } @@ -895,8 +916,9 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, 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); + ctx->remote_max_sid = INT32_MAX; - if(tunnel_stream_init(cf, &ctx->tunnel)) + if(tunnel_stream_init(&ctx->tunnel, ctx->dest)) goto out; rc = nghttp2_session_callbacks_new(&cbs); @@ -958,7 +980,7 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, out: if(cbs) nghttp2_session_callbacks_del(cbs); - CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result); + CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", (int)result); return result; } @@ -1016,21 +1038,6 @@ static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, return result; } -static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_h2_proxy_ctx *ctx = cf->ctx; - - if(ctx) { - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - cf_h2_proxy_ctx_clear(ctx); - CF_DATA_RESTORE(cf, save); - } - if(cf->next) - cf->next->cft->do_close(cf->next, data); -} - static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1133,7 +1140,7 @@ static CURLcode cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf, result = Curl_pollset_set(data, ps, sock, want_recv, want_send); CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d -> %d", - want_recv, want_send, result); + want_recv, want_send, (int)result); CF_DATA_RESTORE(cf, save); } else if(ctx->sent_goaway && !cf->shutdown) { @@ -1145,7 +1152,7 @@ static CURLcode cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf, want_recv = nghttp2_session_want_read(ctx->h2); result = Curl_pollset_set(data, ps, sock, want_recv, want_send); CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d -> %d", - want_recv, want_send, result); + want_recv, want_send, (int)result); CF_DATA_RESTORE(cf, save); } return result; @@ -1186,7 +1193,7 @@ static CURLcode 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->rcvd_goaway && - ctx->last_stream_id < ctx->tunnel.stream_id)) { + ctx->remote_max_sid < ctx->tunnel.stream_id)) { result = CURLE_RECV_ERROR; } else @@ -1194,7 +1201,7 @@ static CURLcode tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %d, %zu", - ctx->tunnel.stream_id, len, result, *pnread); + ctx->tunnel.stream_id, len, (int)result, *pnread); return result; } @@ -1232,14 +1239,15 @@ static CURLcode cf_h2_proxy_recv(struct Curl_cfilter *cf, result = Curl_1st_fatal(result, proxy_h2_progress_egress(cf, data)); out: - if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + if((!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) && (!result || (result == 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) -> %d, %zu", - ctx->tunnel.stream_id, len, result, *pnread); + ctx->tunnel.stream_id, len, (int)result, *pnread); CF_DATA_RESTORE(cf, save); return result; } @@ -1269,7 +1277,8 @@ static CURLcode cf_h2_proxy_send(struct Curl_cfilter *cf, } result = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, pnwritten); - CURL_TRC_CF(data, cf, "cf_send(), bufq_write %d, %zu", result, *pnwritten); + CURL_TRC_CF(data, cf, "cf_send(), bufq_write %d, %zu", (int)result, + *pnwritten); if(result && (result != CURLE_AGAIN)) goto out; @@ -1298,7 +1307,8 @@ static CURLcode cf_h2_proxy_send(struct Curl_cfilter *cf, } out: - if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + if((!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) && (!result || (result == CURLE_AGAIN))) { /* data pending and no fatal error to report. Need to trigger * draining to avoid stalling when no socket events happen. */ @@ -1306,7 +1316,7 @@ static CURLcode cf_h2_proxy_send(struct Curl_cfilter *cf, } CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %d, %zu, " "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", - ctx->tunnel.stream_id, len, result, *pnwritten, + ctx->tunnel.stream_id, len, (int)result, *pnwritten, nghttp2_session_get_stream_remote_window_size( ctx->h2, ctx->tunnel.stream_id), nghttp2_session_get_remote_window_size(ctx->h2), @@ -1338,7 +1348,7 @@ static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf, 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, + ctx->tunnel.stream_id, (int)result, nghttp2_session_get_stream_remote_window_size( ctx->h2, ctx->tunnel.stream_id), nghttp2_session_get_remote_window_size(ctx->h2), @@ -1410,8 +1420,8 @@ static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_HOST_PORT: - *pres1 = (int)cf->conn->http_proxy.port; - *((const char **)pres2) = cf->conn->http_proxy.host.name; + *pres1 = (int)ctx->dest->port; + *((const char **)pres2) = ctx->dest->hostname; return CURLE_OK; case CF_QUERY_NEED_FLUSH: { if(!Curl_bufq_is_empty(&ctx->outbufq) || @@ -1464,7 +1474,6 @@ struct Curl_cftype Curl_cft_h2_proxy = { CURL_LOG_LVL_NONE, cf_h2_proxy_destroy, cf_h2_proxy_connect, - cf_h2_proxy_close, cf_h2_proxy_shutdown, cf_h2_proxy_adjust_pollset, cf_h2_proxy_data_pending, @@ -1477,7 +1486,9 @@ struct Curl_cftype Curl_cft_h2_proxy = { }; CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *dest, + bool udp_tunnel) { struct Curl_cfilter *cf_h2_proxy = NULL; struct cf_h2_proxy_ctx *ctx; @@ -1487,17 +1498,17 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) goto out; + Curl_peer_link(&ctx->dest, dest); + ctx->udp_tunnel = udp_tunnel; result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx); if(result) goto out; - + ctx = NULL; Curl_conn_cf_insert_after(cf, cf_h2_proxy); - result = CURLE_OK; out: - if(result) - cf_h2_proxy_ctx_free(ctx); + cf_h2_proxy_ctx_free(ctx); return result; } diff --git a/lib/cf-h2-proxy.h b/lib/cf-h2-proxy.h index 318ce1973fa1..07e3c9aedf1a 100644 --- a/lib/cf-h2-proxy.h +++ b/lib/cf-h2-proxy.h @@ -28,7 +28,9 @@ #if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *dest, + bool udp_tunnel); extern struct Curl_cftype Curl_cft_h2_proxy; diff --git a/lib/cf-haproxy.c b/lib/cf-haproxy.c index 9ee5e790ebb5..e3286107a328 100644 --- a/lib/cf-haproxy.c +++ b/lib/cf-haproxy.c @@ -28,6 +28,7 @@ #include "urldata.h" #include "cfilters.h" #include "cf-haproxy.h" +#include "connect.h" #include "curl_addrinfo.h" #include "curl_trc.h" #include "select.h" @@ -44,13 +45,6 @@ struct cf_haproxy_ctx { struct dynbuf data_out; }; -static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx) -{ - DEBUGASSERT(ctx); - ctx->state = HAPROXY_INIT; - curlx_dyn_reset(&ctx->data_out); -} - static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx) { if(ctx) { @@ -78,7 +72,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf, DEBUGASSERT(ctx); DEBUGASSERT(ctx->state == HAPROXY_INIT); #ifdef USE_UNIX_SOCKETS - if(cf->conn->unix_domain_socket) + if(Curl_conn_get_first_peer(cf->conn, cf->sockindex)->unix_socket) /* the buffer is large enough to hold this! */ result = curlx_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); else { @@ -87,8 +81,8 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf, if(result) return result; - if(data->set.str[STRING_HAPROXY_CLIENT_IP]) { - client_source_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + client_source_ip = CURL_EASY_STR(data, STRING_HAPROXY_CLIENT_IP); + if(client_source_ip) { client_dest_ip = client_source_ip; is_ipv6 = !Curl_is_ipv4addr(client_source_ip); } @@ -97,7 +91,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf, client_dest_ip = ipquad.remote_ip; } - result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", + result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %d %d\r\n", is_ipv6 ? "TCP6" : "TCP4", client_source_ip, client_dest_ip, ipquad.local_port, ipquad.remote_port); @@ -172,16 +166,6 @@ static void cf_haproxy_destroy(struct Curl_cfilter *cf, cf_haproxy_ctx_free(cf->ctx); } -static void cf_haproxy_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURL_TRC_CF(data, cf, "close"); - cf->connected = FALSE; - cf_haproxy_ctx_reset(cf->ctx); - if(cf->next) - cf->next->cft->do_close(cf->next, data); -} - static CURLcode cf_haproxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, struct easy_pollset *ps) @@ -201,7 +185,6 @@ struct Curl_cftype Curl_cft_haproxy = { 0, cf_haproxy_destroy, cf_haproxy_connect, - cf_haproxy_close, Curl_cf_def_shutdown, cf_haproxy_adjust_pollset, Curl_cf_def_data_pending, diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index d1d8f51076e3..2e13a5cc09fe 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -28,15 +28,16 @@ #include "urldata.h" #include "curl_trc.h" #include "cfilters.h" -#include "cf-dns.h" +#include "cf-setup.h" #include "connect.h" -#include "hostip.h" -#include "httpsrr.h" #include "multiif.h" #include "cf-https-connect.h" #include "http2.h" #include "progress.h" #include "select.h" +#include "vdns/cf-dns.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "vquic/vquic.h" typedef enum { @@ -51,7 +52,6 @@ struct cf_hc_baller { const char *name; struct Curl_cfilter *cf; CURLcode result; - struct curltime started; int reply_ms; uint8_t transport; enum alpnid alpn_id; @@ -62,7 +62,6 @@ static void cf_hc_baller_discard(struct cf_hc_baller *b, struct Curl_easy *data) { if(b->cf) { - Curl_conn_cf_close(b->cf, data); Curl_conn_cf_discard_chain(&b->cf, data); b->cf = NULL; } @@ -110,6 +109,7 @@ static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, struct cf_hc_ctx { cf_hc_state state; + struct Curl_peer *destination; /* who we ultimately want to talk to */ struct curltime started; /* when connect started */ CURLcode result; /* overall result */ CURLcode check_h3_result; @@ -123,21 +123,14 @@ struct cf_hc_ctx { BIT(ballers_complete); }; -static void cf_hc_ctx_close(struct Curl_easy *data, - struct cf_hc_ctx *ctx) +static void cf_hc_ctx_destroy(struct Curl_easy *data, + struct cf_hc_ctx *ctx) { if(ctx) { size_t i; for(i = 0; i < ctx->baller_count; ++i) cf_hc_baller_discard(&ctx->ballers[i], data); - } -} - -static void cf_hc_ctx_destroy(struct Curl_easy *data, - struct cf_hc_ctx *ctx) -{ - if(ctx) { - cf_hc_ctx_close(data, ctx); + Curl_peer_unlink(&ctx->destination); curlx_free(ctx); } } @@ -179,7 +172,6 @@ static void cf_hc_baller_init(struct cf_hc_baller *b, struct Curl_cfilter *save = cf->next; cf->next = NULL; - b->started = *Curl_pgrs_now(data); b->result = Curl_cf_setup_insert_after(cf, data, b->transport, CURL_CF_SSL_ENABLE); b->cf = cf->next; @@ -222,7 +214,6 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, ctx->state = CF_HC_SUCCESS; cf->connected = TRUE; - cf_hc_ctx_close(data, ctx); /* ballers may have failf()'d, the winner resets it, so our * errorbuf is clean again. */ Curl_reset_fail(data); @@ -245,7 +236,8 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, } static bool time_to_start_baller2(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + const struct curltime *pnow) { struct cf_hc_ctx *ctx = cf->ctx; timediff_t elapsed_ms; @@ -260,7 +252,7 @@ static bool time_to_start_baller2(struct Curl_cfilter *cf, return TRUE; } - elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started); + elapsed_ms = curlx_ptimediff_ms(pnow, &ctx->started); if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { CURL_TRC_CF(data, cf, "%s inconclusive after %" FMT_TIMEDIFF_T ", " "starting %s", ctx->ballers[0].name, @@ -296,6 +288,7 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, enum alpnid not_this_one) { #ifdef USE_HTTPSRR + struct cf_hc_ctx *ctx = cf->ctx; /* 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 @@ -304,13 +297,14 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, size_t i; /* Do we have HTTPS-RR information? */ - rr = Curl_conn_dns_get_https(data, cf->sockindex); + rr = Curl_conn_dns_get_https(data, cf->sockindex, ctx->destination); + CURL_TRC_CF(data, cf, "HTTPS-RR %savailable", rr ? "" : "not "); /* We do not support `rr->no_def_alpn`. */ if(Curl_httpsrr_applicable(data, rr) && !rr->no_def_alpn) { for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) { enum alpnid alpn_rr = (enum alpnid)rr->alpns[i]; - if(alpn_rr == not_this_one) /* don't want this one */ + if(alpn_rr == not_this_one) /* do not want this one */ continue; switch(alpn_rr) { case ALPN_h3: @@ -484,6 +478,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, { struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + const struct curltime *pnow = NULL; if(cf->connected) { *done = TRUE; @@ -493,7 +488,8 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->httpsrr_resolved) { - ctx->httpsrr_resolved = Curl_conn_dns_resolved_https(data, cf->sockindex); + ctx->httpsrr_resolved = Curl_conn_dns_resolved_https( + data, cf->sockindex, ctx->destination); #ifdef DEBUGBUILD if(!ctx->httpsrr_resolved && getenv("CURL_DBG_AWAIT_HTTPSRR")) { CURL_TRC_CF(data, cf, "awaiting HTTPS-RR"); @@ -517,10 +513,12 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, goto out; } cf_hc_set_baller2(cf, data); - ctx->started = *Curl_pgrs_now(data); + pnow = Curl_pgrs_now(data); + ctx->started = *pnow; cf_hc_baller_init(&ctx->ballers[0], cf, data); if((ctx->baller_count > 1) || !ctx->ballers_complete) { - Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + Curl_expire_set(data, EXPIRE_ALPN_EYEBALLS, + ctx->soft_eyeballs_timeout_ms, pnow); } ctx->state = CF_HC_CONNECT; FALLTHROUGH(); @@ -537,7 +535,8 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, } } - if(time_to_start_baller2(cf, data)) { + pnow = Curl_pgrs_now(data); + if(time_to_start_baller2(cf, data, pnow)) { cf_hc_baller_init(&ctx->ballers[1], cf, data); } @@ -576,7 +575,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, } out: - CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", (int)result, *done); return result; } @@ -616,7 +615,7 @@ static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, result = ctx->ballers[i].result; } } - CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", (int)result, *done); return result; } @@ -635,7 +634,8 @@ static CURLcode cf_hc_adjust_pollset(struct Curl_cfilter *cf, continue; result = Curl_conn_cf_adjust_pollset(b->cf, data, ps); } - CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", result, ps->n); + CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", (int)result, + ps->n); } return result; } @@ -655,26 +655,6 @@ static bool cf_hc_data_pending(struct Curl_cfilter *cf, return FALSE; } -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 curltime t, tmax; - size_t i; - - memset(&tmax, 0, sizeof(tmax)); - 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_ptimediff_us(&t, &tmax) > 0) - tmax = t; - } - } - return tmax; -} - static CURLcode cf_hc_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) @@ -684,16 +664,6 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, if(!cf->connected) { switch(query) { - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - *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 = 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)) { @@ -720,29 +690,31 @@ static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, 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; + switch(event) { + case CF_CTRL_REPORT_STATS: + for(i = 0; i < ctx->baller_count; i++) { + /* Make the first baller that connected at network level report */ + if(Curl_conn_cf_is_ip_connected(ctx->ballers[i].cf, data)) { + Curl_conn_cf_cntrl(ctx->ballers[i].cf, data, TRUE, + event, arg1, arg2); + break; + } + } + break; + default: + 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; + break; } - result = CURLE_OK; } out: return result; } -static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - CURL_TRC_CF(data, cf, "close"); - cf_hc_ctx_close(data, cf->ctx); - cf->connected = FALSE; - - if(cf->next) { - cf->next->cft->do_close(cf->next, data); - Curl_conn_cf_discard_chain(&cf->next, data); - } -} - static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_hc_ctx *ctx = cf->ctx; @@ -753,11 +725,10 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_http_connect = { "HTTPS-CONNECT", - CF_TYPE_SETUP | CF_TYPE_HTTPSRR, + CF_TYPE_SETUP, CURL_LOG_LVL_NONE, cf_hc_destroy, cf_hc_connect, - cf_hc_close, cf_hc_shutdown, cf_hc_adjust_pollset, cf_hc_data_pending, @@ -771,6 +742,7 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *destination, uint8_t def_transport) { struct Curl_cfilter *cf = NULL; @@ -782,6 +754,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } + Curl_peer_link(&ctx->destination, destination); ctx->def_transport = def_transport; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; @@ -798,35 +771,48 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, } static CURLcode cf_hc_add(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex, + int8_t sockindex, uint8_t def_transport) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, def_transport); + result = cf_hc_create(&cf, data, destination, def_transport); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); + +#ifdef USE_HTTPSRR + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + destination); +#endif out: return result; } CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex) + int8_t sockindex) { CURLcode result = CURLE_OK; DEBUGASSERT(conn->scheme->protocol == CURLPROTO_HTTPS); + /* This filter is intended for HTTPS using ALPN and does + * not support HTTPS Eyeballing to a proxy. */ if((conn->scheme->protocol != CURLPROTO_HTTPS) || +#ifndef CURL_DISABLE_PROXY + conn->bits.origin_is_proxy || +#endif !conn->bits.tls_enable_alpn) - goto out; + goto out; - result = cf_hc_add(data, conn, sockindex, conn->transport_wanted); + result = cf_hc_add(data, destination, conn, sockindex, + conn->transport_wanted); out: return result; diff --git a/lib/cf-https-connect.h b/lib/cf-https-connect.h index 3160c0382a4b..efac194856b4 100644 --- a/lib/cf-https-connect.h +++ b/lib/cf-https-connect.h @@ -31,12 +31,14 @@ struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_cftype; +struct Curl_peer; extern struct Curl_cftype Curl_cft_http_connect; CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, - int sockindex); + int8_t sockindex); #endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_CF_HTTP_H */ diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index f67273e48963..baca4682f0b0 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -49,19 +49,21 @@ #include "urldata.h" #include "connect.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-ip-happy.h" #include "curl_addrinfo.h" #include "curl_trc.h" #include "multiif.h" #include "progress.h" #include "select.h" +#include "sockaddr.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" /* for quic cfilters */ struct transport_provider { - uint8_t transport; cf_ip_connect_create *cf_create; + uint8_t transport; + bool tunnel; }; static @@ -69,23 +71,30 @@ static const #endif struct transport_provider transport_providers[] = { - { TRNSPRT_TCP, Curl_cf_tcp_create }, + { Curl_cf_tcp_create, TRNSPRT_TCP, FALSE }, + { Curl_cf_tcp_create, TRNSPRT_TCP, TRUE }, #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) - { TRNSPRT_QUIC, Curl_cf_quic_create }, + { Curl_cf_quic_create, TRNSPRT_QUIC, FALSE }, +#endif +#if !defined(CURL_DISABLE_HTTP) && defined(USE_PROXY_HTTP3) + { Curl_cf_h3_proxy_create, TRNSPRT_QUIC, TRUE }, #endif #ifndef CURL_DISABLE_TFTP - { TRNSPRT_UDP, Curl_cf_udp_create }, + { Curl_cf_udp_create, TRNSPRT_UDP, FALSE }, #endif #ifdef USE_UNIX_SOCKETS - { TRNSPRT_UNIX, Curl_cf_unix_create }, + { Curl_cf_unix_create, TRNSPRT_UNIX, FALSE }, + { Curl_cf_unix_create, TRNSPRT_UNIX, TRUE }, #endif }; -static cf_ip_connect_create *get_cf_create(uint8_t transport) +static cf_ip_connect_create *get_cf_create(uint8_t transport, + bool tunnel) { size_t i; for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { - if(transport == transport_providers[i].transport) + if((transport == transport_providers[i].transport) && + (tunnel == transport_providers[i].tunnel)) return transport_providers[i].cf_create; } return NULL; @@ -102,7 +111,6 @@ UNITTEST void debug_set_transport_provider( for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { if(transport == transport_providers[i].transport) { transport_providers[i].cf_create = cf_create; - return; } } } @@ -110,15 +118,18 @@ UNITTEST void debug_set_transport_provider( struct cf_ai_iter { struct Curl_cfilter *cf; + struct Curl_peer *peer; int ai_family; unsigned int n; }; static void cf_ai_iter_init(struct cf_ai_iter *iter, struct Curl_cfilter *cf, + struct Curl_peer *peer, int ai_family) { iter->cf = cf; + iter->peer = peer; /* not linked, ctx->ballers owns and has same lifetime */ iter->ai_family = ai_family; iter->n = 0; } @@ -131,7 +142,7 @@ static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter, if(!iter->cf) return NULL; - addr = Curl_conn_dns_get_ai(data, iter->cf->sockindex, + addr = Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex, iter->ai_family, iter->n); if(addr) iter->n++; @@ -142,19 +153,22 @@ static bool cf_ai_iter_has_more(struct cf_ai_iter *iter, struct Curl_easy *data) { return (iter->cf && - !!Curl_conn_dns_get_ai(data, iter->cf->sockindex, + !!Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex, iter->ai_family, iter->n)); } struct cf_ip_attempt { struct cf_ip_attempt *next; + struct Curl_peer *origin; + struct Curl_peer *peer; + struct Curl_peer *tunnel_peer; struct Curl_sockaddr_ex addr; struct Curl_cfilter *cf; /* current sub-cfilter connecting */ cf_ip_connect_create *cf_create; - struct curltime started; /* start of current attempt */ CURLcode result; int ai_family; - uint8_t transport; + uint8_t transport_peer; + uint8_t tunnel_transport; int error; BIT(connected); /* cf has connected */ BIT(shutdown); /* cf has shutdown */ @@ -168,16 +182,23 @@ static void cf_ip_attempt_free(struct cf_ip_attempt *a, if(a) { if(a->cf) Curl_conn_cf_discard_chain(&a->cf, data); + Curl_peer_unlink(&a->origin); + Curl_peer_unlink(&a->peer); + Curl_peer_unlink(&a->tunnel_peer); curlx_free(a); } } static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, - struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_cfilter *cf, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct Curl_sockaddr_ex *addr, int ai_family, - uint8_t transport, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, cf_ip_connect_create *cf_create) { struct Curl_cfilter *wcf; @@ -189,14 +210,20 @@ static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, if(!a) return CURLE_OUT_OF_MEMORY; + Curl_peer_link(&a->origin, origin); + Curl_peer_link(&a->peer, peer); + a->transport_peer = transport_peer; + Curl_peer_link(&a->tunnel_peer, tunnel_peer); + a->tunnel_transport = tunnel_transport; a->addr = *addr; a->ai_family = ai_family; - a->transport = transport; a->result = CURLE_OK; a->cf_create = cf_create; *pa = a; - result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport); + result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer, + cf->conn, &a->addr, a->tunnel_peer, + a->tunnel_transport); if(result) goto out; @@ -245,13 +272,16 @@ struct cf_ip_ballers { #ifdef USE_IPV6 struct cf_ai_iter ipv6_iter; #endif + struct Curl_peer *origin; + struct Curl_peer *peer; + struct Curl_peer *tunnel_peer; cf_ip_connect_create *cf_create; /* for creating cf */ - struct curltime started; struct curltime last_attempt_started; timediff_t attempt_delay_ms; int last_attempt_ai_family; uint32_t max_concurrent; - uint8_t transport; + uint8_t transport_peer; + uint8_t tunnel_transport; }; static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, @@ -269,7 +299,9 @@ static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, a->inconclusive = FALSE; a->cf = NULL; - result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport); + result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer, + cf->conn, &a->addr, + a->tunnel_peer, a->tunnel_transport); if(!result) { bool dummy; /* the new filter might have sub-filters */ @@ -282,11 +314,9 @@ static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, return result; } -static void cf_ip_ballers_clear(struct Curl_cfilter *cf, - struct Curl_easy *data, +static void cf_ip_ballers_clear(struct Curl_easy *data, struct cf_ip_ballers *bs) { - (void)cf; while(bs->running) { struct cf_ip_attempt *a = bs->running; bs->running = a->next; @@ -294,35 +324,36 @@ static void cf_ip_ballers_clear(struct Curl_cfilter *cf, } cf_ip_attempt_free(bs->winner, data); bs->winner = NULL; + Curl_peer_unlink(&bs->origin); + Curl_peer_unlink(&bs->peer); + Curl_peer_unlink(&bs->tunnel_peer); } static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, - struct Curl_cfilter *cf, - cf_ip_connect_create *cf_create, - uint8_t transport, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, timediff_t attempt_delay_ms, uint32_t max_concurrent) { memset(bs, 0, sizeof(*bs)); - bs->cf_create = cf_create; - bs->transport = transport; + bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer); + if(!bs->cf_create) { + failf(data, "unsupported transport type %u%s", + transport_peer, tunnel_peer ? " to proxy" : ""); + return CURLE_UNSUPPORTED_PROTOCOL; + } + Curl_peer_link(&bs->origin, origin); + Curl_peer_link(&bs->peer, peer); + bs->transport_peer = transport_peer; + Curl_peer_link(&bs->tunnel_peer, tunnel_peer); + bs->tunnel_transport = tunnel_transport; bs->attempt_delay_ms = attempt_delay_ms; bs->max_concurrent = max_concurrent; bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */ - - if(transport == TRNSPRT_UNIX) { -#ifdef USE_UNIX_SOCKETS - cf_ai_iter_init(&bs->addr_iter, cf, AF_UNIX); -#else - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - else { /* TCP/UDP/QUIC */ -#ifdef USE_IPV6 - cf_ai_iter_init(&bs->ipv6_iter, cf, AF_INET6); -#endif - cf_ai_iter_init(&bs->addr_iter, cf, AF_INET); - } return CURLE_OK; } @@ -366,6 +397,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, bool do_more; timediff_t next_expire_ms; uint32_t inconclusive, ongoing; + const struct curltime *pnow = NULL; VERBOSE(int i); if(bs->winner) @@ -376,7 +408,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, /* check if a running baller connects now */ VERBOSE(i = -1); - for(panchor = &bs->running; *panchor; panchor = &((*panchor)->next)) { + for(panchor = &bs->running; *panchor; panchor = &(*panchor)->next) { VERBOSE(++i); a = *panchor; a->result = cf_ip_attempt_connect(a, data, connected); @@ -406,9 +438,8 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, "%u ongoing, %u inconclusive", ongoing, inconclusive); /* no attempt connected yet, start another one? */ + pnow = Curl_pgrs_now(data); if(!ongoing) { - if(!bs->started.tv_sec && !bs->started.tv_usec) - bs->started = *Curl_pgrs_now(data); do_more = TRUE; } else { @@ -418,7 +449,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data); #endif do_more = more_possible && - (curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started) >= + (curlx_ptimediff_ms(pnow, &bs->last_attempt_started) >= bs->attempt_delay_ms); if(do_more) CURL_TRC_CF(data, cf, "happy eyeballs timeout expired, " @@ -458,15 +489,17 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, if(bs->max_concurrent) cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1); - result = Curl_socket_addr_from_ai(&addr, ai, bs->transport); + result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_peer); if(result) goto out; - result = cf_ip_attempt_new(&a, cf, data, &addr, ai_family, - bs->transport, bs->cf_create); + result = cf_ip_attempt_new(&a, data, cf, bs->origin, bs->peer, + bs->transport_peer, &addr, ai_family, + bs->tunnel_peer, bs->tunnel_transport, + bs->cf_create); CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d", bs->running ? "next" : "first", - (ai_family == AF_INET) ? "4" : "6", result); + (ai_family == AF_INET) ? "4" : "6", (int)result); if(result) goto out; DEBUGASSERT(a); @@ -474,9 +507,9 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, /* append to running list */ panchor = &bs->running; while(*panchor) - panchor = &((*panchor)->next); + panchor = &(*panchor)->next; *panchor = a; - bs->last_attempt_started = *Curl_pgrs_now(data); + bs->last_attempt_started = *pnow; bs->last_attempt_ai_family = ai_family; /* and run everything again */ goto evaluate; @@ -485,7 +518,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, /* tried all addresses, no success but some where inconclusive. * Let's restart the inconclusive ones. */ timediff_t since_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started); + curlx_ptimediff_ms(pnow, &bs->last_attempt_started); timediff_t delay_ms = bs->attempt_delay_ms - since_ms; if(delay_ms <= 0) { CURL_TRC_CF(data, cf, "all attempts inconclusive, restarting one"); @@ -495,10 +528,10 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, if(!a->inconclusive) continue; result = cf_ip_attempt_restart(a, cf, data); - CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, result); + CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, (int)result); if(result) /* serious failure */ goto out; - bs->last_attempt_started = *Curl_pgrs_now(data); + bs->last_attempt_started = *pnow; goto evaluate; } DEBUGASSERT(0); /* should not come here */ @@ -507,7 +540,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, /* let's wait some more before restarting */ infof(data, "connect attempts inconclusive, retrying " "in %" FMT_TIMEDIFF_T "ms", delay_ms); - Curl_expire(data, delay_ms, EXPIRE_HAPPY_EYEBALLS); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, delay_ms, pnow); } /* attempt timeout for restart has not expired yet */ goto out; @@ -518,7 +551,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, result = CURLE_COULDNT_CONNECT; VERBOSE(i = 0); for(a = bs->running; a; a = a->next) { - CURL_TRC_CF(data, cf, "baller %d: result=%d", i, a->result); + CURL_TRC_CF(data, cf, "baller %d: result=%d", i, (int)a->result); if(a->result) result = a->result; } @@ -530,11 +563,11 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, bool more_possible; /* when do we need to be called again? */ - next_expire_ms = Curl_timeleft_ms(data); + pnow = Curl_pgrs_now(data); + next_expire_ms = Curl_timeleft_now_ms(data, pnow); if(next_expire_ms < 0) { failf(data, "Connection timeout after %" FMT_OFF_T " ms", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle)); + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); return CURLE_OPERATION_TIMEDOUT; } @@ -545,8 +578,8 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, #endif if(more_possible) { timediff_t expire_ms, elapsed_ms; - elapsed_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started); + + elapsed_ms = curlx_ptimediff_ms(pnow, &bs->last_attempt_started); expire_ms = CURLMAX(bs->attempt_delay_ms - elapsed_ms, 0); next_expire_ms = CURLMIN(next_expire_ms, expire_ms); if(next_expire_ms <= 0) { @@ -555,7 +588,7 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, } CURL_TRC_CF(data, cf, "next HAPPY_EYEBALLS timeout in %" FMT_TIMEDIFF_T "ms", next_expire_ms); - Curl_expire(data, next_expire_ms, EXPIRE_HAPPY_EYEBALLS); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, next_expire_ms, pnow); } } return result; @@ -611,24 +644,6 @@ static bool cf_ip_ballers_pending(struct cf_ip_ballers *bs, return FALSE; } -static struct curltime cf_ip_ballers_max_time(struct cf_ip_ballers *bs, - struct Curl_easy *data, - int query) -{ - struct curltime t, tmax; - struct cf_ip_attempt *a; - - memset(&tmax, 0, sizeof(tmax)); - for(a = bs->running; a; a = a->next) { - memset(&t, 0, sizeof(t)); - if(a->cf && !a->cf->cft->query(a->cf, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0) - tmax = t; - } - } - return tmax; -} - static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs, struct Curl_easy *data) { @@ -652,11 +667,9 @@ typedef enum { } cf_connect_state; struct cf_ip_happy_ctx { - uint8_t transport; cf_ip_connect_create *cf_create; cf_connect_state state; struct cf_ip_ballers ballers; - struct curltime started; BIT(dns_resolved); }; @@ -674,42 +687,40 @@ static CURLcode is_connected(struct Curl_cfilter *cf, if(!result) return CURLE_OK; else { - const char *hostname, *proxy_name = NULL; + struct Curl_peer *peer = NULL, *proxy_peer = NULL; char viamsg[160]; + + peer = Curl_conn_get_first_peer(conn, cf->sockindex); + if(!conn->origin || !peer) + return CURLE_FAILED_INIT; + #ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) - proxy_name = conn->socks_proxy.host.name; - else if(conn->bits.httpproxy) - proxy_name = conn->http_proxy.host.name; + if(conn->socks_proxy.peer) + proxy_peer = conn->socks_proxy.peer; + else if(conn->http_proxy.peer) + proxy_peer = conn->http_proxy.peer; #endif - hostname = conn->bits.conn_to_host ? conn->conn_to_host.name : - conn->host.name; + viamsg[0] = 0; + if(!Curl_peer_equal(peer, conn->origin) && + !Curl_peer_equal(peer, proxy_peer)) { #ifdef USE_UNIX_SOCKETS - if(conn->unix_domain_socket) - curl_msnprintf(viamsg, sizeof(viamsg), "over %s", - conn->unix_domain_socket); - else -#endif - { - uint16_t port; - if(cf->sockindex == SECONDARYSOCKET) - port = conn->secondary_port; - else if(cf->conn->bits.conn_to_port) - port = conn->conn_to_port; + if(peer->unix_socket) + curl_msnprintf(viamsg, sizeof(viamsg), " over unix://%s", + peer->hostname); else - port = conn->remote_port; - curl_msnprintf(viamsg, sizeof(viamsg), "port %d", port); +#endif + curl_msnprintf(viamsg, sizeof(viamsg), " via %s:%u", + peer->hostname, peer->port); } - failf(data, "Failed to connect to %s %s %s%s%safter " + failf(data, "Failed to connect to %s:%u%s %s%s%safter " "%" FMT_TIMEDIFF_T " ms: %s", - hostname, viamsg, - proxy_name ? "via " : "", - proxy_name ? proxy_name : "", - proxy_name ? " " : "", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + conn->origin->hostname, conn->origin->port, viamsg, + proxy_peer ? "over proxy " : "", + proxy_peer ? proxy_peer->hostname : "", + proxy_peer ? " " : "", + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), curl_easy_strerror(result)); #ifdef SOCKETIMEDOUT @@ -734,28 +745,40 @@ static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - CURL_TRC_CF(data, cf, "init ip ballers for transport %u", ctx->transport); - ctx->started = *Curl_pgrs_now(data); - return cf_ip_ballers_init(&ctx->ballers, cf, - ctx->cf_create, ctx->transport, - data->set.happy_eyeballs_timeout, - IP_HE_MAX_CONCURRENT_ATTEMPTS); + if(ctx->ballers.transport_peer == TRNSPRT_UNIX) { +#ifdef USE_UNIX_SOCKETS + cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_UNIX); +#else + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + else { /* TCP/UDP/QUIC */ +#ifdef USE_IPV6 + cf_ai_iter_init(&ctx->ballers.ipv6_iter, cf, ctx->ballers.peer, AF_INET6); +#endif + cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_INET); + } + + CURL_TRC_CF(data, cf, "init ip ballers for transport %u", + ctx->ballers.transport_peer); + return CURLE_OK; } -static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf, +static void cf_ip_happy_ctx_clear(struct cf_ip_happy_ctx *ctx, struct Curl_easy *data) { - struct cf_ip_happy_ctx *ctx = cf->ctx; - DEBUGASSERT(ctx); - DEBUGASSERT(data); - cf_ip_ballers_clear(cf, data, &ctx->ballers); + if(ctx) + cf_ip_ballers_clear(data, &ctx->ballers); } -static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx) +static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx, + struct Curl_easy *data) { - if(ctx) + if(ctx) { + cf_ip_happy_ctx_clear(ctx, data); curlx_free(ctx); + } } static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf, @@ -772,7 +795,7 @@ static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf, } result = cf_ip_ballers_shutdown(&ctx->ballers, data, done); - CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", (int)result, *done); return result; } @@ -785,7 +808,8 @@ static CURLcode cf_ip_happy_adjust_pollset(struct Curl_cfilter *cf, if(!cf->connected) { result = cf_ip_ballers_pollset(&ctx->ballers, data, ps); - CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", result, ps->n); + CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", (int)result, + ps->n); } return result; } @@ -810,7 +834,8 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->dns_resolved) { - result = Curl_conn_dns_result(cf->conn, cf->sockindex); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, + ctx->ballers.peer); if(!result) ctx->dns_resolved = TRUE; else if(result == CURLE_AGAIN) { @@ -841,10 +866,6 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, cf->connected = TRUE; cf->next = ctx->ballers.winner->cf; ctx->ballers.winner->cf = NULL; - cf_ip_happy_ctx_clear(cf, data); - Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS); - /* whatever errors where reported by ballers, clear our errorbuf */ - Curl_reset_fail(data); if(cf->conn->scheme->protocol & PROTO_FAMILY_SSH) Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */ @@ -853,13 +874,16 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, struct ip_quadruple ipquad; bool is_ipv6; if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) { - const char *host; - Curl_conn_get_current_host(data, cf->sockindex, &host, NULL); CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u", - host, ipquad.remote_ip, ipquad.remote_port); + ctx->ballers.peer->hostname, + ipquad.remote_ip, ipquad.remote_port); } } #endif + cf_ip_happy_ctx_clear(ctx, data); + Curl_expire_clear(data, EXPIRE_HAPPY_EYEBALLS); + /* whatever errors were reported by ballers, clear our errorbuf */ + Curl_reset_fail(data); data->info.numconnects++; /* to track the # of connections made */ } break; @@ -871,22 +895,6 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, return result; } -static void cf_ip_happy_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ip_happy_ctx *ctx = cf->ctx; - - CURL_TRC_CF(data, cf, "close"); - cf_ip_happy_ctx_clear(cf, data); - cf->connected = FALSE; - ctx->state = SCFST_INIT; - - if(cf->next) { - cf->next->cft->do_close(cf->next, data); - Curl_conn_cf_discard_chain(&cf->next, data); - } -} - static bool cf_ip_happy_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -911,18 +919,6 @@ static CURLcode cf_ip_happy_query(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1); return CURLE_OK; } - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - *when = cf_ip_ballers_max_time(&ctx->ballers, data, - CF_QUERY_TIMER_CONNECT); - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - *when = cf_ip_ballers_max_time(&ctx->ballers, data, - CF_QUERY_TIMER_APPCONNECT); - return CURLE_OK; - } default: break; } @@ -940,8 +936,8 @@ static void cf_ip_happy_destroy(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "destroy"); if(ctx) { - cf_ip_happy_ctx_clear(cf, data); - cf_ip_happy_ctx_destroy(ctx); + cf_ip_happy_ctx_clear(ctx, data); + cf_ip_happy_ctx_destroy(ctx, data); } } @@ -951,7 +947,6 @@ struct Curl_cftype Curl_cft_ip_happy = { CURL_LOG_LVL_NONE, cf_ip_happy_destroy, cf_ip_happy_connect, - cf_ip_happy_close, cf_ip_happy_shutdown, cf_ip_happy_adjust_pollset, cf_ip_happy_data_pending, @@ -963,21 +958,14 @@ struct Curl_cftype Curl_cft_ip_happy = { cf_ip_happy_query, }; -/** - * Create an IP happy eyeball connection filter that uses the, once resolved, - * address information to connect on ip families based on connection - * configuration. - * @param pcf output, the created cfilter - * @param data easy handle used in creation - * @param conn connection the filter is created for - * @param cf_create method to create the sub-filters performing the - * actual connects. - */ static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, - cf_ip_connect_create *cf_create, - uint8_t transport) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_ip_happy_ctx *ctx = NULL; CURLcode result; @@ -990,35 +978,39 @@ static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->transport = transport; - ctx->cf_create = cf_create; + result = cf_ip_ballers_init(&ctx->ballers, data, + origin, peer, transport_peer, + tunnel_peer, tunnel_transport, + data->set.happy_eyeballs_timeout, + IP_HE_MAX_CONCURRENT_ATTEMPTS); + if(result) + goto out; result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx); out: if(result) { curlx_safefree(*pcf); - cf_ip_happy_ctx_destroy(ctx); + cf_ip_happy_ctx_destroy(ctx, data); } return result; } -CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t transport) +CURLcode Curl_cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - cf_ip_connect_create *cf_create; struct Curl_cfilter *cf; CURLcode result; /* Need to be first */ DEBUGASSERT(cf_at); - cf_create = get_cf_create(transport); - if(!cf_create) { - CURL_TRC_CF(data, cf_at, "unsupported transport type %u", transport); - return CURLE_UNSUPPORTED_PROTOCOL; - } - result = cf_ip_happy_create(&cf, data, cf_at->conn, cf_create, transport); + result = cf_ip_happy_create(&cf, data, origin, peer, transport_peer, + cf_at->conn, tunnel_peer, tunnel_transport); if(result) return result; diff --git a/lib/cf-ip-happy.h b/lib/cf-ip-happy.h index 547ee4b4ac90..88de0c32454b 100644 --- a/lib/cf-ip-happy.h +++ b/lib/cf-ip-happy.h @@ -29,28 +29,44 @@ struct connectdata; struct Curl_addrinfo; struct Curl_cfilter; struct Curl_easy; +struct Curl_peer; struct Curl_sockaddr_ex; /** - * Create a cfilter for making an "ip" connection to the - * given address, using parameters from `conn`. The "ip" connection - * can be a TCP socket, a UDP socket or even a QUIC connection. + * Create a cfilter to connect to `origin` via an optional `peer` + * using `transport_peer` and `addr`. + * With a `tunnel_peer` present, the filter will be used to proxy tunnel + * to it and the tunnel will use `tunnel_transport`. + * `pcf`: the filter created on success + * `data`: the transfer initiating the connect + * `conn`: the connection that gets connected * - * It MUST use only the supplied `ai` for its connection attempt. - * - * Such a filter may be used in "happy eyeball" scenarios, and its - * `connect` implementation needs to support non-blocking. Once connected, + * The filter is used in "happy eyeball" scenarios. Once connected, * it MAY be installed in the connection filter chain to serve transfers. */ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); -CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t transport); +/** + * Create an IP happy eyeball connection filter that connects to `origin` + * via an optional `peer` using `transport_peer`. + * With a `tunnel_peer` present, the filter will be used to proxy tunnel + * to it and the tunnel will use `tunnel_transport`. + */ +CURLcode Curl_cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_ip_happy; diff --git a/lib/cf-recvbuf.c b/lib/cf-recvbuf.c new file mode 100644 index 000000000000..55b01a26f5c8 --- /dev/null +++ b/lib/cf-recvbuf.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 CURL_DISABLE_WEBSOCKETS +/* only used for this protocol, so far */ + +#include "urldata.h" +#include "bufq.h" +#include "cfilters.h" +#include "cf-recvbuf.h" +#include "curl_trc.h" + +#define CURL_CF_RECVBUF_CHUNK (16 * 1024) + +struct cf_recvbuf_ctx { + struct bufq recvbuf; +}; + +static void cf_recvbuf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + (void)data; + if(ctx) { + Curl_bufq_free(&ctx->recvbuf); + curlx_free(ctx); + } +} + +static CURLcode cf_recvbuf_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + size_t *pnread) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + + if(!Curl_bufq_is_empty(&ctx->recvbuf)) { + return Curl_bufq_cread(&ctx->recvbuf, buf, len, pnread); + } + + if(cf->next) + return cf->next->cft->do_recv(cf->next, data, buf, len, pnread); + *pnread = 0; + return CURLE_RECV_ERROR; +} + +static bool cf_recvbuf_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + + if(!Curl_bufq_is_empty(&ctx->recvbuf)) + return TRUE; + + return cf->next ? + cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +struct Curl_cftype Curl_cft_recvbuf = { + "RECVBUF", + 0, + CURL_LOG_LVL_NONE, + cf_recvbuf_destroy, + Curl_cf_def_connect, + Curl_cf_def_shutdown, + Curl_cf_def_adjust_pollset, + cf_recvbuf_data_pending, + Curl_cf_def_send, + cf_recvbuf_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_recvbuf_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const uint8_t *buf, size_t blen) +{ + struct Curl_cfilter *cf = NULL; + struct cf_recvbuf_ctx *ctx; + CURLcode result = CURLE_OK; + size_t nwritten = 0; + + (void)data; + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_bufq_init2(&ctx->recvbuf, CURL_CF_RECVBUF_CHUNK, + (blen / CURL_CF_RECVBUF_CHUNK) + 1, + (BUFQ_OPT_SOFT_LIMIT | BUFQ_OPT_NO_SPARES)); + result = Curl_bufq_write(&ctx->recvbuf, buf, blen, &nwritten); + if(result) + goto out; + if(nwritten != blen) { + result = CURLE_FAILED_INIT; + goto out; + } + + result = Curl_cf_create(&cf, &Curl_cft_recvbuf, ctx); + if(result) + goto out; + ctx = NULL; + +out: + *pcf = result ? NULL : cf; + if(ctx) { + Curl_bufq_free(&ctx->recvbuf); + curlx_free(ctx); + } + return result; +} + +CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + const uint8_t *buf, size_t blen) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_recvbuf_create(&cf, data, buf, blen); + if(result) + goto out; + + cf->connected = Curl_conn_is_connected(conn, sockindex); + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +#endif /* !CURL_DISABLE_WEBSOCKETS */ diff --git a/lib/cf-recvbuf.h b/lib/cf-recvbuf.h new file mode 100644 index 000000000000..e126c3c6b679 --- /dev/null +++ b/lib/cf-recvbuf.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_CF_RECVBUF_H +#define HEADER_CURL_CF_RECVBUF_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" + +#ifndef CURL_DISABLE_WEBSOCKETS +/* only used for this protocol, so far */ + +CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + const uint8_t *buf, size_t blen); + +extern struct Curl_cftype Curl_cft_recvbuf; + +#endif /* !CURL_DISABLE_WEBSOCKETS */ + +#endif /* HEADER_CURL_CF_RECVBUF_H */ diff --git a/lib/cf-setup.c b/lib/cf-setup.c new file mode 100644 index 000000000000..2d77b1cb6d94 --- /dev/null +++ b/lib/cf-setup.c @@ -0,0 +1,479 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "cf-haproxy.h" +#include "cf-ip-happy.h" +#include "cf-setup.h" +#include "curl_trc.h" +#include "connect.h" +#include "http_proxy.h" +#include "socks.h" +#include "vquic/cf-capsule.h" +#include "vquic/vquic.h" +#include "vtls/vtls.h" + + +typedef enum { + CF_SETUP_INIT, + CF_SETUP_CNNCT_EYEBALLS, + CF_SETUP_CNNCT_SOCKS, + CF_SETUP_CNNCT_HTTP_PROXY, + CF_SETUP_CNNCT_HAPROXY, + CF_SETUP_CNNCT_SSL, + CF_SETUP_DONE +} cf_setup_state; + +struct cf_setup_ctx { + cf_setup_state state; + int ssl_mode; + uint8_t transport; + uint8_t retry_count; +}; + +#ifndef CURL_DISABLE_PROXY + +static CURLcode cf_setup_add_haproxy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { + if(data->set.haproxyprotocol) { + if(ctx->transport == TRNSPRT_QUIC) { + failf(data, "haproxy protocol does not support QUIC"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = Curl_cf_haproxy_insert_after(cf, data); + if(result) { + CURL_TRC_CF(data, cf, "adding HAPROXY filter failed -> %d", + (int)result); + return result; + } + CURL_TRC_CF(data, cf, "added HAPROXY filter"); + } + ctx->state = CF_SETUP_CNNCT_HAPROXY; + } + return result; +} + +static CURLcode cf_setup_add_socks(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->socks_proxy.peer) { + /* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/ + struct Curl_peer *second_peer; + + if(cf->conn->http_proxy.peer) + second_peer = cf->conn->http_proxy.peer; + else + second_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); + if(!second_peer) + return CURLE_FAILED_INIT; + + result = Curl_cf_socks_proxy_insert_after( + cf, data, second_peer, cf->conn->ip_version, + cf->conn->socks_proxy.proxytype, + cf->conn->socks_proxy.creds); + if(result) { + CURL_TRC_CF(data, cf, "adding SOCKS filter failed -> %d", (int)result); + return result; + } + + CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u", + second_peer->hostname, second_peer->port); + ctx->state = CF_SETUP_CNNCT_SOCKS; + } + return result; +} + +#ifndef CURL_DISABLE_HTTP +static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && + cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { + struct Curl_peer *peer = cf->conn->http_proxy.peer; + struct Curl_peer *tunnel_peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + +#ifdef USE_SSL + if(CURL_PROXY_IS_HTTPS(cf->conn->http_proxy.proxytype) && + !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { + result = Curl_cf_ssl_proxy_insert_after( + cf, data, cf->conn->http_proxy.peer); + if(result) { + CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d", + (int)result); + return result; + } + CURL_TRC_CF(data, cf, "added SSL filter for HTTP proxy"); + } +#endif /* USE_SSL */ + + result = Curl_cf_http_proxy_insert_after( + cf, data, peer, tunnel_peer, + ctx->transport, cf->conn->http_proxy.proxytype); + if(result) { + CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d", + (int)result); + return result; + } + CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter"); + ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; + } + return result; +} +#endif /* !CURL_DISABLE_HTTP */ +#endif /* CURL_DISABLE_PROXY */ + +/* Get the origin curl connects its socket to. + * Can be origin or the first proxy. */ +static struct Curl_peer *conn_get_first_origin(struct connectdata *conn, + int8_t sockindex) +{ +#ifndef CURL_DISABLE_PROXY + if(conn->socks_proxy.peer) + return conn->socks_proxy.peer; + if(conn->http_proxy.peer) + return conn->http_proxy.peer; +#endif + return (sockindex == SECONDARYSOCKET) ? conn->origin2 : conn->origin; +} + +static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { + /* What is the first hop we directly connect to and what transport + * do we use for it? Only on the first hop we can do Happy Eyeballs. + * first_origin and first_peer differ on --connect-to. */ + struct Curl_peer *first_origin = + conn_get_first_origin(cf->conn, cf->sockindex); + struct Curl_peer *first_peer = + Curl_conn_get_first_peer(cf->conn, cf->sockindex); + struct Curl_peer *tunnel_peer = NULL; + uint8_t first_transport = ctx->transport; + + if(!first_peer) + return CURLE_FAILED_INIT; + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + if(cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { + first_transport = + Curl_http_proxy_transport(cf->conn->http_proxy.proxytype); + tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); + if((first_transport == TRNSPRT_QUIC) && cf->conn->socks_proxy.peer) { + failf(data, "HTTP/3 proxy not possible via SOCKS"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + + result = Curl_cf_ip_happy_insert_after(cf, data, first_origin, first_peer, + first_transport, + tunnel_peer, ctx->transport); + if(result) { + CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", (int)result); + return result; + } + + if(tunnel_peer && (first_transport == TRNSPRT_QUIC)) { + CURL_TRC_CF(data, cf, "happy eyeballing to HTTP/3 proxy %s:%u", + first_peer->hostname, first_peer->port); + ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; + } + else { + CURL_TRC_CF(data, cf, "happy eyeballing to %s %s:%u", + tunnel_peer ? "proxy" : "origin", + first_peer->hostname, first_peer->port); + ctx->state = CF_SETUP_CNNCT_EYEBALLS; + } + } + return result; +} + +static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)data; /* not used in all builds */ + if(ctx->state < CF_SETUP_CNNCT_SSL) { +#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ + !defined(CURL_DISABLE_PROXY) + + /* Wanting QUIC with an HTTP tunneling filter, we now need to add + * the QUIC filter on top. Without tunneling, this has already + * happened in the Happy Eyeball filter. */ + if(ctx->transport == TRNSPRT_QUIC && + cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { + struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, cf->sockindex); + struct Curl_peer *peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + + result = Curl_cf_capsule_insert_after(cf, data); + if(result) { + CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d", + (int)result); + return result; + } + result = Curl_cf_quic_insert_after(cf, data, origin, peer); + if(result) { + CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", (int)result); + return result; + } + CURL_TRC_CF(data, cf, "added QUIC filter for origin"); + } + else +#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && CURL_DISABLE_PROXY */ +#ifdef USE_SSL + if((ctx->ssl_mode == CURL_CF_SSL_ENABLE || + (ctx->ssl_mode != CURL_CF_SSL_DISABLE && + cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */ + !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ + +#ifndef CURL_DISABLE_PROXY + if(cf->conn->bits.origin_is_proxy) { + result = Curl_cf_ssl_proxy_insert_after(cf, data, cf->conn->origin); + } + else +#endif + { + /* FTP is a bitch. Wherever we really connect to on the DATA + * (secondary) connection, many servers require TLS sessions reuse + * to prove they are talking to the same client. + * For the TLS session lookup to work, we need to instantiate + * the SSL filter with the same peers as FIRSTSOCKET. See #22225 + * Meaning: cf->sockindex does not matter here. */ + result = Curl_cf_ssl_insert_after(cf, data, + Curl_conn_get_origin(cf->conn, FIRSTSOCKET), + Curl_conn_get_destination(cf->conn, FIRSTSOCKET)); + } + if(result) { + CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d", + (int)result); + return result; + } + CURL_TRC_CF(data, cf, "added SSL filter for origin"); + } +#endif /* USE_SSL */ + ctx->state = CF_SETUP_CNNCT_SSL; + } + return result; +} + +static CURLcode cf_setup_connect_steps(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* connect current sub-chain */ +connect_sub_chain: + VERBOSE(Curl_conn_trc_filters(data, cf->sockindex, "cf_setup_connect")); + + if(cf->next && !cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, done); + if(result || !*done) + return result; + } + + result = cf_setup_add_ip_happy(cf, data); + if(result) + return result; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + +#ifndef CURL_DISABLE_PROXY + result = cf_setup_add_socks(cf, data); + if(result) + return result; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + +#ifndef CURL_DISABLE_HTTP + result = cf_setup_add_http_proxy(cf, data); + if(result) + return result; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; +#endif /* !CURL_DISABLE_HTTP */ + + result = cf_setup_add_haproxy(cf, data); + if(result) + return result; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; +#endif /* !CURL_DISABLE_PROXY */ + + result = cf_setup_add_origin_filters(cf, data); + if(result) + return result; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + + ctx->state = CF_SETUP_DONE; + cf->connected = TRUE; + *done = TRUE; + return CURLE_OK; +} + +static CURLcode cf_setup_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result; + + /* In some situations, a server/proxy may close the connection and + * we need to connect again (HTTP/1.x proxy auth, for example). + * We used to close the filters and reuse them for another attempt, + * however that complicates filter code and it is simpler to tear them + * all down and start over. */ +retry: + result = cf_setup_connect_steps(cf, data, done); + + if(result == CURLE_AGAIN) { + ++ctx->retry_count; + if(ctx->retry_count > 5) /* arbitrary limit, better just timeout? */ + return CURLE_COULDNT_CONNECT; + + CURL_TRC_CF(data, cf, "retrying connect, %d. time", ctx->retry_count); + Curl_conn_cf_discard_chain(&cf->next, data); + ctx->state = CF_SETUP_INIT; + goto retry; + } + return result; +} + +static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "destroy"); + curlx_safefree(ctx); +} + +struct Curl_cftype Curl_cft_setup = { + "SETUP", + CF_TYPE_SETUP, + CURL_LOG_LVL_NONE, + cf_setup_destroy, + cf_setup_connect, + Curl_cf_def_shutdown, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_setup_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + uint8_t transport, + int ssl_mode) +{ + struct Curl_cfilter *cf = NULL; + struct cf_setup_ctx *ctx; + CURLcode result = CURLE_OK; + + (void)data; + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->state = CF_SETUP_INIT; + ctx->ssl_mode = ssl_mode; + ctx->transport = transport; + + result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); + if(result) + goto out; + ctx = NULL; + +out: + *pcf = result ? NULL : cf; + if(ctx) { + curlx_free(ctx); + } + return result; +} + +CURLcode Curl_cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + uint8_t transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + uint8_t transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + return result; +} diff --git a/lib/cf-setup.h b/lib/cf-setup.h new file mode 100644 index 000000000000..71c6093fe52b --- /dev/null +++ b/lib/cf-setup.h @@ -0,0 +1,46 @@ +#ifndef HEADER_CURL_CF_SETUP_H +#define HEADER_CURL_CF_SETUP_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" + +struct Curl_dns_entry; +struct ip_quadruple; +struct Curl_peer; +struct Curl_str; + +CURLcode Curl_cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + uint8_t transport, + int ssl_mode); + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + uint8_t transport, + int ssl_mode); + +extern struct Curl_cftype Curl_cft_setup; + +#endif /* HEADER_CURL_CF_SETUP_H */ diff --git a/lib/cf-socket.c b/lib/cf-socket.c index b99bcdef5518..870a98ecbd5d 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -26,6 +26,9 @@ #ifdef HAVE_NETINET_IN_H #include /* may need it */ #endif +#ifdef HAVE_SYS_UN_H +#include /* for sockaddr_un */ +#endif #ifdef HAVE_LINUX_TCP_H #include #elif defined(HAVE_NETINET_TCP_H) @@ -34,6 +37,9 @@ #ifdef HAVE_NETINET_UDP_H #include #endif +#ifdef HAVE_NETINET_IP_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif @@ -44,16 +50,17 @@ #include #endif +#ifdef HAVE_IFADDRS_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif #ifdef __VMS #include #include #endif -#ifdef __DragonFly__ -/* Required for __DragonFly_version */ -#include -#endif - #include "urldata.h" #include "curl_trc.h" #include "if2ip.h" @@ -63,19 +70,84 @@ #include "curl_addrinfo.h" #include "select.h" #include "multiif.h" +#include "curlx/inet_ntop.h" #include "curlx/inet_pton.h" #include "progress.h" #include "conncache.h" #include "multihandle.h" #include "rand.h" +#include "sockaddr.h" #include "curlx/strdup.h" -#include "system_win32.h" #include "curlx/nonblock.h" #include "curlx/strcopy.h" #include "curlx/version_win32.h" #include "curlx/strerr.h" #include "curlx/strparse.h" +#ifdef _WIN32 +#include /* for TCP_INITIAL_RTO_PARAMETERS */ +#endif + +/* retrieves ip address and port from a sockaddr structure. note it calls + * curlx_inet_ntop() and returns CURLcode. + * @unittest 1607 + */ +UNITTEST CURLcode sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, uint16_t *port); +UNITTEST CURLcode sockaddr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, uint16_t *port) +{ + CURLcode result; + struct sockaddr_in *si = NULL; +#ifdef USE_IPV6 + struct sockaddr_in6 *si6 = NULL; +#endif +#ifdef USE_UNIX_SOCKETS + struct sockaddr_un *su = NULL; +#else + (void)salen; +#endif + + switch(sa->sa_family) { + case AF_INET: + si = (struct sockaddr_in *)(void *)sa; + result = curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, + MAX_IPADR_LEN); + if(!result) { + *port = ntohs(si->sin_port); + return result; + } + break; +#ifdef USE_IPV6 + case AF_INET6: + si6 = (struct sockaddr_in6 *)(void *)sa; + result = curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, + MAX_IPADR_LEN); + if(!result) { + *port = ntohs(si6->sin6_port); + return result; + } + break; +#endif +#ifdef USE_UNIX_SOCKETS + case AF_UNIX: + if(salen > (curl_socklen_t)sizeof(CURL_SA_FAMILY_T)) { + su = (struct sockaddr_un *)sa; + curl_msnprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); + } + else + addr[0] = 0; /* socket with no name */ + *port = 0; + return CURLE_OK; +#endif + default: + result = CURLE_UNSUPPORTED_PROTOCOL; + break; + } + addr[0] = '\0'; + *port = 0; + return result; +} static void tcpnodelay(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -86,8 +158,7 @@ static void tcpnodelay(struct Curl_cfilter *cf, int level = IPPROTO_TCP; VERBOSE(char buffer[STRERROR_LEN]); - if(setsockopt(sockfd, level, TCP_NODELAY, - (void *)&onoff, sizeof(onoff)) < 0) + if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, sizeof(onoff)) < 0) CURL_TRC_CF(data, cf, "Could not set TCP_NODELAY: %s", curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); #else @@ -97,8 +168,8 @@ static void tcpnodelay(struct Curl_cfilter *cf, #endif } -#if defined(USE_WINSOCK) || defined(TCP_KEEPIDLE) || \ - defined(TCP_KEEPALIVE) || defined(TCP_KEEPALIVE_THRESHOLD) || \ +#if defined(USE_WINSOCK) || defined(TCP_KEEPIDLE) || \ + defined(TCP_KEEPALIVE) || defined(TCP_KEEPALIVE_THRESHOLD) || \ defined(TCP_KEEPINTVL) || defined(TCP_KEEPALIVE_ABORT_THRESHOLD) #if defined(USE_WINSOCK) || \ (defined(__sun) && !defined(TCP_KEEPIDLE)) || \ @@ -132,14 +203,15 @@ static void tcpkeepalive(struct Curl_cfilter *cf, VERSION_GREATER_THAN_EQUAL)) { CURL_TRC_CF(data, cf, "Set TCP_KEEP* on fd=%" FMT_SOCKET_T, sockfd); optval = curlx_sltosi(data->set.tcp_keepidle); -/* Offered by mingw-w64 v12+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v12+, MS SDK 6.0A/VS2008+ */ #ifndef TCP_KEEPALIVE #define TCP_KEEPALIVE 3 #endif -/* Offered by mingw-w64 v12+. MS SDK ~10+/~VS2017+. */ +/* Offered by mingw-w64 v12+, MS SDK 10.0.15063.0/VS2017 15.1+ */ #ifndef TCP_KEEPCNT #define TCP_KEEPCNT 16 #endif +/* Offered by mingw-w64 v12+, MS SDK 10.0.16299.0/VS2017 15.4+ */ #ifndef TCP_KEEPIDLE #define TCP_KEEPIDLE TCP_KEEPALIVE #endif @@ -302,6 +374,49 @@ int Curl_sock_nosigpipe(curl_socket_t sockfd) } #endif /* USE_SO_NOSIGPIPE */ +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +static uint32_t get_scope_id(struct Curl_easy *data, + struct sockaddr_in6 *sa6) +{ + uint32_t scope_id = 0; + if(data->conn->scope_id) + return data->conn->scope_id; + /* NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) */ + scope_id = sa6->sin6_scope_id; + if(!scope_id && IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) { + /* The resolver did not set scope_id for this link-local address. + * Try to determine it from the system's network interfaces. + * Without a scope_id, connect() to a link-local address fails + * with EINVAL on Linux. + * NOTE: On multi-homed hosts with several interfaces having + * link-local addresses, this picks the first one found, which + * may not be the correct outgoing interface. */ +#if defined(HAVE_GETIFADDRS) && defined(HAVE_NET_IF_H) + struct ifaddrs *ifa, *ifa_list; + if(getifaddrs(&ifa_list) == 0) { + for(ifa = ifa_list; ifa; ifa = ifa->ifa_next) { + if(ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6 && + (ifa->ifa_flags & IFF_UP) && + !(ifa->ifa_flags & IFF_LOOPBACK)) { + struct sockaddr_in6 *s6 = (void *)ifa->ifa_addr; + if(IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr) && s6->sin6_scope_id) { + scope_id = s6->sin6_scope_id; + infof(data, + "determined scope_id=%lu for link-local address " + "from local interface", + (unsigned long)scope_id); + break; + } + } + } + freeifaddrs(ifa_list); + } +#endif /* HAVE_GETIFADDRS && HAVE_NET_IF_H */ + } + return scope_id; +} +#endif + static CURLcode socket_open(struct Curl_easy *data, struct Curl_sockaddr_ex *addr, curl_socket_t *sockfd) @@ -324,11 +439,12 @@ 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); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fopensocket); *sockfd = data->set.fopensocket(data->set.opensocket_client, CURLSOCKTYPE_IPCXN, (struct curl_sockaddr *)addr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } else { /* opensocket callback not set, so create the socket now */ @@ -371,9 +487,9 @@ static CURLcode socket_open(struct Curl_easy *data, #endif #if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(data->conn->scope_id && (addr->family == AF_INET6)) { + if(addr->family == AF_INET6) { struct sockaddr_in6 * const sa6 = (void *)&addr->curl_sa_addr; - sa6->sin6_scope_id = data->conn->scope_id; + sa6->sin6_scope_id = get_scope_id(data, sa6); } #endif return CURLE_OK; @@ -415,11 +531,12 @@ static int socket_close(struct Curl_easy *data, struct connectdata *conn, return 0; if(use_callback && conn && conn->fclosesocket) { + struct Curl_mapi_guard guard; int rc; Curl_multi_will_close(data, sock); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_closesocket); rc = conn->fclosesocket(conn->closesocket_client, sock); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); return rc; } @@ -511,8 +628,7 @@ CURLcode Curl_parse_interface(const char *input, ++host_part; *host = curlx_memdup0(host_part, len - (host_part - input)); if(!*host) { - curlx_free(*iface); - *iface = NULL; + curlx_safefree(*iface); return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -542,12 +658,12 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, "random" */ /* 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 *dev = CURL_EASY_STR(data, STRING_DEVICE); + const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); + const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); const char *iface = iface_input ? iface_input : dev; const char *host = host_input ? host_input : dev; - int error; + int sockerr; #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif @@ -610,9 +726,9 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, 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; + data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to interface '%s' with errno %d: %s", - iface, error, curlx_strerror(error, buffer, sizeof(buffer))); + iface, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); return CURLE_INTERFACE_FAILED; } break; @@ -624,7 +740,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, * 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", + infof(data, "Local Interface %s is ip %s using address family %d", iface, host, af); done = 1; break; @@ -650,7 +766,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, 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", + infof(data, "Name '%s' family %d resolved to '%s' family %d", host, af, myhost, h_af); Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */ if(af != h_af) { @@ -714,9 +830,9 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, generic resolve error. */ char buffer[STRERROR_LEN]; data->state.errorbuf = FALSE; - data->state.os_errno = error = SOCKERRNO; + data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to '%s' with errno %d: %s", host, - error, curlx_strerror(error, buffer, sizeof(buffer))); + sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); return CURLE_INTERFACE_FAILED; } } @@ -765,9 +881,9 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, } { char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; + data->state.os_errno = sockerr = SOCKERRNO; failf(data, "bind failed with errno %d: %s", - error, curlx_strerror(error, buffer, sizeof(buffer))); + sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); } return CURLE_INTERFACE_FAILED; @@ -777,12 +893,12 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* * verifyconnect() returns TRUE if the connect really has happened. */ -static bool verifyconnect(curl_socket_t sockfd, int *error) +static bool verifyconnect(curl_socket_t sockfd, int *psockerr) { bool rc = TRUE; #ifdef SO_ERROR - int err = 0; - curl_socklen_t errSize = sizeof(err); + int sockerr = 0; + curl_socklen_t errSize = sizeof(sockerr); #ifdef _WIN32 /* @@ -803,27 +919,27 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) SleepEx(0, FALSE); #endif - if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) - err = SOCKERRNO; + if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &errSize)) + sockerr = SOCKERRNO; #if defined(EBADIOCTL) && defined(__minix) /* Minix 3.1.x does not support getsockopt on UDP sockets */ - if(EBADIOCTL == err) { + if(EBADIOCTL == sockerr) { SET_SOCKERRNO(0); - err = 0; + sockerr = 0; } #endif - if((err == 0) || (SOCKEISCONN == err)) + if((sockerr == 0) || (SOCKEISCONN == sockerr)) /* we are connected, awesome! */ rc = TRUE; else /* This was not a successful connect */ rc = FALSE; - if(error) - *error = err; + if(psockerr) + *psockerr = sockerr; #else (void)sockfd; - if(error) - *error = SOCKERRNO; + if(psockerr) + *psockerr = SOCKERRNO; #endif return rc; } @@ -832,38 +948,25 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) * Determine the curl code for a socket connect() == -1 with errno. */ static CURLcode socket_connect_result(struct Curl_easy *data, - const char *ipaddress, int error) + const char *ipaddress, int sockerr) { - switch(error) { - case SOCKEINPROGRESS: - case SOCKEWOULDBLOCK: -#ifdef EAGAIN -#if (EAGAIN) != (SOCKEWOULDBLOCK) - /* On some platforms EAGAIN and EWOULDBLOCK are the - * same value, and on others they are different, hence - * the odd #if - */ - case EAGAIN: -#endif -#endif + if(sockerr == SOCKEINPROGRESS || SOCK_EAGAIN(sockerr)) return CURLE_OK; - default: - /* unknown error, fallthrough and try another address! */ - { - VERBOSE(char buffer[STRERROR_LEN]); - infof(data, "Immediate connect fail for %s: %s", ipaddress, - curlx_strerror(error, buffer, sizeof(buffer))); - NOVERBOSE((void)ipaddress); - } - data->state.os_errno = error; - /* connect failed */ - return CURLE_COULDNT_CONNECT; + /* unknown error, fallthrough and try another address! */ + { + VERBOSE(char buffer[STRERROR_LEN]); + infof(data, "Immediate connect fail for %s: %s", ipaddress, + curlx_strerror(sockerr, buffer, sizeof(buffer))); + NOVERBOSE((void)ipaddress); } + data->state.os_errno = sockerr; + /* connect failed */ + return CURLE_COULDNT_CONNECT; } struct cf_socket_ctx { - uint8_t transport; + struct Curl_peer *peer; struct Curl_sockaddr_ex addr; /* address to connect to */ curl_socket_t sock; /* current attempt socket */ struct ip_quadruple ip; /* The IP quadruple 2x(addr+port) */ @@ -874,25 +977,29 @@ struct cf_socket_ctx { 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 */ + int sockerr; /* socket error 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 + uint8_t transport; 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(stats_reported); }; static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, + struct Curl_peer *peer, struct Curl_sockaddr_ex *addr, uint8_t transport) { memset(ctx, 0, sizeof(*ctx)); + Curl_peer_link(&ctx->peer, peer); ctx->sock = CURL_SOCKET_BAD; ctx->transport = transport; ctx->addr = *addr; @@ -929,22 +1036,12 @@ static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, return CURLE_OK; } -static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_socket_ctx_free(struct cf_socket_ctx *ctx) { - struct cf_socket_ctx *ctx = cf->ctx; - - if(ctx && ctx->sock != CURL_SOCKET_BAD) { - 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; - ctx->active = FALSE; - memset(&ctx->started_at, 0, sizeof(ctx->started_at)); - memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); + if(ctx) { + Curl_peer_unlink(&ctx->peer); + curlx_free(ctx); } - - cf->connected = FALSE; } static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, @@ -973,10 +1070,16 @@ 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); CURL_TRC_CF(data, cf, "destroy"); - curlx_free(ctx); - cf->ctx = NULL; + if(ctx) { + if(ctx->sock != CURL_SOCKET_BAD) { + 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); + } + cf_socket_ctx_free(ctx); + } } static void set_local_ip(struct Curl_cfilter *cf, @@ -992,18 +1095,19 @@ static void set_local_ip(struct Curl_cfilter *cf, /* TFTP does not connect, so it cannot get the IP like this */ struct Curl_sockaddr_storage ssloc; curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); - VERBOSE(char buffer[STRERROR_LEN]); memset(&ssloc, 0, sizeof(ssloc)); if(getsockname(ctx->sock, (struct sockaddr *)&ssloc, &slen)) { - VERBOSE(int error = SOCKERRNO); + VERBOSE(char buffer[STRERROR_LEN]); + VERBOSE(int sockerr = SOCKERRNO); infof(data, "getsockname() failed with errno %d: %s", - error, curlx_strerror(error, buffer, sizeof(buffer))); + sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); } - else if(!Curl_addr2string((struct sockaddr *)&ssloc, slen, - ctx->ip.local_ip, &ctx->ip.local_port)) { - infof(data, "ssloc inet_ntop() failed with errno %d: %s", - errno, curlx_strerror(errno, buffer, sizeof(buffer))); + else { + CURLcode result = sockaddr2string((struct sockaddr *)&ssloc, slen, + ctx->ip.local_ip, &ctx->ip.local_port); + if(result) + infof(data, "ssloc inet_ntop() failed with %d", (int)result); } } #else @@ -1015,18 +1119,17 @@ static CURLcode set_remote_ip(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result; /* store remote address and port used in this connection attempt */ ctx->ip.transport = ctx->transport; - 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; + result = sockaddr2string(&ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen, + ctx->ip.remote_ip, &ctx->ip.remote_port); + if(result) { + ctx->sockerr = SOCKEAFNOSUPPORT; /* malformed address or bug in inet_ntop, try next address */ - failf(data, "curl_sa_addr inet_ntop() failed with errno %d: %s", - errno, curlx_strerror(errno, buffer, sizeof(buffer))); + failf(data, "curl_sa_addr inet_ntop() failed with %d", (int)result); return CURLE_FAILED_INIT; } return CURLE_OK; @@ -1039,12 +1142,56 @@ static int cf_socktype(int x) #ifdef SOCK_CLOEXEC x &= ~SOCK_CLOEXEC; #endif -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK x &= ~SOCK_NONBLOCK; #endif return x; } +#ifdef _WIN32 +/* Offered by mingw-w64 v10+, MS SDK 8.0/~VS2012+ */ +#ifndef SIO_TCP_INITIAL_RTO +#define SIO_TCP_INITIAL_RTO _WSAIOW(IOC_VENDOR, 17) +#define TCP_INITIAL_RTO_DEFAULT_RTT 0 + +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct _TCP_INITIAL_RTO_PARAMETERS { + USHORT Rtt; + UCHAR MaxSynRetransmissions; +} TCP_INITIAL_RTO_PARAMETERS; +#endif /* SIO_TCP_INITIAL_RTO */ + +#ifndef TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS +#define TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS 0xFE /* -2 */ +#endif + +static bool targets_localhost(struct cf_socket_ctx *ctx) +{ + return (((ctx->addr.family == AF_INET) && + !strcmp(ctx->ip.remote_ip, "127.0.0.1")) || + ((ctx->addr.family == AF_INET6) && + !strcmp(ctx->ip.remote_ip, "::1"))); +} + +/* disable TCP SYN retransmissions for localhost connection on Windows to + detect problems faster */ +static void tcplocalhost(struct Curl_cfilter *cf, + curl_socket_t sockfd) +{ + if(targets_localhost(cf->ctx)) { + TCP_INITIAL_RTO_PARAMETERS rto; + DWORD bytes = 0; + memset(&rto, 0, sizeof(rto)); + rto.Rtt = TCP_INITIAL_RTO_DEFAULT_RTT; + rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + (void)WSAIoctl(sockfd, SIO_TCP_INITIAL_RTO, &rto, sizeof(rto), + NULL, 0, &bytes, NULL, NULL); + } +} +#else +#define tcplocalhost(x, y) +#endif /* _WIN32 */ + static CURLcode cf_socket_open(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1056,16 +1203,16 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); ctx->started_at = *Curl_pgrs_now(data); -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_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 + * because we would not know how socktype 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 +#ifdef CURL_USE_SOCK_NONBLOCK /* Restore the socktype after the socket is created. */ if(!data->set.fopensocket) ctx->addr.socktype &= ~SOCK_NONBLOCK; @@ -1091,7 +1238,20 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); #endif - infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + { + struct sockaddr_in6 *sa6 = (void *)&ctx->addr.curl_sa_addr; + if(sa6->sin6_scope_id) + infof(data, " Trying [%s]:%d scope_id=%lu...", + ctx->ip.remote_ip, ctx->ip.remote_port, + (unsigned long)sa6->sin6_scope_id); + else +#endif + infof(data, " Trying [%s]:%d...", + ctx->ip.remote_ip, ctx->ip.remote_port); +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + } +#endif } else #endif @@ -1105,19 +1265,24 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, is_tcp = (ctx->addr.family == AF_INET) && cf_socktype(ctx->addr.socktype) == SOCK_STREAM; #endif - if(is_tcp && data->set.tcp_nodelay) - tcpnodelay(cf, data, ctx->sock); + if(is_tcp) { + if(data->set.tcp_nodelay) + tcpnodelay(cf, data, ctx->sock); + + if(data->set.tcp_keepalive) + tcpkeepalive(cf, data, ctx->sock); - if(is_tcp && data->set.tcp_keepalive) - tcpkeepalive(cf, data, ctx->sock); + tcplocalhost(cf, ctx->sock); + } if(data->set.fsockopt) { /* activate callback for setting socket options */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fsockopt); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_IPCXN); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(error == CURL_SOCKOPT_ALREADY_CONNECTED) isconnected = TRUE; @@ -1148,13 +1313,13 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, } #endif -#ifndef SOCK_NONBLOCK +#ifndef CURL_USE_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; + ctx->sockerr = SOCKERRNO; goto out; } #else @@ -1164,7 +1329,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, error = curlx_nonblock(ctx->sock, TRUE); if(error < 0) { result = CURLE_UNSUPPORTED_PROTOCOL; - ctx->error = SOCKERRNO; + ctx->sockerr = SOCKERRNO; goto out; } } @@ -1183,7 +1348,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, cf->connected = TRUE; } CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" FMT_SOCKET_T, - result, ctx->sock); + (int)result, ctx->sock); return result; } @@ -1257,7 +1422,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, *done = FALSE; /* a negative world view is best */ if(ctx->sock == CURL_SOCKET_BAD) { - int error; + int sockerr; result = cf_socket_open(cf, data); if(result) @@ -1270,12 +1435,13 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, /* Connect TCP socket */ rc = do_connect(cf, data, (bool)cf->conn->bits.tcp_fastopen); - error = SOCKERRNO; + sockerr = 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(rc == -1) { - result = socket_connect_result(data, ctx->ip.remote_ip, error); + ctx->sockerr = sockerr; + result = socket_connect_result(data, ctx->ip.remote_ip, sockerr); goto out; } } @@ -1295,7 +1461,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, return CURLE_OK; } else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) { - if(verifyconnect(ctx->sock, &ctx->error)) { + if(verifyconnect(ctx->sock, &ctx->sockerr)) { /* we are connected with TCP, awesome! */ ctx->connected_at = *Curl_pgrs_now(data); set_local_ip(cf, data); @@ -1307,7 +1473,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, } else if(rc & CURL_CSELECT_ERR) { CURL_TRC_CF(data, cf, "poll/select error on fd=%" FMT_SOCKET_T, ctx->sock); - (void)verifyconnect(ctx->sock, &ctx->error); + (void)verifyconnect(ctx->sock, &ctx->sockerr); result = CURLE_COULDNT_CONNECT; } @@ -1315,10 +1481,10 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, if(result) { VERBOSE(char buffer[STRERROR_LEN]); set_local_ip(cf, data); - if(ctx->error) { - data->state.os_errno = ctx->error; - SET_SOCKERRNO(ctx->error); - VERBOSE(curlx_strerror(ctx->error, buffer, sizeof(buffer))); + if(ctx->sockerr) { + data->state.os_errno = ctx->sockerr; + SET_SOCKERRNO(ctx->sockerr); + VERBOSE(curlx_strerror(ctx->sockerr, buffer, sizeof(buffer))); } else { VERBOSE(curlx_strcopy(buffer, sizeof(buffer), STRCONST("peer closed"))); @@ -1330,7 +1496,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, infof(data, "connect to %s port %u from %s port %d failed: %s", ctx->ip.remote_ip, ctx->ip.remote_port, ctx->ip.local_ip, ctx->ip.local_port, - curlx_strerror(ctx->error, buffer, sizeof(buffer))); + curlx_strerror(ctx->sockerr, buffer, sizeof(buffer))); *done = FALSE; } return result; @@ -1369,7 +1535,7 @@ static CURLcode cf_socket_adjust_pollset(struct Curl_cfilter *cf, #ifdef USE_WINSOCK -/* Offered by mingw-w64 v13+. MS SDK 7.0A+. */ +/* Offered by mingw-w64 v13+, MS SDK 7.0A/VS2010+ */ #ifndef SIO_IDEAL_SEND_BACKLOG_QUERY #define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B #endif @@ -1442,22 +1608,12 @@ static CURLcode cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(!curlx_sztouz(rv, pnwritten)) { 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) || - (SOCKEINPROGRESS == sockerr) + if(SOCK_EAGAIN(sockerr) +#ifndef USE_WINSOCK + || (sockerr == SOCKEINTR) || (sockerr == SOCKEINPROGRESS) #endif ) { - /* EWOULDBLOCK */ - result = CURLE_AGAIN; + result = CURLE_AGAIN; /* EWOULDBLOCK */ } else { char buffer[STRERROR_LEN]; @@ -1474,7 +1630,7 @@ static CURLcode cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, #endif CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, %zu", - orig_len, result, *pnwritten); + orig_len, (int)result, *pnwritten); cf->conn->sock[cf->sockindex] = fdsave; return result; } @@ -1508,21 +1664,12 @@ static CURLcode cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!curlx_sztouz(rv, pnread)) { 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) + if(SOCK_EAGAIN(sockerr) +#ifndef USE_WINSOCK + || (sockerr == SOCKEINTR) #endif ) { - /* EWOULDBLOCK */ - result = CURLE_AGAIN; + result = CURLE_AGAIN; /* EWOULDBLOCK */ } else { char buffer[STRERROR_LEN]; @@ -1533,7 +1680,7 @@ static CURLcode cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } } - CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, %zu", len, result, *pnread); + CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, %zu", len, (int)result, *pnread); if(!result && !ctx->got_first_byte) { ctx->first_byte_at = *Curl_pgrs_now(data); ctx->got_first_byte = TRUE; @@ -1548,8 +1695,6 @@ static void cf_socket_update_data(struct Curl_cfilter *cf, 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; } } @@ -1586,6 +1731,26 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, case CF_CTRL_FORGET_SOCKET: ctx->sock = CURL_SOCKET_BAD; break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported) { + struct curltime *ts = NULL; + switch(ctx->transport) { + case TRNSPRT_UDP: + case TRNSPRT_QUIC: + /* Since UDP connected sockets work different from TCP, we use the + * time of the first byte from the peer as the "connect" time. */ + if(ctx->got_first_byte) + ts = &ctx->first_byte_at; + break; + default: + ts = &ctx->connected_at; + break; + } + if(ts) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, *ts); + ctx->stats_reported = TRUE; + } + } } return CURLE_OK; } @@ -1656,24 +1821,6 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, else *pres1 = -1; return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - switch(ctx->transport) { - case TRNSPRT_UDP: - case TRNSPRT_QUIC: - /* Since UDP connected sockets work different from TCP, we use the - * time of the first byte from the peer as the "connect" time. */ - if(ctx->got_first_byte) { - *when = ctx->first_byte_at; - break; - } - FALLTHROUGH(); - default: - *when = ctx->connected_at; - break; - } - return CURLE_OK; - } case CF_QUERY_IP_INFO: #ifdef USE_IPV6 *pres1 = (ctx->addr.family == AF_INET6); @@ -1682,6 +1829,12 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, #endif *(struct ip_quadruple *)pres2 = ctx->ip; return CURLE_OK; + case CF_QUERY_REALLY_CONNECTED: + if(cf->cft != &Curl_cft_udp) + *pres1 = cf->connected; + else + *pres1 = ctx->got_first_byte; + return CURLE_OK; default: break; } @@ -1696,7 +1849,6 @@ struct Curl_cftype Curl_cft_tcp = { CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_connect, - cf_socket_close, cf_socket_shutdown, cf_socket_adjust_pollset, Curl_cf_def_data_pending, @@ -1710,17 +1862,24 @@ struct Curl_cftype Curl_cft_tcp = { CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - DEBUGASSERT(transport == TRNSPRT_TCP); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_TCP); if(!addr) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; @@ -1732,7 +1891,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, goto out; } - result = cf_socket_ctx_init(ctx, addr, transport); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -1742,7 +1901,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; @@ -1786,6 +1945,47 @@ static void linux_quic_gro(struct cf_socket_ctx *ctx) #define linux_quic_gro(x) #endif +#if (defined(__linux__) || defined(__APPLE__)) && defined(IP_RECVTOS) +static void linux_quic_ecn(struct cf_socket_ctx *ctx) +{ + unsigned int tos = 1; + switch(ctx->addr.family) { + case AF_INET: + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_RECVTOS, &tos, sizeof(tos)); + break; +#ifdef IPV6_RECVTCLASS + case AF_INET6: + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_RECVTCLASS, + &tos, sizeof(tos)); + break; +#endif + } +} +#else +#define linux_quic_ecn(x) +#endif + +#if (defined(__linux__) || defined(__APPLE__)) && defined(IP_DONTFRAG) +static void linux_ip_dontfrag(struct cf_socket_ctx *ctx) +{ + int val = 1; + + switch(ctx->addr.family) { + case AF_INET: + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val)); + break; +#ifdef IPV6_DONTFRAG + case AF_INET6: + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_DONTFRAG, + &val, sizeof(val)); + break; +#endif + } +} +#else +#define linux_ip_dontfrag(x) +#endif + static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1815,7 +2015,9 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, * 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. */ + linux_quic_ecn(ctx); linux_quic_mtu(ctx); + linux_ip_dontfrag(ctx); linux_quic_gro(ctx); return CURLE_OK; @@ -1837,7 +2039,8 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, if(ctx->sock == CURL_SOCKET_BAD) { result = cf_socket_open(cf, data); if(result) { - CURL_TRC_CF(data, cf, "cf_udp_connect(), open failed -> %d", result); + CURL_TRC_CF(data, cf, "cf_udp_connect(), open failed -> %d", + (int)result); goto out; } @@ -1862,7 +2065,6 @@ struct Curl_cftype Curl_cft_udp = { CURL_LOG_LVL_NONE, cf_socket_destroy, cf_udp_connect, - cf_socket_close, cf_socket_shutdown, cf_socket_adjust_pollset, Curl_cf_def_data_pending, @@ -1876,24 +2078,31 @@ struct Curl_cftype Curl_cft_udp = { CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_UDP || transport_peer == TRNSPRT_QUIC); ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = cf_socket_ctx_init(ctx, addr, transport); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -1903,7 +2112,7 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; @@ -1916,7 +2125,6 @@ struct Curl_cftype Curl_cft_unix = { CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_connect, - cf_socket_close, cf_socket_shutdown, cf_socket_adjust_pollset, Curl_cf_def_data_pending, @@ -1929,25 +2137,32 @@ struct Curl_cftype Curl_cft_unix = { }; CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - struct connectdata *conn, - struct Curl_sockaddr_ex *addr, - uint8_t transport) + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - DEBUGASSERT(transport == TRNSPRT_UNIX); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_UNIX); ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = cf_socket_ctx_init(ctx, addr, transport); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -1957,7 +2172,7 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; @@ -1996,7 +2211,7 @@ static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, { struct cf_socket_ctx *ctx = cf->ctx; #ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; + CURLcode result; struct Curl_sockaddr_storage ssrem; curl_socklen_t plen; @@ -2005,15 +2220,16 @@ static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, plen = sizeof(ssrem); memset(&ssrem, 0, plen); if(getpeername(ctx->sock, (struct sockaddr *)&ssrem, &plen)) { - int error = SOCKERRNO; + char buffer[STRERROR_LEN]; + int sockerr = SOCKERRNO; failf(data, "getpeername() failed with errno %d: %s", - error, curlx_strerror(error, buffer, sizeof(buffer))); + sockerr, curlx_strerror(sockerr, 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, curlx_strerror(errno, buffer, sizeof(buffer))); + result = sockaddr2string((struct sockaddr *)&ssrem, plen, + ctx->ip.remote_ip, &ctx->ip.remote_port); + if(result) { + failf(data, "ssrem inet_ntop() failed with %d", (int)result); return; } #else @@ -2058,7 +2274,7 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, 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 = SOCKET_READABLE(ctx->sock, 0); - CURL_TRC_CF(data, cf, "socket_check -> %x", socketstate); + CURL_TRC_CF(data, cf, "socket_check -> %x", (unsigned int)socketstate); switch(socketstate) { case -1: /* error */ /* let's die here */ @@ -2125,13 +2341,14 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_port); if(data->set.fsockopt) { + struct Curl_mapi_guard guard; int error = 0; /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); + CURL_CBAPI_START(&guard, data, easy_fsockopt); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_ACCEPT); - Curl_set_in_callback(data, false); + CURL_CBAPI_END(&guard); if(error) return CURLE_ABORTED_BY_CALLBACK; @@ -2146,7 +2363,6 @@ struct Curl_cftype Curl_cft_tcp_accept = { CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_accept_connect, - cf_socket_close, cf_socket_shutdown, cf_socket_adjust_pollset, Curl_cf_def_data_pending, @@ -2160,7 +2376,7 @@ struct Curl_cftype Curl_cft_tcp_accept = { CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, struct connectdata *conn, - int sockindex, curl_socket_t *s) + int8_t sockindex, curl_socket_t *s) { CURLcode result; struct Curl_cfilter *cf = NULL; @@ -2200,7 +2416,7 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, } bool Curl_conn_is_tcp_listen(struct Curl_easy *data, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; while(cf) { diff --git a/lib/cf-socket.h b/lib/cf-socket.h index 40c001cc14fd..f4a6755fe3b8 100644 --- a/lib/cf-socket.h +++ b/lib/cf-socket.h @@ -25,8 +25,6 @@ ***************************************************************************/ #include "curl_setup.h" -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ - struct Curl_addrinfo; struct Curl_cfilter; struct Curl_easy; @@ -34,23 +32,6 @@ struct connectdata; struct Curl_sockaddr_ex; struct ip_quadruple; -/* - * The Curl_sockaddr_ex structure is libcurl's external API curl_sockaddr - * structure with enough space available to directly hold any - * protocol-specific address structures. The variable declared here will be - * used to pass / receive data to/from the fopensocket callback if this has - * been set, before that, it is initialized from parameters. - */ -struct Curl_sockaddr_ex { - int family; - int socktype; - int protocol; - unsigned int addrlen; - union { - struct sockaddr sa; - struct Curl_sockaddr_storage buf; - } addr; -}; #define curl_sa_addr addr.sa #define curl_sa_addrbuf addr.buf @@ -94,9 +75,13 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, */ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that opens a UDP socket to the given address @@ -107,9 +92,13 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, */ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that opens a UNIX socket to the given address @@ -120,16 +109,20 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, */ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that keeps a listening socket. */ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, curl_socket_t *s); /** @@ -137,7 +130,7 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, * Curl_conn_tcp_listen_set(). */ bool Curl_conn_is_tcp_listen(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /** * Peek at the socket and remote ip/port the socket filter is using. diff --git a/lib/cfilters.c b/lib/cfilters.c index 6d7d8ef7385e..6ffa262eb165 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -33,16 +33,26 @@ #include "select.h" #include "curlx/strparse.h" -#ifdef UNITTESTS -/* @unittest 2600 */ -UNITTEST void cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); -UNITTEST void cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) +CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { - cf->connected = FALSE; - if(cf->next) - cf->next->cft->do_close(cf->next, data); + CURLcode result; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + if(cf->next) { + result = cf->next->cft->do_connect(cf->next, data, done); + if(result || !*done) + return result; + } + + cf->connected = TRUE; + *done = TRUE; + return CURLE_OK; } -#endif CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) @@ -117,29 +127,28 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, } #ifdef CURLVERBOSE -static void conn_trc_filters(struct Curl_easy *data, - int sockindex, - const char *info) +void Curl_conn_trc_filters(struct Curl_easy *data, + int8_t sockindex, const char *info) { if(CURL_TRC_M_is_verbose(data) && data->conn) { struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; if(cf) { - struct dynbuf msg; - CURLcode result = CURLE_OK; - - curlx_dyn_init(&msg, 1024); - result = curlx_dyn_addf(&msg, "%s [%d]", info, sockindex); - for(; cf && !result; cf = cf->next) { - result = curlx_dyn_addf(&msg, "[%s%s]", - cf->connected ? "" : "!", cf->cft->name); + char msg[256], *buf; + int blen, n; + + buf = msg; + blen = sizeof(msg) - 1; + n = curl_msnprintf(buf, blen, "%s [%d]", info, sockindex); + buf += n; + blen -= n; + for(; cf && blen; cf = cf->next) { + n = curl_msnprintf(buf, blen, "[%s%s]", + cf->connected ? "" : "!", cf->cft->name); + buf += n; + blen -= n; } - if(!result) - CURL_TRC_M(data, "%s", curlx_dyn_ptr(&msg)); - else - CURL_TRC_M(data, "%s [%d] error %d tracing chain", - info, sockindex, result); - curlx_dyn_free(&msg); + CURL_TRC_M(data, "%s%s", msg, blen ? "" : "..."); } else CURL_TRC_M(data, "%s [%d][-]", info, sockindex); @@ -168,25 +177,15 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, } void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, int sockindex) + struct connectdata *conn, int8_t sockindex) { + struct curltime *pt = &conn->shutdown.start[sockindex]; + memset(pt, 0, sizeof(*pt)); Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } -void Curl_conn_close(struct Curl_easy *data, int sockindex) -{ - struct Curl_cfilter *cf; - - DEBUGASSERT(data->conn); - /* it is valid to call that without filters being present */ - cf = data->conn->cfilter[sockindex]; - if(cf) { - cf->cft->do_close(cf, data); - } - Curl_shutdown_clear(data, sockindex); -} - -CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) +CURLcode Curl_conn_shutdown(struct Curl_easy *data, + int8_t sockindex, bool *done) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; @@ -208,7 +207,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) } *done = FALSE; - if(!Curl_shutdown_started(data, sockindex)) { + if(!Curl_shutdown_started(data->conn, sockindex)) { Curl_shutdown_start(data, sockindex, 0); } else { @@ -225,7 +224,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) bool cfdone = FALSE; result = cf->cft->do_shutdown(cf, data, &cfdone); if(result) { - CURL_TRC_CF(data, cf, "shut down failed with %d", result); + CURL_TRC_CF(data, cf, "shut down failed with %d", (int)result); return result; } else if(!cfdone) { @@ -241,7 +240,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) return result; } -CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, +CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { struct Curl_cfilter *cf; @@ -259,7 +258,7 @@ CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, return CURLE_FAILED_INIT; } -CURLcode Curl_cf_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_cf_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten) { @@ -360,7 +359,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, void Curl_conn_cf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, struct Curl_cfilter *cf) { DEBUGASSERT(conn); @@ -410,7 +409,7 @@ bool Curl_conn_cf_discard(struct Curl_cfilter **pcf, found = TRUE; break; } - pprev = &((*pprev)->next); + pprev = &(*pprev)->next; } } Curl_conn_cf_discard_chain(pcf, data); @@ -427,12 +426,6 @@ CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; } -void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - if(cf) - cf->cft->do_close(cf, data); -} - CURLcode Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten) @@ -452,38 +445,15 @@ CURLcode Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_RECV_ERROR; } -#ifdef CURLVERBOSE -static CURLcode cf_verboseconnect(struct Curl_easy *data, - struct Curl_cfilter *cf) -{ - if(Curl_trc_is_verbose(data)) { - struct ip_quadruple ipquad; - bool is_ipv6; - CURLcode result; - - result = Curl_conn_cf_get_ip_info(cf, data, &is_ipv6, &ipquad); - if(result) - return result; - - infof(data, "Established %sconnection to %s (%s port %u) from %s port %u ", - (cf->sockindex == SECONDARYSOCKET) ? "2nd " : "", - CURL_CONN_HOST_DISPNAME(data->conn), - ipquad.remote_ip, ipquad.remote_port, - ipquad.local_ip, ipquad.local_port); - } - return CURLE_OK; -} -#endif - static CURLcode cf_cntrl_all(struct connectdata *conn, struct Curl_easy *data, bool ignore_result, int event, int arg1, void *arg2) { CURLcode result = CURLE_OK; - size_t i; + int i; - for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { + for(i = 0; i < (int)CURL_ARRAYSIZE(conn->cfilter); ++i) { result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, event, arg1, arg2); if(!ignore_result && result) @@ -492,36 +462,34 @@ static CURLcode cf_cntrl_all(struct connectdata *conn, return result; } -static void cf_cntrl_update_info(struct Curl_easy *data, - struct connectdata *conn) +bool Curl_conn_cf_is_ip_connected(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + if(cf) { + int value = 0; + if(!cf->cft->query(cf, data, CF_QUERY_REALLY_CONNECTED, &value, NULL)) + return !!value; + } + return FALSE; +} + +void Curl_conn_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn) { cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); } -/** - * Update connection statistics - */ -static void conn_report_connect_stats(struct Curl_cfilter *cf, - struct Curl_easy *data) +void Curl_conn_cntrl_report_stats(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) { - if(cf) { - struct curltime connected; - struct curltime appconnected; - - memset(&connected, 0, sizeof(connected)); - cf->cft->query(cf, data, CF_QUERY_TIMER_CONNECT, NULL, &connected); - if(connected.tv_sec || connected.tv_usec) - Curl_pgrsTimeWas(data, TIMER_CONNECT, connected); - - memset(&appconnected, 0, sizeof(appconnected)); - cf->cft->query(cf, data, CF_QUERY_TIMER_APPCONNECT, NULL, &appconnected); - if(appconnected.tv_sec || appconnected.tv_usec) - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, appconnected); - } + if((unsigned)sockindex < CURL_ARRAYSIZE(conn->cfilter)) + (void)Curl_conn_cf_cntrl(conn->cfilter[sockindex], data, TRUE, + CF_CTRL_REPORT_STATS, 0, NULL); } -static void conn_remove_setup_filters(struct Curl_easy *data, - int sockindex) +void Curl_conn_remove_setup_filters(struct Curl_easy *data, + int8_t sockindex) { struct Curl_cfilter **anchor = &data->conn->cfilter[sockindex]; while(*anchor) { @@ -538,126 +506,14 @@ static void conn_remove_setup_filters(struct Curl_easy *data, } } -CURLcode Curl_conn_connect(struct Curl_easy *data, - int sockindex, - 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 easy_pollset ps; - struct curl_pollfds cpfds; - struct Curl_cfilter *cf; - CURLcode result = CURLE_OK; - - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - if(!CONN_SOCK_IDX_VALID(sockindex)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(data->conn->scheme->flags & PROTOPT_NONETWORK) { - *done = TRUE; - return CURLE_OK; - } - - cf = data->conn->cfilter[sockindex]; - if(!cf) { - *done = FALSE; - return CURLE_FAILED_INIT; - } - - *done = (bool)cf->connected; - if(*done) - return CURLE_OK; - - Curl_pollset_init(&ps); - 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) { - /* 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(cf, data); - data->conn->keepalive = *Curl_pgrs_now(data); - VERBOSE(result = cf_verboseconnect(data, cf)); - VERBOSE(conn_trc_filters(data, sockindex, "connected")); - conn_remove_setup_filters(data, sockindex); - VERBOSE(conn_trc_filters(data, sockindex, "reduced to")); - goto out; - } - else if(result) { - CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", result); - VERBOSE(conn_trc_filters(data, sockindex, "failed to connect")); - conn_report_connect_stats(cf, data); - goto out; - } - - if(!blocking) - goto out; - else { - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft_ms(data); - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - 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_pollset_reset(&ps); - Curl_pollfds_reset(&cpfds); - /* In general, we want to send after connect, wait on that. */ - if(sockfd != CURL_SOCKET_BAD) - result = Curl_pollset_set_out_only(data, &ps, sockfd); - if(!result) - result = Curl_conn_adjust_pollset(data, data->conn, &ps); - if(result) - goto out; - 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_pollset_cleanup(&ps); - Curl_pollfds_cleanup(&cpfds); - return result; -} - -bool Curl_conn_is_setup(struct connectdata *conn, int sockindex) +bool Curl_conn_is_setup(struct connectdata *conn, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; - return (conn->cfilter[sockindex] != NULL); + return !!conn->cfilter[sockindex]; } -bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) +bool Curl_conn_is_connected(struct connectdata *conn, int8_t sockindex) { struct Curl_cfilter *cf; @@ -671,7 +527,7 @@ bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) return FALSE; } -bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) +bool Curl_conn_is_ip_connected(struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf; @@ -688,10 +544,37 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) return FALSE; } +#ifndef CURL_DISABLE_PROXY +static bool cf_is_tunneling(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if((cf->cft->flags & CF_TYPE_PROXY)) + return TRUE; + } + return FALSE; +} + +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex) +{ + if(!CONN_SOCK_IDX_VALID(sockindex)) + return FALSE; + return conn ? cf_is_tunneling(conn->cfilter[sockindex]) : FALSE; +} +#else +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex) +{ + (void)conn; + (void)sockindex; + return FALSE; +} +#endif /* CURL_DISABLE_PROXY */ + static bool cf_is_ssl(struct Curl_cfilter *cf) { for(; cf; cf = cf->next) { - if(cf->cft->flags & CF_TYPE_SSL) + /* A tunneling proxy does not offer end2end encryption, even if + * it does SSL itself (e.g. QUIC H3 proxy) */ + if((cf->cft->flags & CF_TYPE_SSL) && !(cf->cft->flags & CF_TYPE_PROXY)) return TRUE; if(cf->cft->flags & CF_TYPE_IP_CONNECT) return FALSE; @@ -699,7 +582,7 @@ static bool cf_is_ssl(struct Curl_cfilter *cf) return FALSE; } -bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) +bool Curl_conn_is_ssl(struct connectdata *conn, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; @@ -707,7 +590,7 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) } bool Curl_conn_get_ssl_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, int query, struct curl_tlssessioninfo *info) { @@ -724,7 +607,7 @@ bool Curl_conn_get_ssl_info(struct Curl_easy *data, } CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, bool *is_ipv6, struct ip_quadruple *ipquad) { struct Curl_cfilter *cf; @@ -734,15 +617,13 @@ CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, return Curl_conn_cf_get_ip_info(cf, data, is_ipv6, ipquad); } -bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) +bool Curl_conn_is_multiplex(struct connectdata *conn, int8_t sockindex) { struct Curl_cfilter *cf; - if(!CONN_SOCK_IDX_VALID(sockindex)) + if(!conn || !CONN_SOCK_IDX_VALID(sockindex)) return FALSE; - cf = conn ? conn->cfilter[sockindex] : NULL; - - for(; cf; cf = cf->next) { + for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_MULTIPLEX) return TRUE; if(cf->cft->flags & (CF_TYPE_IP_CONNECT | CF_TYPE_SSL)) @@ -782,17 +663,6 @@ int Curl_protocol_for_transport(uint8_t transport) } } -bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - (void)data; - for(; cf; cf = cf->next) { - if(cf->cft->flags & CF_TYPE_HTTPSRR) - return TRUE; - } - return FALSE; -} - const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data, struct connectdata *conn) { @@ -824,7 +694,7 @@ unsigned char Curl_conn_http_version(struct Curl_easy *data, return (unsigned char)(result ? 0 : v); } -bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) +bool Curl_conn_data_pending(struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf; @@ -854,7 +724,7 @@ bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, return (result || !pending) ? FALSE : TRUE; } -bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex) +bool Curl_conn_needs_flush(struct Curl_easy *data, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return FALSE; @@ -866,17 +736,17 @@ CURLcode Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - /* Get the lowest not-connected filter, if there are any */ - while(cf && !cf->connected && cf->next && !cf->next->connected) - cf = cf->next; - /* 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 && !result) { - result = cf->cft->adjust_pollset(cf, data, ps); - cf = cf->next; + /* Go through all filters, top to bottom, and let them manage the pollset + * - connected filters can do so + * - CF_TYPE_DNS filters can + * - unconnected filters without next or connect next can + */ + for(; cf && !result; cf = cf->next) { + if(cf->shutdown) + continue; + if(cf->connected || (cf->cft->flags & CF_TYPE_DNS) || + !cf->next || cf->next->connected) + result = cf->cft->adjust_pollset(cf, data, ps); } return result; } @@ -886,12 +756,29 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - int i; + bool want_io = !!ps->n; + int8_t i; DEBUGASSERT(data); DEBUGASSERT(conn); - for(i = 0; (i < 2) && !result; ++i) { - result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); + /* During connect time, connection filters may add sockets to the pollset + * even when the transfer neither wants to send nor receive. And those + * sockets, when having events, are served. The same applies to a + * filter chain whose shutdown has started. + * Once a filter chain is connected however and before its shutdown + * starts, a transfer that neither wants to send nor receive + * will never call the connection filters. Any sockets added by the filters + * will not change state and POLLIN/POLLOUT events will trigger forever, + * making us busy loop. See #21671. + * Gate each filter chain on its own state, so that one chain being in + * connect or shutdown does not add poll events for the other. Check + * against the transfer's own interest, before any chain added sockets + * of its own. */ + for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { + if(conn->cfilter[i] && + (want_io || !Curl_conn_is_connected(conn, i) || + Curl_shutdown_started(conn, i))) + result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); } return result; } @@ -924,7 +811,7 @@ int Curl_conn_cf_poll(struct Curl_cfilter *cf, return rc; } -void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, +void Curl_conn_get_current_host(struct Curl_easy *data, int8_t sockindex, const char **phost, int *pport) { struct Curl_cfilter *cf, *cf_proxy = NULL; @@ -953,8 +840,8 @@ void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, &portarg, CURL_UNCONST(phost))) { /* Everything connected or query unsuccessful, the overall * connection's destination is the answer */ - *phost = data->conn->host.name; - portarg = data->conn->remote_port; + *phost = data->conn->origin->hostname; + portarg = data->conn->origin->port; } if(pport) *pport = portarg; @@ -1018,8 +905,8 @@ const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf, return NULL; } -static const struct Curl_sockaddr_ex * -cf_get_remote_addr(struct Curl_cfilter *cf, struct Curl_easy *data) +static const struct Curl_sockaddr_ex *cf_get_remote_addr( + struct Curl_cfilter *cf, struct Curl_easy *data) { const struct Curl_sockaddr_ex *remote_addr = NULL; if(cf && @@ -1057,8 +944,8 @@ curl_socket_t Curl_conn_get_first_socket(struct Curl_easy *data) return data->conn->sock[FIRSTSOCKET]; } -const struct Curl_sockaddr_ex * -Curl_conn_get_remote_addr(struct Curl_easy *data, int sockindex) +const struct Curl_sockaddr_ex *Curl_conn_get_remote_addr( + struct Curl_easy *data, int8_t sockindex) { struct Curl_cfilter *cf = (data->conn && CONN_SOCK_IDX_VALID(sockindex)) ? @@ -1071,7 +958,7 @@ CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data) return cf_cntrl_all(data->conn, data, FALSE, CF_CTRL_DATA_SETUP, 0, NULL); } -CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex) +CURLcode Curl_conn_flush(struct Curl_easy *data, int8_t sockindex) { if(!CONN_SOCK_IDX_VALID(sockindex)) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -1112,20 +999,22 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, } CURLcode Curl_conn_keep_alive(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) + struct connectdata *conn) { - struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + int i; - if(!CONN_SOCK_IDX_VALID(sockindex)) - return CURLE_BAD_FUNCTION_ARGUMENT; - cf = conn->cfilter[sockindex]; - return cf ? cf->cft->keep_alive(cf, data) : CURLE_OK; + for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { + struct Curl_cfilter *cf = conn->cfilter[i]; + if(cf) + result = cf->cft->keep_alive(cf, data); + } + return result; } size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; CURLcode result; @@ -1145,7 +1034,7 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, int Curl_conn_get_stream_error(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; CURLcode result; @@ -1160,7 +1049,7 @@ int Curl_conn_get_stream_error(struct Curl_easy *data, return (result || n < 0) ? 0 : n; } -int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) +int8_t Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) { if(data && data->conn && sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) @@ -1168,7 +1057,7 @@ int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) return FIRSTSOCKET; } -CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { DEBUGASSERT(data); @@ -1181,7 +1070,7 @@ CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, return CURLE_FAILED_INIT; } -CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, const void *buf, size_t len, bool eos, size_t *pnwritten) { diff --git a/lib/cfilters.h b/lib/cfilters.h index ac56737ccb99..979275b72979 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -39,10 +39,6 @@ struct curl_tlssessioninfo; 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. */ @@ -125,6 +121,7 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, #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 */ +#define CF_CTRL_REPORT_STATS (256 + 3) /* 0 NULL ignored */ /** * Handle event/control for the filter. @@ -160,13 +157,17 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, null-terminated string or NULL if none selected/handshake not done. Implemented by filter types CF_TYPE_SSL or CF_TYPE_IP_CONNECT. + * - CF_QUERY_REALLY_CONNECTED: implemented in socket filters to return + * if a reply from a server has really arrived. For + * non-UDP sockets this is TRUE when the socket became + * writable. For UDP sockets, this is TRUE when the + * first byte from the peer was received. */ /* query res1 res2 */ #define CF_QUERY_MAX_CONCURRENT 1 /* number - */ #define CF_QUERY_CONNECT_REPLY_MS 2 /* number - */ #define CF_QUERY_SOCKET 3 /* - curl_socket_t */ -#define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */ -#define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */ +/* unused 4 + 5 */ #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 */ @@ -179,6 +180,7 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, #define CF_QUERY_SSL_CTX_INFO 13 /* - struct curl_tlssessioninfo * */ #define CF_QUERY_TRANSPORT 14 /* TRNSPRT_* - * */ #define CF_QUERY_ALPN_NEGOTIATED 15 /* - const char * */ +#define CF_QUERY_REALLY_CONNECTED 16 /* TRUE/FALSE - */ /** * Query the cfilter for properties. Filters ignorant of a query will @@ -201,7 +203,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * CF_TYPE_HTTP implement a version of the HTTP protocol * CF_TYPE_SETUP filter is only needed for connection setup and * can be removed once connected - * CF_TYPE_HTTPSRR filter that wants HTTPS-RR information + * CF_TYPE_DNS filter managing DNS resolve operations */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) @@ -209,7 +211,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, #define CF_TYPE_PROXY (1 << 3) #define CF_TYPE_HTTP (1 << 4) #define CF_TYPE_SETUP (1 << 5) -#define CF_TYPE_HTTPSRR (1 << 6) +#define CF_TYPE_DNS (1 << 6) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -218,7 +220,6 @@ struct Curl_cftype { int log_level; /* log level for such filters */ Curl_cft_destroy_this *destroy; /* destroy resources of this cf */ Curl_cft_connect *do_connect; /* establish connection */ - Curl_cft_close *do_close; /* close conn */ Curl_cft_shutdown *do_shutdown; /* shutdown conn */ Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */ Curl_cft_data_pending *has_data_pending; /* conn has data pending */ @@ -236,7 +237,7 @@ struct Curl_cfilter { struct Curl_cfilter *next; /* next filter in chain */ void *ctx; /* filter type specific settings */ struct connectdata *conn; /* the connection this filter belongs to */ - int sockindex; /* the index the filter is installed at */ + int8_t 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 */ }; @@ -270,6 +271,8 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, int query, int *pres1, void *pres2); CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done); +CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); /** * Create a new filter instance, unattached to the filter chain. @@ -289,7 +292,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, */ void Curl_conn_cf_add(struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, struct Curl_cfilter *cf); /** @@ -317,12 +320,11 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, * Remove and destroy all filters at chain `sockindex` on connection `conn`. */ void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, int sockindex); + struct connectdata *conn, int8_t sockindex); CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done); -void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); CURLcode Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten); @@ -347,6 +349,9 @@ CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, struct Curl_easy *data); +bool Curl_conn_cf_is_ip_connected(struct Curl_cfilter *cf, + struct Curl_easy *data); + unsigned char Curl_conn_cf_get_transport(struct Curl_cfilter *cf, struct Curl_easy *data); @@ -356,48 +361,38 @@ int Curl_protocol_for_transport(uint8_t transport); const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data); -/* The filter (or one of its sub-filters) wants HTTPS-RR information. */ -bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, - struct Curl_easy *data); - #define CURL_CF_SSL_DEFAULT (-1) #define CURL_CF_SSL_DISABLE 0 #define CURL_CF_SSL_ENABLE 1 -/** - * Bring the filter chain at `sockindex` for connection `data->conn` into - * connected state. Which will set `*done` to TRUE. - * This can be called on an already connected chain with no side effects. - * When not `blocking`, calls may return without error and `*done != TRUE`, - * while the individual filters negotiated the connection. - */ -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); +bool Curl_conn_is_setup(struct connectdata *conn, int8_t sockindex); /** * Check if the filter chain at `sockindex` for connection `conn` is * completely connected. */ -bool Curl_conn_is_connected(struct connectdata *conn, int sockindex); +bool Curl_conn_is_connected(struct connectdata *conn, int8_t sockindex); /** * Determine if we have reached the remote host on IP level, e.g. * have a TCP connection. This turns TRUE before a possible SSL * handshake has been started/done. */ -bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex); +bool Curl_conn_is_ip_connected(struct Curl_easy *data, int8_t sockindex); /** * Determine if the connection is using SSL to the remote host * (or will be once connected). This will return FALSE, if SSL * is only used in proxying and not for the tunnel itself. */ -bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); +bool Curl_conn_is_ssl(struct connectdata *conn, int8_t sockindex); + +/* Determine if the connection has one or more proxy filters. + * e.g. is tunneling. */ +bool Curl_conn_is_tunneling(struct connectdata *conn, int8_t sockindex); /* * Fill `info` with information about the TLS instance securing the connection @@ -405,18 +400,18 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); * FALSE. 'query' should be CF_QUERY_SSL_INFO or CF_QUERY_SSL_CTX_INFO. */ bool Curl_conn_get_ssl_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, int query, struct curl_tlssessioninfo *info); CURLcode Curl_conn_get_ip_info(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, bool *is_ipv6, struct ip_quadruple *ipquad); /** * Connection provides multiplexing of easy handles at `socketindex`. */ -bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); +bool Curl_conn_is_multiplex(struct connectdata *conn, int8_t sockindex); /** * Return the HTTP version used on the FIRSTSOCKET connection filters @@ -433,36 +428,41 @@ unsigned char Curl_conn_get_transport(struct Curl_easy *data, const char *Curl_conn_get_alpn_negotiated(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); +void Curl_conn_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn); + +void Curl_conn_cntrl_report_stats(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +void Curl_conn_remove_setup_filters(struct Curl_easy *data, + int8_t sockindex); /** * Shutdown the connection at `sockindex` non-blocking, using timeout * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. * Return CURLE_OK and *done == FALSE if not finished. */ -CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done); +CURLcode Curl_conn_shutdown(struct Curl_easy *data, + int8_t sockindex, bool *done); /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. */ bool Curl_conn_data_pending(struct Curl_easy *data, - int sockindex); + int8_t 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); +bool Curl_conn_needs_flush(struct Curl_easy *data, int8_t sockindex); /** * Flush any pending data on the connection filters at chain `sockindex`. */ -CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex); +CURLcode Curl_conn_flush(struct Curl_easy *data, int8_t sockindex); /** * Return the socket used on data's connection for FIRSTSOCKET, @@ -473,12 +473,12 @@ curl_socket_t Curl_conn_get_first_socket(struct Curl_easy *data); /* Return a pointer to the connected socket address or NULL. */ const struct Curl_sockaddr_ex * -Curl_conn_get_remote_addr(struct Curl_easy *data, int sockindex); +Curl_conn_get_remote_addr(struct Curl_easy *data, int8_t sockindex); /** * Tell filters to forget about the socket at sockindex. */ -void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex); +void Curl_conn_forget_socket(struct Curl_easy *data, int8_t sockindex); /** * Adjust the pollset for the filter chain starting at `cf`. @@ -508,7 +508,7 @@ int Curl_conn_cf_poll(struct Curl_cfilter *cf, * `data->conn`. Copy at most `len` bytes into `buf`. Return the * actual number of bytes copied in `*pnread`or an error. */ -CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, +CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread); /** @@ -516,7 +516,7 @@ CURLcode Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, * at connection `data->conn`. Return the actual number of bytes written * in `*pnwritten` or on error. */ -CURLcode Curl_cf_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_cf_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten); @@ -572,21 +572,20 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, bool *input_pending); /** - * Try to upkeep the connection filters at sockindex. + * Try to upkeep the connection filters. */ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); + struct connectdata *conn); /** * Get the remote hostname and port that the connection is currently * talking to (or will talk to). * Once connected or before connect starts, - * it is `conn->host.name` and `conn->remote_port`. + * it is `conn->origin->hostname` and `conn->origin->port`. * During connect, when tunneling proxies are involved (http or socks), * it will be the name and port the proxy currently negotiates with. */ -void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, +void Curl_conn_get_current_host(struct Curl_easy *data, int8_t sockindex, const char **phost, int *pport); /** @@ -595,34 +594,39 @@ void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex, */ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, - int sockindex); + int8_t 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); + int8_t sockindex); + +#ifdef CURLVERBOSE +void Curl_conn_trc_filters(struct Curl_easy *data, + int8_t sockindex, const char *info); +#endif /** * 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); +int8_t Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); /* * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. * Return CURLE_AGAIN iff blocked on receiving. */ -CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread); /* * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. * Return CURLE_AGAIN iff blocked on sending. */ -CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, +CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, const void *buf, size_t len, bool eos, size_t *pnwritten); @@ -664,7 +668,7 @@ struct cf_call_data { #define CF_DATA_SAVE(save, cf, data) \ do { \ (save) = CF_CTX_CALL_DATA(cf); \ - DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + DEBUGASSERT(!(save).data || (save).depth > 0); \ CF_CTX_CALL_DATA(cf).depth++; \ CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)CURL_UNCONST(data); \ } while(0) @@ -672,7 +676,7 @@ struct cf_call_data { #define CF_DATA_RESTORE(cf, save) \ do { \ DEBUGASSERT(CF_CTX_CALL_DATA(cf).depth == (save).depth + 1); \ - DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + DEBUGASSERT(!(save).data || (save).depth > 0); \ CF_CTX_CALL_DATA(cf) = (save); \ } while(0) diff --git a/lib/config-mac.h b/lib/config-mac.h index fd9d3e7b6032..1e88ca35d8e0 100644 --- a/lib/config-mac.h +++ b/lib/config-mac.h @@ -25,7 +25,7 @@ ***************************************************************************/ /* =================================================================== */ -/* Hand crafted config file for Mac OS 9 */ +/* Handcrafted config file for Mac OS 9 */ /* =================================================================== */ /* On macOS you must run configure to generate curl_config.h file */ /* =================================================================== */ @@ -52,7 +52,6 @@ #define HAVE_SYS_IOCTL_H 1 #define HAVE_ALARM 1 #define HAVE_UTIME 1 -#define HAVE_SELECT 1 #define HAVE_SOCKET 1 #define HAVE_STRUCT_TIMEVAL 1 diff --git a/lib/config-os400.h b/lib/config-os400.h index 3d8d59c97e4b..95db6d424b65 100644 --- a/lib/config-os400.h +++ b/lib/config-os400.h @@ -25,7 +25,7 @@ ***************************************************************************/ /* ================================================================ */ -/* Hand crafted config file for OS/400 */ +/* Handcrafted config file for OS/400 */ /* ================================================================ */ #pragma enum(int) @@ -34,11 +34,10 @@ /* Global configuration parameters: normally generated by autoconf. */ /* ---------------------------------------------------------------- */ -/* Location of default ca bundle */ +/* Location of default CA bundle */ /* Use the system keyring as the default CA bundle. */ #define CURL_CA_BUNDLE "/QIBM/UserData/ICSS/Cert/Server/DEFAULT.KDB" - /* Definition to make a library symbol externally visible. */ #define CURL_EXTERN_SYMBOL @@ -143,9 +142,6 @@ /* Define to 1 if symbol `sa_family_t' exists */ #define HAVE_SA_FAMILY_T 1 -/* Define to 1 if you have the select function. */ -#define HAVE_SELECT 1 - /* Define to 1 if you have the send function. */ #define HAVE_SEND 1 @@ -239,11 +235,6 @@ /* Size of time_t in number of bytes */ #define SIZEOF_TIME_T 4 -/* Define to 1 if all of the C89 standard headers exist (not just the ones - required in a freestanding environment). This macro is provided for - backward compatibility; new code need not use it. */ -#define STDC_HEADERS 1 - /* Define if you want to enable IPv6 support */ #define USE_IPV6 diff --git a/lib/config-riscos.h b/lib/config-riscos.h deleted file mode 100644 index afff218a76c7..000000000000 --- a/lib/config-riscos.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_RISCOS_H -#define HEADER_CURL_CONFIG_RISCOS_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 - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for RISC OS */ -/* ================================================================ */ - -/* Define cpu-machine-OS */ -#ifndef CURL_OS -#define CURL_OS "ARM-RISC OS" -#endif - -/* Define if you want the built-in manual */ -#define USE_MANUAL - -/* Define if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* Define if you have the alarm function. */ -#define HAVE_ALARM - -/* Define if you have the header file. */ -#define HAVE_ARPA_INET_H - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H - -/* Define if getaddrinfo exists and works */ -#define HAVE_GETADDRINFO - -/* Define if you have the `gethostname' function. */ -#define HAVE_GETHOSTNAME - -/* Define if you have the `gettimeofday' function. */ -#define HAVE_GETTIMEOFDAY - -/* Define if you have the `timeval' struct. */ -#define HAVE_STRUCT_TIMEVAL - -/* Define if you have the header file. */ -#define HAVE_NETDB_H - -/* Define if you have the header file. */ -#define HAVE_NETINET_IN_H - -/* Define if you have the header file. */ -#define HAVE_NET_IF_H - -/* Define if you have the `select' function. */ -#define HAVE_SELECT - -/* Define if you have the `signal' function. */ -#define HAVE_SIGNAL - -/* Define if you have the `socket' function. */ -#define HAVE_SOCKET - -/* Define if you have the `stricmp' function. */ -#define HAVE_STRICMP - -/* Define if you have the header file. */ -#define HAVE_SYS_TYPES_H - -/* Define if you have the header file. */ -#define HAVE_TERMIOS_H - -/* Define if you have the header file. */ -#define HAVE_UNISTD_H - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 4 - -/* Define if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO - -/* to disable LDAP */ -#define CURL_DISABLE_LDAP - -/* Define if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 int - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 void * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV ssize_t - -/* Define if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 int - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 void * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV ssize_t - -#endif /* HEADER_CURL_CONFIG_RISCOS_H */ diff --git a/lib/config-win32.h b/lib/config-win32.h index 08e97ff5faaf..a0095df751e9 100644 --- a/lib/config-win32.h +++ b/lib/config-win32.h @@ -24,26 +24,10 @@ * ***************************************************************************/ -/* ================================================================ */ -/* Hand crafted config file for Windows */ -/* ================================================================ */ +/* Handcrafted config file for building via Visual Studio IDE Project Files */ -/* ---------------------------------------------------------------- */ -/* HEADER FILES */ -/* ---------------------------------------------------------------- */ - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define if you have the header file. */ -#define HAVE_IO_H 1 - -/* Define if you have the header file. */ -#define HAVE_LOCALE_H 1 - -/* Define to 1 if you have the header file. */ -#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) -#define HAVE_STDBOOL_H 1 +#if !defined(_MSC_VER) || _MSC_VER > 1800 +#error This manual configuration requires MSVC 2010-2013 (IDE Project builds) #endif /* Define if you have the header file. */ @@ -51,12 +35,6 @@ #define HAVE_SYS_PARAM_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define if you have the header file. */ -#define HAVE_SYS_UTIME_H 1 - #define HAVE_ZLIB_H 1 #define HAVE_LIBZ 1 @@ -70,243 +48,116 @@ #define HAVE_LIBGEN_H 1 #endif -/* ---------------------------------------------------------------- */ -/* OTHER HEADER INFO */ -/* ---------------------------------------------------------------- */ - -/* Define if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to 1 if bool is an available type. */ -#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) -#define HAVE_BOOL_T 1 +/* Define if you have the gettimeofday function. */ +#ifdef __MINGW32__ +#define HAVE_GETTIMEOFDAY 1 #endif -/* ---------------------------------------------------------------- */ -/* FUNCTIONS */ -/* ---------------------------------------------------------------- */ - -/* Define if you have the closesocket function. */ -#define HAVE_CLOSESOCKET 1 +/* Must always use local implementations on Windows. */ +/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ +/* #undef HAVE_INET_NTOP */ +/* Define to 1 if you have an IPv6 capable working inet_pton function. */ +/* #undef HAVE_INET_PTON */ -/* Define to 1 if you have the `getpeername' function. */ -#define HAVE_GETPEERNAME 1 +/* Define to 1 if you have the `basename' function. */ +#ifdef __MINGW32__ +#define HAVE_BASENAME 1 +#endif -/* Define to 1 if you have the getsockname function. */ -#define HAVE_GETSOCKNAME 1 +/* Number of bits in a file offset, on hosts where this is settable. */ +#ifdef __MINGW32__ +# undef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif -/* Define if you have the gethostname function. */ -#define HAVE_GETHOSTNAME 1 +/* + * Headers and functions + */ -/* Define if you have the gettimeofday function. */ -#ifdef __MINGW32__ -#define HAVE_GETTIMEOFDAY 1 +#define HAVE_FCNTL_H 1 +#define HAVE_IO_H 1 +#define HAVE_LOCALE_H 1 +#if _MSC_VER >= 1800 +#define HAVE_STDBOOL_H 1 +#define HAVE_BOOL_T 1 #endif +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UTIME_H 1 -/* Define if you have the ioctlsocket function. */ +#define HAVE_CLOSESOCKET 1 +#define HAVE_FREEADDRINFO 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETADDRINFO_THREADSAFE 1 +#define HAVE_GETHOSTNAME 1 +#define HAVE_GETPEERNAME 1 +#define HAVE_GETSOCKNAME 1 #define HAVE_IOCTLSOCKET 1 - -/* Define if you have a working ioctlsocket FIONBIO function. */ #define HAVE_IOCTLSOCKET_FIONBIO 1 - -/* Define if you have the select function. */ -#define HAVE_SELECT 1 - -/* Define if you have the setlocale function. */ #define HAVE_SETLOCALE 1 - -/* Define if you have the socket function. */ #define HAVE_SOCKET 1 - -/* Define if you have the utime function. */ #define HAVE_UTIME 1 - -/* Define if you have the recv function. */ #define HAVE_RECV 1 - -/* Define to the type of arg 1 for recv. */ #define RECV_TYPE_ARG1 SOCKET - -/* Define to the type of arg 2 for recv. */ #define RECV_TYPE_ARG2 char * - -/* Define to the type of arg 3 for recv. */ #define RECV_TYPE_ARG3 int - -/* Define to the type of arg 4 for recv. */ #define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ #define RECV_TYPE_RETV int - -/* Define if you have the send function. */ #define HAVE_SEND 1 - -/* Define to the type of arg 1 for send. */ #define SEND_TYPE_ARG1 SOCKET - -/* Define to the type of arg 2 for send. */ #define SEND_TYPE_ARG2 char * - -/* Define to the type of arg 3 for send. */ #define SEND_TYPE_ARG3 int - -/* Define to the type of arg 4 for send. */ #define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ #define SEND_TYPE_RETV int - -/* Must always use local implementations on Windows. */ -/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ -/* #undef HAVE_INET_NTOP */ -/* Define to 1 if you have an IPv6 capable working inet_pton function. */ -/* #undef HAVE_INET_PTON */ - -/* Define to 1 if you have the `basename' function. */ -#ifdef __MINGW32__ -#define HAVE_BASENAME 1 -#endif - -/* Define to 1 if you have the signal function. */ #define HAVE_SIGNAL 1 -/* ---------------------------------------------------------------- */ -/* TYPEDEF REPLACEMENTS */ -/* ---------------------------------------------------------------- */ - -/* Define if ssize_t is not an available 'typedefed' type. */ -#ifndef _SSIZE_T_DEFINED -# ifdef __MINGW32__ -# elif defined(_WIN64) -# define _SSIZE_T_DEFINED -# define ssize_t __int64 -# else -# define _SSIZE_T_DEFINED -# define ssize_t int -# endif -#endif - -/* ---------------------------------------------------------------- */ -/* TYPE SIZES */ -/* ---------------------------------------------------------------- */ +/* + * Types and sizes + */ -/* Define to the size of `int', as computed by sizeof. */ #define SIZEOF_INT 4 - -/* Define to the size of `long', as computed by sizeof. */ #define SIZEOF_LONG 4 - -/* Define to the size of `size_t', as computed by sizeof. */ #ifdef _WIN64 # define SIZEOF_SIZE_T 8 +# define ssize_t __int64 #else # define SIZEOF_SIZE_T 4 +# define ssize_t int #endif - -/* Define to the size of `curl_off_t', as computed by sizeof. */ #define SIZEOF_CURL_OFF_T 8 - -/* ---------------------------------------------------------------- */ -/* COMPILER SPECIFIC */ -/* ---------------------------------------------------------------- */ - /* Default to 64-bit time_t unless _USE_32BIT_TIME_T is defined */ -#if defined(_MSC_VER) || defined(__MINGW32__) -# ifndef _USE_32BIT_TIME_T -# define SIZEOF_TIME_T 8 -# else -# define SIZEOF_TIME_T 4 -# endif +#ifndef _USE_32BIT_TIME_T +# define SIZEOF_TIME_T 8 +#else +# define SIZEOF_TIME_T 4 #endif +#define SIZEOF_OFF_T 4 -/* Windows XP is required for freeaddrinfo, getaddrinfo */ -#define HAVE_FREEADDRINFO 1 -#define HAVE_GETADDRINFO 1 -#define HAVE_GETADDRINFO_THREADSAFE 1 - -/* ---------------------------------------------------------------- */ -/* STRUCT RELATED */ -/* ---------------------------------------------------------------- */ - -/* Define if you have struct sockaddr_storage. */ #define HAVE_STRUCT_SOCKADDR_STORAGE 1 - -/* Define if you have struct timeval. */ #define HAVE_STRUCT_TIMEVAL 1 - -/* Define if struct sockaddr_in6 has the sin6_scope_id member. */ #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 -/* ---------------------------------------------------------------- */ -/* LARGE FILE SUPPORT */ -/* ---------------------------------------------------------------- */ - -/* Number of bits in a file offset, on hosts where this is settable. */ -#ifdef __MINGW32__ -# undef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -#endif - -/* Define to the size of `off_t', as computed by sizeof. */ -#ifdef __MINGW32__ -# define SIZEOF_OFF_T 8 -#else -# define SIZEOF_OFF_T 4 -#endif - -/* ---------------------------------------------------------------- */ -/* DNS RESOLVER SPECIALTY */ -/* ---------------------------------------------------------------- */ - /* - * Undefine both USE_ARES and USE_RESOLV_THREADED for synchronous DNS. + * Additional definitions */ /* Default define to enable threaded asynchronous DNS lookups. */ -#if !defined(USE_SYNC_DNS) && !defined(USE_ARES) && \ - !defined(USE_RESOLV_THREADED) +#if !defined(USE_RESOLV_THREADED) && !defined(USE_SYNC_DNS) # define USE_RESOLV_THREADED 1 #endif -#if defined(USE_ARES) && defined(USE_RESOLV_THREADED) -# error "Only one DNS lookup specialty may be defined at most" -#endif - -/* ---------------------------------------------------------------- */ -/* LDAP SUPPORT */ -/* ---------------------------------------------------------------- */ - -#ifndef CURL_WINDOWS_UWP -#define HAVE_LDAP_SSL 1 +#define HAVE_LDAP_SSL 1 #define USE_WIN32_LDAP 1 +#define USE_WIN32_CRYPTO 1 +#define USE_UNIX_SOCKETS 1 -/* Define to use the Windows crypto library. */ -#define USE_WIN32_CRYPTO -#endif /* CURL_WINDOWS_UWP */ - -/* Define to use Unix sockets. */ -#define USE_UNIX_SOCKETS - -/* ---------------------------------------------------------------- */ -/* ADDITIONAL DEFINITIONS */ -/* ---------------------------------------------------------------- */ - -/* Define cpu-machine-OS */ #ifndef CURL_OS -# if defined(_M_IX86) || defined(__i386__) /* x86 (MSVC or gcc) */ +# ifdef _M_IX86 # define CURL_OS "i386-pc-win32" -# elif defined(_M_X64) || defined(__x86_64__) /* x86_64 (VS2005+ or gcc) */ +# elif defined(_M_X64) # define CURL_OS "x86_64-pc-win32" -# elif defined(_M_IA64) || defined(__ia64__) /* Itanium */ -# define CURL_OS "ia64-pc-win32" -# elif defined(_M_ARM_NT) || defined(__arm__) /* ARMv7-Thumb2 */ -# define CURL_OS "thumbv7a-pc-win32" -# elif defined(_M_ARM64) || defined(__aarch64__) /* ARM64 (Windows 10) */ -# define CURL_OS "aarch64-pc-win32" # else # define CURL_OS "unknown-pc-win32" # endif -#endif /* !CURL_OS */ +#endif #endif /* HEADER_CURL_CONFIG_WIN32_H */ diff --git a/lib/conncache.c b/lib/conncache.c index 33fb68b124c6..01681d4ebbc9 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -42,7 +42,7 @@ do { \ if(c) { \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \ + Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT, \ CURL_LOCK_ACCESS_SINGLE); \ DEBUGASSERT(!(c)->locked); \ (c)->locked = TRUE; \ @@ -55,7 +55,7 @@ DEBUGASSERT((c)->locked); \ (c)->locked = FALSE; \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \ + Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \ } \ } while(0) @@ -111,18 +111,14 @@ static void cpool_bundle_free_entry(void *freethis) } void Curl_cpool_init(struct cpool *cpool, - struct Curl_easy *idata, struct Curl_share *share, size_t size) { Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, curlx_str_key_compare, cpool_bundle_free_entry); - DEBUGASSERT(idata); - - cpool->idata = idata; cpool->share = share; - cpool->initialised = TRUE; + cpool->initialized = TRUE; } /* Return the "first" connection in the pool or NULL. */ @@ -186,6 +182,8 @@ static void cpool_discard_conn(struct cpool *cpool, struct connectdata *conn, bool aborted) { + struct cshutdn *cshutdn; + struct Curl_easy *admin; bool done = FALSE; DEBUGASSERT(data); @@ -193,12 +191,13 @@ static void cpool_discard_conn(struct cpool *cpool, DEBUGASSERT(cpool); DEBUGASSERT(!conn->bits.in_cpool); + admin = Curl_get_admin(data); /* * If this connection is not marked to force-close, leave it open if there * are other users of it */ if(CONN_INUSE(conn) && !aborted) { - CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T + CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T " still in use by %u transfers", conn->connection_id, conn->attached_xfers); return; @@ -219,35 +218,36 @@ static void cpool_discard_conn(struct cpool *cpool, done = TRUE; if(!done) { /* Attempt to shutdown the connection right away. */ - Curl_cshutdn_run_once(cpool->idata, conn, &done); + Curl_conn_shutdown_once(admin, conn, &done); } - if(done || !data->multi) - Curl_cshutdn_terminate(cpool->idata, conn, FALSE); + cshutdn = Curl_cshutdn_get(data); + if(done || !cshutdn) + Curl_conn_terminate(admin, conn, FALSE); else - Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn); + Curl_cshutdn_add(cshutdn, conn, cpool->num_conn); } -void Curl_cpool_destroy(struct cpool *cpool) +void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) { - if(cpool && cpool->initialised && cpool->idata) { + if(cpool && cpool->initialized && admin) { struct connectdata *conn; struct Curl_sigpipe_ctx pipe_ctx; - CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections", + CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections", cpool->share ? "[SHARE] " : "", cpool->num_conn); /* Move all connections to the shutdown list */ sigpipe_init(&pipe_ctx); - CPOOL_LOCK(cpool, cpool->idata); + CPOOL_LOCK(cpool, admin); conn = cpool_get_first(cpool); if(conn) - sigpipe_apply(cpool->idata, &pipe_ctx); + sigpipe_apply(admin, &pipe_ctx); while(conn) { cpool_remove_conn(cpool, conn); - cpool_discard_conn(cpool, cpool->idata, conn, FALSE); + cpool_discard_conn(cpool, admin, conn, FALSE); conn = cpool_get_first(cpool); } - CPOOL_UNLOCK(cpool, cpool->idata); + CPOOL_UNLOCK(cpool, admin); sigpipe_restore(&pipe_ctx); Curl_hash_destroy(&cpool->dest2bundle); } @@ -255,27 +255,31 @@ void Curl_cpool_destroy(struct cpool *cpool) 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; - } + /* admin handles do not necessarily find the correct pool */ + DEBUGASSERT(data->mid); + 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; } +struct cpool *Curl_cpool_get_instance(struct Curl_easy *data) +{ + return cpool_get_instance(data); +} + void Curl_cpool_xfer_init(struct Curl_easy *data) { 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) + if(cpool->next_easy_id == CURL_OFF_T_MAX) cpool->next_easy_id = 0; data->state.lastconnect_id = -1; @@ -283,6 +287,7 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) } else { /* We should not get here, but in a non-debug build, do something */ + DEBUGASSERT(0); data->id = 0; data->state.lastconnect_id = -1; } @@ -305,9 +310,9 @@ static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool, return bundle; } -static struct connectdata * -cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle, - const struct curltime *pnow) +static struct connectdata *cpool_bundle_get_oldest_idle( + struct cpool_bundle *bundle, + const struct curltime *pnow) { struct Curl_llist_node *curr; timediff_t highscore = -1; @@ -334,15 +339,16 @@ cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle, } static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, - const struct curltime *pnow) + const struct curltime *pnow, + timediff_t min_age_ms) { struct Curl_hash_iterator iter; struct Curl_llist_node *curr; struct Curl_hash_element *he; - struct connectdata *oldest_idle = NULL; struct cpool_bundle *bundle; - timediff_t highscore = -1; - timediff_t score; + struct connectdata *oldest_idle = NULL; + timediff_t oldest_idle_ms = -1; + timediff_t idle_ms; Curl_hash_start_iterate(&cpool->dest2bundle, &iter); @@ -356,10 +362,9 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, conn = Curl_node_elem(curr); if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only) continue; - /* Set higher score for the age passed since the connection was used */ - score = curlx_ptimediff_ms(pnow, &conn->lastused); - if(score > highscore) { - highscore = score; + idle_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { + oldest_idle_ms = idle_ms; oldest_idle = conn; } } @@ -367,10 +372,91 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, return oldest_idle; } +static void cpool_conn_close(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + struct Curl_easy *admin; + bool do_lock; + + DEBUGASSERT(cpool); + DEBUGASSERT(data && !data->conn); + if(!cpool) + return; + + /* 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, "conn terminate when inuse: %u", conn->attached_xfers)); + return; + } + + /* This method may be called while we are under lock, e.g. from a + * user callback in find. */ + admin = Curl_get_admin(data); + do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool, admin); + + if(conn->bits.in_cpool) { + cpool_remove_conn(cpool, conn); + DEBUGASSERT(!conn->bits.in_cpool); + } + + /* treat the connection as aborted in CONNECT_ONLY situations, + * so no graceful shutdown is attempted. */ + if(conn->bits.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); + } + else { + /* No multi available, terminate */ + infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); + Curl_conn_terminate(admin, conn, !aborted); + } + + if(do_lock) + CPOOL_UNLOCK(cpool, admin); +} + +void Curl_conn_close(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + struct cpool *cpool = cpool_get_instance(data); + cpool_conn_close(cpool, data, conn, aborted); +} + +/* Evict an idle connection to make room in the pool. A pool owned by + * a share has no multi that could perform a controlled shutdown of the + * connection; terminate it right away. Otherwise, hand it to the + * transfer's multi for shutdown. Expects the pool to be locked. */ +static void cpool_evict_conn(struct cpool *cpool, + struct Curl_easy *admin, + struct connectdata *conn) +{ + if(cpool->share) { + cpool_remove_conn(cpool, conn); + Curl_conn_terminate(admin, conn, TRUE); + } + else + cpool_conn_close(cpool, admin, conn, FALSE); +} + int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); + struct cshutdn *cshutdn = Curl_cshutdn_get(data); + struct Curl_easy *admin; struct cpool_bundle *bundle; size_t dest_limit = 0; size_t total_limit = 0; @@ -380,25 +466,27 @@ int Curl_cpool_check_limits(struct Curl_easy *data, 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; + /* multi determines the limits, no matter who owns the pool */ + if(data->multi) { + dest_limit = data->multi->max_host_connections; + total_limit = data->multi->max_total_connections; } if(!dest_limit && !total_limit) return CPOOL_LIMIT_OK; - CPOOL_LOCK(cpool, cpool->idata); + admin = Curl_get_admin(data); + CPOOL_LOCK(cpool, admin); if(dest_limit) { size_t live; bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = Curl_cshutdn_dest_count(data, conn->destination); + shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); while((live + shutdowns) >= dest_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(data, conn->destination)) + if(!Curl_cshutdn_close_oldest(cshutdn, conn->destination)) break; } else if(!bundle) @@ -407,22 +495,21 @@ int Curl_cpool_check_limits(struct Curl_easy *data, 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, - Curl_pgrs_now(data)); + oldest_idle = cpool_bundle_get_oldest_idle(bundle, pnow); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" FMT_OFF_T + CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T " from %zu to reach destination limit of %zu", oldest_idle->connection_id, Curl_llist_count(&bundle->conns), dest_limit); - Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); /* in case the bundle was destroyed in disconnect, look it up again */ bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; } - shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination); + shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); } if((live + shutdowns) >= dest_limit) { res = CPOOL_LIMIT_DEST; @@ -431,26 +518,26 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } if(total_limit) { - shutdowns = Curl_cshutdn_count(cpool->idata); + shutdowns = Curl_cshutdn_count(cshutdn); while((cpool->num_conn + shutdowns) >= total_limit) { if(shutdowns) { /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(data, NULL)) + if(!Curl_cshutdn_close_oldest(cshutdn, NULL)) break; } else { struct connectdata *oldest_idle = - cpool_get_oldest_idle(cpool, Curl_pgrs_now(data)); + cpool_get_oldest_idle(cpool, pnow, 0); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" + CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T " from %zu to reach total " "limit of %zu", oldest_idle->connection_id, cpool->num_conn, total_limit); - Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); } - shutdowns = Curl_cshutdn_count(cpool->idata); + shutdowns = Curl_cshutdn_count(cshutdn); } if((cpool->num_conn + shutdowns) >= total_limit) { res = CPOOL_LIMIT_TOTAL; @@ -459,7 +546,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } out: - CPOOL_UNLOCK(cpool, cpool->idata); + CPOOL_UNLOCK(cpool, admin); return res; } @@ -512,7 +599,8 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, static bool cpool_foreach(struct Curl_easy *data, struct cpool *cpool, void *param, - int (*func)(struct Curl_easy *data, + int (*func)(struct cpool *cpool, + struct Curl_easy *data, struct connectdata *conn, void *param)) { struct Curl_hash_iterator iter; @@ -536,7 +624,7 @@ static bool cpool_foreach(struct Curl_easy *data, struct connectdata *conn = Curl_node_elem(curr); curr = Curl_node_next(curr); - if(func(data, conn, param) == 1) { + if(func(cpool, data, conn, param) == 1) { return TRUE; } } @@ -556,37 +644,46 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, unsigned int maxconnects; struct connectdata *oldest_idle = NULL; struct cpool *cpool = cpool_get_instance(data); + struct Curl_easy *admin; bool kept = TRUE; + timediff_t min_age_ms = 0; if(!data || !data->multi) return kept; if(!data->multi->maxconnects) { - unsigned int running = Curl_multi_xfers_running(data->multi); - maxconnects = (running <= UINT_MAX / 4) ? running * 4 : UINT_MAX; + /* Attached transfers is a weak indicator of business. */ + uint32_t attached = Curl_multi_xfers_attached(data->multi); + maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX; + /* We are guessing. So, only evict a "seemingly superfluous" connection + * when has not been used for this long, */ + min_age_ms = 1000; } else { maxconnects = data->multi->maxconnects; } - conn->lastused = *Curl_pgrs_now(data); /* it was used up until now */ + /* remember times, connection had been used just before */ + conn->lastchecked = conn->lastupkeep = conn->lastused = *Curl_pgrs_now(data); if(cpool && maxconnects) { /* may be called form a callback already under lock */ bool do_lock = !CPOOL_IS_LOCKED(cpool); + + admin = Curl_get_admin(data); if(do_lock) - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool, admin); if(cpool->num_conn > maxconnects) { infof(data, "Connection pool is full, closing the oldest of %zu/%u", cpool->num_conn, maxconnects); - oldest_idle = cpool_get_oldest_idle(cpool, Curl_pgrs_now(data)); + oldest_idle = cpool_get_oldest_idle(cpool, &conn->lastused, min_age_ms); kept = (oldest_idle != conn); if(oldest_idle) { - Curl_conn_terminate(data, oldest_idle, FALSE); + cpool_evict_conn(cpool, admin, oldest_idle); } } if(do_lock) - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } return kept; @@ -632,78 +729,25 @@ bool Curl_cpool_find(struct Curl_easy *data, return found; } -void Curl_conn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool aborted) -{ - struct cpool *cpool = cpool_get_instance(data); - bool do_lock; - - DEBUGASSERT(cpool); - DEBUGASSERT(data && !data->conn); - if(!cpool) - return; - - /* 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, "conn terminate when inuse: %u", conn->attached_xfers)); - return; - } - - /* 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->bits.in_cpool) { - cpool_remove_conn(cpool, conn); - DEBUGASSERT(!conn->bits.in_cpool); - } - - /* treat the connection as aborted in CONNECT_ONLY situations, - * so no graceful shutdown is attempted. */ - if(conn->bits.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); - } - else { - /* No multi available, terminate */ - infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - Curl_cshutdn_terminate(cpool->idata, conn, !aborted); - } - - if(do_lock) - CPOOL_UNLOCK(cpool, data); -} - struct cpool_reaper_ctx { - size_t checked; size_t reaped; + struct curltime now; }; -static int cpool_reap_dead_cb(struct Curl_easy *data, +static int cpool_reap_dead_cb(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { struct cpool_reaper_ctx *reaper = param; - bool terminate = !CONN_INUSE(conn) && conn->bits.no_reuse; - if(!terminate) { - reaper->checked++; - terminate = Curl_conn_seems_dead(conn, data); - } - if(terminate) { - /* stop the iteration here, pass back the connection that was pruned */ - reaper->reaped++; - Curl_conn_terminate(data, conn, FALSE); - return 1; + if(!CONN_INUSE(conn)) { + if(conn->bits.no_reuse || conn->bits.close || + !Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) { + /* terminate conn and stop the iteration */ + reaper->reaped++; + cpool_conn_close(cpool, admin, conn, FALSE); + return 1; + } } return 0; /* continue iteration */ } @@ -715,46 +759,69 @@ static int cpool_reap_dead_cb(struct Curl_easy *data, * * When called, this transfer has no connection attached. */ -void Curl_cpool_prune_dead(struct Curl_easy *data) +void Curl_cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data) { - struct cpool *cpool = cpool_get_instance(data); - struct cpool_reaper_ctx reaper; + struct Curl_easy *admin; timediff_t elapsed; if(!cpool) return; - memset(&reaper, 0, sizeof(reaper)); - CPOOL_LOCK(cpool, data); - elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), &cpool->last_cleanup); + admin = Curl_get_admin(data); + CPOOL_LOCK(cpool, admin); + elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup); if(elapsed >= 1000L) { - while(cpool_foreach(data, cpool, &reaper, cpool_reap_dead_cb)) + struct cpool_reaper_ctx reaper; + + memset(&reaper, 0, sizeof(reaper)); + reaper.now = *Curl_pgrs_now(admin); + while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb)) ; - cpool->last_cleanup = *Curl_pgrs_now(data); + cpool->last_cleanup = *Curl_pgrs_now(admin); } - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } -static int conn_upkeep(struct Curl_easy *data, +static int conn_upkeep(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { + const struct curltime *pnow = Curl_pgrs_now(admin); + (void)param; - Curl_conn_upkeep(data, conn); + if(curlx_ptimediff_ms(pnow, &conn->lastupkeep) >= + admin->set.upkeep_interval_ms) { + CURLcode result; + + conn->lastupkeep = *pnow; + /* briefly attach for action */ + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_keep_alive(admin, conn); + Curl_detach_connection(admin); + + if(result && !CONN_INUSE(conn)) { + cpool_conn_close(cpool, admin, conn, FALSE); + return 1; + } + } return 0; /* continue iteration */ } CURLcode Curl_cpool_upkeep(struct Curl_easy *data) { struct cpool *cpool = cpool_get_instance(data); + struct Curl_easy *admin = Curl_get_admin(data); if(!cpool) return CURLE_OK; - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, NULL, conn_upkeep); - CPOOL_UNLOCK(cpool, data); + CPOOL_LOCK(cpool, admin); + while(cpool_foreach(admin, cpool, NULL, conn_upkeep)) + ; + CPOOL_UNLOCK(cpool, admin); return CURLE_OK; } @@ -763,10 +830,12 @@ struct cpool_find_ctx { struct connectdata *conn; }; -static int cpool_find_conn(struct Curl_easy *data, +static int cpool_find_conn(struct cpool *cpool, + struct Curl_easy *data, struct connectdata *conn, void *param) { struct cpool_find_ctx *fctx = param; + (void)cpool; (void)data; if(conn->connection_id == fctx->id) { fctx->conn = conn; @@ -791,40 +860,6 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, return fctx.conn; } -struct cpool_do_conn_ctx { - curl_off_t id; - Curl_cpool_conn_do_cb *cb; - void *cbdata; -}; - -static int cpool_do_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct cpool_do_conn_ctx *dctx = param; - - 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) @@ -839,39 +874,113 @@ void Curl_cpool_do_locked(struct Curl_easy *data, cb(conn, data, cbdata); } -static int cpool_mark_stale(struct Curl_easy *data, +static int cpool_mark_stale(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { - (void)data; + (void)cpool; + (void)admin; (void)param; conn->bits.no_reuse = TRUE; return 0; } -static int cpool_reap_no_reuse(struct Curl_easy *data, +static int cpool_reap_no_reuse(struct cpool *cpool, + struct Curl_easy *admin, struct connectdata *conn, void *param) { (void)param; if(!CONN_INUSE(conn) && conn->bits.no_reuse) { - Curl_conn_terminate(data, conn, FALSE); + cpool_conn_close(cpool, admin, conn, FALSE); return 1; } return 0; /* continue iteration */ } -void Curl_cpool_nw_changed(struct Curl_easy *data) +void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) { - struct cpool *cpool = cpool_get_instance(data); - - if(cpool) { - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, NULL, cpool_mark_stale); - while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse)) + if(cpool && admin) { + CPOOL_LOCK(cpool, admin); + cpool_foreach(admin, cpool, NULL, cpool_mark_stale); + while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse)) ; - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool, admin); } } +/* A connection has to have been idle for less than 'conn_max_idle_ms' + (the success rate is too low after this), or created less than + 'conn_max_age_ms' ago, to be subject for reuse. */ +static bool cpool_conn_maxage(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + timediff_t age_ms; + + if(data->set.conn_max_idle_ms) { + age_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + if(age_ms > data->set.conn_max_idle_ms) { + infof(data, "Too old connection (%" FMT_TIMEDIFF_T + " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", + age_ms, data->set.conn_max_idle_ms); + return TRUE; + } + } + + if(data->set.conn_max_age_ms) { + age_ms = curlx_ptimediff_ms(pnow, &conn->created); + if(age_ms > data->set.conn_max_age_ms) { + infof(data, + "Too old connection (created %" FMT_TIMEDIFF_T + " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it", + age_ms, data->set.conn_max_age_ms); + return TRUE; + } + } + + return FALSE; +} + +bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, + struct Curl_easy *data, + const struct curltime *pnow) +{ + struct Curl_easy *admin; + bool healthy = TRUE; + + DEBUGASSERT(!data->conn); + if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ + return FALSE; + if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) + return TRUE; + + admin = Curl_get_admin(data); + if(conn->scheme->run->connection_is_dead) { + Curl_attach_connection(admin, conn, FALSE); + healthy = !conn->scheme->run->connection_is_dead(admin, conn); + Curl_detach_connection(admin); + } + else { + bool input_pending = FALSE; + + Curl_attach_connection(admin, conn, FALSE); + healthy = Curl_conn_is_alive(admin, conn, &input_pending); + Curl_detach_connection(admin); + if(healthy && input_pending && + !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + /* Non-multiplexed connections without attached transfers should + * not have input pending. The input might be a TLS Notify Close, + * for all we know. */ + DEBUGF(infof(data, "connection has no transfer but input, not healthy")); + healthy = FALSE; + } + } + + if(healthy) + conn->lastchecked = *pnow; + return healthy; +} + #if 0 /* Useful for debugging the connection pool */ void Curl_cpool_print(struct cpool *cpool) diff --git a/lib/conncache.h b/lib/conncache.h index 7cee4d4729a5..5e766c99d73e 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -34,7 +34,7 @@ struct Curl_multi; struct Curl_share; /** - * Terminate the connection, e.g. close and destroy. + * Close and destroy the connection. * 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. @@ -42,9 +42,9 @@ struct Curl_share; * 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); +void Curl_conn_close(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); struct cpool { /* the pooled connections, bundled per destination */ @@ -53,22 +53,24 @@ struct cpool { curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; - struct Curl_easy *idata; /* internal handle for maintenance */ struct Curl_share *share; /* != NULL if pool belongs to share */ BIT(locked); - BIT(initialised); + BIT(initialized); }; +/* Get connection pool instance for data or NULL if none exists */ +struct cpool *Curl_cpool_get_instance(struct Curl_easy *data); + /* 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 *cpool); +void Curl_cpool_destroy(struct cpool *cpool, + struct Curl_easy *admin); /* Init the transfer to be used within its connection pool. * Assigns `data->id`. */ @@ -91,7 +93,8 @@ CURLcode Curl_cpool_add(struct Curl_easy *data, #define CPOOL_LIMIT_DEST 1 #define CPOOL_LIMIT_TOTAL 2 int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn, + const struct curltime *pnow); /* Return of conn is suitable. If so, stops iteration. */ typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, @@ -126,13 +129,12 @@ 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 + * Scans the 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); +void Curl_cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data); /** * Perform upkeep actions on connections in the transfer's pool. @@ -143,14 +145,6 @@ 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. @@ -162,6 +156,12 @@ void Curl_cpool_do_locked(struct Curl_easy *data, Curl_cpool_conn_do_cb *cb, void *cbdata); /* Close all unused connections, prevent reuse of existing ones. */ -void Curl_cpool_nw_changed(struct Curl_easy *data); +void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin); + +/* Return TRUE iff the given connection is considered healthy, e.g. + * usable for more transfers. */ +bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, + struct Curl_easy *data, + const struct curltime *pnow); #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/lib/connect.c b/lib/connect.c index b13d496848a1..cb3647544ef8 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -23,51 +23,20 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef HAVE_NETINET_IN_H -#include /* may need it */ -#endif -#ifdef HAVE_SYS_UN_H -#include /* for sockaddr_un */ -#endif -#ifdef HAVE_LINUX_TCP_H -#include -#elif defined(HAVE_NETINET_TCP_H) -#include -#endif -#ifdef HAVE_SYS_IOCTL_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 "curl_trc.h" #include "strerror.h" #include "cfilters.h" #include "connect.h" -#include "cf-dns.h" -#include "cf-haproxy.h" #include "cf-https-connect.h" -#include "cf-ip-happy.h" -#include "cf-socket.h" +#include "cf-setup.h" #include "multiif.h" -#include "curlx/inet_ntop.h" -#include "curlx/strparse.h" -#include "vtls/vtls.h" /* for vtls cfilters */ #include "progress.h" #include "conncache.h" #include "multihandle.h" -#include "http_proxy.h" -#include "socks.h" +#include "select.h" +#include "vdns/cf-dns.h" +#include "curlx/strparse.h" #if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR) @@ -88,12 +57,6 @@ enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len) return ALPN_none; /* unknown, probably rubbish input */ } -enum alpnid Curl_str2alpnid(const struct Curl_str *cstr) -{ - return Curl_alpn2alpnid((const unsigned char *)curlx_str(cstr), - curlx_strlen(cstr)); -} - #endif /* @@ -112,13 +75,13 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, timediff_t timeleft_ms = 0; timediff_t ctimeleft_ms = 0; - if(Curl_shutdown_started(data, FIRSTSOCKET)) + if(data->conn && Curl_shutdown_started(data->conn, FIRSTSOCKET)) return Curl_shutdown_timeleft(data, data->conn, FIRSTSOCKET); else if(Curl_is_connecting(data)) { timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; ctimeleft_ms = ctimeout_ms - - curlx_ptimediff_ms(pnow, &data->progress.t_startsingle); + Curl_pgrs_since_ms(data, pnow, TIMER_STARTSINGLE); if(!ctimeleft_ms) ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ } @@ -128,7 +91,7 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, if(data->set.timeout) { timeleft_ms = data->set.timeout - - curlx_ptimediff_ms(pnow, &data->progress.t_startop); + Curl_pgrs_since_ms(data, pnow, TIMER_STARTOP); if(!timeleft_ms) timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ } @@ -145,27 +108,34 @@ timediff_t Curl_timeleft_ms(struct Curl_easy *data) return timeleft_now_ms(data, Curl_pgrs_now(data)); } -void Curl_shutdown_start(struct Curl_easy *data, int sockindex, +timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, + const struct curltime *pnow) +{ + return timeleft_now_ms(data, pnow); +} + +void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, int timeout_ms) { struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); DEBUGASSERT(conn); - conn->shutdown.start[sockindex] = *Curl_pgrs_now(data); + conn->shutdown.start[sockindex] = *pnow; conn->shutdown.timeout_ms = (timeout_ms > 0) ? (timediff_t)timeout_ms : ((data->set.shutdowntimeout > 0) ? data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); /* Set a timer, unless we operate on the admin handle */ if(data->mid) - Curl_expire_ex(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN); + Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); CURL_TRC_M(data, "shutdown start on%s connection", sockindex ? " secondary" : ""); } timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int8_t sockindex) { timediff_t left_ms; @@ -183,7 +153,7 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn) { timediff_t left_ms = 0, ms; - int i; + int8_t i; for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { if(!conn->shutdown.start[i].tv_sec) @@ -195,72 +165,16 @@ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, return left_ms; } -void Curl_shutdown_clear(struct Curl_easy *data, int sockindex) +void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) { struct curltime *pt = &data->conn->shutdown.start[sockindex]; memset(pt, 0, sizeof(*pt)); } -bool Curl_shutdown_started(struct Curl_easy *data, int sockindex) +bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) { - if(data->conn) { - struct curltime *pt = &data->conn->shutdown.start[sockindex]; - return (pt->tv_sec > 0) || (pt->tv_usec > 0); - } - return FALSE; -} - -/* retrieves ip address and port from a sockaddr structure. note it calls - curlx_inet_ntop which sets errno on fail, not SOCKERRNO. */ -bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, - char *addr, uint16_t *port) -{ - struct sockaddr_in *si = NULL; -#ifdef USE_IPV6 - struct sockaddr_in6 *si6 = NULL; -#endif -#ifdef USE_UNIX_SOCKETS - struct sockaddr_un *su = NULL; -#else - (void)salen; -#endif - - switch(sa->sa_family) { - case AF_INET: - si = (struct sockaddr_in *)(void *)sa; - if(curlx_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { - *port = ntohs(si->sin_port); - return TRUE; - } - break; -#ifdef USE_IPV6 - case AF_INET6: - si6 = (struct sockaddr_in6 *)(void *)sa; - if(curlx_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { - *port = ntohs(si6->sin6_port); - return TRUE; - } - break; -#endif -#ifdef USE_UNIX_SOCKETS - case AF_UNIX: - if(salen > (curl_socklen_t)sizeof(CURL_SA_FAMILY_T)) { - su = (struct sockaddr_un *)sa; - curl_msnprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); - } - else - addr[0] = 0; /* socket with no name */ - *port = 0; - return TRUE; -#endif - default: - break; - } - - addr[0] = '\0'; - *port = 0; - errno = SOCKEAFNOSUPPORT; - return FALSE; + const struct curltime *pt = &conn->shutdown.start[sockindex]; + return (pt->tv_sec > 0) || (pt->tv_usec > 0); } /* @@ -285,353 +199,250 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); if(!conn) { data->state.lastconnect_id = -1; + if(connp) + *connp = NULL; return CURL_SOCKET_BAD; } if(connp) - /* only store this if the caller cares for it */ *connp = conn; return conn->sock[FIRSTSOCKET]; } + if(connp) + *connp = NULL; return CURL_SOCKET_BAD; } -/* - * Curl_conncontrol() marks streams or connection for closure. - */ -void Curl_conncontrol(struct connectdata *conn, - int ctrl /* see defines in header */ -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - , const char *reason -#endif - ) +void Curl_conncontrol(struct connectdata *conn, int ctrl) { - /* 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; - DEBUGASSERT(conn); -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - (void)reason; /* useful for debugging */ -#endif - is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); - closeit = (ctrl == CONNCTRL_CONNECTION) || - ((ctrl == CONNCTRL_STREAM) && !is_multiplex); - if((ctrl == CONNCTRL_STREAM) && is_multiplex) - ; /* stream signal on multiplex conn never affects close state */ - else if((curl_bit)closeit != conn->bits.close) { - conn->bits.close = closeit; /* the only place in the source code that - should assign this bit */ + if(!conn) { + DEBUGASSERT(0); + return; + } + switch(ctrl) { + case CONNCTRL_CONN_KEEP: + conn->bits.close = FALSE; + break; + case CONNCTRL_CONN_CLOSE: + conn->bits.close = TRUE; + break; + case CONNCTRL_STREAM_CLOSE: + /* stream close when multiplexing does not affect connection */ + if(!Curl_conn_is_multiplex(conn, FIRSTSOCKET)) + conn->bits.close = TRUE; + break; + default: + DEBUGASSERT(0); + break; } } -typedef enum { - CF_SETUP_INIT, - CF_SETUP_CNNCT_EYEBALLS, - CF_SETUP_CNNCT_SOCKS, - CF_SETUP_CNNCT_HTTP_PROXY, - CF_SETUP_CNNCT_HAPROXY, - CF_SETUP_CNNCT_SSL, - CF_SETUP_DONE -} cf_setup_state; - -struct cf_setup_ctx { - cf_setup_state state; - int ssl_mode; - uint8_t transport; -}; - -static CURLcode cf_setup_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + int ssl_mode) { - struct cf_setup_ctx *ctx = cf->ctx; + struct Curl_peer *first_peer = Curl_conn_get_first_peer(conn, sockindex); CURLcode result = CURLE_OK; + uint8_t dns_queries; - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - - /* connect current sub-chain */ -connect_sub_chain: - - if(cf->next && !cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, done); - if(result || !*done) - return result; - } + DEBUGASSERT(data); + DEBUGASSERT(conn->scheme); + DEBUGASSERT(!conn->cfilter[sockindex]); - if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { - result = cf_ip_happy_insert_after(cf, data, ctx->transport); - if(result) - return result; - ctx->state = CF_SETUP_CNNCT_EYEBALLS; - if(!cf->next || !cf->next->connected) - goto connect_sub_chain; - } + if(!first_peer) + return CURLE_FAILED_INIT; - /* sub-chain connected, do we need to add more? */ -#ifndef CURL_DISABLE_PROXY - if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) { - /* for the secondary socket (FTP), use the "connect to host" - * but ignore the "connect to port" (use the secondary port) - */ - const char *hostname = - cf->conn->bits.httpproxy ? - cf->conn->http_proxy.host.name : - cf->conn->bits.conn_to_host ? - cf->conn->conn_to_host.name : - cf->sockindex == SECONDARYSOCKET ? - cf->conn->secondaryhostname : cf->conn->host.name; - uint16_t port = - cf->conn->bits.httpproxy ? cf->conn->http_proxy.port : - cf->sockindex == SECONDARYSOCKET ? cf->conn->secondary_port : - cf->conn->bits.conn_to_port ? cf->conn->conn_to_port : - cf->conn->remote_port; - const char *user = cf->conn->socks_proxy.user; - const char *passwd = cf->conn->socks_proxy.passwd; - - result = Curl_cf_socks_proxy_insert_after( - cf, data, hostname, port, cf->conn->ip_version, - cf->conn->socks_proxy.proxytype, user, passwd); +#ifndef CURL_DISABLE_HTTP + if(!conn->cfilter[sockindex] && + conn->scheme->protocol == CURLPROTO_HTTPS) { + DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); + result = Curl_cf_https_setup( + data, Curl_conn_get_destination(conn, sockindex), conn, sockindex); if(result) - return result; - ctx->state = CF_SETUP_CNNCT_SOCKS; - if(!cf->next || !cf->next->connected) - goto connect_sub_chain; + goto out; } - - if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) { -#ifdef USE_SSL - if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) && - !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { - result = Curl_cf_ssl_proxy_insert_after(cf, data); - if(result) - return result; - } -#endif /* USE_SSL */ - -#ifndef CURL_DISABLE_HTTP - if(cf->conn->bits.tunnel_proxy) { - result = Curl_cf_http_proxy_insert_after(cf, data); - if(result) - return result; - } #endif /* !CURL_DISABLE_HTTP */ - ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; - if(!cf->next || !cf->next->connected) - goto connect_sub_chain; - } -#endif /* !CURL_DISABLE_PROXY */ - if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { -#ifndef CURL_DISABLE_PROXY - if(data->set.haproxyprotocol) { - if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) { - failf(data, "haproxy protocol not supported with SSL " - "encryption in place (QUIC?)"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - result = Curl_cf_haproxy_insert_after(cf, data); - if(result) - return result; - } -#endif /* !CURL_DISABLE_PROXY */ - ctx->state = CF_SETUP_CNNCT_HAPROXY; - if(!cf->next || !cf->next->connected) - goto connect_sub_chain; + /* Still no cfilter set, apply default. */ + if(!conn->cfilter[sockindex]) { + result = Curl_cf_setup_add(data, conn, sockindex, + conn->transport_wanted, ssl_mode); + if(result) + goto out; } - if(ctx->state < CF_SETUP_CNNCT_SSL) { -#ifdef USE_SSL - if((ctx->ssl_mode == CURL_CF_SSL_ENABLE || - (ctx->ssl_mode != CURL_CF_SSL_DISABLE && - cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */ - !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ - result = Curl_cf_ssl_insert_after(cf, data); - if(result) - return result; - } -#endif /* USE_SSL */ - ctx->state = CF_SETUP_CNNCT_SSL; - if(!cf->next || !cf->next->connected) - goto connect_sub_chain; - } + /* Whatever the filter chain will be in the end, it will need the + * resolving of `first_peer`. Add that now so the resolve is started + * right away. */ + dns_queries = Curl_resolv_dns_queries(data, conn->ip_version); + result = Curl_conn_dns_add_addr_resolve(data, conn, sockindex, + first_peer, dns_queries, + conn->transport_wanted); + if(result) + goto out; - ctx->state = CF_SETUP_DONE; - cf->connected = TRUE; - *done = TRUE; - return CURLE_OK; + DEBUGASSERT(conn->cfilter[sockindex]); +out: + return result; } -static void cf_setup_close(struct Curl_cfilter *cf, - struct Curl_easy *data) +#ifdef CURLVERBOSE +static CURLcode conn_connect_trace(struct Curl_easy *data, + struct Curl_cfilter *cf) { - struct cf_setup_ctx *ctx = cf->ctx; + if(Curl_trc_is_verbose(data)) { + struct ip_quadruple ipquad; + bool is_ipv6; + CURLcode result; - CURL_TRC_CF(data, cf, "close"); - cf->connected = FALSE; - ctx->state = CF_SETUP_INIT; + result = Curl_conn_cf_get_ip_info(cf, data, &is_ipv6, &ipquad); + if(result) + return result; - if(cf->next) { - cf->next->cft->do_close(cf->next, data); - Curl_conn_cf_discard_chain(&cf->next, data); + infof(data, "Established %sconnection to %s (%s port %u) from %s port %u ", + (cf->sockindex == SECONDARYSOCKET) ? "2nd " : "", + CURL_CONN_HOST_DISPNAME(data->conn), + ipquad.remote_ip, ipquad.remote_port, + ipquad.local_ip, ipquad.local_port); } + return CURLE_OK; } +#endif -static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_setup_ctx *ctx = cf->ctx; - - CURL_TRC_CF(data, cf, "destroy"); - curlx_safefree(ctx); -} - -struct Curl_cftype Curl_cft_setup = { - "SETUP", - CF_TYPE_SETUP, - CURL_LOG_LVL_NONE, - cf_setup_destroy, - cf_setup_connect, - cf_setup_close, - Curl_cf_def_shutdown, - Curl_cf_def_adjust_pollset, - Curl_cf_def_data_pending, - Curl_cf_def_send, - Curl_cf_def_recv, - Curl_cf_def_cntrl, - Curl_cf_def_conn_is_alive, - Curl_cf_def_conn_keep_alive, - Curl_cf_def_query, -}; - -static CURLcode cf_setup_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - uint8_t transport, - int ssl_mode) +/** + * Update connection statistics + */ +static void conn_report_stats(struct Curl_easy *data, int sockindex) { - struct Curl_cfilter *cf = NULL; - struct cf_setup_ctx *ctx; - CURLcode result = CURLE_OK; - - (void)data; - ctx = curlx_calloc(1, sizeof(*ctx)); - if(!ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; + /* We do gather stats for the second socket...yet */ + if(sockindex == FIRSTSOCKET) { + Curl_conn_cntrl_report_stats(data, data->conn, sockindex); } - ctx->state = CF_SETUP_INIT; - ctx->ssl_mode = ssl_mode; - ctx->transport = transport; - - result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); - if(result) - goto out; - ctx = NULL; - -out: - *pcf = result ? NULL : cf; - if(ctx) { - curlx_free(ctx); - } - return result; } -static CURLcode cf_setup_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - uint8_t transport, - int ssl_mode) +CURLcode Curl_conn_connect(struct Curl_easy *data, + int8_t sockindex, + 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 easy_pollset ps; + struct curl_pollfds cpfds; struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_setup_create(&cf, data, transport, ssl_mode); - if(result) - goto out; - Curl_conn_cf_add(data, conn, sockindex, cf); -out: - return result; -} + DEBUGASSERT(data->conn); + if(!CONN_SOCK_IDX_VALID(sockindex)) + return CURLE_BAD_FUNCTION_ARGUMENT; -CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t transport, - int ssl_mode) -{ - struct Curl_cfilter *cf; - CURLcode result; + if(data->conn->scheme->flags & PROTOPT_NONETWORK) { + *done = TRUE; + return CURLE_OK; + } - DEBUGASSERT(data); - result = cf_setup_create(&cf, data, transport, ssl_mode); - if(result) - goto out; - Curl_conn_cf_insert_after(cf_at, cf); -out: - return result; -} + cf = data->conn->cfilter[sockindex]; + if(!cf) { + *done = FALSE; + return CURLE_FAILED_INIT; + } -CURLcode Curl_conn_setup(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - struct Curl_dns_entry *dns, - int ssl_mode) -{ - CURLcode result = CURLE_OK; - uint8_t dns_queries; + *done = (bool)cf->connected; + if(*done) + return CURLE_OK; - DEBUGASSERT(data); - DEBUGASSERT(conn->scheme); - DEBUGASSERT(!conn->cfilter[sockindex]); + Curl_pollset_init(&ps); + 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)) + goto out; + } -#ifndef CURL_DISABLE_HTTP - if(!conn->cfilter[sockindex] && - conn->scheme->protocol == CURLPROTO_HTTPS) { - DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); - result = Curl_cf_https_setup(data, conn, sockindex); - if(result) + result = cf->cft->do_connect(cf, data, done); + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=%d) -> %d, done=%d", + blocking, (int)result, *done); + if(!result && *done) { + /* A final sanity check on connection security */ + if((data->state.origin->scheme->flags & PROTOPT_SSL) && + (sockindex == FIRSTSOCKET) && + !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { + DEBUGASSERT(0); + failf(data, "transfer requires SSL, but not connected via SSL"); + result = CURLE_FAILED_INIT; + goto out; + } + /* 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. */ + Curl_conn_cntrl_update_info(data, data->conn); + conn_report_stats(data, sockindex); + data->conn->lastupkeep = *Curl_pgrs_now(data); + VERBOSE(result = conn_connect_trace(data, cf)); + VERBOSE(Curl_conn_trc_filters(data, sockindex, "connected")); + Curl_conn_remove_setup_filters(data, sockindex); + VERBOSE(Curl_conn_trc_filters(data, sockindex, "reduced to")); goto out; - } -#endif /* !CURL_DISABLE_HTTP */ + } + else if(result) { + CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", + (int)result); + VERBOSE(Curl_conn_trc_filters(data, sockindex, "failed to connect")); + conn_report_stats(data, sockindex); + goto out; + } - /* Still no cfilter set, apply default. */ - if(!conn->cfilter[sockindex]) { - result = cf_setup_add(data, conn, sockindex, - conn->transport_wanted, ssl_mode); - if(result) + if(!blocking) goto out; + else { + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft_ms(data); + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + 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_pollset_reset(&ps); + Curl_pollfds_reset(&cpfds); + /* In general, we want to send after connect, wait on that. */ + if(sockfd != CURL_SOCKET_BAD) + result = Curl_pollset_set_out_only(data, &ps, sockfd); + if(!result) + result = Curl_conn_adjust_pollset(data, data->conn, &ps); + if(result) + goto out; + 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 */ + } } - dns_queries = Curl_resolv_dns_queries(data, conn->ip_version); -#ifdef USE_HTTPSRR - if(sockindex == FIRSTSOCKET) - dns_queries |= CURL_DNSQ_HTTPS; -#endif - result = Curl_cf_dns_add(data, conn, sockindex, dns_queries, - conn->transport_wanted, dns); - DEBUGASSERT(conn->cfilter[sockindex]); out: + Curl_pollset_cleanup(&ps); + Curl_pollfds_cleanup(&cpfds); return result; } -#ifdef USE_UNIX_SOCKETS -const char *Curl_conn_get_unix_path(struct connectdata *conn) -{ - const char *unix_path = conn->unix_domain_socket; - -#ifndef CURL_DISABLE_PROXY - if(!unix_path && conn->bits.proxy && conn->socks_proxy.host.name && - !strncmp(UNIX_SOCKET_PREFIX "/", - conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) - unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; -#endif - - return unix_path; -} -#endif /* USE_UNIX_SOCKETS */ - void Curl_conn_set_multiplex(struct connectdata *conn) { if(!conn->bits.multiplex) { @@ -641,3 +452,32 @@ void Curl_conn_set_multiplex(struct connectdata *conn) } } } + +struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, + int8_t sockindex) +{ + return (sockindex == SECONDARYSOCKET) ? + conn->origin2 : conn->origin; +} + +struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, + int8_t sockindex) +{ + return (sockindex == SECONDARYSOCKET) ? + (conn->via_peer2 ? conn->via_peer2 : conn->origin2) : + (conn->via_peer ? conn->via_peer : conn->origin); +} + +struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, + int8_t sockindex) +{ +#ifndef CURL_DISABLE_PROXY + if(conn->socks_proxy.peer) + return conn->socks_proxy.peer; + if(conn->http_proxy.peer) + return conn->http_proxy.peer; +#endif + return (sockindex == SECONDARYSOCKET) ? + (conn->via_peer2 ? conn->via_peer2 : conn->origin2) : + (conn->via_peer ? conn->via_peer : conn->origin); +} diff --git a/lib/connect.h b/lib/connect.h index 40f1c9c57297..bc8c53aa83b5 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -25,42 +25,42 @@ ***************************************************************************/ #include "curl_setup.h" -#include "hostip.h" +#include "vdns/hostip.h" #include "curlx/timeval.h" -struct Curl_dns_entry; -struct ip_quadruple; +struct Curl_peer; struct Curl_str; enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len); -enum alpnid Curl_str2alpnid(const struct Curl_str *cstr); /* generic function that returns how much time there is left to run, according to the timeouts set */ timediff_t Curl_timeleft_ms(struct Curl_easy *data); +timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, + const struct curltime *pnow); #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ #define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) -void Curl_shutdown_start(struct Curl_easy *data, int sockindex, +void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, int timeout_ms); /* return how much time there is left to shutdown the connection at * sockindex. Returns 0 if there is no limit or shutdown has not started. */ timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn, - int sockindex); + int8_t sockindex); /* return how much time there is left to shutdown the connection. * Returns 0 if there is no limit or shutdown has not started. */ timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, struct connectdata *conn); -void Curl_shutdown_clear(struct Curl_easy *data, int sockindex); +void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex); /* TRUE iff shutdown has been started */ -bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); +bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex); /* * Used to extract socket and connectdata struct for the most recent @@ -71,70 +71,61 @@ bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp); -bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, - char *addr, uint16_t *port); - /* - * Curl_conncontrol() marks the end of a connection/stream. The 'ctrl' - * argument specifies if it is the end of a connection or a stream. - * - * For stream-based protocols (such as HTTP/2), a stream close will not cause - * a connection close. Other protocols will close the connection for both - * cases. - * - * It sets the bit.close bit to TRUE (with an explanation for debug builds), - * when the connection will close. + * Curl_conncontrol() manipulates the `conn->bits.close` bit on + * a connection: + * - CONNCTRL_CONN_KEEP: clear the bit + * - CONNCTRL_CONN_CLOSE: set the bit + * - CONNCTRL_STREAM_CLOSE: set the bit when the connection is not + * multiplexed + * The call does *NOT* cause any immediate connection close. */ +#define CONNCTRL_CONN_KEEP 0 +#define CONNCTRL_CONN_CLOSE 1 +#define CONNCTRL_STREAM_CLOSE 2 + +void Curl_conncontrol(struct connectdata *conn, int ctrl); -#define CONNCTRL_KEEP 0 /* undo a marked closure */ -#define CONNCTRL_CONNECTION 1 -#define CONNCTRL_STREAM 2 - -void Curl_conncontrol(struct connectdata *conn, - int ctrl -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - , const char *reason -#endif - ); - -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) -#define streamclose(x, y) Curl_conncontrol(x, CONNCTRL_STREAM, y) -#define connclose(x, y) Curl_conncontrol(x, CONNCTRL_CONNECTION, y) -#define connkeep(x, y) Curl_conncontrol(x, CONNCTRL_KEEP, y) -#else /* !DEBUGBUILD || !CURLVERBOSE */ -#define streamclose(x, y) Curl_conncontrol(x, CONNCTRL_STREAM) -#define connclose(x, y) Curl_conncontrol(x, CONNCTRL_CONNECTION) -#define connkeep(x, y) Curl_conncontrol(x, CONNCTRL_KEEP) -#endif - -CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t transport, - int ssl_mode); +#define streamclose(x) Curl_conncontrol((x), CONNCTRL_STREAM_CLOSE) +#define connclose(x) Curl_conncontrol((x), CONNCTRL_CONN_CLOSE) +#define connkeep(x) Curl_conncontrol((x), CONNCTRL_CONN_KEEP) /** * Setup the cfilters at `sockindex` in connection `conn`. * If no filter chain is installed yet, inspects the configuration - * in `data` and `conn? to install a suitable filter chain. + * in `data` and `conn` to install a suitable filter chain. */ CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, - struct Curl_dns_entry *dns, + int8_t sockindex, int ssl_mode); +/** + * Bring the filter chain at `sockindex` for connection `data->conn` into + * connected state. Which will set `*done` to TRUE. + * This can be called on an already connected chain with no side effects. + * When not `blocking`, calls may return without error and `*done != TRUE`, + * while the individual filters negotiated the connection. + */ +CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, + bool blocking, bool *done); + /* Set conn to allow multiplexing. */ void Curl_conn_set_multiplex(struct connectdata *conn); -#ifdef USE_UNIX_SOCKETS -#ifndef CURL_DISABLE_PROXY -#define UNIX_SOCKET_PREFIX "localhost" -#endif -const char *Curl_conn_get_unix_path(struct connectdata *conn); -#else -#define Curl_conn_get_unix_path(c) NULL -#endif +/* Get the origin peer at sockindex. */ +struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, + int8_t sockindex); + +/* Get the peer the connection actually connects to at sockindex. + * Often the same as "origin", but can be redirected via "connect-to" + * or "alt-svc". May tunnel through proxies. */ +struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, + int8_t sockindex); -extern struct Curl_cftype Curl_cft_setup; +/* Get the peer curl connects its socket to. + * Can be origin, "connect-to" or the first proxy. */ +struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, + int8_t sockindex); #endif /* HEADER_CURL_CONNECT_H */ diff --git a/lib/content_encoding.c b/lib/content_encoding.c index 0224a8bfe9e8..0feecceedc7a 100644 --- a/lib/content_encoding.c +++ b/lib/content_encoding.c @@ -46,6 +46,7 @@ #include #endif +#include "connect.h" #include "sendf.h" #include "curl_trc.h" #include "content_encoding.h" @@ -64,7 +65,7 @@ #ifdef HAVE_LIBZ #if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1252) -#error "requires zlib 1.2.5.2 or newer" +#error "zlib 1.2.5.2 or greater required" #endif typedef enum { @@ -88,7 +89,7 @@ static voidpf zalloc_cb(voidpf opaque, unsigned int items, unsigned int size) { (void)opaque; /* not a typo, keep it curlx_calloc() */ - return (voidpf)curlx_calloc(items, size); + return curlx_calloc(items, size); } static void zfree_cb(voidpf opaque, voidpf ptr) @@ -100,8 +101,7 @@ static void zfree_cb(voidpf opaque, voidpf ptr) static CURLcode process_zlib_error(struct Curl_easy *data, z_stream *z) { if(z->msg) - failf(data, "Error while processing content unencoding: %s", - z->msg); + failf(data, "Error while processing content unencoding: %s", z->msg); else failf(data, "Error while processing content unencoding: " "Unknown failure within decompression software."); @@ -154,6 +154,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, z_const Bytef *orig_in = z->next_in; bool done = FALSE; CURLcode result = CURLE_OK; /* Curl_client_write status */ + int i = 0; /* Check state. */ if(zp->zlib_init != ZLIB_INIT && @@ -164,9 +165,18 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* because the buffer size is fixed, iteratively decompress and transfer to the client via next_write function. */ while(!done) { - int status; /* zlib status */ + int status; /* zlib status */ done = TRUE; + if(++i > (1024 * 1024 / DECOMPRESS_BUFFER_SIZE)) { + /* check every MB of output if we are not exceeding time limit */ + i = 0; + if(Curl_timeleft_ms(data) < 0) { + failf(data, "Operation timed out while decoding payload"); + return exit_zlib(data, z, &zp->zlib_init, CURLE_OPERATION_TIMEDOUT); + } + } + /* (re)set buffer for decompressed output for every iteration */ z->next_out = (Bytef *)zp->buffer; z->avail_out = DECOMPRESS_BUFFER_SIZE; @@ -176,7 +186,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* Flush output data if some. */ if(z->avail_out != DECOMPRESS_BUFFER_SIZE) { if(status == Z_OK || status == Z_STREAM_END) { - zp->zlib_init = started; /* Data started. */ + zp->zlib_init = started; /* Data started. */ result = Curl_cwriter_write(data, writer->next, type, zp->buffer, DECOMPRESS_BUFFER_SIZE - z->avail_out); if(result) { @@ -196,6 +206,14 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* No more data to flush: exit loop. */ break; case Z_STREAM_END: + if((started == ZLIB_INIT_GZIP) && (z->avail_in >= 2) && + (z->next_in[0] == 0x1f) && (z->next_in[1] == 0x8b)) { + /* a second gzip member follows; curl does not support + multi-member gzip responses */ + failf(data, "Multi-member gzip response not supported"); + result = exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); + break; + } result = process_trailer(data, zp); break; case Z_DATA_ERROR: @@ -210,7 +228,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, done = FALSE; break; } - zp->zlib_init = ZLIB_UNINIT; /* inflateEnd() already called. */ + zp->zlib_init = ZLIB_UNINIT; /* inflateEnd() already called. */ } result = exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); break; @@ -224,7 +242,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, 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) - zp->zlib_init = started; /* Cannot restart anymore. */ + zp->zlib_init = started; /* Cannot restart anymore. */ return result; } @@ -234,7 +252,7 @@ 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 */ + z_stream *z = &zp->z; /* zlib state structure */ /* Initialize zlib */ z->zalloc = (alloc_func)zalloc_cb; @@ -251,7 +269,7 @@ static CURLcode deflate_do_write(struct Curl_easy *data, const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *)writer; - z_stream *z = &zp->z; /* zlib state structure */ + z_stream *z = &zp->z; /* zlib state structure */ if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); @@ -271,7 +289,7 @@ 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 */ + z_stream *z = &zp->z; /* zlib state structure */ exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } @@ -279,8 +297,10 @@ static void deflate_do_close(struct Curl_easy *data, static const struct Curl_cwtype deflate_encoding = { "deflate", NULL, + CURL_CW_FLAG_BLOWUP, deflate_do_init, deflate_do_write, + Curl_cwriter_def_flush, deflate_do_close, sizeof(struct zlib_writer) }; @@ -293,7 +313,7 @@ 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 */ + z_stream *z = &zp->z; /* zlib state structure */ /* Initialize zlib */ z->zalloc = (alloc_func)zalloc_cb; @@ -311,7 +331,7 @@ static CURLcode gzip_do_write(struct Curl_easy *data, const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *)writer; - z_stream *z = &zp->z; /* zlib state structure */ + z_stream *z = &zp->z; /* zlib state structure */ if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); @@ -332,7 +352,7 @@ 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 */ + z_stream *z = &zp->z; /* zlib state structure */ exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } @@ -340,8 +360,10 @@ static void gzip_do_close(struct Curl_easy *data, static const struct Curl_cwtype gzip_encoding = { "gzip", "x-gzip", + CURL_CW_FLAG_BLOWUP, gzip_do_init, gzip_do_write, + Curl_cwriter_def_flush, gzip_do_close, sizeof(struct zlib_writer) }; @@ -353,7 +375,7 @@ static const struct Curl_cwtype gzip_encoding = { struct brotli_writer { struct Curl_cwriter super; char buffer[DECOMPRESS_BUFFER_SIZE]; - BrotliDecoderState *br; /* State structure for brotli. */ + BrotliDecoderState *br; /* State structure for brotli. */ }; static CURLcode brotli_map_error(BrotliDecoderErrorCode be) @@ -412,15 +434,26 @@ static CURLcode brotli_do_write(struct Curl_easy *data, size_t dstleft; CURLcode result = CURLE_OK; BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + int i = 0; 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. */ + return CURLE_WRITE_ERROR; /* Stream already ended. */ while((nbytes || r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) && result == CURLE_OK) { + + if(++i > (1024 * 1024 / DECOMPRESS_BUFFER_SIZE)) { + /* check every MB of output if we are not exceeding time limit */ + i = 0; + if(Curl_timeleft_ms(data) < 0) { + failf(data, "Operation timed out while decoding payload"); + return CURLE_OPERATION_TIMEDOUT; + } + } + dst = (uint8_t *)bp->buffer; dstleft = DECOMPRESS_BUFFER_SIZE; r = BrotliDecoderDecompressStream(bp->br, @@ -462,8 +495,10 @@ static void brotli_do_close(struct Curl_easy *data, static const struct Curl_cwtype brotli_encoding = { "br", NULL, + CURL_CW_FLAG_BLOWUP, brotli_do_init, brotli_do_write, + Curl_cwriter_def_flush, brotli_do_close, sizeof(struct brotli_writer) }; @@ -473,7 +508,7 @@ static const struct Curl_cwtype brotli_encoding = { /* Zstd writer. */ struct zstd_writer { struct Curl_cwriter super; - ZSTD_DStream *zds; /* State structure for zstd. */ + ZSTD_DStream *zds; /* State structure for zstd. */ char buffer[DECOMPRESS_BUFFER_SIZE]; }; @@ -520,6 +555,7 @@ static CURLcode zstd_do_write(struct Curl_easy *data, ZSTD_inBuffer in; ZSTD_outBuffer out; size_t errorCode; + int i = 0; if(!(type & CLIENTWRITE_BODY) || !nbytes) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); @@ -529,6 +565,15 @@ static CURLcode zstd_do_write(struct Curl_easy *data, in.size = nbytes; for(;;) { + if(++i > (1024 * 1024 / DECOMPRESS_BUFFER_SIZE)) { + /* check every MB of output if we are not exceeding time limit */ + i = 0; + if(Curl_timeleft_ms(data) < 0) { + failf(data, "Operation timed out while decoding payload"); + return CURLE_OPERATION_TIMEDOUT; + } + } + out.pos = 0; out.dst = zp->buffer; out.size = DECOMPRESS_BUFFER_SIZE; @@ -565,8 +610,10 @@ static void zstd_do_close(struct Curl_easy *data, static const struct Curl_cwtype zstd_encoding = { "zstd", NULL, + CURL_CW_FLAG_BLOWUP, zstd_do_init, zstd_do_write, + Curl_cwriter_def_flush, zstd_do_close, sizeof(struct zstd_writer) }; @@ -576,8 +623,10 @@ static const struct Curl_cwtype zstd_encoding = { static const struct Curl_cwtype identity_encoding = { "identity", "none", + 0, Curl_cwriter_def_init, Curl_cwriter_def_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; @@ -663,8 +712,10 @@ static void error_do_close(struct Curl_easy *data, static const struct Curl_cwtype error_writer = { "ce-error", NULL, + 0, error_do_init, error_do_write, + Curl_cwriter_def_flush, error_do_close, sizeof(struct Curl_cwriter) }; @@ -732,7 +783,8 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, * 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 = curl_strnequal(name, "identity", 8); + bool is_identity = (namelen == 8) && + curl_strnequal(name, "identity", 8); /* not requested, ignore */ CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", (int)namelen, name); @@ -750,24 +802,14 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, return CURLE_OK; } - if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) { - failf(data, "Reject response due to more than %d content encodings", - MAX_ENCODE_STACK); + if(Curl_cwriter_count(data, phase) >= MAX_ENCODE_STACK) { + failf(data, "Reject response exceeding limit of %d %s encodings", + MAX_ENCODE_STACK, + is_transfer ? "transfer" : "content"); return CURLE_BAD_CONTENT_ENCODING; } 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: @@ -782,20 +824,31 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, "Transfer-Encoding"); return CURLE_BAD_CONTENT_ENCODING; } - - 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(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"); + } + else { + 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, + (int)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; diff --git a/lib/cookie.c b/lib/cookie.c index 0f822ea7cf20..3255668ec7b9 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -29,6 +29,7 @@ #include "cookie.h" #include "psl.h" #include "curl_trc.h" +#include "transfer.h" #include "slist.h" #include "curl_share.h" #include "strcase.h" @@ -47,8 +48,7 @@ future. (from RFC6265bis draft-19) For the sake of easier testing, align the capped time to an even 60 second - boundary. -*/ + boundary. */ static void cap_expires(time_t now, struct Cookie *co) { if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { @@ -71,7 +71,7 @@ static void freecookie(struct Cookie *co, bool maintoo) } static bool cookie_tailmatch(const char *cookie_domain, - size_t cookie_domain_len, + const size_t cookie_domain_len, const char *hostname) { size_t hostname_len = strlen(hostname); @@ -250,19 +250,11 @@ static char *sanitize_cookie_path(const char *cookie_path, size_t len) /* * strstore * - * A thin wrapper around strdup which ensures that any memory allocated at - * *str will be freed before the string allocated by strdup is stored there. - * The intended usecase is repeated assignments to the same variable during - * parsing in a last-wins scenario. The caller is responsible for checking - * for OOM errors. + * A thin wrapper around curlx_memdup0(). */ static CURLcode strstore(char **str, const char *newstr, size_t len) { DEBUGASSERT(str); - if(!len) { - len++; - newstr = ""; - } *str = curlx_memdup0(newstr, len); if(!*str) return CURLE_OUT_OF_MEMORY; @@ -342,21 +334,20 @@ static bool bad_domain(const char *domain, size_t len) } #endif -/* - RFC 6265 section 4.1.1 says a server should accept this range: +/* RFC 6265 section 4.1.1 says a server should accept this range: - cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E - Yet, Firefox and Chrome as of June 2022 accept space, comma and double-quotes - fine. The prime reason for filtering out control bytes is that some HTTP - servers return 400 for requests that contain such. -*/ + Yet, Firefox and Chrome as of June 2022 accept space, comma and + double-quotes fine. The prime reason for filtering out control bytes is that + some HTTP servers return 400 for requests that contain such. + */ static bool invalid_octets(const char *ptr, size_t len) { const unsigned char *p = (const unsigned char *)ptr; - /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */ + /* Reject all bytes \x01 - \x1f + \x7f */ while(len && *p) { - if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f)) + if((*p < 0x20) || (*p == 0x7f)) return TRUE; p++; len--; @@ -366,8 +357,7 @@ static bool invalid_octets(const char *ptr, size_t len) /* 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 to make sure it realistically works with what is used out there. -*/ + margin to make sure it realistically works with what is used out there. */ #define MAX_DATE_LENGTH 80 #define COOKIE_NAME 0 @@ -377,7 +367,7 @@ static bool invalid_octets(const char *ptr, size_t len) #define COOKIE_PIECES 4 /* the list above */ -static CURLcode storecookie(struct Cookie *co, struct Curl_str *cp, +static CURLcode storecookie(struct Cookie *co, const struct Curl_str *cp, const char *path, const char *domain) { CURLcode result; @@ -421,19 +411,189 @@ static CURLcode storecookie(struct Cookie *co, struct Curl_str *cp, return result; } -/* this function return errors on OOM etc, not on plain cookie format - problems */ -static CURLcode parse_cookie_header( - struct Curl_easy *data, - struct Cookie *co, - struct CookieInfo *ci, - bool *okay, /* if the cookie was fine */ - const char *ptr, - const char *domain, /* default domain */ - const char *path, /* full path used when this cookie is - set, used to get default path for - the cookie unless set */ - bool secure) /* TRUE if connection is over secure origin */ +/* + * Parse the first name/value pair of the cookie header, which is the actual + * cookie name and value. + */ +static bool parse_first_pair(struct Curl_easy *data, struct Cookie *co, + struct Curl_str *cookie, + struct Curl_str *name, + struct Curl_str *val, + bool sep) +{ + /* The first name/value pair is the actual cookie name */ + if(!sep || !curlx_strlen(name)) { + infof(data, "invalid cookie, dropped"); + return FALSE; + } + + /* + * Check for too long individual name or contents. Chrome and Firefox + * support 4095 or 4096 bytes combo + */ + if((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 FALSE; + } + + /* Check if we have a reserved prefix set. */ + if(!strncmp("__Secure-", curlx_str(name), 9)) + co->prefix_secure = TRUE; + else if(!strncmp("__Host-", curlx_str(name), 7)) + co->prefix_host = TRUE; + + cookie[COOKIE_NAME] = *name; + cookie[COOKIE_VALUE] = *val; + return TRUE; +} + +static bool parse_flag(struct Curl_easy *data, struct Cookie *co, + const struct CookieInfo *ci, + struct Curl_str *name, bool secure) +{ + /* + * secure cookies are only allowed to be set when the connection is + * using a secure protocol, or when the cookie is being set by + * reading from file + */ + if(curlx_str_casecompare(name, "secure")) { + if(secure || !ci->running) + co->secure = TRUE; + else { + infof(data, "skipped cookie because not 'secure'"); + return FALSE; + } + } + else if(curlx_str_casecompare(name, "httponly")) + co->httponly = TRUE; + + return TRUE; +} + +static bool parse_domain(struct Curl_easy *data, struct Cookie *co, + struct Curl_str *cookie_domain, + struct Curl_str *val, + const char **domainp) +{ + bool is_ip; + const char *domain = *domainp; + 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('.' == *v) + curlx_str_nudge(val, 1); + +#ifndef USE_LIBPSL + /* + * 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))) { + *domainp = ":"; + domain = ":"; + } +#endif + + 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))) { + *cookie_domain = *val; + 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. + */ + infof(data, "skipped cookie with bad tailmatch domain: %s", + curlx_str(val)); + return FALSE; + } + return TRUE; +} + +static void parse_maxage(struct Cookie *co, struct Curl_str *val, + time_t *nowp) +{ + int rc; + const char *maxage = curlx_str(val); + if(*maxage == '\"') + maxage++; + rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX); + if(!*nowp) + *nowp = time(NULL); + 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) + co->expires = 1; /* expire now */ + else if(CURL_OFF_T_MAX - *nowp < co->expires) + /* would overflow */ + co->expires = CURL_OFF_T_MAX; + else + co->expires += *nowp; + break; + } + cap_expires(*nowp, co); +} + +static void parse_expires(struct Cookie *co, struct Curl_str *val, + time_t *nowp) +{ + /* + * Let max-age have priority. + * + * If the date cannot get parsed for whatever reason, the cookie + * will be treated as a session cookie + */ + if(!co->expires && (curlx_strlen(val) < MAX_DATE_LENGTH)) { + char dbuf[MAX_DATE_LENGTH + 1]; + time_t date = 0; + memcpy(dbuf, curlx_str(val), curlx_strlen(val)); + dbuf[curlx_strlen(val)] = 0; + if(!Curl_getdate_capped(dbuf, &date)) { + if(!date) + date++; + co->expires = (curl_off_t)date; + } + else + co->expires = 0; + if(!*nowp) + *nowp = time(NULL); + cap_expires(*nowp, co); + } +} + +/* this function returns errors on OOM etc, not for cookie format problems */ +static CURLcode +parse_cookie_header(struct Curl_easy *data, + struct Cookie *co, + const struct CookieInfo *ci, + bool *okay, /* if the cookie was fine */ + const char *ptr, /* the header */ + const char *domain, /* default domain */ + /* full path used when this cookie is set */ + const char *path, + bool secure_origin) { /* This line was read off an HTTP-header */ time_t now = 0; @@ -449,22 +609,25 @@ static CURLcode parse_cookie_header( memset(cookie, 0, sizeof(cookie)); 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=")) { + if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) { + struct Curl_str val; bool sep = FALSE; curlx_str_trimblanks(&name); + if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) { + infof(data, "invalid octets in name, cookie dropped"); + return CURLE_OK; + } + 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(curlx_strlen(&val) && - memchr(curlx_str(&val), '\t', curlx_strlen(&val))) { - infof(data, "cookie contains TAB, dropping"); + if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) { + infof(data, "invalid octets in value, cookie dropped"); return CURLE_OK; } } @@ -472,167 +635,23 @@ static CURLcode parse_cookie_header( curlx_str_init(&val); if(!curlx_strlen(&cookie[COOKIE_NAME])) { - /* The first name/value pair is the actual cookie name */ - if(!sep || - /* Bad name/value pair. */ - invalid_octets(curlx_str(&name), curlx_strlen(&name)) || - invalid_octets(curlx_str(&val), curlx_strlen(&val)) || - !curlx_strlen(&name)) { - infof(data, "invalid octets in name/value, cookie dropped"); + if(!parse_first_pair(data, co, cookie, &name, &val, sep)) return CURLE_OK; - } - - /* - * 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 CURLE_OK; - } - - /* Check if we have a reserved prefix set. */ - if(!strncmp("__Secure-", curlx_str(&name), 9)) - co->prefix_secure = TRUE; - else if(!strncmp("__Host-", curlx_str(&name), 7)) - co->prefix_host = TRUE; - - cookie[COOKIE_NAME] = name; - cookie[COOKIE_VALUE] = val; } else if(!sep) { - /* - * this is a "" with no content - */ - - /* - * secure cookies are only allowed to be set when the connection is - * using a secure protocol, or when the cookie is being set by - * reading from file - */ - if(curlx_str_casecompare(&name, "secure")) { - if(secure || !ci->running) - co->secure = TRUE; - else { - infof(data, "skipped cookie because not 'secure'"); - return CURLE_OK; - } - } - else if(curlx_str_casecompare(&name, "httponly")) - co->httponly = TRUE; + if(!parse_flag(data, co, ci, &name, secure_origin)) + return CURLE_OK; } - else if(curlx_str_casecompare(&name, "path")) { + else if(curlx_str_casecompare(&name, "path")) cookie[COOKIE_PATH] = val; - } 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('.' == *v) - curlx_str_nudge(&val, 1); - -#ifndef USE_LIBPSL - /* - * 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 : 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))) { - cookie[COOKIE_DOMAIN] = val; - 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. - */ - infof(data, "skipped cookie with bad tailmatch domain: %s", - curlx_str(&val)); + if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) return CURLE_OK; - } - } - else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) { - /* - * 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); - if(!now) - now = time(NULL); - 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) - co->expires = 1; /* expire now */ - 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 if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val) && - !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]; - time_t date = 0; - memcpy(dbuf, curlx_str(&val), curlx_strlen(&val)); - dbuf[curlx_strlen(&val)] = 0; - if(!Curl_getdate_capped(dbuf, &date)) { - if(!date) - date++; - co->expires = (curl_off_t)date; - } - else - co->expires = 0; - if(!now) - now = time(NULL); - cap_expires(now, co); } + else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) + parse_maxage(co, &val, &now); + else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) + parse_expires(co, &val, &now); } } while(!curlx_str_single(&ptr, ';')); @@ -646,11 +665,10 @@ static CURLcode parse_cookie_header( } static CURLcode parse_netscape(struct Cookie *co, - struct CookieInfo *ci, + const struct CookieInfo *ci, bool *okay, const char *lineptr, - bool secure) /* TRUE if connection is over - secure origin */ + bool secure_origin) { /* * This line is NOT an HTTP header style line, we do offer support for @@ -667,7 +685,7 @@ static CURLcode parse_netscape(struct Cookie *co, * 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) { + if(!strncmp(lineptr, "#HttpOnly_", 10)) { lineptr += 10; co->httponly = TRUE; } @@ -723,7 +741,7 @@ static CURLcode parse_netscape(struct Cookie *co, case 3: co->secure = FALSE; if(curl_strnequal(ptr, "TRUE", len)) { - if(secure || ci->running) + if(secure_origin || ci->running) co->secure = TRUE; else return CURLE_OK; @@ -738,10 +756,12 @@ static CURLcode parse_netscape(struct Cookie *co, if(!co->name) return CURLE_OUT_OF_MEMORY; else { - /* For Netscape file format cookies we check prefix on the name */ - if(curl_strnequal("__Secure-", co->name, 9)) + /* For Netscape file format cookies we check prefix on the name. + These prefixes are matched case sensitively, same as on the + header path and as the 6265bis document specifies. */ + if(!strncmp("__Secure-", co->name, 9)) co->prefix_secure = TRUE; - else if(curl_strnequal("__Host-", co->name, 7)) + else if(!strncmp("__Host-", co->name, 7)) co->prefix_host = TRUE; } break; @@ -765,6 +785,14 @@ static CURLcode parse_netscape(struct Cookie *co, /* we did not find the sufficient number of fields */ return CURLE_OK; + /* Reject control octets in the name or value, matching the filtering done + for cookies set over HTTP. A cookie loaded from a file is later sent in + request headers, so the same bytes that make a server reject a request + must not slip in through the file. */ + if(invalid_octets(co->name, strlen(co->name)) || + invalid_octets(co->value, strlen(co->value))) + return CURLE_OK; + *okay = TRUE; return CURLE_OK; } @@ -775,25 +803,57 @@ static bool is_public_suffix(struct Curl_easy *data, { #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 is not NULL since the psl code will - * dereference it. + * Check if the domain is a Public Suffix and if yes, ignore the cookie. + * 'domain' is NULL when the cookie is loaded from file or + * CURLOPT_COOKIELIST. */ + DEBUGASSERT(data); + DEBUGASSERT(co); 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))) { + co->name, co->domain ? co->domain : "[blank]", + domain ? domain : "[file]")); + if(!co->domain || Curl_host_is_ipnum(co->domain)) + return FALSE; + + else { bool acceptable = FALSE; char lcase[256]; char lcookie[256]; - size_t dlen = strlen(domain); + size_t dlen = domain ? strlen(domain) : 0; size_t clen = strlen(co->domain); + + /* trim trailing dots */ + if(dlen && (domain[dlen - 1] == '.')) + dlen--; + if(clen && (co->domain[clen - 1] == '.')) + clen--; + 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_strntolower(lcookie, co->domain, clen); + lcookie[clen] = 0; + if(domain) { + Curl_strntolower(lcase, domain, dlen); + lcase[dlen] = 0; + acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + + /* if the cookie is acceptable, but is set for a PSL domain, then it + cannot be tailmatching */ + if(acceptable && co->tailmatch && + curl_strequal(co->domain, domain) && + psl_is_public_suffix(psl, lcookie)) + co->tailmatch = FALSE; + } + else { + /* libpsl says localhost is a PSL, we think not */ + acceptable = + curl_strequal(lcookie, "localhost") || + /* note that this PSL function returns the opposite value than + psl_is_cookie_domain_acceptable() does */ + !psl_is_public_suffix(psl, lcookie); + } Curl_psl_release(data); } else @@ -802,7 +862,8 @@ static bool is_public_suffix(struct Curl_easy *data, if(!acceptable) { infof(data, "cookie '%s' dropped, domain '%s' must not " - "set cookies for '%s'", co->name, domain, co->domain); + "set cookies for '%s'", co->name, + domain ? domain : "[file]", co->domain); return TRUE; } } @@ -811,7 +872,7 @@ static bool is_public_suffix(struct Curl_easy *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)); + co->name, co->domain, domain ? domain : "[file]")); #endif return FALSE; } @@ -819,7 +880,7 @@ static bool is_public_suffix(struct Curl_easy *data, /* returns TRUE when replaced */ static bool replace_existing(struct Curl_easy *data, struct Cookie *co, - struct CookieInfo *ci, + const struct CookieInfo *ci, bool secure, bool *replacep) { @@ -834,8 +895,10 @@ static bool replace_existing(struct Curl_easy *data, bool matching_domains = FALSE; if(clist->domain && co->domain) { - if(curl_strequal(clist->domain, co->domain)) - /* The domains are identical */ + if(cookie_tailmatch(clist->domain, strlen(clist->domain), + co->domain) || + cookie_tailmatch(co->domain, strlen(co->domain), clist->domain)) + /* The existing one is a tail of the new or vice versa */ matching_domains = TRUE; } else if(!clist->domain && !co->domain) @@ -862,7 +925,7 @@ static bool replace_existing(struct Curl_easy *data, else cllen = strlen(clist->path); - if(curl_strnequal(clist->path, co->path, cllen)) { + if(!strncmp(clist->path, co->path, cllen)) { infof(data, "cookie '%s' for domain '%s' dropped, would " "overlay an existing cookie", co->name, co->domain); return FALSE; @@ -886,7 +949,7 @@ static bool replace_existing(struct Curl_easy *data, /* the domains were identical */ if(clist->path && co->path && - !curl_strequal(clist->path, co->path)) + strcmp(clist->path, co->path)) replace_old = FALSE; else if(!clist->path != !co->path) replace_old = FALSE; @@ -929,16 +992,15 @@ static bool replace_existing(struct Curl_easy *data, * IPv6 address. * */ -CURLcode Curl_cookie_add( - struct Curl_easy *data, - struct CookieInfo *ci, - bool httpheader, /* TRUE if HTTP header-style line */ - bool noexpire, /* if TRUE, skip remove_expired() */ - const char *lineptr, /* first character of the line */ - const char *domain, /* default domain */ - const char *path, /* full path used when this cookie is set, used - to get default path for the cookie unless set */ - bool secure) /* TRUE if connection is over secure origin */ +CURLcode Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *ci, + 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 */ + const int flags) { struct Cookie comem; struct Cookie *co; @@ -955,11 +1017,11 @@ CURLcode Curl_cookie_add( co = &comem; memset(co, 0, sizeof(comem)); - if(httpheader) + if(flags & COOKIE_HTTPHEADER) result = parse_cookie_header(data, co, ci, &okay, - lineptr, domain, path, secure); + lineptr, domain, path, flags & COOKIE_SECURE); else - result = parse_netscape(co, ci, &okay, lineptr, secure); + result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE); if(result || !okay) goto fail; @@ -968,6 +1030,9 @@ CURLcode Curl_cookie_add( /* The __Secure- prefix only requires that the cookie be set secure */ goto fail; + if(!(flags & COOKIE_NOPSL) && is_public_suffix(data, co, domain)) + goto fail; + if(co->prefix_host) { /* * The __Host- prefix requires the cookie to be secure, have a "/" path @@ -987,20 +1052,15 @@ CURLcode Curl_cookie_add( co->livecookie = ci->running; co->creationtime = ++ci->lastct; + if(!(flags & COOKIE_NOEXPIRE)) + remove_expired(ci); + /* * 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)) + if(!replace_existing(data, co, ci, flags & COOKIE_SECURE, &replaces)) goto fail; /* clone the stack struct into heap */ @@ -1032,7 +1092,7 @@ CURLcode Curl_cookie_add( if(co->expires && (co->expires < ci->next_expiration)) ci->next_expiration = co->expires; - if(httpheader) + if(flags & COOKIE_HTTPHEADER) data->req.setcookies++; return result; @@ -1081,11 +1141,11 @@ struct CookieInfo *Curl_cookie_init(void) * Reads cookies from a local file. This is always called before any cookies * are set. If file is "-" then STDIN is read. * - * If 'newsession' is TRUE, discard all "session cookies" on read from file. - * + * If 'flags' has the COOKIE_NOSESSION bit set, discard all "session cookies" + * read from file. */ static CURLcode cookie_load(struct Curl_easy *data, const char *file, - struct CookieInfo *ci, bool newsession) + struct CookieInfo *ci, int flags) { FILE *handle = NULL; CURLcode result = CURLE_OK; @@ -1094,7 +1154,7 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, DEBUGASSERT(data); DEBUGASSERT(file); - ci->newsession = newsession; /* new session? */ + ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */ ci->running = FALSE; /* this is not running, this is init */ if(file && *file) { @@ -1134,8 +1194,10 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, curlx_str_passblanks(&lineptr); } - result = Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, - NULL, TRUE); + result = Curl_cookie_add(data, ci, lineptr, NULL, NULL, + (headerline ? COOKIE_HTTPHEADER : 0) | + COOKIE_NOEXPIRE | COOKIE_SECURE | + (flags & COOKIE_NOPSL)); /* File reading cookie failures are not propagated back to the caller because there is no way to do that */ } @@ -1152,7 +1214,7 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, curlx_fclose(handle); } data->state.cookie_engine = TRUE; - ci->running = TRUE; /* now, we are running */ + ci->running = TRUE; /* now, we are running */ return result; } @@ -1160,7 +1222,8 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, /* * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). */ -CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) +CURLcode Curl_cookie_loadfiles(struct Curl_easy *data, + int flags) { CURLcode result = CURLE_OK; struct curl_slist *list = data->state.cookielist; @@ -1173,8 +1236,7 @@ CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) else { data->state.cookie_engine = TRUE; while(list) { - result = cookie_load(data, list->data, data->cookies, - (bool)data->set.cookiesession); + result = cookie_load(data, list->data, data->cookies, flags); if(result) break; list = list->next; @@ -1237,9 +1299,9 @@ static int cookie_sort_ct(const void *p1, const void *p2) return (c2->creationtime > c1->creationtime) ? 1 : -1; } -bool Curl_secure_context(struct connectdata *conn, const char *host) +bool Curl_secure_context(struct Curl_easy *data, const char *host) { - return conn->scheme->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS) || + return Curl_xfer_is_secure(data) || curl_strequal("localhost", host) || !strcmp(host, "127.0.0.1") || !strcmp(host, "::1"); @@ -1249,15 +1311,13 @@ bool Curl_secure_context(struct connectdata *conn, const char *host) * 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. + * should send to the server if used now. * * It shall only return cookies that have not expired. * * 'okay' is TRUE when there is a list returned. */ CURLcode Curl_cookie_getlist(struct Curl_easy *data, - struct connectdata *conn, bool *okay, const char *host, struct Curl_llist *list) @@ -1266,7 +1326,7 @@ CURLcode Curl_cookie_getlist(struct Curl_easy *data, const bool is_ip = Curl_host_is_ipnum(host); const size_t myhash = cookiehash(host); struct Curl_llist_node *n; - const bool secure = Curl_secure_context(conn, host); + const bool secure = Curl_secure_context(data, host); struct CookieInfo *ci = data->cookies; const char *path = data->state.up.path; CURLcode result = CURLE_OK; @@ -1561,7 +1621,7 @@ static CURLcode cookie_output(struct Curl_easy *data, return result; } -static struct curl_slist *cookie_list(struct Curl_easy *data) +static struct curl_slist *cookie_list(const struct Curl_easy *data) { struct curl_slist *list = NULL; struct curl_slist *beg; @@ -1616,13 +1676,13 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup) might be cookie files that were not loaded so saving the file is the wrong thing. */ if(data->cookies) { - if(data->set.str[STRING_COOKIEJAR] && data->cookies->running) { + const char *cookiejar = CURL_EASY_STR(data, STRING_COOKIEJAR); + if(cookiejar && data->cookies->running) { /* if we have a destination file for all the cookies to get dumped to */ - CURLcode result = cookie_output(data, data->cookies, - data->set.str[STRING_COOKIEJAR]); + CURLcode result = cookie_output(data, data->cookies, cookiejar); if(result) infof(data, "WARNING: failed to save cookies in %s: %s", - data->set.str[STRING_COOKIEJAR], curl_easy_strerror(result)); + cookiejar, curl_easy_strerror(result)); } if(cleanup && (!data->share || (data->cookies != data->share->cookies))) { diff --git a/lib/cookie.h b/lib/cookie.h index f66e0ef59182..973ec0954761 100644 --- a/lib/cookie.h +++ b/lib/cookie.h @@ -72,7 +72,7 @@ struct CookieInfo { In the 6265bis draft document section 5.4 it is phrased even stronger: "If the sum of the lengths of the name string and the value string is more than 4096 octets, abort these steps and ignore the set-cookie-string entirely." -*/ + */ /** Limits for INCOMING cookies **/ @@ -104,21 +104,26 @@ struct CookieInfo { struct Curl_easy; struct connectdata; +bool Curl_secure_context(struct Curl_easy *data, const char *host); + /* * Add a cookie to the internal list of cookies. The domain and path arguments - * are only used if the header boolean is TRUE. + * are only used if the COOKIE_HTTPHEADER bit is set in the flags. */ -bool Curl_secure_context(struct connectdata *conn, const char *host); +#define COOKIE_HTTPHEADER (1<<0) /* if HTTP header-style line */ +#define COOKIE_NOEXPIRE (1<<1) /* skip remove_expired() */ +#define COOKIE_SECURE (1<<2) /* connection is over secure origin */ +#define COOKIE_NOPSL (1<<3) /* skip PSL check */ +#define COOKIE_NOSESSION (1<<6) /* drop session cookies */ + CURLcode Curl_cookie_add(struct Curl_easy *data, struct CookieInfo *ci, - bool httpheader, - bool noexpire, const char *lineptr, const char *domain, const char *path, - bool secure) WARN_UNUSED_RESULT; -CURLcode Curl_cookie_getlist(struct Curl_easy *data, struct connectdata *conn, + const int flags) WARN_UNUSED_RESULT; +CURLcode Curl_cookie_getlist(struct Curl_easy *data, bool *okay, const char *host, struct Curl_llist *list) WARN_UNUSED_RESULT; void Curl_cookie_clearall(struct CookieInfo *ci); @@ -126,7 +131,7 @@ void Curl_cookie_clearsess(struct CookieInfo *ci); #if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES) #define Curl_cookie_list(x) NULL -#define Curl_cookie_loadfiles(x) CURLE_OK +#define Curl_cookie_loadfiles(x, y) CURLE_OK #define Curl_cookie_init() NULL #define Curl_cookie_run(x) Curl_nop_stmt #define Curl_cookie_cleanup(x) Curl_nop_stmt @@ -136,7 +141,8 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup); void Curl_cookie_cleanup(struct CookieInfo *ci); struct CookieInfo *Curl_cookie_init(void); struct curl_slist *Curl_cookie_list(struct Curl_easy *data); -CURLcode Curl_cookie_loadfiles(struct Curl_easy *data) WARN_UNUSED_RESULT; +CURLcode Curl_cookie_loadfiles(struct Curl_easy *data, + int flags) WARN_UNUSED_RESULT; void Curl_cookie_run(struct Curl_easy *data); #endif diff --git a/lib/creds.c b/lib/creds.c new file mode 100644 index 000000000000..5507ae86f6ac --- /dev/null +++ b/lib/creds.c @@ -0,0 +1,192 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 /* for offsetof() */ + +#include "creds.h" +#include "curl_trc.h" +#include "strcase.h" +#include "urldata.h" + + +CURLcode Curl_creds_create(const char *user, + const char *passwd, + const char *oauth_bearer, + const char *sasl_authzid, + const char *sasl_service, + uint8_t source, + struct Curl_creds **pcreds) +{ + struct Curl_creds *creds = NULL; + size_t ulen = user ? strlen(user) : 0; + size_t plen = passwd ? strlen(passwd) : 0; + size_t olen = oauth_bearer ? strlen(oauth_bearer) : 0; + size_t salen = sasl_authzid ? strlen(sasl_authzid) : 0; + size_t sslen = sasl_service ? strlen(sasl_service) : 0; + char *s, *buf; + size_t bufsize; + CURLcode result = CURLE_OK; + + Curl_creds_unlink(pcreds); + + /* Everything empty/NULL, this is the NULL credential */ + if(!user && !passwd && !olen && !salen && !sslen) + goto out; + + if((ulen > CURL_MAX_INPUT_LENGTH) || + (plen > CURL_MAX_INPUT_LENGTH) || + (olen > CURL_MAX_INPUT_LENGTH) || + (salen > CURL_MAX_INPUT_LENGTH) || + (sslen > CURL_MAX_INPUT_LENGTH)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + /* null-terminator for user already part of struct */ + bufsize = ulen + plen + 1 + olen + 1 + salen + 1 + sslen + 1; + creds = curlx_calloc(1, sizeof(*creds) + bufsize); + if(!creds) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + creds->bufsize = bufsize; + creds->refcount = 1; + creds->source = source; + /* Some compilers try to be too smart about our dynamic struct size */ + buf = ((char *)creds) + offsetof(struct Curl_creds, buf); + creds->user = s = buf; + if(ulen) + memcpy(s, user, ulen + 1); + creds->passwd = s = buf + ulen + 1; + if(plen) + memcpy(s, passwd, plen + 1); + creds->oauth_bearer = s = buf + ulen + 1 + plen + 1; + if(olen) + memcpy(s, oauth_bearer, olen + 1); + creds->sasl_authzid = s = buf + ulen + 1 + plen + 1 + olen + 1; + if(salen) + memcpy(s, sasl_authzid, salen + 1); + creds->sasl_service = s = buf + ulen + 1 + plen + 1 + olen + 1 + salen + 1; + if(sslen) + memcpy(s, sasl_service, sslen + 1); + +out: + if(!result) + *pcreds = creds; + else + Curl_creds_unlink(&creds); + return result; +} + +CURLcode Curl_creds_merge(const char *user, + const char *passwd, + struct Curl_creds *creds_in, + uint8_t source, + struct Curl_creds **pcreds_out) +{ + struct Curl_creds *creds_out = NULL; + CURLcode result; + + if(!creds_in) { + result = Curl_creds_create(user, passwd, NULL, NULL, NULL, + source, &creds_out); + } + else { + result = Curl_creds_create(user ? user : Curl_creds_user(creds_in), + passwd ? passwd : Curl_creds_passwd(creds_in), + Curl_creds_oauth_bearer(creds_in), + Curl_creds_sasl_authzid(creds_in), + Curl_creds_sasl_service(creds_in), + source, &creds_out); + } + Curl_creds_link(pcreds_out, creds_out); + Curl_creds_unlink(&creds_out); + return result; +} + +void Curl_creds_link(struct Curl_creds **pdest, struct Curl_creds *src) +{ + if(*pdest != src) { + Curl_creds_unlink(pdest); + *pdest = src; + if(src) { + DEBUGASSERT(src->refcount < UINT32_MAX); + src->refcount++; + } + } +} + +void Curl_creds_unlink(struct Curl_creds **pcreds) +{ + if(*pcreds) { + struct Curl_creds *creds = *pcreds; + + DEBUGASSERT(creds->refcount); + *pcreds = NULL; + if(creds->refcount) + creds->refcount--; + if(!creds->refcount) { + curlx_memzero(creds, sizeof(*creds) + creds->bufsize); + curlx_free(creds); + } + } +} + +bool Curl_creds_same(struct Curl_creds *c1, struct Curl_creds *c2) +{ + return (c1 == c2) || + (c1 && c2 && + !Curl_timestrcmp(c1->user, c2->user) && + !Curl_timestrcmp(c1->passwd, c2->passwd) && + !Curl_timestrcmp(c1->oauth_bearer, c2->oauth_bearer) && + !Curl_timestrcmp(c1->sasl_authzid, c2->sasl_authzid) && + !Curl_timestrcmp(c1->sasl_service, c2->sasl_service)); +} + +bool Curl_creds_equal(struct Curl_creds *c1, struct Curl_creds *c2) +{ + return Curl_creds_same(c1, c2) && + ((c1 == c2) || (c1 && c2 && (c1->source == c2->source))); +} + +#ifdef CURLVERBOSE +void Curl_creds_trace(struct Curl_easy *data, struct Curl_creds *creds, + const char *msg) +{ + if(creds) { + CURL_TRC_M(data, "%s: user=%s, passwd=%s, " + "sasl_authzid=%s, oauth_bearer=%s, source=%d", + msg, + Curl_creds_user(creds), + Curl_creds_has_passwd(creds) ? "***" : "", + Curl_creds_sasl_authzid(creds), + Curl_creds_has_oauth_bearer(creds) ? "***" : "", + creds->source); + } + else + CURL_TRC_M(data, "%s: -", msg); +} +#endif diff --git a/lib/creds.h b/lib/creds.h new file mode 100644 index 000000000000..5db3f8f22e12 --- /dev/null +++ b/lib/creds.h @@ -0,0 +1,92 @@ +#ifndef HEADER_CURL_CREDS_H +#define HEADER_CURL_CREDS_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; + +#define CREDS_NONE 0 /* used for default username/passwd */ +#define CREDS_URL 1 /* username/passwd from URL */ +#define CREDS_OPTION 2 /* username/passwd set with a CURLOPT_ */ +#define CREDS_NETRC 3 /* username/passwd found in netrc */ + +struct Curl_creds { + const char *user; /* non-NULL, maybe empty string */ + const char *passwd; /* non-NULL, maybe empty string */ + const char *oauth_bearer; /* non-NULL, maybe empty string */ + const char *sasl_authzid; /* non-NULL, maybe empty string */ + const char *sasl_service; /* non-NULL, maybe empty string */ + uint32_t refcount; + uint8_t source; /* CREDS_* value */ + size_t bufsize; /* extra bytes added to sizeof(struct Curl_creds) */ + char buf[1]; +}; + +CURLcode Curl_creds_create(const char *user, + const char *passwd, + const char *oauth_bearer, + const char *sasl_authzid, + const char *sasl_service, + uint8_t source, + struct Curl_creds **pcreds); + +/* Create credentials by overriding `user` and/or `passwd` in `creds_in` */ +CURLcode Curl_creds_merge(const char *user, + const char *passwd, + struct Curl_creds *creds_in, + uint8_t source, + struct Curl_creds **pcreds_out); + +/* Unlink any creds in `*pdest`, assign src, increase src + * refcount when not NULL. */ +void Curl_creds_link(struct Curl_creds **pdest, struct Curl_creds *src); + +/* Drop a reference, creds may be passed as NULL */ +void Curl_creds_unlink(struct Curl_creds **pcreds); + +/* TRUE if both creds are NULL or have same values, except source. */ +bool Curl_creds_same(struct Curl_creds *c1, struct Curl_creds *c2); + +/* TRUE if both creds are NULL or have all values equal. */ +bool Curl_creds_equal(struct Curl_creds *c1, struct Curl_creds *c2); + +/* Provides properties for creds or, if creds is NULL, the empty string */ +#define Curl_creds_has_user(c) ((c) && (c)->user[0]) +#define Curl_creds_has_passwd(c) ((c) && (c)->passwd[0]) +#define Curl_creds_has_user_or_pass(c) \ + ((c) && ((c)->user[0] || (c)->passwd[0])) +#define Curl_creds_has_oauth_bearer(c) ((c) && (c)->oauth_bearer[0]) +#define Curl_creds_has_sasl_service(c) ((c) && (c)->sasl_service[0]) +#define Curl_creds_user(c) ((c) ? (c)->user : "") +#define Curl_creds_passwd(c) ((c) ? (c)->passwd : "") +#define Curl_creds_oauth_bearer(c) ((c) ? (c)->oauth_bearer : "") +#define Curl_creds_sasl_authzid(c) ((c) ? (c)->sasl_authzid : "") +#define Curl_creds_sasl_service(c) ((c) ? (c)->sasl_service : "") + +#ifdef CURLVERBOSE +void Curl_creds_trace(struct Curl_easy *data, struct Curl_creds *creds, + const char *msg); +#endif + +#endif /* HEADER_CURL_CREDS_H */ diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 27b4a9f0dd31..10cafdd065e3 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -38,6 +38,13 @@ #include "curlx/strparse.h" +struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data) +{ + if(data && data->multi) + return &data->multi->cshutdn; + return NULL; +} + static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { @@ -76,7 +83,7 @@ static void cshutdn_run_once(struct Curl_easy *data, /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); - if(!Curl_shutdown_started(data, FIRSTSOCKET)) { + if(!Curl_shutdown_started(conn, FIRSTSOCKET)) { Curl_shutdown_start(data, FIRSTSOCKET, 0); } @@ -107,22 +114,22 @@ static void cshutdn_run_once(struct Curl_easy *data, conn->bits.shutdown_filters = TRUE; } -void Curl_cshutdn_run_once(struct Curl_easy *data, +void Curl_conn_shutdown_once(struct Curl_easy *admin, 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); + DEBUGASSERT(!admin->conn); + DEBUGASSERT(!admin->mid); + Curl_attach_connection(admin, conn, FALSE); + cshutdn_run_once(admin, conn, done); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown, done=%d", *done); + Curl_detach_connection(admin); } -void Curl_cshutdn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool do_shutdown) +void Curl_conn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown) { - struct Curl_easy *admin = data; bool done; /* there must be a connection to close */ @@ -130,16 +137,10 @@ void Curl_cshutdn_terminate(struct Curl_easy *data, /* 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); + DEBUGASSERT(admin && !admin->conn); + DEBUGASSERT(!admin->mid); - /* 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); + Curl_attach_connection(admin, conn, FALSE); cshutdn_run_conn_handler(admin, conn); if(do_shutdown) { @@ -150,22 +151,21 @@ void Curl_cshutdn_terminate(struct Curl_easy *data, 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_conn_cf_discard_all(admin, conn, SECONDARYSOCKET); + Curl_conn_cf_discard_all(admin, conn, FIRSTSOCKET); Curl_detach_connection(admin); - if(data->multi) - Curl_multi_ev_conn_done(data->multi, data, conn); + if(admin->multi) + Curl_multi_ev_conn_done(admin->multi, admin, conn); Curl_conn_free(admin, conn); - if(data->multi) { - CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); - Curl_multi_connchanged(data->multi); + if(admin->multi) { + CURL_TRC_M(admin, "[SHUTDOWN] trigger multi connchanged"); + Curl_multi_connchanged(admin->multi); } } static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, - struct Curl_easy *data, const char *destination) { struct Curl_llist_node *e; @@ -184,20 +184,19 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, conn = Curl_node_elem(e); Curl_node_remove(e); sigpipe_init(&sigpipe_ctx); - sigpipe_apply(data, &sigpipe_ctx); - Curl_cshutdn_terminate(data, conn, FALSE); + sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx); + Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE); sigpipe_restore(&sigpipe_ctx); return TRUE; } return FALSE; } -bool Curl_cshutdn_close_oldest(struct Curl_easy *data, +bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, const char *destination) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; - return cshutdn_destroy_oldest(csd, data, destination); + if(cshutdn) { + return cshutdn_destroy_oldest(cshutdn, destination); } return FALSE; } @@ -205,7 +204,6 @@ bool Curl_cshutdn_close_oldest(struct Curl_easy *data, #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]; @@ -214,7 +212,7 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); + result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds); if(result) goto out; @@ -226,11 +224,11 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, } static void cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx) { struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); struct Curl_llist_node *enext; + struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; timediff_t next_expire_ms = 0, ms; bool done; @@ -238,67 +236,64 @@ static void cshutdn_perform(struct cshutdn *cshutdn, if(!e) return; - CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", + CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections", Curl_llist_count(&cshutdn->list)); - sigpipe_apply(data, sigpipe_ctx); + sigpipe_apply(admin, sigpipe_ctx); while(e) { enext = Curl_node_next(e); conn = Curl_node_elem(e); - Curl_cshutdn_run_once(data, conn, &done); + Curl_conn_shutdown_once(admin, conn, &done); if(done) { Curl_node_remove(e); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); } else { /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ - ms = Curl_conn_shutdown_timeleft(data, conn); - if(ms && ms < next_expire_ms) + ms = Curl_conn_shutdown_timeleft(admin, conn); + if(ms && (!next_expire_ms || (ms < next_expire_ms))) next_expire_ms = ms; } e = enext; } if(next_expire_ms) - Curl_expire_ex(data, next_expire_ms, EXPIRE_SHUTDOWN); + Curl_expire(admin, next_expire_ms, EXPIRE_SHUTDOWN); } static void cshutdn_terminate_all(struct cshutdn *cshutdn, - struct Curl_easy *data, int timeout_ms) { - struct curltime started = *Curl_pgrs_now(data); + struct Curl_easy *admin = cshutdn->multi->admin; + struct curltime started = *Curl_pgrs_now(admin); struct Curl_llist_node *e; struct Curl_sigpipe_ctx sigpipe_ctx; - DEBUGASSERT(cshutdn); - DEBUGASSERT(data); - - CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown all"); sigpipe_init(&sigpipe_ctx); while(Curl_llist_head(&cshutdn->list)) { timediff_t spent_ms; int remain_ms; - cshutdn_perform(cshutdn, data, &sigpipe_ctx); + cshutdn_perform(cshutdn, &sigpipe_ctx); if(!Curl_llist_head(&cshutdn->list)) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly"); break; } /* wait for activity, timeout or "nothing" */ - spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &started); + spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(admin), &started); if(spent_ms >= (timediff_t)timeout_ms) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, %s", (timeout_ms > 0) ? "timeout" : "best effort done"); break; } remain_ms = timeout_ms - (int)spent_ms; - if(cshutdn_wait(cshutdn, data, remain_ms)) { - CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); + if(cshutdn_wait(cshutdn, remain_ms)) { + CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted"); break; } } @@ -308,7 +303,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, while(e) { struct connectdata *conn = Curl_node_elem(e); Curl_node_remove(e); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); e = Curl_llist_head(&cshutdn->list); } DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); @@ -322,17 +317,18 @@ int Curl_cshutdn_init(struct cshutdn *cshutdn, DEBUGASSERT(multi); cshutdn->multi = multi; Curl_llist_init(&cshutdn->list, NULL); - cshutdn->initialised = TRUE; + cshutdn->initialized = TRUE; return 0; /* good */ } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, - struct Curl_easy *data) + struct Curl_easy *admin) { - if(cshutdn->initialised && data) { + if(cshutdn->initialized && admin) { int timeout_ms = 0; /* for testing, run graceful shutdown */ #ifdef DEBUGBUILD + DEBUGASSERT(!admin->mid); { const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); if(p) { @@ -343,29 +339,28 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, } #endif - CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", + CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", Curl_llist_count(&cshutdn->list), timeout_ms); - cshutdn_terminate_all(cshutdn, data, timeout_ms); + cshutdn_terminate_all(cshutdn, timeout_ms); } cshutdn->multi = NULL; + cshutdn->initialized = FALSE; } -size_t Curl_cshutdn_count(struct Curl_easy *data) +size_t Curl_cshutdn_count(struct cshutdn *cshutdn) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; - return Curl_llist_count(&csd->list); + if(cshutdn) { + return Curl_llist_count(&cshutdn->list); } return 0; } -size_t Curl_cshutdn_dest_count(struct Curl_easy *data, +size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, const char *destination) { - if(data && data->multi) { - struct cshutdn *csd = &data->multi->cshutdn; + if(cshutdn) { size_t n = 0; - struct Curl_llist_node *e = Curl_llist_head(&csd->list); + struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); while(e) { struct connectdata *conn = Curl_node_elem(e); if(!strcmp(destination, conn->destination)) @@ -378,17 +373,17 @@ size_t Curl_cshutdn_dest_count(struct Curl_easy *data, } static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, - struct Curl_easy *data, struct connectdata *conn) { + struct Curl_easy *admin = cshutdn->multi->admin; 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); + Curl_attach_connection(admin, conn, FALSE); + mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn); + Curl_detach_connection(admin); return mresult; } @@ -396,47 +391,46 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, struct connectdata *conn, size_t conns_in_pool) { - struct Curl_easy *data = cshutdn->multi->admin; + struct Curl_easy *admin = cshutdn->multi->admin; size_t max_total = cshutdn->multi->max_total_connections; /* 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 " + CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " "due to connection limit of %zu", max_total); - cshutdn_destroy_oldest(cshutdn, data, NULL); + cshutdn_destroy_oldest(cshutdn, NULL); } if(cshutdn->multi->socket_cb) { - if(cshutdn_update_ev(cshutdn, data, conn)) { - CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" + if(cshutdn_update_ev(cshutdn, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" FMT_OFF_T, conn->connection_id); - Curl_cshutdn_terminate(data, conn, FALSE); + Curl_conn_terminate(admin, conn, FALSE); return; } } Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); - CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T + CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T " to shutdowns, now %zu conns in shutdown", conn->connection_id, Curl_llist_count(&cshutdn->list)); } void Curl_cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx) { - cshutdn_perform(cshutdn, data, sigpipe_ctx); + cshutdn_perform(cshutdn, sigpipe_ctx); } /* 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_easy *admin = cshutdn->multi->admin; struct Curl_llist_node *e; struct easy_pollset ps; @@ -447,9 +441,9 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, CURLcode result; Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(result) continue; @@ -473,13 +467,13 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, /* return information about the shutdown connections */ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_waitfds *cwfds) { unsigned int need = 0; if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; + struct Curl_easy *admin = cshutdn->multi->admin; struct easy_pollset ps; struct connectdata *conn; CURLcode result; @@ -488,9 +482,9 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(!result) need += Curl_waitfds_add_ps(cwfds, &ps); @@ -501,7 +495,6 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, } CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, - struct Curl_easy *data, struct curl_pollfds *cpfds) { CURLcode result = CURLE_OK; @@ -509,15 +502,16 @@ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; struct easy_pollset ps; + struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; Curl_pollset_init(&ps); for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); Curl_pollset_reset(&ps); - Curl_attach_connection(data, conn); - result = Curl_conn_adjust_pollset(data, conn, &ps); - Curl_detach_connection(data); + Curl_attach_connection(admin, conn, FALSE); + result = Curl_conn_adjust_pollset(admin, conn, &ps); + Curl_detach_connection(admin); if(!result) result = Curl_pollfds_add_ps(cpfds, &ps); diff --git a/lib/cshutdn.h b/lib/cshutdn.h index b2e83f3d1aad..a0338f64882f 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -33,20 +33,20 @@ struct Curl_share; struct Curl_sigpipe_ctx; /* Run the shutdown of the connection once. - * Shortly attach/detach `data` to `conn` while doing so. + * Shortly attach/detach the admin handle to `conn` while doing so. * `done` will be set TRUE if any error was encountered or if * the connection was shut down completely. */ -void Curl_cshutdn_run_once(struct Curl_easy *data, - struct connectdata *conn, - bool *done); +void Curl_conn_shutdown_once(struct Curl_easy *admin, + struct connectdata *conn, + bool *done); /* Terminates the connection, e.g. closes and destroys it. * If `do_shutdown` is TRUE, the shutdown will be run once before * terminating it. * Takes ownership of `conn`. */ -void Curl_cshutdn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool do_shutdown); +void Curl_conn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown); /* A `cshutdown` is always owned by a multi handle to maintain * the connections to be shut down. It registers timers and @@ -54,28 +54,31 @@ void Curl_cshutdn_terminate(struct Curl_easy *data, struct cshutdn { struct Curl_llist list; /* connections being shut down */ struct Curl_multi *multi; /* the multi owning this */ - BIT(initialised); + BIT(initialized); }; +/* Get the cshutdn instance relevant for `data` or NULL if there is none */ +struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data); + /* Init as part of the given multi handle. */ int Curl_cshutdn_init(struct cshutdn *cshutdn, struct Curl_multi *multi); /* Terminate all remaining connections and free resources. */ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, - struct Curl_easy *data); + struct Curl_easy *admin); /* Number of connections being shut down. */ -size_t Curl_cshutdn_count(struct Curl_easy *data); +size_t Curl_cshutdn_count(struct cshutdn *cshutdn); /* Number of connections to the destination being shut down. */ -size_t Curl_cshutdn_dest_count(struct Curl_easy *data, +size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, 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, +bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, const char *destination); /* Add a connection to have it shut down. Terminate the oldest @@ -86,21 +89,17 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn, /* 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 maintenance on all connections. */ void Curl_cshutdn_perform(struct cshutdn *cshutdn, - struct Curl_easy *data, struct Curl_sigpipe_ctx *sigpipe_ctx); #endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/lib/curl_addrinfo.c b/lib/curl_addrinfo.c index 4cd1c4de848c..cf366ce4538f 100644 --- a/lib/curl_addrinfo.c +++ b/lib/curl_addrinfo.c @@ -24,24 +24,24 @@ #include "curl_setup.h" #ifdef HAVE_NETINET_IN_H -# include +#include #endif #ifdef HAVE_NETINET_IN6_H -# include +#include #endif #ifdef HAVE_NETDB_H -# include +#include #endif #ifdef HAVE_ARPA_INET_H -# include +#include #endif #ifdef HAVE_SYS_UN_H -# include +#include #endif #ifdef __VMS -# include -# include +#include +#include #endif #if defined(USE_UNIX_SOCKETS) && defined(WINAPI_FAMILY) && \ @@ -58,6 +58,7 @@ #include "curl_addrinfo.h" #include "fake_addrinfo.h" #include "curlx/inet_pton.h" +#include "curlx/strparse.h" /* * Curl_freeaddrinfo() @@ -68,7 +69,7 @@ * any function call which actually allocates a Curl_addrinfo struct. */ -#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) /* workaround icc 9.1 optimizer issue */ # define vqualifier volatile @@ -87,6 +88,21 @@ void Curl_freeaddrinfo(struct Curl_addrinfo *cahead) } } +struct Curl_addrinfo *Curl_addrinfo_get(struct Curl_addrinfo *ai, + int ai_family, + unsigned int n) +{ + unsigned int i; + for(i = 0; ai; ai = ai->ai_next) { + if(ai->ai_family == ai_family) { + if(i == n) + return ai; + ++i; + } + } + return NULL; +} + #ifdef HAVE_GETADDRINFO /* * Curl_getaddrinfo_ex() @@ -122,7 +138,7 @@ int Curl_getaddrinfo_ex(const char *nodename, /* traverse the addrinfo list */ - for(ai = aihead; ai != NULL; ai = ai->ai_next) { + for(ai = aihead; ai; ai = ai->ai_next) { size_t namelen = ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0; /* ignore elements with unsupported address family, settle family-specific sockaddr structure size. */ @@ -265,7 +281,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) /* no input == no output! */ return NULL; - DEBUGASSERT((he->h_name != NULL) && (he->h_addr_list != NULL)); + DEBUGASSERT(he->h_name && he->h_addr_list); for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { size_t ss_size; @@ -452,6 +468,48 @@ bool Curl_is_ipaddr(const char *address) return FALSE; } +bool Curl_looks_like_ipv6(const char *s, size_t len, bool maybe_url_encoded, + struct Curl_str *host, struct Curl_str *zone) +{ + const char *zonep = NULL; + size_t i = 0, hlen = 0, zlen = 0; + + if(host) + memset(host, 0, sizeof(*host)); + if(zone) + memset(zone, 0, sizeof(*zone)); + + for(i = 0; i < len; ++i, ++hlen) { + if(!s[i] || !(ISXDIGIT(s[i]) || (s[i] == ':') || (s[i] == '.'))) + break; + } + + if((i < len) && (s[i] == '%')) { /* address followed by a zone? */ + i += 1; + if(maybe_url_encoded && !strncmp("25", s + i, 2)) + i += 2; + zonep = s + i; + for(; i < len; ++i, ++zlen) { + /* Allow unreserved characters as defined in RFC 3986 */ + if(!s[i] || !(ISALPHA(s[i]) || ISXDIGIT(s[i]) || (s[i] == '-') || + (s[i] == '.') || (s[i] == '_') || (s[i] == '~'))) + break; + } + } + + if(i != len) + return FALSE; /* invalid chars in zone */ + if(host && hlen) { + host->str = s; + host->len = hlen; + } + if(zone && zlen) { + zone->str = zonep; + zone->len = zlen; + } + return TRUE; +} + #ifdef USE_UNIX_SOCKETS /** * Given a path to a Unix domain socket, return a newly allocated Curl_addrinfo @@ -569,7 +627,7 @@ int 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 macOS + * Works around 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) @@ -579,7 +637,7 @@ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) #ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif - for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { + for(ca = addrinfo; ca; ca = ca->ai_next) { switch(ca->ai_family) { case AF_INET: addr = (void *)ca->ai_addr; /* storage area for this info */ diff --git a/lib/curl_addrinfo.h b/lib/curl_addrinfo.h index da2da872cfcc..7b2b4d28345f 100644 --- a/lib/curl_addrinfo.h +++ b/lib/curl_addrinfo.h @@ -40,6 +40,8 @@ # include #endif +struct Curl_str; + /* * 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 @@ -60,6 +62,11 @@ struct Curl_addrinfo { void Curl_freeaddrinfo(struct Curl_addrinfo *cahead); +/* Get the n-th addrinfo of family ai_family. */ +struct Curl_addrinfo *Curl_addrinfo_get(struct Curl_addrinfo *ai, + int ai_family, + unsigned int n); + #ifdef HAVE_GETADDRINFO int Curl_getaddrinfo_ex(const char *nodename, const char *servname, @@ -73,6 +80,9 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port); bool Curl_is_ipv4addr(const char *address); bool Curl_is_ipaddr(const char *address); +bool Curl_looks_like_ipv6(const char *s, size_t len, bool maybe_url_encoded, + struct Curl_str *host, struct Curl_str *zone); + CURLcode Curl_str2addr(const char *dotted, uint16_t port, struct Curl_addrinfo **addrp); diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index f4b9f4f45806..0e50e839e43c 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -22,13 +22,13 @@ * ***************************************************************************/ -/* Location of default ca bundle */ +/* Location of default CA bundle */ #cmakedefine CURL_CA_BUNDLE "${CURL_CA_BUNDLE}" -/* define "1" to use built-in ca store of TLS backend */ +/* define "1" to use built-in CA store of TLS backend */ #cmakedefine CURL_CA_FALLBACK 1 -/* Location of default ca path */ +/* Location of default CA path */ #cmakedefine CURL_CA_PATH "${CURL_CA_PATH}" /* Default SSL backend */ @@ -61,6 +61,9 @@ /* disables aws-sigv4 */ #cmakedefine CURL_DISABLE_AWS 1 +/* disables HTTP Message Signatures (RFC 9421) */ +#cmakedefine CURL_DISABLE_HTTPSIG 1 + /* disables DICT */ #cmakedefine CURL_DISABLE_DICT 1 @@ -234,6 +237,12 @@ /* Define to 1 if you have the `opendir' function. */ #cmakedefine HAVE_OPENDIR 1 +/* Define to 1 if you have the memset_explicit (C23) function. */ +#cmakedefine HAVE_MEMSET_EXPLICIT 1 + +/* Define to 1 if you have the memset_s (C11) function. */ +#cmakedefine HAVE_MEMSET_S 1 + /* Define to 1 if you have the fcntl function. */ #cmakedefine HAVE_FCNTL 1 @@ -255,7 +264,7 @@ /* Define to 1 if you have a working getaddrinfo function. */ #cmakedefine HAVE_GETADDRINFO 1 -/* Define to 1 if the getaddrinfo function is threadsafe. */ +/* Define to 1 if the getaddrinfo function is thread-safe. */ #cmakedefine HAVE_GETADDRINFO_THREADSAFE 1 /* Define to 1 if you have the `geteuid' function. */ @@ -315,9 +324,15 @@ /* if you have the gssapi libraries */ #cmakedefine HAVE_GSSAPI 1 +/* if you have Apple GSS */ +#cmakedefine HAVE_GSSAPPLE 1 + /* if you have the GNU gssapi libraries */ #cmakedefine HAVE_GSSGNU 1 +/* if you have gss_set_neg_mechs */ +#cmakedefine HAVE_GSS_SET_NEG_MECHS 1 + /* MIT Kerberos version */ #cmakedefine CURL_KRB5_VERSION ${CURL_KRB5_VERSION} @@ -330,12 +345,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 -/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ -#cmakedefine HAVE_INET_NTOP 1 - -/* 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 */ #cmakedefine HAVE_SA_FAMILY_T 1 @@ -346,7 +355,7 @@ #cmakedefine HAVE_IOCTLSOCKET_CAMEL 1 /* Define to 1 if you have a working IoctlSocket camel case FIONBIO function. - */ + */ #cmakedefine HAVE_IOCTLSOCKET_CAMEL_FIONBIO 1 /* Define to 1 if you have a working ioctlsocket FIONBIO function. */ @@ -415,6 +424,9 @@ /* 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_NETINET_IP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_TCP_H 1 @@ -451,9 +463,6 @@ /* Define to 1 if you have the recv function. */ #cmakedefine HAVE_RECV 1 -/* 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 @@ -604,14 +613,12 @@ /* cpu-machine-OS */ #cmakedefine CURL_OS ${CURL_OS} -/* - Note: SIZEOF_* variables are fetched with CMake through check_type_size(). - As per CMake documentation on CheckTypeSize, C preprocessor code is - generated by CMake into SIZEOF_*_CODE. This is what we use in the - following statements. - - Reference: https://cmake.org/cmake/help/latest/module/CheckTypeSize.html -*/ +/* Note: SIZEOF_* variables are fetched with CMake through check_type_size(). + As per CMake documentation on CheckTypeSize, C preprocessor code is + generated by CMake into SIZEOF_*_CODE. This is what we use in the + following statements. + Ref: https://cmake.org/cmake/help/latest/module/CheckTypeSize.html + */ /* The size of `int', as computed by sizeof. */ ${SIZEOF_INT_CODE} @@ -634,9 +641,6 @@ ${SIZEOF_SIZE_T_CODE} /* The size of `time_t', as computed by sizeof. */ ${SIZEOF_TIME_T_CODE} -/* Define to 1 if you have the ANSI C header files. */ -#cmakedefine STDC_HEADERS 1 - /* Define if you have POSIX pthreads */ #cmakedefine HAVE_THREADS_POSIX 1 @@ -709,6 +713,9 @@ ${SIZEOF_TIME_T_CODE} /* if libuv is in use */ #cmakedefine USE_LIBUV 1 +/* if HTTP/3 proxy support is available */ +#cmakedefine USE_PROXY_HTTP3 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UV_H 1 @@ -770,14 +777,8 @@ ${SIZEOF_TIME_T_CODE} /* to enable Apple OS-native certificate verification */ #cmakedefine USE_APPLE_SECTRUST 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 +/* to use Apple fast UDP (SYS_recvmsg_x, SYS_sendmsg_x) */ +#cmakedefine USE_APPLE_FAST_UDP /* Define to 1 to query for HTTPSRR when using DoH */ #cmakedefine USE_HTTPSRR 1 diff --git a/lib/curl_ctype.h b/lib/curl_ctype.h index f3291ad8184b..fd6b7d723cf5 100644 --- a/lib/curl_ctype.h +++ b/lib/curl_ctype.h @@ -30,10 +30,8 @@ #define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f) #define IS7F(x) ((x) == 0x7f) -#define ISLOWPRINT(x) (((x) >= 9) && ((x) <= 0x0d)) - -#define ISPRINT(x) (ISLOWPRINT(x) || (((x) >= ' ') && ((x) <= 0x7e))) -#define ISGRAPH(x) (ISLOWPRINT(x) || (((x) > ' ') && ((x) <= 0x7e))) +#define ISPRINT(x) (((x) >= ' ') && ((x) <= 0x7e)) +#define ISGRAPH(x) (((x) > ' ') && ((x) <= 0x7e)) #define ISCNTRL(x) (ISLOWCNTRL(x) || IS7F(x)) #define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) #define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALPHA(x) || ISUPHEXALPHA(x)) diff --git a/lib/curl_ed25519.c b/lib/curl_ed25519.c new file mode 100644 index 000000000000..44311ec54e4c --- /dev/null +++ b/lib/curl_ed25519.c @@ -0,0 +1,175 @@ +/*************************************************************************** + * _ _ ____ _ + * 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(CURL_DISABLE_HTTPSIG) + +/* Please keep the SSL backend-specific #if branches in this order: + * + * 1. USE_OPENSSL + * 2. USE_WOLFSSL + * 3. USE_GNUTLS + * 4. USE_MBEDTLS + */ + +#include "curl_ed25519.h" + +#ifdef USE_WOLFSSL +#include +#include +#endif + +#ifdef USE_OPENSSL +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + EVP_PKEY *pkey; + EVP_MD_CTX *mdctx; + size_t slen; + int rc; + + if(keylen != CURL_ED25519_KEYLEN) + return CURLE_BAD_FUNCTION_ARGUMENT; + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, key, keylen); + if(!pkey) + return CURLE_AUTH_ERROR; + + mdctx = EVP_MD_CTX_new(); + if(!mdctx) { + EVP_PKEY_free(pkey); + return CURLE_OUT_OF_MEMORY; + } + + rc = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey); + if(rc != 1) { + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return CURLE_AUTH_ERROR; + } + + slen = CURL_ED25519_SIGLEN; + rc = EVP_DigestSign(mdctx, sig, &slen, msg, msglen); + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + + if(rc != 1) + return CURLE_AUTH_ERROR; + + *siglen = slen; + return CURLE_OK; +} + +#elif defined(USE_WOLFSSL) && \ + (defined(HAVE_ED25519) || defined(WOLFSSL_CURVE25519_USE_ED25519)) && \ + defined(HAVE_ED25519_KEY_IMPORT) && defined(HAVE_ED25519_SIGN) +#include +#include +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + int ret; + ed25519_key edkey; + word32 outlen; + unsigned char pubkey[ED25519_PUB_KEY_SIZE]; + + if(keylen != ED25519_KEY_SIZE) + return CURLE_BAD_FUNCTION_ARGUMENT; + + ret = wc_ed25519_init(&edkey); + if(ret) + return CURLE_AUTH_ERROR; + + ret = wc_ed25519_import_private_only(key, ED25519_KEY_SIZE, &edkey); + if(ret) + goto fail; + + ret = wc_ed25519_make_public(&edkey, pubkey, ED25519_PUB_KEY_SIZE); + if(ret) + goto fail; + + ret = wc_ed25519_import_private_key(key, ED25519_KEY_SIZE, + pubkey, ED25519_PUB_KEY_SIZE, &edkey); + if(ret) + goto fail; + + outlen = ED25519_SIG_SIZE; + ret = wc_ed25519_sign_msg(msg, (word32)msglen, sig, &outlen, &edkey); + if(ret) + goto fail; + + *siglen = (size_t)outlen; + wc_ed25519_free(&edkey); + return CURLE_OK; + +fail: + wc_ed25519_free(&edkey); + return CURLE_AUTH_ERROR; +} + +#elif defined(USE_GNUTLS) +#include + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + uint8_t pubkey[ED25519_KEY_SIZE]; + + if(keylen != ED25519_KEY_SIZE) + return CURLE_BAD_FUNCTION_ARGUMENT; + + nettle_ed25519_sha512_public_key(pubkey, key); + + nettle_ed25519_sha512_sign(pubkey, key, msglen, msg, sig); + *siglen = CURL_ED25519_SIGLEN; + + return CURLE_OK; +} + +#else /* no Ed25519-capable backend */ + +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen) +{ + (void)key; + (void)keylen; + (void)msg; + (void)msglen; + (void)sig; + (void)siglen; + return CURLE_NOT_BUILT_IN; +} + +#endif /* Ed25519 backends */ + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ diff --git a/lib/curl_ed25519.h b/lib/curl_ed25519.h new file mode 100644 index 000000000000..381b6511eba2 --- /dev/null +++ b/lib/curl_ed25519.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_ED25519_H +#define HEADER_CURL_ED25519_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(CURL_DISABLE_HTTPSIG) + +#define CURL_ED25519_SIGLEN 64 +#define CURL_ED25519_KEYLEN 32 + +/* Sign with Ed25519 (RFC 8032). + * key/keylen: raw 32-byte private seed + * msg/msglen: data to sign + * sig: output buffer (at least CURL_ED25519_SIGLEN bytes) + * siglen: out - actual signature length on success + * Returns CURLE_OK or CURLE_NOT_BUILT_IN if no backend supports Ed25519. */ +CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen, + const unsigned char *msg, size_t msglen, + unsigned char *sig, size_t *siglen); + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ +#endif /* HEADER_CURL_ED25519_H */ diff --git a/lib/curl_endian.c b/lib/curl_endian.c index 864b411b68cc..b3689417a4c7 100644 --- a/lib/curl_endian.c +++ b/lib/curl_endian.c @@ -34,7 +34,7 @@ * * Parameters: * - * buf [in] - A pointer to a 2 byte buffer. + * buf [in] - A pointer to a 2-byte buffer. * * Returns the integer. */ @@ -53,7 +53,7 @@ unsigned short Curl_read16_le(const unsigned char *buf) * * Parameters: * - * buf [in] - A pointer to a 4 byte buffer. + * buf [in] - A pointer to a 4-byte buffer. * * Returns the integer. */ @@ -72,7 +72,7 @@ unsigned int Curl_read32_le(const unsigned char *buf) * * Parameters: * - * buf [in] - A pointer to a 2 byte buffer. + * buf [in] - A pointer to a 2-byte buffer. * * Returns the integer. */ diff --git a/lib/curl_fnmatch.c b/lib/curl_fnmatch.c index dde956c3ac2a..d165554f8db5 100644 --- a/lib/curl_fnmatch.c +++ b/lib/curl_fnmatch.c @@ -96,25 +96,25 @@ static int parsekeyword(const unsigned char **pattern, unsigned char *charset) #undef KEYLEN *pattern = p; /* move caller's pattern pointer */ - if(strcmp(keyword, "digit") == 0) + if(!strcmp(keyword, "digit")) charset[CURLFNM_DIGIT] = 1; - else if(strcmp(keyword, "alnum") == 0) + else if(!strcmp(keyword, "alnum")) charset[CURLFNM_ALNUM] = 1; - else if(strcmp(keyword, "alpha") == 0) + else if(!strcmp(keyword, "alpha")) charset[CURLFNM_ALPHA] = 1; - else if(strcmp(keyword, "xdigit") == 0) + else if(!strcmp(keyword, "xdigit")) charset[CURLFNM_XDIGIT] = 1; - else if(strcmp(keyword, "print") == 0) + else if(!strcmp(keyword, "print")) charset[CURLFNM_PRINT] = 1; - else if(strcmp(keyword, "graph") == 0) + else if(!strcmp(keyword, "graph")) charset[CURLFNM_GRAPH] = 1; - else if(strcmp(keyword, "space") == 0) + else if(!strcmp(keyword, "space")) charset[CURLFNM_SPACE] = 1; - else if(strcmp(keyword, "blank") == 0) + else if(!strcmp(keyword, "blank")) charset[CURLFNM_BLANK] = 1; - else if(strcmp(keyword, "upper") == 0) + else if(!strcmp(keyword, "upper")) charset[CURLFNM_UPPER] = 1; - else if(strcmp(keyword, "lower") == 0) + else if(!strcmp(keyword, "lower")) charset[CURLFNM_LOWER] = 1; else return SETCHARSET_FAIL; diff --git a/lib/curl_fopen.c b/lib/curl_fopen.c index cc888f761648..75054ffb77bf 100644 --- a/lib/curl_fopen.c +++ b/lib/curl_fopen.c @@ -30,25 +30,24 @@ #include "rand.h" #include "curl_fopen.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. +/* 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. + This function returns a pointer to malloc'ed memory. - The input path to this function is expected to have a filename part. -*/ + The input path to this function is expected to have a filename part. + */ #ifdef _WIN32 -#define PATHSEP "\\" +#define PATHSEP "\\" #define IS_SEP(x) (((x) == '/') || ((x) == '\\')) #elif defined(MSDOS) || defined(OS2) -#define PATHSEP "\\" +#define PATHSEP "\\" #define IS_SEP(x) ((x) == '\\') #else -#define PATHSEP "/" +#define PATHSEP "/" #define IS_SEP(x) ((x) == '/') #endif diff --git a/lib/curl_gssapi.c b/lib/curl_gssapi.c index 650d1908d0f3..437f76d04a46 100644 --- a/lib/curl_gssapi.c +++ b/lib/curl_gssapi.c @@ -55,7 +55,7 @@ #define CURL_ALIGN8 #endif -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -81,7 +81,6 @@ enum min_err_code { /* libcurl is also passing this struct to these functions, which are not yet * stubbed: - * gss_inquire_context() * gss_unwrap() * gss_wrap() */ @@ -93,6 +92,12 @@ struct stub_gss_ctx_id_t_desc { char creds[250]; }; +/* Stub credential: tracks which mechanisms are allowed */ +struct stub_gss_cred_id_t_desc { + int allow_krb5; + int allow_ntlm; +}; + static OM_uint32 stub_gss_init_sec_context( OM_uint32 *min, gss_cred_id_t initiator_cred_handle, @@ -116,7 +121,6 @@ static OM_uint32 stub_gss_init_sec_context( char *token = NULL; const char *creds = NULL; - (void)initiator_cred_handle; (void)mech_type; (void)time_req; (void)input_chan_bindings; @@ -214,6 +218,16 @@ static OM_uint32 stub_gss_init_sec_context( if(strstr(creds, "NTLM")) ctx->have_ntlm = 1; + /* If a credential restricts allowed mechs, honour it */ + if(initiator_cred_handle != GSS_C_NO_CREDENTIAL) { + struct stub_gss_cred_id_t_desc *cred = + (struct stub_gss_cred_id_t_desc *)initiator_cred_handle; + if(!cred->allow_krb5) + ctx->have_krb5 = 0; + if(!cred->allow_ntlm) + ctx->have_ntlm = 0; + } + if(ctx->have_krb5) ctx->sent = STUB_GSS_KRB5; else if(ctx->have_ntlm) @@ -260,7 +274,7 @@ static OM_uint32 stub_gss_init_sec_context( used = curl_msnprintf(token, length, "%s:%.*s:%d:", creds, (int)target_desc.length, (const char *)target_desc.value, - ctx->sent); + (int)ctx->sent); gss_release_buffer(&minor_status, &target_desc); } @@ -302,12 +316,179 @@ static OM_uint32 stub_gss_delete_sec_context( return GSS_S_FAILURE; } - curlx_free(*context); - *context = NULL; + curlx_safefree(*context); + *min = 0; + + return GSS_S_COMPLETE; +} + +/* NTLMSSP OID: 1.3.6.1.4.1.311.2.2.10 */ +static gss_OID_desc stub_ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") +}; + +static OM_uint32 stub_gss_inquire_context( + OM_uint32 *min, + struct stub_gss_ctx_id_t_desc *context, + gss_name_t *src_name, + gss_name_t *targ_name, + OM_uint32 *lifetime_rec, + gss_OID *mech_type, + OM_uint32 *ctx_flags, + int *locally_initiated, + int *open_context) +{ + (void)src_name; + (void)targ_name; + (void)lifetime_rec; + (void)ctx_flags; + (void)locally_initiated; + (void)open_context; + + if(!min) + return GSS_S_FAILURE; + + if(!context) { + *min = STUB_GSS_INVALID_CTX; + return GSS_S_FAILURE; + } + + *min = 0; + if(mech_type) { + switch(context->sent) { + case STUB_GSS_NTLM1: + case STUB_GSS_NTLM3: + *mech_type = &stub_ntlmssp_oid; + break; + default: + *mech_type = (gss_OID)&Curl_krb5_mech_oid; + break; + } + } + + return GSS_S_COMPLETE; +} +static OM_uint32 stub_gss_acquire_cred( + OM_uint32 *min, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + (void)desired_name; + (void)time_req; + (void)desired_mechs; + (void)cred_usage; + (void)actual_mechs; + (void)time_rec; + + if(!min) + return GSS_S_FAILURE; + + *min = 0; + /* Allocate a stub credential that initially allows all mechanisms */ + if(output_cred_handle) { + struct stub_gss_cred_id_t_desc *cred = + curlx_calloc(1, sizeof(*cred)); + if(!cred) { + *min = STUB_GSS_NO_MEMORY; + return GSS_S_FAILURE; + } + cred->allow_krb5 = 1; + cred->allow_ntlm = 1; + *output_cred_handle = (gss_cred_id_t)cred; + } + return GSS_S_COMPLETE; +} + +static OM_uint32 stub_gss_indicate_mechs( + OM_uint32 *min, + gss_OID_set *mech_set) +{ + const char *creds; + OM_uint32 major; + + if(!min) + return GSS_S_FAILURE; + + *min = 0; + creds = getenv("CURL_STUB_GSS_CREDS"); + if(!creds) { + *min = STUB_GSS_INVALID_CREDS; + return GSS_S_FAILURE; + } + + major = gss_create_empty_oid_set(min, mech_set); + if(GSS_ERROR(major)) + return major; + + /* Always include Kerberos */ + gss_add_oid_set_member(min, (gss_OID)&Curl_krb5_mech_oid, mech_set); + + /* Include NTLM if the stub creds contain NTLM */ + if(strstr(creds, "NTLM")) + gss_add_oid_set_member(min, &stub_ntlmssp_oid, mech_set); + + return GSS_S_COMPLETE; +} + +#ifdef HAVE_GSS_SET_NEG_MECHS /* MIT Kerberos 1.8+ (2010-03-02), + missing from Apple GSS, GNU GSS */ +static OM_uint32 stub_gss_set_neg_mechs(OM_uint32 *min, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set) +{ + struct stub_gss_cred_id_t_desc *cred; + size_t i; + int found_krb5 = 0; + int found_ntlm = 0; + + if(!min) + return GSS_S_FAILURE; + *min = 0; + if(cred_handle == GSS_C_NO_CREDENTIAL) + return GSS_S_FAILURE; + cred = (struct stub_gss_cred_id_t_desc *)cred_handle; + + /* Determine which mechs are in the allowed set */ + if(mech_set) { + for(i = 0; i < mech_set->count; i++) { + gss_OID oid = &mech_set->elements[i]; + if(oid->length == Curl_krb5_mech_oid.length && + !memcmp(oid->elements, Curl_krb5_mech_oid.elements, oid->length)) + found_krb5 = 1; + if(oid->length == stub_ntlmssp_oid.length && + !memcmp(oid->elements, stub_ntlmssp_oid.elements, oid->length)) + found_ntlm = 1; + } + } + + cred->allow_krb5 = found_krb5; + cred->allow_ntlm = found_ntlm; return GSS_S_COMPLETE; } +#endif /* HAVE_GSS_SET_NEG_MECHS */ + +static OM_uint32 stub_gss_release_cred( + OM_uint32 *min, + gss_cred_id_t *cred_handle) +{ + if(!min) + return GSS_S_FAILURE; + + *min = 0; + if(cred_handle && *cred_handle != GSS_C_NO_CREDENTIAL) { + curlx_free(*cred_handle); + *cred_handle = GSS_C_NO_CREDENTIAL; + } + return GSS_S_COMPLETE; +} + #endif /* CURL_GSS_STUB */ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, @@ -319,7 +500,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags) + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle) { OM_uint32 req_flags = GSS_C_REPLAY_FLAG; @@ -327,7 +509,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, req_flags |= GSS_C_MUTUAL_FLAG; if(data->set.gssapi_delegation & CURLGSSAPI_DELEGATION_POLICY_FLAG) { -#ifdef GSS_C_DELEG_POLICY_FLAG /* MIT Kerberos 1.8+, missing from GNU GSS */ +#ifdef GSS_C_DELEG_POLICY_FLAG /* MIT Kerberos 1.7+ (2009-06-02), Apple GSS, + missing from GNU GSS */ req_flags |= GSS_C_DELEG_POLICY_FLAG; #else infof(data, "WARNING: support for CURLGSSAPI_DELEGATION_POLICY_FLAG not " @@ -341,7 +524,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #ifdef CURL_GSS_STUB if(getenv("CURL_STUB_GSS_CREDS")) return stub_gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, (struct stub_gss_ctx_id_t_desc **)context, target_name, mech_type, @@ -356,7 +539,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, #endif /* CURL_GSS_STUB */ return gss_init_sec_context(minor_status, - GSS_C_NO_CREDENTIAL, /* cred_handle */ + cred_handle, context, target_name, mech_type, @@ -384,6 +567,80 @@ OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, return gss_delete_sec_context(min, context, output_token); } +OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, + gss_ctx_id_t context, + gss_OID *mech_type) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_inquire_context(minor_status, + (struct stub_gss_ctx_id_t_desc *)context, + NULL, NULL, NULL, mech_type, + NULL, NULL, NULL); +#endif /* CURL_GSS_STUB */ + + return gss_inquire_context(minor_status, context, + NULL, NULL, NULL, mech_type, + NULL, NULL, NULL); +} + +OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_acquire_cred(minor_status, desired_name, time_req, + desired_mechs, cred_usage, + output_cred_handle, actual_mechs, time_rec); +#endif /* CURL_GSS_STUB */ + + return gss_acquire_cred(minor_status, desired_name, time_req, + desired_mechs, cred_usage, + output_cred_handle, actual_mechs, time_rec); +} + +OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, + gss_OID_set *mech_set) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_indicate_mechs(minor_status, mech_set); +#endif /* CURL_GSS_STUB */ + + return gss_indicate_mechs(minor_status, mech_set); +} + +#ifdef HAVE_GSS_SET_NEG_MECHS +OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_set_neg_mechs(minor_status, cred_handle, mech_set); +#endif /* CURL_GSS_STUB */ + + return gss_set_neg_mechs(minor_status, cred_handle, mech_set); +} +#endif /* HAVE_GSS_SET_NEG_MECHS */ + +OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle) +{ +#ifdef CURL_GSS_STUB + if(getenv("CURL_STUB_GSS_CREDS")) + return stub_gss_release_cred(minor_status, cred_handle); +#endif /* CURL_GSS_STUB */ + + return gss_release_cred(minor_status, cred_handle); +} + #ifdef CURLVERBOSE #define GSS_LOG_BUFFER_LEN 1024 static size_t display_gss_error(OM_uint32 status, int type, @@ -441,7 +698,7 @@ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, } #endif /* CURLVERBOSE */ -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/lib/curl_gssapi.h b/lib/curl_gssapi.h index fc3759ebcbcf..b33df77c17ae 100644 --- a/lib/curl_gssapi.h +++ b/lib/curl_gssapi.h @@ -41,12 +41,38 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, gss_buffer_t input_token, gss_buffer_t output_token, const bool mutual_auth, - OM_uint32 *ret_flags); + OM_uint32 *ret_flags, + gss_cred_id_t cred_handle); OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, gss_ctx_id_t *context, gss_buffer_t output_token); +OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, + gss_ctx_id_t context, + gss_OID *mech_type); + +OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec); + +OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, + gss_OID_set *mech_set); + +#ifdef HAVE_GSS_SET_NEG_MECHS +OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_set); +#endif + +OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle); + #ifdef CURLVERBOSE /* Helper to log a GSS-API error status */ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, diff --git a/lib/curl_hmac.h b/lib/curl_hmac.h index 301d44fee809..4fd8ec47bbe3 100644 --- a/lib/curl_hmac.h +++ b/lib/curl_hmac.h @@ -24,26 +24,27 @@ * ***************************************************************************/ -#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \ - !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \ + !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_HTTPSIG) || \ defined(USE_LIBSSH2) || defined(USE_SSL) #define HMAC_MD5_LENGTH 16 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); +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 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_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. */ @@ -66,7 +67,6 @@ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, const unsigned char *key, const size_t keylen, const unsigned char *data, size_t datalen, unsigned char *output); - #endif #endif /* HEADER_CURL_HMAC_H */ diff --git a/lib/curl_md5.h b/lib/curl_md5.h index 8a0cc2623e38..1beaed5e66c6 100644 --- a/lib/curl_md5.h +++ b/lib/curl_md5.h @@ -38,16 +38,16 @@ typedef void (*Curl_MD5_update_func)(void *context, typedef void (*Curl_MD5_final_func)(unsigned char *result, void *context); struct MD5_params { - Curl_MD5_init_func md5_init_func; /* Initialize context procedure */ - Curl_MD5_update_func md5_update_func; /* Update context with data */ - Curl_MD5_final_func md5_final_func; /* Get final result procedure */ - unsigned int md5_ctxtsize; /* Context structure size */ - unsigned int md5_resultlen; /* Result length (bytes) */ + Curl_MD5_init_func md5_init_func; /* Initialize context procedure */ + Curl_MD5_update_func md5_update_func; /* Update context with data */ + Curl_MD5_final_func md5_final_func; /* Get final result procedure */ + unsigned int md5_ctxtsize; /* Context structure size */ + unsigned int md5_resultlen; /* Result length (bytes) */ }; struct MD5_context { - const struct MD5_params *md5_hash; /* Hash function definition */ - void *md5_hashctx; /* Hash function context */ + const struct MD5_params *md5_hash; /* Hash function definition */ + void *md5_hashctx; /* Hash function context */ }; extern const struct MD5_params Curl_DIGEST_MD5; diff --git a/lib/curl_ntlm_core.c b/lib/curl_ntlm_core.c index 4b2007bbad89..7aceb65e9626 100644 --- a/lib/curl_ntlm_core.c +++ b/lib/curl_ntlm_core.c @@ -49,6 +49,13 @@ in NTLM type-3 messages. */ +#ifdef USE_GNUTLS +#include +#if NETTLE_VERSION_MAJOR < 4 +#define USE_GNUTLS_DES +#endif +#endif + #if defined(USE_OPENSSL) && defined(HAVE_DES_ECB_ENCRYPT) # include @@ -63,13 +70,13 @@ # include # define USE_WOLFSSL_DES -#elif defined(USE_GNUTLS) +#elif defined(USE_GNUTLS_DES) # include # define USE_CURL_DES_SET_ODD_PARITY #elif defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB) # include # if MBEDTLS_VERSION_NUMBER < 0x03020000 -# error "mbedTLS 3.2.0 or later required" +# error "mbedTLS 3.2.0 or greater required" # endif # include # define USE_MBEDTLS_DES @@ -175,7 +182,7 @@ static void setup_des_key(const unsigned char *key_56, Des *des) wc_Des_SetKey(des, key, NULL, 0); } -#elif defined(USE_GNUTLS) +#elif defined(USE_GNUTLS_DES) static void setup_des_key(const unsigned char *key_56, struct des_ctx *des) { char key[8]; @@ -244,6 +251,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, char key[8]; } blob; DWORD len = 8; + BOOL res; /* Acquire the crypto provider */ if(!CryptAcquireContext(&hprov, NULL, NULL, PROV_RSA_FULL, @@ -273,19 +281,19 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, memcpy(out, in, 8); /* Perform the encryption */ - CryptEncrypt(hkey, 0, FALSE, 0, out, &len, len); + res = CryptEncrypt(hkey, 0, FALSE, 0, out, &len, len); CryptDestroyKey(hkey); CryptReleaseContext(hprov, 0); - return TRUE; + return res; } #endif /* crypto backends */ /* - * takes a 21 byte array and treats it as 3 56-bit DES keys. The - * 8 byte plaintext is encrypted with each key and the resulting 24 + * takes a 21-byte array and treats it as 3 56-bit DES keys. The + * 8-byte plaintext is encrypted with each key and the resulting 24 * bytes are stored in the results array. */ void Curl_ntlm_core_lm_resp(const unsigned char *keys, @@ -314,7 +322,7 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, wc_Des_EcbEncrypt(&des, results + 8, plaintext, DES_KEY_SIZE); setup_des_key(keys + 14, &des); wc_Des_EcbEncrypt(&des, results + 16, plaintext, DES_KEY_SIZE); -#elif defined(USE_GNUTLS) +#elif defined(USE_GNUTLS_DES) struct des_ctx des; setup_des_key(keys, &des); des_encrypt(&des, 8, results, plaintext); @@ -367,7 +375,7 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, wc_Des_EcbEncrypt(&des, lmbuffer, magic, DES_KEY_SIZE); setup_des_key(pw + 7, &des); wc_Des_EcbEncrypt(&des, lmbuffer + 8, magic, DES_KEY_SIZE); -#elif defined(USE_GNUTLS) +#elif defined(USE_GNUTLS_DES) struct des_ctx des; setup_des_key(pw, &des); des_encrypt(&des, 8, lmbuffer, magic); @@ -543,7 +551,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(const unsigned char *ntlmv2hash, unsigned char **ntresp, unsigned int *ntresp_len) { - /* NTLMv2 response structure : + /* NTLMv2 response structure: ----------------------------------------------------------------------------- 0 HMAC MD5 16 bytes ------BLOB------------------------------------------------------------------- @@ -625,7 +633,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(const unsigned char *ntlmv2hash, * * ntlmv2hash [in] - The NTLMv2 hash (16 bytes) * challenge_client [in] - The client nonce (8 bytes) - * challenge_client [in] - The server challenge (8 bytes) + * challenge_server [in] - The server challenge (8 bytes) * lmresp [out] - The LMv2 response (24 bytes) * * Returns CURLE_OK on success. diff --git a/lib/curl_ntlm_core.h b/lib/curl_ntlm_core.h index f96bf0ada5ba..df24158f8e30 100644 --- a/lib/curl_ntlm_core.h +++ b/lib/curl_ntlm_core.h @@ -30,7 +30,7 @@ struct ntlmdata; /* Helpers to generate function byte arguments in little endian order */ -#define SHORTPAIR(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)) +#define SHORTPAIR(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)) #define LONGQUARTET(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)), \ ((int)(((x) >> 16) & 0xff)), ((int)(((x) >> 24) & 0xff)) diff --git a/lib/curl_sasl.c b/lib/curl_sasl.c index 60f085901f95..c4ff1e526220 100644 --- a/lib/curl_sasl.c +++ b/lib/curl_sasl.c @@ -35,7 +35,7 @@ #include "curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_POP3) || \ + !defined(CURL_DISABLE_POP3) || \ (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include "urldata.h" @@ -142,7 +142,7 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, const struct SASLproto *params) { - unsigned long auth = data->set.httpauth; + uint32_t auth = data->set.httpauth; sasl->params = params; /* Set protocol dependent parameters */ sasl->state = SASL_STOP; /* Not yet running */ @@ -229,7 +229,7 @@ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, if(!result && (sasl->params->flags & SASL_FLAG_BASE64)) { const char *serverdata = Curl_bufref_ptr(out); - if(!*serverdata || *serverdata == '=') + if(!*serverdata) Curl_bufref_set(out, NULL, 0, NULL); else { unsigned char *msg; @@ -252,9 +252,7 @@ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) if(sasl->params->flags & SASL_FLAG_BASE64) { if(!Curl_bufref_ptr(msg)) /* Empty message. */ Curl_bufref_set(msg, "", 0, NULL); - else if(!Curl_bufref_len(msg)) /* Explicit empty response. */ - Curl_bufref_set(msg, "=", 1, NULL); - else { + else if(Curl_bufref_len(msg)) { char *base64; size_t base64len; @@ -276,7 +274,7 @@ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data) { /* Have credentials been provided? */ - if(data->conn->user[0]) + if(data->conn->creds) return TRUE; /* EXTERNAL can authenticate without a username and/or password */ @@ -299,13 +297,15 @@ struct sasl_ctx { static bool sasl_choose_external(struct Curl_easy *data, struct sasl_ctx *sctx) { - if((sctx->enabledmechs & SASL_MECH_EXTERNAL) && !sctx->conn->passwd[0]) { + if((sctx->enabledmechs & SASL_MECH_EXTERNAL) && + !Curl_creds_has_passwd(sctx->conn->creds)) { sctx->mech = SASL_MECH_STRING_EXTERNAL; sctx->state1 = SASL_EXTERNAL; sctx->sasl->authused = SASL_MECH_EXTERNAL; if(sctx->sasl->force_ir || data->set.sasl_ir) - Curl_auth_create_external_message(sctx->conn->user, &sctx->resp); + Curl_auth_create_external_message( + Curl_creds_user(sctx->conn->creds), &sctx->resp); return TRUE; } return FALSE; @@ -316,10 +316,9 @@ static bool sasl_choose_krb5(struct Curl_easy *data, struct sasl_ctx *sctx) { if((sctx->enabledmechs & SASL_MECH_GSSAPI) && Curl_auth_is_gssapi_supported() && - Curl_auth_user_contains_domain(sctx->conn->user)) { - const char *service = data->set.str[STRING_SERVICE_NAME] ? - data->set.str[STRING_SERVICE_NAME] : - sctx->sasl->params->service; + Curl_auth_user_contains_domain(sctx->conn->creds)) { + const char *service = Curl_creds_has_sasl_service(sctx->conn->creds) ? + Curl_creds_sasl_service(sctx->conn->creds) : sctx->sasl->params->service; sctx->sasl->mutual_auth = FALSE; sctx->mech = SASL_MECH_STRING_GSSAPI; @@ -330,9 +329,9 @@ static bool sasl_choose_krb5(struct Curl_easy *data, struct sasl_ctx *sctx) if(sctx->sasl->force_ir || data->set.sasl_ir) { struct kerberos5data *krb5 = Curl_auth_krb5_get(sctx->conn); sctx->result = !krb5 ? CURLE_OUT_OF_MEMORY : - Curl_auth_create_gssapi_user_message(data, sctx->conn->user, - sctx->conn->passwd, - service, sctx->conn->host.name, + Curl_auth_create_gssapi_user_message(data, sctx->conn->creds, + service, + sctx->conn->origin->hostname, (bool)sctx->sasl->mutual_auth, NULL, krb5, &sctx->resp); } @@ -374,8 +373,7 @@ static bool sasl_choose_gsasl(struct Curl_easy *data, struct sasl_ctx *sctx) Curl_bufref_init(&nullmsg); sctx->state1 = SASL_GSASL; sctx->state2 = SASL_GSASL; - sctx->result = Curl_auth_gsasl_start(data, sctx->conn->user, - sctx->conn->passwd, gsasl); + sctx->result = Curl_auth_gsasl_start(data, sctx->conn->creds, gsasl); if(!sctx->result && (sctx->sasl->force_ir || data->set.sasl_ir)) sctx->result = Curl_auth_gsasl_token(data, &nullmsg, gsasl, &sctx->resp); return TRUE; @@ -411,9 +409,8 @@ static bool sasl_choose_ntlm(struct Curl_easy *data, struct sasl_ctx *sctx) { if((sctx->enabledmechs & SASL_MECH_NTLM) && Curl_auth_is_ntlm_supported()) { - const char *service = data->set.str[STRING_SERVICE_NAME] ? - data->set.str[STRING_SERVICE_NAME] : - sctx->sasl->params->service; + const char *service = Curl_creds_has_sasl_service(sctx->conn->creds) ? + Curl_creds_sasl_service(sctx->conn->creds) : sctx->sasl->params->service; const char *hostname; Curl_conn_get_current_host(data, FIRSTSOCKET, &hostname, NULL); @@ -426,9 +423,7 @@ static bool sasl_choose_ntlm(struct Curl_easy *data, struct sasl_ctx *sctx) if(sctx->sasl->force_ir || data->set.sasl_ir) { struct ntlmdata *ntlm = Curl_auth_ntlm_get(sctx->conn, FALSE); sctx->result = !ntlm ? CURLE_OUT_OF_MEMORY : - Curl_auth_create_ntlm_type1_message(data, - sctx->conn->user, - sctx->conn->passwd, + Curl_auth_create_ntlm_type1_message(data, sctx->conn->creds, service, hostname, ntlm, &sctx->resp); } @@ -440,11 +435,8 @@ static bool sasl_choose_ntlm(struct Curl_easy *data, struct sasl_ctx *sctx) static bool sasl_choose_oauth(struct Curl_easy *data, struct sasl_ctx *sctx) { - const char *oauth_bearer = - (!data->state.this_is_a_follow || data->set.allow_auth_to_other_hosts) ? - data->set.str[STRING_BEARER] : NULL; - - if(oauth_bearer && (sctx->enabledmechs & SASL_MECH_OAUTHBEARER)) { + if(Curl_creds_has_oauth_bearer(data->state.creds) && + (sctx->enabledmechs & SASL_MECH_OAUTHBEARER)) { const char *hostname; int port; Curl_conn_get_current_host(data, FIRSTSOCKET, &hostname, &port); @@ -456,9 +448,8 @@ static bool sasl_choose_oauth(struct Curl_easy *data, struct sasl_ctx *sctx) if(sctx->sasl->force_ir || data->set.sasl_ir) sctx->result = - Curl_auth_create_oauth_bearer_message(sctx->conn->user, - hostname, port, - oauth_bearer, &sctx->resp); + Curl_auth_create_oauth_bearer_message(sctx->conn->creds, + hostname, port, &sctx->resp); return TRUE; } return FALSE; @@ -466,19 +457,15 @@ static bool sasl_choose_oauth(struct Curl_easy *data, struct sasl_ctx *sctx) static bool sasl_choose_oauth2(struct Curl_easy *data, struct sasl_ctx *sctx) { - const char *oauth_bearer = - (!data->state.this_is_a_follow || data->set.allow_auth_to_other_hosts) ? - data->set.str[STRING_BEARER] : NULL; - - if(oauth_bearer && (sctx->enabledmechs & SASL_MECH_XOAUTH2)) { + if(Curl_creds_has_oauth_bearer(sctx->conn->creds) && + (sctx->enabledmechs & SASL_MECH_XOAUTH2)) { sctx->mech = SASL_MECH_STRING_XOAUTH2; sctx->state1 = SASL_OAUTH2; sctx->sasl->authused = SASL_MECH_XOAUTH2; if(sctx->sasl->force_ir || data->set.sasl_ir) - sctx->result = Curl_auth_create_xoauth_bearer_message(sctx->conn->user, - oauth_bearer, - &sctx->resp); + sctx->result = Curl_auth_create_xoauth_bearer_message( + sctx->conn->creds, &sctx->resp); return TRUE; } return FALSE; @@ -493,9 +480,7 @@ static bool sasl_choose_plain(struct Curl_easy *data, struct sasl_ctx *sctx) if(sctx->sasl->force_ir || data->set.sasl_ir) sctx->result = - Curl_auth_create_plain_message(sctx->conn->sasl_authzid, - sctx->conn->user, sctx->conn->passwd, - &sctx->resp); + Curl_auth_create_plain_message(sctx->conn->creds, &sctx->resp); return TRUE; } return FALSE; @@ -510,7 +495,8 @@ static bool sasl_choose_login(struct Curl_easy *data, struct sasl_ctx *sctx) sctx->sasl->authused = SASL_MECH_LOGIN; if(sctx->sasl->force_ir || data->set.sasl_ir) - Curl_auth_create_login_message(sctx->conn->user, &sctx->resp); + Curl_auth_create_login_message( + Curl_creds_user(sctx->conn->creds), &sctx->resp); return TRUE; } return FALSE; @@ -599,13 +585,6 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, struct bufref resp; const char *hostname; int port; -#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; -#endif - const char *oauth_bearer = data->set.str[STRING_BEARER]; struct bufref serverdata; Curl_conn_get_current_host(data, FIRSTSOCKET, &hostname, &port); @@ -633,18 +612,17 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, *progress = SASL_DONE; return result; case SASL_PLAIN: - result = Curl_auth_create_plain_message(conn->sasl_authzid, - conn->user, conn->passwd, &resp); + result = Curl_auth_create_plain_message(conn->creds, &resp); break; case SASL_LOGIN: - Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(Curl_creds_user(conn->creds), &resp); newstate = SASL_LOGIN_PASSWD; break; case SASL_LOGIN_PASSWD: - Curl_auth_create_login_message(conn->passwd, &resp); + Curl_auth_create_login_message(Curl_creds_passwd(conn->creds), &resp); break; case SASL_EXTERNAL: - Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(Curl_creds_user(conn->creds), &resp); break; #ifdef USE_GSASL case SASL_GSASL: @@ -662,15 +640,16 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, case SASL_CRAMMD5: result = get_server_message(sasl, data, &serverdata); if(!result) - result = Curl_auth_create_cram_md5_message(&serverdata, conn->user, - conn->passwd, &resp); + result = Curl_auth_create_cram_md5_message(&serverdata, conn->creds, + &resp); break; case SASL_DIGESTMD5: result = get_server_message(sasl, data, &serverdata); if(!result) result = Curl_auth_create_digest_md5_message(data, &serverdata, - conn->user, conn->passwd, - service, &resp); + conn->creds, + sasl->params->service, + &resp); if(!result && (sasl->params->flags & SASL_FLAG_BASE64)) newstate = SASL_DIGESTMD5_RESP; break; @@ -684,9 +663,8 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, /* Create the type-1 message */ struct ntlmdata *ntlm = Curl_auth_ntlm_get(conn, FALSE); result = !ntlm ? CURLE_OUT_OF_MEMORY : - Curl_auth_create_ntlm_type1_message(data, - conn->user, conn->passwd, - service, hostname, + Curl_auth_create_ntlm_type1_message(data, conn->creds, + sasl->params->service, hostname, ntlm, &resp); newstate = SASL_NTLM_TYPE2MSG; break; @@ -699,9 +677,8 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, if(!result) result = Curl_auth_decode_ntlm_type2_message(data, &serverdata, ntlm); if(!result) - result = Curl_auth_create_ntlm_type3_message(data, conn->user, - conn->passwd, ntlm, - &resp); + result = Curl_auth_create_ntlm_type3_message(data, conn->creds, + ntlm, &resp); break; } #endif @@ -710,8 +687,9 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, case SASL_GSSAPI: { struct kerberos5data *krb5 = Curl_auth_krb5_get(conn); result = !krb5 ? CURLE_OUT_OF_MEMORY : - Curl_auth_create_gssapi_user_message(data, conn->user, conn->passwd, - service, conn->host.name, + Curl_auth_create_gssapi_user_message(data, conn->creds, + sasl->params->service, + conn->origin->hostname, (bool)sasl->mutual_auth, NULL, krb5, &resp); newstate = SASL_GSSAPI_TOKEN; @@ -726,7 +704,7 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, else if(sasl->mutual_auth) { /* Decode the user token challenge and create the optional response message */ - result = Curl_auth_create_gssapi_user_message(data, NULL, NULL, + result = Curl_auth_create_gssapi_user_message(data, NULL, NULL, NULL, (bool)sasl->mutual_auth, &serverdata, @@ -735,10 +713,9 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, } else /* Decode the security challenge and create the response message */ - result = Curl_auth_create_gssapi_security_message(data, - conn->sasl_authzid, - &serverdata, - krb5, &resp); + result = Curl_auth_create_gssapi_security_message( + data, Curl_creds_sasl_authzid(conn->creds), &serverdata, + krb5, &resp); } break; case SASL_GSSAPI_NO_DATA: @@ -749,10 +726,9 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, if(!krb5) result = CURLE_OUT_OF_MEMORY; else - result = Curl_auth_create_gssapi_security_message(data, - conn->sasl_authzid, - &serverdata, - krb5, &resp); + result = Curl_auth_create_gssapi_security_message( + data, Curl_creds_sasl_authzid(conn->creds), &serverdata, + krb5, &resp); } break; #endif @@ -760,18 +736,16 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, case SASL_OAUTH2: /* Create the authorization message */ if(sasl->authused == SASL_MECH_OAUTHBEARER) { - result = Curl_auth_create_oauth_bearer_message(conn->user, + result = Curl_auth_create_oauth_bearer_message(conn->creds, hostname, port, - oauth_bearer, &resp); /* Failures maybe sent by the server as continuations for OAUTHBEARER */ newstate = SASL_OAUTH2_RESP; } else - result = Curl_auth_create_xoauth_bearer_message(conn->user, - oauth_bearer, + result = Curl_auth_create_xoauth_bearer_message(conn->creds, &resp); break; @@ -861,7 +835,7 @@ static void sasl_unchosen(struct Curl_easy *data, unsigned short mech, else { if(param_missing) infof(data, "SASL: %s is missing %s", mname, param_missing); - if(!data->conn->user[0]) + if(!Curl_creds_has_user(data->conn->creds)) infof(data, "SASL: %s is missing username", mname); } } @@ -903,7 +877,8 @@ CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data) "auth mechanisms"); else { infof(data, "SASL: no auth mechanism offered could be selected"); - if((enabledmechs & SASL_MECH_EXTERNAL) && data->conn->passwd[0]) + if((enabledmechs & SASL_MECH_EXTERNAL) && + Curl_creds_has_passwd(data->conn->creds)) 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); @@ -918,10 +893,10 @@ CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data) 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] ? + Curl_creds_has_oauth_bearer(data->conn->creds) ? NULL : "CURLOPT_XOAUTH2_BEARER"); sasl_unchosen(data, SASL_MECH_XOAUTH2, enabledmechs, TRUE, TRUE, - data->set.str[STRING_BEARER] ? + Curl_creds_has_oauth_bearer(data->conn->creds) ? NULL : "CURLOPT_XOAUTH2_BEARER"); } #endif /* CURLVERBOSE */ diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 6578b664d422..e91ab3b6b15f 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -77,7 +77,7 @@ #if defined(__MINGW32__) && \ (!defined(__MINGW64_VERSION_MAJOR) || (__MINGW64_VERSION_MAJOR < 3)) -#error "Building curl requires mingw-w64 3.0 or later" +#error "mingw-w64 3.0 or greater required" #endif /* Visual Studio 2010 is the minimum Visual Studio version we support. @@ -90,7 +90,7 @@ /* Disable Visual Studio warnings: 4127 "conditional expression is constant" */ #pragma warning(disable:4127) #ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS /* for getenv(), sscanf() */ +#define _CRT_SECURE_NO_WARNINGS /* for getenv(), sscanf(), vsnprintf() */ #endif #endif /* _MSC_VER */ @@ -161,10 +161,6 @@ # include "config-mac.h" #endif -#ifdef __riscos__ -# include "config-riscos.h" -#endif - #ifdef __OS400__ # include "config-os400.h" #endif @@ -219,7 +215,7 @@ #ifdef HAVE_LIBZ # ifndef ZLIB_CONST -# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 and upper. */ +# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 or greater. */ # endif #endif @@ -256,7 +252,7 @@ /* 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 */ +/* Give calloc a chance to be included early, so we do not redefine */ #ifdef HAVE_THREADS_POSIX # include #endif @@ -309,13 +305,6 @@ # endif #endif -/* - * When http is disabled rtsp is not supported. - */ -#if defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_RTSP) -# define CURL_DISABLE_RTSP -#endif - /* * When HTTP is disabled, disable HTTP-only features */ @@ -323,8 +312,8 @@ # ifndef CURL_DISABLE_ALTSVC # define CURL_DISABLE_ALTSVC # endif -# ifndef CURL_DISABLE_COOKIES -# define CURL_DISABLE_COOKIES +# ifndef CURL_DISABLE_AWS +# define CURL_DISABLE_AWS # endif # ifndef CURL_DISABLE_BASIC_AUTH # define CURL_DISABLE_BASIC_AUTH @@ -332,8 +321,8 @@ # ifndef CURL_DISABLE_BEARER_AUTH # define CURL_DISABLE_BEARER_AUTH # endif -# ifndef CURL_DISABLE_AWS -# define CURL_DISABLE_AWS +# ifndef CURL_DISABLE_COOKIES +# define CURL_DISABLE_COOKIES # endif # ifndef CURL_DISABLE_DOH # define CURL_DISABLE_DOH @@ -347,9 +336,15 @@ # ifndef CURL_DISABLE_HSTS # define CURL_DISABLE_HSTS # endif +# ifndef CURL_DISABLE_HTTPSIG +# define CURL_DISABLE_HTTPSIG +# endif # ifndef CURL_DISABLE_HTTP_AUTH # define CURL_DISABLE_HTTP_AUTH # endif +# ifndef CURL_DISABLE_RTSP +# define CURL_DISABLE_RTSP +# endif # ifndef CURL_DISABLE_WEBSOCKETS # define CURL_DISABLE_WEBSOCKETS /* no WebSockets without HTTP present */ # endif @@ -514,7 +509,7 @@ # undef HAVE_FCNTL # undef HAVE_FCNTL_O_NONBLOCK # else - /* use libc networking and hence close() and fnctl() */ + /* use libc networking and hence close() and fcntl() */ # undef HAVE_CLOSESOCKET_CAMEL # undef HAVE_IOCTLSOCKET_CAMEL # endif @@ -543,10 +538,6 @@ # endif #endif -#ifndef STDC_HEADERS /* no standard C headers! */ -#include -#endif - #include #define HAVE_UINTPTR_T /* assume uintptr_t is provided by stdint.h */ @@ -563,12 +554,15 @@ #endif #include +/* Include after setting system macros that may affect type sizes + (e.g. 'off_t' or 'time_t'), or suppress warnings + (e.g. '_CRT_SECURE_NO_WARNINGS`), but before including sys/stat.h */ +#include #ifdef _WIN32 # ifdef HAVE_IO_H # include # endif -# include # include /* Large file (>2Gb) support using Win32 functions. */ # define curl_lseek _lseeki64 @@ -640,7 +634,7 @@ # endif #endif -#if (SIZEOF_CURL_OFF_T < 8) +#if SIZEOF_CURL_OFF_T < 8 #error "too small curl_off_t" #else /* assume SIZEOF_CURL_OFF_T == 8 */ @@ -651,7 +645,7 @@ #define FMT_OFF_T CURL_FORMAT_CURL_OFF_T #define FMT_OFF_TU CURL_FORMAT_CURL_OFF_TU -#if (SIZEOF_TIME_T == 4) +#if SIZEOF_TIME_T == 4 # ifdef HAVE_TIME_T_UNSIGNED # define TIME_T_MAX UINT_MAX # define TIME_T_MIN 0 @@ -888,10 +882,6 @@ #include #include -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include #if !defined(_WIN32) || defined(__MINGW32__) @@ -1011,7 +1001,7 @@ 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 */ @@ -1034,7 +1024,7 @@ struct timeval { #endif /* SEND_NONCONST_ARG2 */ #else /* HAVE_SEND */ #ifndef swrite -#error "Missing definition of macro swrite!" +#error "Missing definition of macro swrite" #endif #endif /* HAVE_SEND */ @@ -1114,6 +1104,10 @@ typedef unsigned int curl_bit; #include "curl_ctype.h" +#if defined(DEBUGBUILD) && defined(NDEBUG) +#error "Debug-enabled builds cannot be combined with NDEBUG" +#endif + /* * Macro used to include code only in debug builds. */ @@ -1191,6 +1185,15 @@ typedef unsigned int curl_bit; #define SOCKEWOULDBLOCK EWOULDBLOCK #endif +/* The socket error may be EWOULDBLOCK or on some systems EAGAIN when + it returned due to its inability to send/read data without blocking. + We treat both error codes the same here. */ +#if !defined(USE_WINSOCK) && EAGAIN != SOCKEWOULDBLOCK +#define SOCK_EAGAIN(e) ((e) == SOCKEWOULDBLOCK || (e) == EAGAIN) +#else +#define SOCK_EAGAIN(e) ((e) == SOCKEWOULDBLOCK) +#endif + /* * Macro argv_item_t hides platform details to code using it. */ @@ -1239,7 +1242,7 @@ typedef unsigned int curl_bit; */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) # if defined(SOCKET) || defined(USE_WINSOCK) -# error "Winsock and lwIP TCP/IP stack definitions shall not coexist!" +# error "Winsock and lwIP TCP/IP stack definitions shall not coexist" # endif #endif @@ -1299,19 +1302,17 @@ typedef unsigned int curl_bit; #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 -*/ + Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 */ #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. -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 -endings either CRLF or LF so 't' is appropriate. -*/ + 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 endings either CRLF or LF so 't' is appropriate. */ #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "w" #define FOPEN_APPENDTEXT "a" @@ -1340,10 +1341,14 @@ endings either CRLF or LF so 't' is appropriate. #define CURLMAX(x, y) ((x) > (y) ? (x) : (y)) #define CURLMIN(x, y) ((x) < (y) ? (x) : (y)) +/* Convenience macro to provide the length of a string literal size without + the null-terminator. Equivalent to strlen() for constant strings. */ +#define CURL_CSTRLEN(x) (sizeof(x) - 1) + /* A convenience macro to provide both the string literal and the length of the string literal in one go, useful for functions that take "string,len" as their argument */ -#define STRCONST(x) x, sizeof(x) - 1 +#define STRCONST(x) x, CURL_CSTRLEN(x) #define CURL_ARRAYSIZE(A) (sizeof(A) / sizeof((A)[0])) @@ -1418,13 +1423,15 @@ extern FILE *curl_dbg_logfile; /* memory functions */ CURL_EXTERN void curl_dbg_free(void *ptr, int line, const char *source); CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) - void *curl_dbg_malloc(size_t size, int line, const char *source); + void *curl_dbg_malloc(size_t wantedsize, 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); + void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_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); + void *curl_dbg_realloc(void *ptr, size_t wantedsize, int line, + const char *source); CURL_EXTERN ALLOC_FUNC - char *curl_dbg_strdup(const char *str, int line, const char *src); + char *curl_dbg_strdup(const char *str, int line, const char *source); #if defined(_WIN32) && defined(UNICODE) CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source); @@ -1596,7 +1603,7 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #endif #if defined(USE_UNIX_SOCKETS) && defined(_WIN32) -/* Offered by mingw-w64 v10+. MS SDK 10.17763/~VS2017+. */ +/* Offered by mingw-w64 v10+, MS SDK 10.0.16299.0/VS2017 15.4+ */ #if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR >= 10) # include #elif !defined(UNIX_PATH_MAX) /* Replicate logic present in afunix.h */ @@ -1612,7 +1619,7 @@ typedef struct sockaddr_un { #ifdef USE_OPENSSL /* OpenSSL 3 marks these functions deprecated but we have no replacements (yet) so tell the compiler to not warn for them: - - DES_* (for NTLM), SSL_CTX_set_srp_* (for TLS-SRP) + - DES_* (for NTLM) - EVP_PKEY_get1_RSA, MD5_*, RSA_flags, RSA_free (auto-skipped for OpenSSL built with no-deprecated) */ # define OPENSSL_SUPPRESS_DEPRECATED @@ -1632,7 +1639,7 @@ typedef struct sockaddr_un { /* 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 +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 (and later) supports 'inline' keyword */ # define CURL_INLINE inline /* 'inline' keyword supported */ #elif defined(__GNUC__) && __GNUC__ >= 3 @@ -1662,4 +1669,49 @@ typedef struct sockaddr_un { #define NOVERBOSE(x) x #endif +/* For FreeBSD it is included from curl/curl.h */ +#if defined(__DragonFly__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include /* for __DragonFly_version, OpenBSD, + __NetBSD_Version__ */ +#endif + +/* NetBSD before 6.1 did not set SS_NBIO for SOCK_NONBLOCK. */ +#if defined(SOCK_NONBLOCK) && \ + (!defined(__NetBSD__) || (__NetBSD_Version__ >= 601000000)) +#define CURL_USE_SOCK_NONBLOCK +#endif + +#ifndef _CURL_LOCAL_MEMZERO /* to be removed after a couple of releases */ +#ifdef _WIN32 +#if defined(_MSC_VER) && defined(NTDDI_VERSION) && \ + (NTDDI_VERSION >= 0x0A000010) /* MS SDK 10.0.26100.0+ */ +#pragma comment(lib, "volatileaccessu.lib") +#define curlx_memzero_low(buf, size) SecureZeroMemory2(buf, size) +#else +#define curlx_memzero_low(buf, size) SecureZeroMemory(buf, size) +#endif +#elif defined(HAVE_MEMSET_S) +#define curlx_memzero_low(buf, size) (void)memset_s(buf, size, 0, size) +#elif defined(HAVE_MEMSET_EXPLICIT) +#define curlx_memzero_low(buf, size) (void)memset_explicit(buf, 0, size) +#elif defined(__CYGWIN__) || \ + (defined(__NEWLIB__) && !defined(__CLIB2__)) || \ + (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) || \ + (defined(__DragonFly__) && __DragonFly_version >= 500600 /* 5.6+ */) || \ + (defined(__FreeBSD__) && __FreeBSD_version >= 1100037 /* 11.0+ */) || \ + (defined(__OpenBSD__) && OpenBSD >= 201405 /* 5.5+ */) +#define curlx_memzero_low(buf, size) explicit_bzero(buf, size) +#elif defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 /* 7.2+ */ +#define curlx_memzero_low(buf, size) (void)explicit_memset(buf, 0, size) +#endif +#endif /* !_CURL_LOCAL_MEMZERO */ + +#ifndef curlx_memzero_low +#define USE_CURLX_MEMZERO +void curlx_memzero_low(void *buf, size_t size); +#endif +void curlx_memzero(void *buf, size_t size); +void curlx_strzero(void *buf); + #endif /* HEADER_CURL_SETUP_H */ diff --git a/lib/curl_sha256.h b/lib/curl_sha256.h index 6c48a8e1764c..fb823169f7fe 100644 --- a/lib/curl_sha256.h +++ b/lib/curl_sha256.h @@ -26,7 +26,8 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_HTTPSIG) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ defined(USE_LIBSSH2) || defined(USE_SSL) #include "curl_hmac.h" diff --git a/lib/curl_sha512_256.c b/lib/curl_sha512_256.c index 73b959c91df0..d0c81e7a5528 100644 --- a/lib/curl_sha512_256.c +++ b/lib/curl_sha512_256.c @@ -44,7 +44,7 @@ # define USE_OPENSSL_SHA512_256 1 # define HAS_SHA512_256_IMPLEMENTATION 1 # ifdef __NetBSD__ -/* Some NetBSD versions has a bug in SHA-512/256. +/* Some NetBSD versions have 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 @@ -54,7 +54,6 @@ * NetBSD 10.99.11 development. * It is safe to apply the workaround even if the bug is not present, as * the workaround reduces performance slightly. */ -# include # if __NetBSD_Version__ < 904000000 || \ (__NetBSD_Version__ >= 999000000 && \ __NetBSD_Version__ < 1000000000) || \ @@ -75,7 +74,8 @@ #endif #if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_GNUTLS) -# include +# include +# include # ifdef SHA512_256_DIGEST_SIZE # define USE_GNUTLS_SHA512_256 1 # endif @@ -102,7 +102,7 @@ typedef EVP_MD_CTX *Curl_sha512_256_ctx; /** - * Initialise structure for SHA-512/256 calculation. + * Initialize structure for SHA-512/256 calculation. * * @param context the calculation context * @return CURLE_OK if succeed, @@ -112,7 +112,7 @@ static CURLcode Curl_sha512_256_init(void *context) { Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context; - *ctx = EVP_MD_CTX_create(); + *ctx = EVP_MD_CTX_new(); if(!*ctx) return CURLE_OUT_OF_MEMORY; @@ -126,7 +126,7 @@ static CURLcode Curl_sha512_256_init(void *context) } /* Cleanup */ - EVP_MD_CTX_destroy(*ctx); + EVP_MD_CTX_free(*ctx); return CURLE_FAILED_INIT; } @@ -145,7 +145,7 @@ static CURLcode Curl_sha512_256_update(void *context, Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context; if(!EVP_DigestUpdate(*ctx, data, length)) - return CURLE_SSL_CIPHER; + return CURLE_BAD_FUNCTION_ARGUMENT; return CURLE_OK; } @@ -168,17 +168,17 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context) /* 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]; - result = EVP_DigestFinal_ex(*ctx, - tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; + result = EVP_DigestFinal_ex(*ctx, tmp_digest, NULL) ? + CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; if(result == CURLE_OK) memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE); - explicit_memset(tmp_digest, 0, sizeof(tmp_digest)); + curlx_memzero(tmp_digest, sizeof(tmp_digest)); #else /* !NEED_NETBSD_SHA512_256_WORKAROUND */ result = EVP_DigestFinal_ex(*ctx, digest, NULL) ? - CURLE_OK : CURLE_SSL_CIPHER; + CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; #endif /* NEED_NETBSD_SHA512_256_WORKAROUND */ - EVP_MD_CTX_destroy(*ctx); + EVP_MD_CTX_free(*ctx); *ctx = NULL; return result; @@ -206,7 +206,7 @@ static CURLcode Curl_sha512_256_update(void *ctx, do { word32 ilen = (word32)CURLMIN(length, UINT_MAX); if(wc_Sha512_256Update(ctx, data, ilen)) - return CURLE_SSL_CIPHER; + return CURLE_BAD_FUNCTION_ARGUMENT; length -= ilen; data += ilen; } while(length); @@ -216,7 +216,7 @@ static CURLcode Curl_sha512_256_update(void *ctx, static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *ctx) { if(wc_Sha512_256Final(ctx, digest)) - return CURLE_SSL_CIPHER; + return CURLE_BAD_FUNCTION_ARGUMENT; return CURLE_OK; } @@ -231,7 +231,7 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *ctx) typedef struct sha512_256_ctx Curl_sha512_256_ctx; /** - * Initialise structure for SHA-512/256 calculation. + * Initialize structure for SHA-512/256 calculation. * * @param context the calculation context * @return always CURLE_OK @@ -262,7 +262,7 @@ static CURLcode Curl_sha512_256_update(void *context, { Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context; - DEBUGASSERT((data != NULL) || (length == 0)); + DEBUGASSERT(data || (length == 0)); sha512_256_update(ctx, length, (const uint8_t *)data); @@ -281,8 +281,12 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context) { Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context; +#if NETTLE_VERSION_MAJOR >= 4 + sha512_256_digest(ctx, (uint8_t *)digest); +#else sha512_256_digest(ctx, (size_t)CURL_SHA512_256_DIGEST_SIZE, (uint8_t *)digest); +#endif return CURLE_OK; } @@ -296,7 +300,7 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context) #ifdef __GNUC__ # if defined(__has_attribute) && defined(__STDC_VERSION__) -# if __has_attribute(always_inline) && __STDC_VERSION__ >= 199901 +# if __has_attribute(always_inline) && __STDC_VERSION__ >= 199901L # define CURL_FORCEINLINE CURL_INLINE __attribute__((always_inline)) # endif # endif @@ -430,7 +434,7 @@ struct Curl_sha512_256ctx { typedef struct Curl_sha512_256ctx Curl_sha512_256_ctx; /** - * Initialise structure for SHA-512/256 calculation. + * Initialize structure for SHA-512/256 calculation. * * @param context the calculation context * @return always CURLE_OK @@ -456,7 +460,7 @@ static CURLcode Curl_sha512_256_init(void *context) ctx->H[6] = UINT64_C(0x2B0199FC2C85B8AA); ctx->H[7] = UINT64_C(0x0EB72DDC81C52CA2); - /* Initialise number of bytes and high part of number of bits. */ + /* Initialize number of bytes and high part of number of bits. */ ctx->count = UINT64_C(0); ctx->count_bits_hi = UINT64_C(0); @@ -494,14 +498,10 @@ static void Curl_sha512_256_transform(uint64_t H[SHA512_256_HASH_SIZE_WORDS], /* 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)) +#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; @@ -645,12 +645,12 @@ static CURLcode Curl_sha512_256_update(void *context, /* the void pointer here is required to mute Intel compiler warning */ void * const ctx_buf = ctx->buffer; - DEBUGASSERT((data != NULL) || (length == 0)); + DEBUGASSERT(data || (length == 0)); if(length == 0) return CURLE_OK; /* Shortcut, do nothing */ - /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + /* 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; @@ -711,7 +711,7 @@ static CURLcode Curl_sha512_256_update(void *context, static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context) { struct Curl_sha512_256ctx * const ctx = (struct Curl_sha512_256ctx *)context; - uint64_t num_bits; /* Number of processed bits */ + 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; diff --git a/lib/curl_share.c b/lib/curl_share.c index 386a2b547d33..cbadd57d3795 100644 --- a/lib/curl_share.c +++ b/lib/curl_share.c @@ -34,8 +34,11 @@ static void share_destroy(struct Curl_share *share) { + if(!share) + return; + if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { - Curl_cpool_destroy(&share->cpool); + Curl_cpool_destroy(&share->cpool, share->admin); } Curl_dnscache_destroy(&share->dnscache); @@ -61,7 +64,7 @@ static void share_destroy(struct Curl_share *share) #ifdef USE_MUTEX Curl_mutex_destroy(&share->lock); #endif - share->magic = 0; + curlx_memzero(share, sizeof(*share)); curlx_free(share); } @@ -97,11 +100,11 @@ static uint32_t share_ref_inc(struct Curl_share *share) uint32_t n; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); - n = ++(share->ref_count); + n = ++share->ref_count; share->has_been_shared = TRUE; Curl_mutex_release(&share->lock); #else - n = ++(share->ref_count); + n = ++share->ref_count; share->has_been_shared = TRUE; #endif return n; @@ -113,10 +116,10 @@ static uint32_t share_ref_dec(struct Curl_share *share) #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); DEBUGASSERT(share->ref_count); - n = --(share->ref_count); + n = --share->ref_count; Curl_mutex_release(&share->lock); #else - n = --(share->ref_count); + n = --share->ref_count; #endif return n; } @@ -260,8 +263,8 @@ CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) case CURL_LOCK_DATA_CONNECT: /* 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); + if(!share->cpool.initialized) { + Curl_cpool_init(&share->cpool, share, 103); } break; @@ -368,11 +371,11 @@ CURLSHcode curl_share_cleanup(CURLSH *sh) return CURLSHE_OK; } -CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, - curl_lock_access accesstype) +CURLSHcode Curl_share_lock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type, + curl_lock_access accesstype) { - struct Curl_share *share = data->share; - if(!share) return CURLSHE_INVALID; @@ -385,10 +388,16 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, return CURLSHE_OK; } -CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) +CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, + curl_lock_access accesstype) { - struct Curl_share *share = data->share; + return Curl_share_lock_share(data->share, data, type, accesstype); +} +CURLSHcode Curl_share_unlock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type) +{ if(!share) return CURLSHE_INVALID; @@ -400,6 +409,11 @@ CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) return CURLSHE_OK; } +CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) +{ + return Curl_share_unlock_share(data->share, data, type); +} + CURLcode Curl_share_easy_unlink(struct Curl_easy *data) { struct Curl_share *share = data->share; @@ -407,9 +421,13 @@ CURLcode Curl_share_easy_unlink(struct Curl_easy *data) if(share) { bool locked = share_lock_acquire(share, data); - /* If data has a connection from this share, detach it. */ - if(data->conn && (share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) - Curl_detach_connection(data); + /* If share caches connections, detach any existing connection and + * forget its identifier. */ + if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { + if(data->conn) + Curl_detach_connection(data); + data->state.lastconnect_id = -1; + } #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies == data->cookies) diff --git a/lib/curl_share.h b/lib/curl_share.h index 69001be705a5..5c687989e01f 100644 --- a/lib/curl_share.h +++ b/lib/curl_share.h @@ -80,10 +80,18 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, curl_lock_access accesstype); CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type); +CURLSHcode Curl_share_lock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type, + curl_lock_access accesstype); +CURLSHcode Curl_share_unlock_share(struct Curl_share *share, + struct Curl_easy *data, + curl_lock_data type); + /* 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 << CURL_LOCK_DATA_SSL_SESSION))) +#define CURL_SHARE_ssl_scache(data) \ + ((data)->share && \ + ((data)->share->specifier & (1 << CURL_LOCK_DATA_SSL_SESSION))) CURLcode Curl_share_easy_unlink(struct Curl_easy *data); CURLcode Curl_share_easy_link(struct Curl_easy *data, diff --git a/lib/curl_sspi.c b/lib/curl_sspi.c index 3ea17621b148..799f3429a472 100644 --- a/lib/curl_sspi.c +++ b/lib/curl_sspi.c @@ -93,26 +93,29 @@ void Curl_sspi_global_cleanup(void) * Returns CURLE_OK on success. */ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, - SEC_WINNT_AUTH_IDENTITY *identity) + SEC_WINNT_AUTH_IDENTITY_EX *identity) { xcharp_u useranddomain; xcharp_u user, dup_user; xcharp_u domain, dup_domain; xcharp_u passwd, dup_passwd; size_t domlen = 0; + size_t pwlen; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); + identity->Version = SEC_WINNT_AUTH_IDENTITY_VERSION; + identity->Length = sizeof(*identity); useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('\\')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('\\')); if(!user.const_tchar_ptr) - user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('/')); + user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, _TEXT('/')); if(user.tchar_ptr) { domain.tchar_ptr = useranddomain.tchar_ptr; @@ -121,7 +124,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, } else { user.tchar_ptr = useranddomain.tchar_ptr; - domain.const_tchar_ptr = TEXT(""); + domain.const_tchar_ptr = _TEXT(""); domlen = 0; } @@ -155,17 +158,20 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, curlx_free(dup_domain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } + pwlen = _tcslen(passwd.tchar_ptr); dup_passwd.tchar_ptr = curlx_tcsdup(passwd.tchar_ptr); if(!dup_passwd.tchar_ptr) { curlx_free(dup_user.tchar_ptr); curlx_free(dup_domain.tchar_ptr); + curlx_memzero(passwd.tchar_ptr, pwlen * sizeof(*passwd.tchar_ptr)); curlx_free(passwd.tchar_ptr); return CURLE_OUT_OF_MEMORY; } identity->Password = dup_passwd.tbyte_ptr; - identity->PasswordLength = curlx_uztoul(_tcslen(dup_passwd.tchar_ptr)); + identity->PasswordLength = curlx_uztoul(pwlen); dup_passwd.tchar_ptr = NULL; + curlx_memzero(passwd.tchar_ptr, pwlen * sizeof(*passwd.tchar_ptr)); curlx_free(passwd.tchar_ptr); identity->User = dup_user.tbyte_ptr; @@ -175,13 +181,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, identity->DomainLength = curlx_uztoul(domlen); dup_domain.tchar_ptr = NULL; - /* Setup the identity's flags */ - identity->Flags = (unsigned long) -#ifdef UNICODE - SEC_WINNT_AUTH_IDENTITY_UNICODE; -#else - SEC_WINNT_AUTH_IDENTITY_ANSI; -#endif + identity->Flags = CURL_SEC_WINNT_AUTH_IDENTITY; return CURLE_OK; } @@ -195,10 +195,12 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, * * identity [in/out] - The identity structure. */ -void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity) +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity) { if(identity) { curlx_safefree(identity->User); + curlx_memzero(identity->Password, + identity->PasswordLength * sizeof(*identity->Password)); curlx_safefree(identity->Password); curlx_safefree(identity->Domain); } diff --git a/lib/curl_sspi.h b/lib/curl_sspi.h index 3779d5175340..da8e26d642f5 100644 --- a/lib/curl_sspi.h +++ b/lib/curl_sspi.h @@ -29,19 +29,36 @@ #include +/* Local helper macro */ +#ifdef UNICODE +#define CURL_SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_UNICODE +#else +#define CURL_SEC_WINNT_AUTH_IDENTITY SEC_WINNT_AUTH_IDENTITY_ANSI +#endif + +/* Offered by mingw-w64 v9+, MS SDK 7.0A/VS2010+ */ +#ifndef SECPKG_ATTR_ENDPOINT_BINDINGS +#define SECPKG_ATTR_ENDPOINT_BINDINGS 26 +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct { + unsigned long BindingsLength; + SEC_CHANNEL_BINDINGS *Bindings; +} SecPkgContext_Bindings; +#endif + CURLcode Curl_sspi_global_init(void); void Curl_sspi_global_cleanup(void); /* 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); + SEC_WINNT_AUTH_IDENTITY_EX *identity); /* This is used to generate an SSPI identity structure */ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, - SEC_WINNT_AUTH_IDENTITY *identity); + SEC_WINNT_AUTH_IDENTITY_EX *identity); /* This is used to free an SSPI identity structure */ -void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity); /* Forward-declaration of global variables defined in curl_sspi.c */ extern PSecurityFunctionTable Curl_pSecFn; @@ -52,25 +69,25 @@ extern PSecurityFunctionTable Curl_pSecFn; #define SP_NAME_NEGOTIATE "Negotiate" #define SP_NAME_KERBEROS "Kerberos" -/* Offered by mingw-w64 v9+. MS SDK 7.0A+. */ +/* Offered by mingw-w64 v9+, MS SDK 7.0A/VS2010+ */ #ifndef ISC_REQ_USE_HTTP_STYLE #define ISC_REQ_USE_HTTP_STYLE 0x01000000 #endif -/* Offered by mingw-w64 v8+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v8+, MS SDK 6.0A/VS2008+ */ #ifndef SEC_E_INVALID_PARAMETER #define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) #endif -/* Offered by mingw-w64 v8+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v8+, MS SDK 6.0A/VS2008+ */ #ifndef SEC_E_DELEGATION_POLICY #define SEC_E_DELEGATION_POLICY ((HRESULT)0x8009035EL) #endif -/* Offered by mingw-w64 v8+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v8+, MS SDK 6.0A/VS2008+ */ #ifndef SEC_E_POLICY_NLTM_ONLY #define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) #endif -/* Offered by mingw-w64 v8+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v8+, MS SDK 6.0A/VS2008+ */ #ifndef SEC_I_SIGNATURE_NEEDED #define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) #endif diff --git a/lib/curl_threads.c b/lib/curl_threads.c index 01041f63c3ac..a5857b43b9c7 100644 --- a/lib/curl_threads.c +++ b/lib/curl_threads.c @@ -27,7 +27,41 @@ #ifdef USE_THREADS -#ifdef HAVE_THREADS_POSIX +#ifdef _WIN32 + +curl_thread_t Curl_thread_create( + CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg) +{ + curl_thread_t t = CreateThread(NULL, 0, func, arg, 0, NULL); + if(!t) { + DWORD gle = GetLastError(); + /* !checksrc! disable ERRNOVAR 1 */ + errno = (gle == ERROR_ACCESS_DENIED || + gle == ERROR_NOT_ENOUGH_MEMORY) ? + EACCES : EINVAL; + return curl_thread_t_null; + } + return t; +} + +void Curl_thread_destroy(curl_thread_t *hnd) +{ + if(*hnd != curl_thread_t_null) { + CloseHandle(*hnd); + *hnd = curl_thread_t_null; + } +} + +int Curl_thread_join(curl_thread_t *hnd) +{ + int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); + + Curl_thread_destroy(hnd); + + return ret; +} + +#elif defined(HAVE_THREADS_POSIX) struct Curl_actual_call { unsigned int (*func)(void *); @@ -95,48 +129,47 @@ int Curl_thread_join(curl_thread_t *hnd) return ret; } -#elif defined(_WIN32) +#else +#error neither HAVE_THREADS_POSIX nor _WIN32 defined +#endif +#endif /* USE_THREADS */ -curl_thread_t Curl_thread_create( - CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg) +#ifdef USE_MUTEX + +#ifdef _WIN32 + +void Curl_cond_signal(CONDITION_VARIABLE *c) { - curl_thread_t t = CreateThread(NULL, 0, func, arg, 0, NULL); - if(!t) { - DWORD gle = GetLastError(); - /* !checksrc! disable ERRNOVAR 1 */ - errno = (gle == ERROR_ACCESS_DENIED || - gle == ERROR_NOT_ENOUGH_MEMORY) ? - EACCES : EINVAL; - return curl_thread_t_null; - } - return t; + WakeConditionVariable(c); } -void Curl_thread_destroy(curl_thread_t *hnd) +void Curl_cond_wait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m) { - if(*hnd != curl_thread_t_null) { - CloseHandle(*hnd); - *hnd = curl_thread_t_null; - } + SleepConditionVariableCS(c, m, INFINITE); } -int Curl_thread_join(curl_thread_t *hnd) +CURLcode Curl_cond_timedwait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m, + uint32_t timeout_ms) { - int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); - - Curl_thread_destroy(hnd); - - return ret; + if(!SleepConditionVariableCS(c, m, (DWORD)timeout_ms)) { + DWORD err = GetLastError(); + return (err == ERROR_TIMEOUT) ? + CURLE_OPERATION_TIMEDOUT : CURLE_UNRECOVERABLE_POLL; + } + return CURLE_OK; } -#else -#error neither HAVE_THREADS_POSIX nor _WIN32 defined -#endif -#endif /* USE_THREADS */ +curl_thread_id_t Curl_thread_get_current_id(void) +{ + return GetCurrentThreadId(); +} -#ifdef USE_MUTEX +bool Curl_thread_is_current(curl_thread_id_t tid) +{ + return tid == GetCurrentThreadId(); +} -#ifdef HAVE_THREADS_POSIX +#elif defined(HAVE_THREADS_POSIX) void Curl_cond_signal(pthread_cond_t *c) { @@ -186,28 +219,16 @@ CURLcode Curl_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m, return rc ? CURLE_UNRECOVERABLE_POLL : CURLE_OK; } -#elif defined(_WIN32) - -void Curl_cond_signal(CONDITION_VARIABLE *c) +curl_thread_id_t Curl_thread_get_current_id(void) { - WakeConditionVariable(c); + return pthread_self(); } -void Curl_cond_wait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m) +bool Curl_thread_is_current(curl_thread_id_t tid) { - SleepConditionVariableCS(c, m, INFINITE); + return !!pthread_equal(tid, pthread_self()); } -CURLcode Curl_cond_timedwait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m, - uint32_t timeout_ms) -{ - if(!SleepConditionVariableCS(c, m, (DWORD)timeout_ms)) { - DWORD err = GetLastError(); - return (err == ERROR_TIMEOUT) ? - CURLE_OPERATION_TIMEDOUT : CURLE_UNRECOVERABLE_POLL; - } - return CURLE_OK; -} #else #error neither HAVE_THREADS_POSIX nor _WIN32 defined #endif diff --git a/lib/curl_threads.h b/lib/curl_threads.h index 081d155695f8..fc73c1222e9e 100644 --- a/lib/curl_threads.h +++ b/lib/curl_threads.h @@ -26,24 +26,12 @@ #include "curl_setup.h" #ifdef USE_MUTEX -#ifdef HAVE_THREADS_POSIX -# define CURL_THREAD_RETURN_T unsigned int -# define CURL_STDCALL -# define curl_mutex_t pthread_mutex_t -# define curl_thread_t pthread_t * -# define curl_thread_t_null (pthread_t *)0 -# define Curl_mutex_init(m) pthread_mutex_init(m, NULL) -# define Curl_mutex_acquire(m) pthread_mutex_lock(m) -# define Curl_mutex_release(m) pthread_mutex_unlock(m) -# define Curl_mutex_destroy(m) pthread_mutex_destroy(m) -# define curl_cond_t pthread_cond_t -# define Curl_cond_init(c) pthread_cond_init(c, NULL) -# define Curl_cond_destroy(c) pthread_cond_destroy(c) -#elif defined(_WIN32) +#ifdef _WIN32 # define CURL_THREAD_RETURN_T DWORD # define CURL_STDCALL WINAPI # define curl_mutex_t CRITICAL_SECTION # define curl_thread_t HANDLE +# define curl_thread_id_t DWORD # define curl_thread_t_null (HANDLE)0 # define Curl_mutex_init(m) InitializeCriticalSectionEx(m, 0, 1) # define Curl_mutex_acquire(m) EnterCriticalSection(m) @@ -52,6 +40,20 @@ # define curl_cond_t CONDITION_VARIABLE # define Curl_cond_init(c) InitializeConditionVariable(c) # define Curl_cond_destroy(c) (void)(c) +#elif defined(HAVE_THREADS_POSIX) +# define CURL_THREAD_RETURN_T unsigned int +# define CURL_STDCALL +# define curl_mutex_t pthread_mutex_t +# define curl_thread_t pthread_t * +# define curl_thread_id_t pthread_t +# define curl_thread_t_null (pthread_t *)0 +# define Curl_mutex_init(m) pthread_mutex_init(m, NULL) +# define Curl_mutex_acquire(m) pthread_mutex_lock(m) +# define Curl_mutex_release(m) pthread_mutex_unlock(m) +# define Curl_mutex_destroy(m) pthread_mutex_destroy(m) +# define curl_cond_t pthread_cond_t +# define Curl_cond_init(c) pthread_cond_init(c, NULL) +# define Curl_cond_destroy(c) pthread_cond_destroy(c) #else #error neither HAVE_THREADS_POSIX nor _WIN32 defined #endif @@ -61,6 +63,10 @@ void Curl_cond_wait(curl_cond_t *c, curl_mutex_t *m); /* Returns CURLE_OPERATION_TIMEDOUT on timeout */ CURLcode Curl_cond_timedwait(curl_cond_t *c, curl_mutex_t *m, uint32_t timeout_ms); + +curl_thread_id_t Curl_thread_get_current_id(void); +bool Curl_thread_is_current(curl_thread_id_t tid); + #endif /* USE_MUTEX */ #ifdef USE_THREADS diff --git a/lib/curl_trc.c b/lib/curl_trc.c index c6115cf7f636..2bf81e811964 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -28,9 +28,9 @@ #include "cfilters.h" #include "multiif.h" -#include "cf-dns.h" +#include "cf-recvbuf.h" #include "cf-socket.h" -#include "connect.h" +#include "cf-setup.h" #include "http2.h" #include "http_proxy.h" #include "cf-h1-proxy.h" @@ -41,6 +41,7 @@ #include "progress.h" #include "socks.h" #include "curlx/strparse.h" +#include "vdns/cf-dns.h" #include "vtls/vtls.h" #include "vquic/vquic.h" #include "curlx/strcopy.h" @@ -50,11 +51,11 @@ static void trc_write(struct Curl_easy *data, curl_infotype type, { if(data->set.verbose) { if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } else { static const char s_infotype[CURLINFO_END][3] = { @@ -91,8 +92,7 @@ static struct curl_trc_feat Curl_trc_feat_ids = { static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) { - curl_off_t cid = data->conn ? - data->conn->connection_id : data->state.recent_conn_id; + curl_off_t cid = data->state.lastconnect_id; if(data->id >= 0) { if(cid >= 0) return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); @@ -132,22 +132,21 @@ void Curl_debug(struct Curl_easy *data, curl_infotype type, char buf[TRC_LINE_MAX]; size_t len; if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - + struct Curl_mapi_guard guard; if(CURL_TRC_IDS(data) && (size < TRC_LINE_MAX)) { len = trc_print_ids(data, buf, TRC_LINE_MAX); len += curl_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); + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, buf, len, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } else { - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fdebug); (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); + CURL_CBAPI_END(&guard); } } else { @@ -219,6 +218,12 @@ struct curl_trc_feat Curl_trc_feat_dns = { "DNS", CURL_LOG_LVL_NONE, }; +#ifndef CURL_DISABLE_DOH +struct curl_trc_feat Curl_trc_feat_doh = { + "DoH", + CURL_LOG_LVL_NONE, +}; +#endif struct curl_trc_feat Curl_trc_feat_timer = { "TIMER", CURL_LOG_LVL_NONE, @@ -302,9 +307,6 @@ static const char * const Curl_trc_timer_names[] = { "100_TIMEOUT", "ASYNC_NAME", "CONNECTTIMEOUT", - "DNS_PER_NAME", - "DNS_PER_NAME2", - "HAPPY_EYEBALLS_DNS", "HAPPY_EYEBALLS", "MULTI_PENDING", "SPEEDCHECK", @@ -320,7 +322,7 @@ static const char *trc_timer_name(int tid) { if((tid >= 0) && ((size_t)tid < CURL_ARRAYSIZE(Curl_trc_timer_names))) return Curl_trc_timer_names[(size_t)tid]; - return "UNKNOWN?"; + return "TIMER-???"; } void Curl_trc_timer(struct Curl_easy *data, int tid, const char *fmt, ...) @@ -337,15 +339,15 @@ void Curl_trc_timer(struct Curl_easy *data, int tid, const char *fmt, ...) void Curl_trc_easy_timers(struct Curl_easy *data) { - if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - const struct curltime *pnow = Curl_pgrs_now(data); - while(e) { - struct time_node *n = Curl_node_elem(e); - e = Curl_node_next(e); - CURL_TRC_TIMER(data, n->eid, "expires in %" FMT_TIMEDIFF_T "ns", - curlx_ptimediff_us(&n->time, pnow)); + if(CURL_TRC_TIMER_is_verbose(data) && data->multi) { + if(data->state.timeouts.first < EXPIRE_LAST) { + struct expire_timers *timeouts = &data->state.timeouts; + timediff_t base_us = + Curl_timeouts_offset_us(&data->multi->timeouts, Curl_pgrs_now(data)); + uint8_t id = data->state.timeouts.first; + for(; id < EXPIRE_LAST; id = timeouts->next[id]) { + CURL_TRC_TIMER(data, id, "expires in %" FMT_TIMEDIFF_T "us", + timeouts->offset_us[id] - base_us); } } } @@ -530,6 +532,9 @@ static struct trc_feat_def trc_feats[] = { { &Curl_trc_feat_read, TRC_CT_NONE }, { &Curl_trc_feat_write, TRC_CT_NONE }, { &Curl_trc_feat_dns, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_DOH + { &Curl_trc_feat_doh, TRC_CT_NETWORK }, +#endif { &Curl_trc_feat_timer, TRC_CT_NETWORK }, #ifdef USE_THREADS { &Curl_trc_feat_threads, TRC_CT_NONE }, @@ -544,7 +549,7 @@ static struct trc_feat_def trc_feats[] = { { &Curl_trc_feat_ssls, TRC_CT_NETWORK }, #endif #ifdef USE_SSH - { &Curl_trc_feat_ssh, TRC_CT_PROTOCOL }, + { &Curl_trc_feat_ssh, TRC_CT_PROTOCOL }, #endif #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) { &Curl_trc_feat_ws, TRC_CT_PROTOCOL }, @@ -563,6 +568,9 @@ static struct trc_cft_def trc_cfts[] = { { &Curl_cft_unix, TRC_CT_NETWORK }, { &Curl_cft_tcp_accept, TRC_CT_NETWORK }, { &Curl_cft_ip_happy, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_WEBSOCKETS + { &Curl_cft_recvbuf, TRC_CT_PROTOCOL }, +#endif { &Curl_cft_setup, TRC_CT_PROTOCOL }, #if !defined(CURL_DISABLE_HTTP) && defined(USE_NGHTTP2) { &Curl_cft_nghttp2, TRC_CT_PROTOCOL }, @@ -578,6 +586,9 @@ static struct trc_cft_def trc_cfts[] = { { &Curl_cft_h1_proxy, TRC_CT_PROXY }, #ifdef USE_NGHTTP2 { &Curl_cft_h2_proxy, TRC_CT_PROXY }, +#endif +#if defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) + { &Curl_cft_h3_proxy, TRC_CT_PROXY }, #endif { &Curl_cft_http_proxy, TRC_CT_PROXY }, #endif /* !CURL_DISABLE_HTTP */ @@ -646,10 +657,6 @@ static CURLcode trc_opt(const char *config) 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); diff --git a/lib/curl_trc.h b/lib/curl_trc.h index b4ae8e5314cd..fc0e6dedcf96 100644 --- a/lib/curl_trc.h +++ b/lib/curl_trc.h @@ -307,6 +307,9 @@ 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; +#ifndef CURL_DISABLE_DOH +extern struct curl_trc_feat Curl_trc_feat_doh; +#endif extern struct curl_trc_feat Curl_trc_feat_timer; #ifdef USE_THREADS extern struct curl_trc_feat Curl_trc_feat_threads; diff --git a/lib/curlx/base64.c b/lib/curlx/base64.c index 7f51576f5098..519b045cfe30 100644 --- a/lib/curlx/base64.c +++ b/lib/curlx/base64.c @@ -149,7 +149,7 @@ CURLcode curlx_base64_decode(const char *src, pos += 3 - padding; } - /* Null-terminate */ + /* null-terminate */ *pos = '\0'; /* Return the decoded data */ @@ -213,7 +213,7 @@ static CURLcode base64_encode(const char *table64, } } - /* Null-terminate */ + /* null-terminate */ *output = '\0'; /* Return the pointer to the new data (allocated memory) */ diff --git a/lib/curlx/basename.c b/lib/curlx/basename.c index d2fd160ff224..eda3d1195ac7 100644 --- a/lib/curlx/basename.c +++ b/lib/curlx/basename.c @@ -27,30 +27,28 @@ #include "curlx/basename.h" -/* - (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 - Edition) +/* (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 + Edition) - The basename() function shall take the pathname pointed to by path and - return a pointer to the final component of the pathname, deleting any - trailing '/' characters. + The basename() function shall take the pathname pointed to by path and + return a pointer to the final component of the pathname, deleting any + trailing '/' characters. - If the string pointed to by path consists entirely of the '/' character, - basename() shall return a pointer to the string "/". If the string pointed - to by path is exactly "//", it is implementation-defined whether '/' or "//" - is returned. + If the string pointed to by path consists entirely of the '/' character, + basename() shall return a pointer to the string "/". If the string pointed + to by path is exactly "//", it is implementation-defined whether '/' or "//" + is returned. - If path is a null pointer or points to an empty string, basename() shall - return a pointer to the string ".". + If path is a null pointer or points to an empty string, basename() shall + return a pointer to the string ".". - The basename() function may modify the string pointed to by path, and may - return a pointer to static storage that may then be overwritten by a - subsequent call to basename(). + The basename() function may modify the string pointed to by path, and may + return a pointer to static storage that may then be overwritten by a + subsequent call to basename(). - The basename() function need not be reentrant. A function that is not - required to be reentrant is not required to be thread-safe. - -*/ + The basename() function need not be reentrant. A function that is not + required to be reentrant is not required to be thread-safe. + */ char *curlx_basename(char *path) { /* Ignore all the details above for now and make a quick and simple diff --git a/lib/curlx/fopen.c b/lib/curlx/fopen.c index 6733010468ad..09264aeb101a 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/fopen.c @@ -68,7 +68,7 @@ static wchar_t *fn_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = CURLX_MALLOC(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str_utf8, -1, str_w, str_w_len) == 0) { CURLX_FREE(str_w); return NULL; @@ -80,7 +80,7 @@ static wchar_t *fn_convert_UTF8_to_wchar(const char *str_utf8) } #endif -/* declare GetFullPathNameW for mingw-w64 UWP builds targeting old windows */ +/* 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 *); @@ -110,7 +110,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) const wchar_t *in_w; wchar_t *fbuf = NULL; - /* MS documented "approximate" limit for the maximum path length */ + /* MS-documented "approximate" limit for the maximum path length */ const size_t max_path_len = 32767; #ifndef _UNICODE @@ -121,7 +121,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) *out = NULL; /* skip paths already normalized */ - if(!_tcsncmp(in, _T("\\\\?\\"), 4)) + if(!_tcsncmp(in, _TEXT("\\\\?\\"), 4)) goto cleanup; #ifndef _UNICODE @@ -247,7 +247,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) CURLX_FREE(ibuf); CURLX_FREE(obuf); #endif - return *out ? true : false; + return !!*out; } #ifndef CURL_WINDOWS_UWP @@ -291,6 +291,37 @@ HANDLE curlx_CreateFile(const char *filename, return handle; } + +HANDLE curlx_FindFirstFile(const char *filename, + WIN32_FIND_DATA *find_data) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + +#ifdef UNICODE + TCHAR *filename_t = curlx_convert_UTF8_to_wchar(filename); +#else + const TCHAR *filename_t = filename; +#endif + + if(filename_t) { + TCHAR *fixed = NULL; + const TCHAR *target; + + if(fix_excessive_path(filename_t, &fixed)) + target = fixed; + else + target = filename_t; + + handle = FindFirstFile(target, find_data); + CURLX_FREE(fixed); + +#ifdef UNICODE + curlx_free(filename_t); +#endif + } + + return handle; +} #endif /* !CURL_WINDOWS_UWP */ int curlx_win32_open(const char *filename, int oflag, ...) diff --git a/lib/curlx/fopen.h b/lib/curlx/fopen.h index b64fbf6514c6..469d3c881303 100644 --- a/lib/curlx/fopen.h +++ b/lib/curlx/fopen.h @@ -43,6 +43,8 @@ HANDLE curlx_CreateFile(const char *filename, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); +HANDLE curlx_FindFirstFile(const char *filename, + WIN32_FIND_DATA *find_data); #endif /* !CURL_WINDOWS_UWP */ #define curlx_fstat _fstati64 #define curlx_struct_stat struct _stati64 diff --git a/lib/curlx/inet_ntop.c b/lib/curlx/inet_ntop.c index 803b9887ac91..ed06314520e5 100644 --- a/lib/curlx/inet_ntop.c +++ b/lib/curlx/inet_ntop.c @@ -18,8 +18,6 @@ */ #include "curl_setup.h" -#ifndef HAVE_INET_NTOP - #ifdef HAVE_SYS_PARAM_H #include #endif @@ -50,12 +48,12 @@ /* * Format an IPv4 address, more or less like inet_ntop(). * - * Returns `dst' (as a const) + * Returns CURLcode. * Note: * - uses no static variables * - takes an unsigned char* not an in_addr as input */ -static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) +static CURLcode inet_ntop4(const unsigned char *src, char *dst, size_t size) { char tmp[sizeof("255.255.255.255")]; size_t len; @@ -70,22 +68,16 @@ static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) ((int)((unsigned char)src[3])) & 0xff); len = strlen(tmp); - if(len == 0 || len >= size) { -#ifdef USE_WINSOCK - errno = WSAEINVAL; -#else - errno = ENOSPC; -#endif - return NULL; - } + if(len == 0 || len >= size) + return CURLE_TOO_LARGE; curlx_strcopy(dst, size, tmp, len); - return dst; + return CURLE_OK; } /* * Convert IPv6 binary address into presentation (printable) format. */ -static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) +static CURLcode 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 @@ -154,9 +146,9 @@ 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))) { - return NULL; - } + CURLcode result = inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp)); + if(result) + return result; tp += strlen(tp); break; } @@ -183,31 +175,18 @@ static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) *tp++ = ':'; /* Check for overflow, copy, and we are done. */ - if((size_t)(tp - tmp) >= size) { -#ifdef USE_WINSOCK - errno = WSAEINVAL; -#else - errno = ENOSPC; -#endif - return NULL; - } - + if((size_t)(tp - tmp) >= size) + return CURLE_TOO_LARGE; curlx_strcopy(dst, size, tmp, tp - tmp); - return dst; + return CURLE_OK; } /* * Convert a network format address to presentation format. * - * Returns pointer to presentation format address (`buf'). - * 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. When this - * function returns NULL, check errno not SOCKERRNO. + * Copies result to 'buf' and returns CURLcode. */ -char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size) +CURLcode curlx_inet_ntop(int af, const void *src, char *buf, size_t size) { switch(af) { case AF_INET: @@ -215,8 +194,6 @@ char *curlx_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 = SOCKEAFNOSUPPORT; - return NULL; + return CURLE_UNSUPPORTED_PROTOCOL; } } -#endif /* HAVE_INET_NTOP */ diff --git a/lib/curlx/inet_ntop.h b/lib/curlx/inet_ntop.h index ba8299fe4e3d..f0abeb80b723 100644 --- a/lib/curlx/inet_ntop.h +++ b/lib/curlx/inet_ntop.h @@ -25,26 +25,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef HAVE_INET_NTOP -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifndef _WIN32 -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __AMIGA__ -#define curlx_inet_ntop(af, src, buf, size) \ - (char *)inet_ntop(af, CURL_UNCONST(src), (unsigned char *)(buf), \ - (curl_socklen_t)(size)) -#else -#define curlx_inet_ntop(af, src, buf, size) \ - inet_ntop(af, src, buf, (curl_socklen_t)(size)) -#endif -#else -char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size); -#endif /* HAVE_INET_NTOP */ +CURLcode curlx_inet_ntop(int af, const void *src, char *buf, size_t size); #endif /* HEADER_CURL_INET_NTOP_H */ diff --git a/lib/curlx/inet_pton.c b/lib/curlx/inet_pton.c index 7994f258febd..23e76725fe68 100644 --- a/lib/curlx/inet_pton.c +++ b/lib/curlx/inet_pton.c @@ -19,8 +19,6 @@ */ #include "curl_setup.h" -#ifndef HAVE_INET_PTON - #ifdef HAVE_SYS_PARAM_H #include #endif @@ -189,18 +187,15 @@ static int inet_pton6(const char *src, unsigned char *dst) return 1; } -/* 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. +/* + * 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) + * * author: * Paul Vixie, 1996. */ @@ -212,10 +207,7 @@ int curlx_inet_pton(int af, const char *src, void *dst) case AF_INET6: return inet_pton6(src, (unsigned char *)dst); default: - errno = SOCKEAFNOSUPPORT; return -1; } /* NOTREACHED */ } - -#endif /* HAVE_INET_PTON */ diff --git a/lib/curlx/inet_pton.h b/lib/curlx/inet_pton.h index 02ae7f2269dd..4e45e83285c4 100644 --- a/lib/curlx/inet_pton.h +++ b/lib/curlx/inet_pton.h @@ -25,25 +25,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef HAVE_INET_PTON -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifndef _WIN32 -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#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 -#else int curlx_inet_pton(int af, const char *src, void *dst); -#endif /* HAVE_INET_PTON */ #endif /* HEADER_CURL_INET_PTON_H */ diff --git a/lib/curlx/multibyte.c b/lib/curlx/multibyte.c index 715d2b8dc20d..4ee39d962af0 100644 --- a/lib/curlx/multibyte.c +++ b/lib/curlx/multibyte.c @@ -41,8 +41,8 @@ wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) if(str_w_len > 0) { str_w = curlx_malloc(str_w_len * sizeof(wchar_t)); if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, - str_w_len) == 0) { + if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str_utf8, -1, str_w, str_w_len) == 0) { curlx_free(str_w); return NULL; } diff --git a/lib/curlx/strcopy.c b/lib/curlx/strcopy.c index 3e58ea24f1bd..3b80930a1950 100644 --- a/lib/curlx/strcopy.c +++ b/lib/curlx/strcopy.c @@ -30,10 +30,10 @@ * * Provide the target buffer @dest and size of the target buffer @dsize, If * the source string @src with its *string length* @slen fits in the target - * buffer it is copied there - including storing a null terminator. + * buffer it is copied there - including storing a null-terminator. * * If the target buffer is too small, the copy is not performed but if the - * target buffer has a non-zero size it gets a null terminator stored. + * target buffer has a non-zero size it gets a null-terminator stored. */ void curlx_strcopy(char *dest, /* destination buffer */ size_t dsize, /* size of target buffer */ diff --git a/lib/curlx/strdup.c b/lib/curlx/strdup.c index 3c967dbe0a5d..ebc58c23634b 100644 --- a/lib/curlx/strdup.c +++ b/lib/curlx/strdup.c @@ -94,3 +94,30 @@ void *curlx_memdup0(const char *src, size_t length) buf[length] = 0; return buf; } + +#ifdef USE_CURLX_MEMZERO +static void *(* const volatile p_curlx_memset)(void *buf, int val, + size_t size) = memset; + +/* Local fallback in case there is no system function to securely zero a memory + buffer. */ +void curlx_memzero_low(void *buf, size_t size) +{ + if(buf) + p_curlx_memset(buf, 0, size); +} +#endif + +/* Fill 'buf' with zeroes. */ +void curlx_memzero(void *buf, size_t size) +{ + if(buf) + curlx_memzero_low(buf, size); +} + +/* Fill 'buf' with zeroes, where 'buf' is null-terminated. */ +void curlx_strzero(void *buf) +{ + if(buf) + curlx_memzero_low(buf, strlen(buf)); +} diff --git a/lib/curlx/strerr.c b/lib/curlx/strerr.c index b53173c57839..8e906cc6bdfb 100644 --- a/lib/curlx/strerr.c +++ b/lib/curlx/strerr.c @@ -190,7 +190,7 @@ static const char *get_winsock_error(int err, char *buf, size_t len) p = "Winsock library is not ready"; break; case WSANOTINITIALISED: - p = "Winsock library not initialised"; + p = "Winsock library not initialized"; break; case WSAVERNOTSUPPORTED: p = "Winsock version not supported"; @@ -268,7 +268,7 @@ const char *curlx_strerror(int err, char *buf, size_t buflen) !get_winsock_error(err, buf, buflen) && #endif !curlx_get_winapi_error((DWORD)err, buf, buflen)) - SNPRINTF(buf, buflen, "Unknown error %d (%#x)", err, err); + SNPRINTF(buf, buflen, "Unknown error %d (%#x)", err, (unsigned int)err); #else /* !_WIN32 */ #if defined(HAVE_STRERROR_R) && defined(HAVE_POSIX_STRERROR_R) diff --git a/lib/curlx/strparse.c b/lib/curlx/strparse.c index 7866b2087ab7..9e8fe4963d53 100644 --- a/lib/curlx/strparse.c +++ b/lib/curlx/strparse.c @@ -45,13 +45,17 @@ void curlx_str_trim(struct Curl_str *out, size_t len) } /* Get a word until the first DELIM or end of string. At least one byte long. - return non-zero on error */ + return non-zero on error. If 'max' is zero, it will always return error. */ int curlx_str_until(const char **linep, struct Curl_str *out, const size_t max, char delim) { - const char *s = *linep; + const char *s; size_t len = 0; - DEBUGASSERT(linep && *linep && out && max && delim); + DEBUGASSERT(linep); + DEBUGASSERT(*linep); + DEBUGASSERT(out); + DEBUGASSERT(delim); + s = *linep; curlx_str_init(out); while(*s && (*s != delim)) { @@ -259,7 +263,7 @@ int curlx_str_cmp(struct Curl_str *str, const char *check) size_t clen = strlen(check); return ((str->len == clen) && !strncmp(str->str, check, clen)); } - return !!(str->len); + return !!str->len; } /* Trim off 'num' number of bytes from the beginning (left side) of the diff --git a/lib/curlx/strparse.h b/lib/curlx/strparse.h index c7801b2cb346..7963dd62128a 100644 --- a/lib/curlx/strparse.h +++ b/lib/curlx/strparse.h @@ -106,8 +106,7 @@ 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! -*/ + before calling this. */ extern const unsigned char curlx_hexasciitable[]; #define curlx_hexval(x) (unsigned char)(curlx_hexasciitable[(x) - '0'] & 0x0f) diff --git a/lib/curlx/timeval.c b/lib/curlx/timeval.c index 2363e6083944..b43aedf15cc2 100644 --- a/lib/curlx/timeval.c +++ b/lib/curlx/timeval.c @@ -25,26 +25,24 @@ #ifdef _WIN32 -#include "system_win32.h" - -LARGE_INTEGER Curl_freq; +static LARGE_INTEGER s_time_freq; /* For tool or tests, we must initialize before calling curlx_now(). Providing this function here is wrong. */ void curlx_now_init(void) { - QueryPerformanceFrequency(&Curl_freq); + QueryPerformanceFrequency(&s_time_freq); } /* In case of bug fix this function has a counterpart in tool_util.c */ void curlx_pnow(struct curltime *pnow) { LARGE_INTEGER count; - DEBUGASSERT(Curl_freq.QuadPart); + DEBUGASSERT(s_time_freq.QuadPart); QueryPerformanceCounter(&count); - pnow->tv_sec = (time_t)(count.QuadPart / Curl_freq.QuadPart); - pnow->tv_usec = (int)((count.QuadPart % Curl_freq.QuadPart) * 1000000 / - Curl_freq.QuadPart); + pnow->tv_sec = (time_t)(count.QuadPart / s_time_freq.QuadPart); + pnow->tv_usec = (int)((count.QuadPart % s_time_freq.QuadPart) * 1000000 / + s_time_freq.QuadPart); } #elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ @@ -216,6 +214,11 @@ timediff_t curlx_timediff_ceil_ms(struct curltime newer, return (diff * 1000) + ((newer.tv_usec - older.tv_usec + 999) / 1000); } +timediff_t curlx_us_to_ceil_ms(timediff_t us) +{ + return (us / 1000) + ((us > 0) && (us % 1000)); +} + /* * Returns: time difference in number of microseconds. For too large diffs it * returns max value. diff --git a/lib/curlx/timeval.h b/lib/curlx/timeval.h index c01f95d87df0..284c452df021 100644 --- a/lib/curlx/timeval.h +++ b/lib/curlx/timeval.h @@ -59,6 +59,9 @@ timediff_t curlx_ptimediff_ms(const struct curltime *newer, timediff_t curlx_timediff_ceil_ms(struct curltime newer, struct curltime older); +/* Returns milliseconds from microseconds, rounded up. */ +timediff_t curlx_us_to_ceil_ms(timediff_t us); + /* * 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... diff --git a/lib/curlx/wait.c b/lib/curlx/wait.c index e50a0f0af697..7610a4ba367c 100644 --- a/lib/curlx/wait.c +++ b/lib/curlx/wait.c @@ -23,10 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#ifndef HAVE_SELECT -#error "We cannot compile without select() support." -#endif - #ifdef HAVE_SYS_SELECT_H #include #elif defined(HAVE_UNISTD_H) diff --git a/lib/curlx/winapi.c b/lib/curlx/winapi.c index f025ca48c941..1d6ef87abe82 100644 --- a/lib/curlx/winapi.c +++ b/lib/curlx/winapi.c @@ -103,4 +103,24 @@ const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen) return buf; } + +#ifndef WITHOUT_LIBCURL + +#include +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +CURLcode curlx_win32_random(unsigned char *entropy, size_t length) +{ + memset(entropy, 0, length); + + if(BCryptGenRandom(NULL, entropy, (ULONG)length, + BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) + return CURLE_FAILED_INIT; + + return CURLE_OK; +} +#endif /* WITHOUT_LIBCURL */ + #endif /* _WIN32 */ diff --git a/lib/curlx/winapi.h b/lib/curlx/winapi.h index d30f5efa13c7..21e864c028d6 100644 --- a/lib/curlx/winapi.h +++ b/lib/curlx/winapi.h @@ -28,6 +28,7 @@ #define WINAPI_ERROR_LEN 100 const char *curlx_get_winapi_error(DWORD err, char *buf, size_t buflen); const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen); +CURLcode curlx_win32_random(unsigned char *entropy, size_t length); #endif #endif /* HEADER_CURLX_WINAPI_H */ diff --git a/lib/cw-out.c b/lib/cw-out.c index 2af074e5871c..0137975b1eba 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -97,7 +97,6 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf) struct cw_out_ctx { struct Curl_cwriter super; struct cw_out_buf *buf; - BIT(paused); BIT(errored); }; @@ -171,8 +170,7 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, } } -static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, - struct Curl_easy *data, +static CURLcode cw_out_cb_write(struct Curl_easy *data, curl_write_callback wcb, void *wcb_data, cw_out_type otype, @@ -186,21 +184,24 @@ static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, DEBUGASSERT(data->conn); *pnwritten = 0; - Curl_set_in_callback(data, TRUE); - nwritten = wcb((char *)CURL_UNCONST(buf), 1, blen, wcb_data); - Curl_set_in_callback(data, FALSE); - CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_cw_out_cb); + nwritten = wcb((char *)CURL_UNCONST(buf), 1, blen, wcb_data); + CURL_CBAPI_END(&guard); + } + CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes, type=%x -> %zu", blen, (otype == CW_OUT_HDS) ? "header" : "body", - nwritten); + (unsigned int)otype, nwritten); if(nwritten == CURL_WRITEFUNC_PAUSE) { if(data->conn->scheme->flags & PROTOPT_NONETWORK) { /* Protocols that work without network cannot be paused. This is - actually only FILE:// now, and it cannot pause since the transfer is + actually only file:// 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; } - ctx->paused = TRUE; + data->req.writer.paused = TRUE; CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); result = Curl_xfer_pause_recv(data, TRUE); return result ? result : CURLE_AGAIN; @@ -245,18 +246,18 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, *pconsumed = 0; if(otype == CW_OUT_BODY_0LEN) { DEBUGASSERT(!blen); - return cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + return cw_out_cb_write(data, wcb, wcb_data, otype, buf, blen, &nwritten); } else { - while(blen && !ctx->paused) { + while(blen && !data->req.writer.paused) { if(!flush_all && blen < min_write) break; wlen = max_write ? CURLMIN(blen, max_write) : blen; if(otype == CW_OUT_BODY) result = Curl_pgrs_deliver_check(data, wlen); if(!result) - result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + result = cw_out_cb_write(data, wcb, wcb_data, otype, buf, wlen, &nwritten); if(result) return result; @@ -314,7 +315,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, if(!cwbuf) return CURLE_OK; - if(ctx->paused) + if(data->req.writer.paused) return CURLE_OK; /* write the end of the chain until it blocks or gets empty */ @@ -327,7 +328,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, return result; if(*plast) { /* could not write last, paused again? */ - DEBUGASSERT(ctx->paused); + DEBUGASSERT(data->req.writer.paused); return CURLE_OK; } } @@ -412,7 +413,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, out: if(result) { - /* We do not want to invoked client callbacks a second time after + /* We do not want to invoke client callbacks a second time after * encountering an error. See issue #13337 */ ctx->errored = TRUE; cw_out_bufs_free(ctx); @@ -446,66 +447,46 @@ static CURLcode cw_out_write(struct Curl_easy *data, return CURLE_OK; } -const struct Curl_cwtype Curl_cwt_out = { - "cw-out", - NULL, - cw_out_init, - cw_out_write, - cw_out_close, - sizeof(struct cw_out_ctx) -}; - -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 (bool)ctx->paused; -} - -static CURLcode cw_out_flush(struct Curl_easy *data, - struct Curl_cwriter *cw_out, - bool flush_all) +static CURLcode cw_out_do_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; + if(!data->req.writer.paused && ctx->buf) { + CURLcode result; + + CURL_TRC_WRITE(data, "[OUT] flush"); + 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; + return CURLE_OK; } -CURLcode Curl_cw_out_unpause(struct Curl_easy *data) +static CURLcode cw_out_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) { - 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; + return cw_out_do_flush(data, writer, FALSE); } +const struct Curl_cwtype Curl_cwt_out = { + "cw-out", + NULL, + 0, + cw_out_init, + cw_out_write, + cw_out_flush, + cw_out_close, + sizeof(struct cw_out_ctx) +}; + CURLcode Curl_cw_out_done(struct Curl_easy *data) { struct Curl_cwriter *cw_out; @@ -514,9 +495,9 @@ CURLcode Curl_cw_out_done(struct Curl_easy *data) 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); + result = Curl_client_flush(data); if(!result) - result = cw_out_flush(data, cw_out, TRUE); + result = cw_out_do_flush(data, cw_out, TRUE); } return result; } diff --git a/lib/cw-out.h b/lib/cw-out.h index 7de6524bc580..ee2b4b6307bc 100644 --- a/lib/cw-out.h +++ b/lib/cw-out.h @@ -34,16 +34,6 @@ struct Curl_easy; */ 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. */ diff --git a/lib/cw-pause.c b/lib/cw-pause.c index ec611879d1e8..a5b9ba148e08 100644 --- a/lib/cw-pause.c +++ b/lib/cw-pause.c @@ -100,7 +100,6 @@ 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 */ @@ -112,12 +111,13 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, 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)) ? + wlen = ((*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); + wlen, ctx->buf_total, (unsigned int)(*plast)->type, + (int)result); Curl_bufq_skip(&(*plast)->b, wlen); DEBUGASSERT(ctx->buf_total >= wlen); ctx->buf_total -= wlen; @@ -128,7 +128,8 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, 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); + ctx->buf_total, (unsigned int)(*plast)->type, + (int)result); } if(Curl_bufq_is_empty(&(*plast)->b)) { @@ -136,6 +137,9 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, *plast = NULL; } } + + if(!result) + result = Curl_cwriter_flush(data, cw_pause->next); return result; } @@ -146,7 +150,6 @@ static CURLcode cw_pause_write(struct Curl_easy *data, 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); @@ -159,13 +162,11 @@ static CURLcode cw_pause_write(struct Curl_easy *data, DEBUGASSERT(!ctx->buf); /* content decoding might blow up size considerably, write smaller * chunks to make pausing need buffer less. */ - wlen = (decoding && (type & CLIENTWRITE_BODY)) ? + wlen = (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; @@ -191,8 +192,8 @@ static CURLcode cw_pause_write(struct Curl_easy *data, 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); + "total=%zu -> %d", nwritten, (unsigned int)type, + ctx->buf_total + wlen, (int)result); if(result) return result; buf += nwritten; @@ -206,20 +207,10 @@ static CURLcode cw_pause_write(struct Curl_easy *data, const struct Curl_cwtype Curl_cwt_pause = { "cw-pause", NULL, + 0, cw_pause_init, cw_pause_write, + cw_pause_flush, cw_pause_close, sizeof(struct cw_pause_ctx) }; - -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/lib/cw-pause.h b/lib/cw-pause.h index 544cbfa57778..9a41a1c9b1a7 100644 --- a/lib/cw-pause.h +++ b/lib/cw-pause.h @@ -33,6 +33,4 @@ struct Curl_easy; */ 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/lib/dict.c b/lib/dict.c index 7b83c6cff193..75cdac371453 100644 --- a/lib/dict.c +++ b/lib/dict.c @@ -55,6 +55,8 @@ #include "transfer.h" #include "curl_trc.h" +#include "connect.h" +#include "select.h" #include "escape.h" #define DICT_MATCH "/MATCH:" @@ -93,9 +95,12 @@ static CURLcode sendf(struct Curl_easy *data, static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) { + curl_socket_t sockfd = data->conn->sock[FIRSTSOCKET]; size_t bytes_written; size_t write_len; CURLcode result = CURLE_OK; + timediff_t timeout_ms; + int what; char *s; char *sptr; va_list ap; @@ -126,6 +131,29 @@ static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) } else break; + + timeout_ms = Curl_timeleft_ms(data); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Do not busyloop. The entire loop thing is a workaround 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 are not + possible to send now will be sent in the doing function repeatedly + until the entire request is sent. */ + what = SOCKET_WRITABLE(sockfd, timeout_ms); + if(what < 0) { + result = CURLE_SEND_ERROR; + break; + } + else if(!what) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } } curlx_free(s); /* free the output string */ @@ -148,14 +176,14 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) *done = TRUE; /* unconditionally */ - /* url-decode path before further evaluation */ + /* URL-decode path before further evaluation */ result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); if(result) return result; - if(curl_strnequal(path, DICT_MATCH, sizeof(DICT_MATCH) - 1) || - curl_strnequal(path, DICT_MATCH2, sizeof(DICT_MATCH2) - 1) || - curl_strnequal(path, DICT_MATCH3, sizeof(DICT_MATCH3) - 1)) { + if(curl_strnequal(path, DICT_MATCH, CURL_CSTRLEN(DICT_MATCH)) || + curl_strnequal(path, DICT_MATCH2, CURL_CSTRLEN(DICT_MATCH2)) || + curl_strnequal(path, DICT_MATCH3, CURL_CSTRLEN(DICT_MATCH3))) { word = strchr(path, ':'); if(word) { @@ -200,9 +228,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) } Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); } - else if(curl_strnequal(path, DICT_DEFINE, sizeof(DICT_DEFINE) - 1) || - curl_strnequal(path, DICT_DEFINE2, sizeof(DICT_DEFINE2) - 1) || - curl_strnequal(path, DICT_DEFINE3, sizeof(DICT_DEFINE3) - 1)) { + else if(curl_strnequal(path, DICT_DEFINE, CURL_CSTRLEN(DICT_DEFINE)) || + curl_strnequal(path, DICT_DEFINE2, CURL_CSTRLEN(DICT_DEFINE2)) || + curl_strnequal(path, DICT_DEFINE3, CURL_CSTRLEN(DICT_DEFINE3))) { word = strchr(path, ':'); if(word) { diff --git a/lib/dllmain.c b/lib/dllmain.c index f715b6d30161..5aa6565010d8 100644 --- a/lib/dllmain.c +++ b/lib/dllmain.c @@ -31,7 +31,7 @@ #if defined(_WIN32) && !defined(CURL_STATICLIB) #if defined(USE_OPENSSL) && \ - !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) && \ + !defined(OPENSSL_IS_AWSLC) && !defined(OPENSSL_IS_BORINGSSL) && \ !defined(LIBRESSL_VERSION_NUMBER) #define PREVENT_OPENSSL_MEMLEAK #endif diff --git a/lib/easy.c b/lib/easy.c index a472d6ddc52e..04c5a003a048 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -44,13 +44,14 @@ #endif #include "urldata.h" +#include "api.h" #include "transfer.h" +#include "vdns/hostip.h" #include "vtls/vtls.h" #include "vtls/vtls_scache.h" #include "vquic/vquic.h" #include "url.h" #include "getinfo.h" -#include "hostip.h" #include "curlx/strdup.h" #include "easyif.h" #include "multiif.h" @@ -78,8 +79,10 @@ #include "easy_lock.h" /* true globals -- for curl_global_init() and curl_global_cleanup() */ -static unsigned int initialized; -static long easy_init_flags; +static unsigned int initialized; +#ifdef _WIN32 +static long easy_init_flags; +#endif #ifdef GLOBAL_INIT_IS_THREADSAFE @@ -135,6 +138,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_ccalloc = (curl_calloc_callback)calloc; } + if(Curl_win32_init(flags)) { + DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); + goto fail; + } + if(Curl_trc_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); goto fail; @@ -150,11 +158,6 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } - if(Curl_win32_init(flags)) { - DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); - goto fail; - } - if(Curl_amiga_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_amiga_init failed\n")); goto fail; @@ -166,7 +169,7 @@ static CURLcode global_init(long flags, bool memoryfuncs) } if(Curl_async_global_init()) { - DEBUGF(curl_mfprintf(stderr, "Error: resolver_global_init failed\n")); + DEBUGF(curl_mfprintf(stderr, "Error: Curl_async_global_init failed\n")); goto fail; } @@ -175,7 +178,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } +#ifdef _WIN32 easy_init_flags = flags; +#else + (void)flags; +#endif #ifdef DEBUGBUILD if(getenv("CURL_GLOBAL_INIT")) @@ -267,10 +274,12 @@ void curl_global_cleanup(void) } Curl_ssl_cleanup(); + Curl_vquic_cleanup(); Curl_async_global_cleanup(); #ifdef _WIN32 Curl_win32_cleanup(easy_init_flags); + easy_init_flags = 0; #endif Curl_amiga_cleanup(); @@ -281,8 +290,6 @@ void curl_global_cleanup(void) curlx_free(leakpointer); #endif - easy_init_flags = 0; - global_init_unlock(); } @@ -756,13 +763,9 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* 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; + struct connectdata *conn = data->conn; Curl_detach_connection(data); - s = Curl_getconnectinfo(data, &c); - if((s != CURL_SOCKET_BAD) && c) { - Curl_conn_terminate(data, c, TRUE); - } + Curl_conn_close(data, conn, TRUE); DEBUGASSERT(!data->conn); } @@ -776,7 +779,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) return CURLE_OUT_OF_MEMORY; } - if(multi->in_callback) + if(Curl_api_multi_is_in_callback(multi)) return CURLE_RECURSIVE_API_CALL; /* Copy relevant easy options to the multi handle */ @@ -784,7 +787,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) curl_multi_setopt(multi, CURLMOPT_QUICK_EXIT, (long)data->set.quick_exit); data->multi_easy = NULL; /* pretend it does not exist */ - mresult = curl_multi_add_handle(multi, data); + mresult = Curl_multi_add_handle(multi, data); if(mresult) { curl_multi_cleanup(multi); if(mresult == CURLM_OUT_OF_MEMORY) @@ -803,7 +806,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* 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); + (void)Curl_multi_remove_handle(multi, data); sigpipe_restore(&sigpipe_ctx); @@ -817,7 +820,14 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) */ CURLcode curl_easy_perform(CURL *curl) { - return easy_perform(curl, FALSE); + struct Curl_eapi_guard guard = { 0 }; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_perform, &result)) { + result = easy_perform(curl, FALSE); + } + CURL_EAPI_LEAVE(&guard); + return result; } #ifdef DEBUGBUILD @@ -827,7 +837,14 @@ CURLcode curl_easy_perform(CURL *curl) */ CURLcode curl_easy_perform_ev(struct Curl_easy *easy) { - return easy_perform(easy, TRUE); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, easy, easy_perform_ev, &result)) { + result = easy_perform(easy, TRUE); + } + CURL_EAPI_LEAVE(&guard); + return result; } #endif @@ -837,13 +854,16 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *easy) */ void curl_easy_cleanup(CURL *curl) { - struct Curl_easy *data = curl; - if(GOOD_EASY_HANDLE(data)) { + struct Curl_eapi_guard guard; + + if(CURL_EAPI_ENTER(&guard, curl, easy_cleanup, NULL)) { + struct Curl_easy *data = curl; struct Curl_sigpipe_ctx sigpipe_ctx; sigpipe_ignore(data, &sigpipe_ctx); Curl_close(&data); sigpipe_restore(&sigpipe_ctx); } + CURL_EAPI_LEAVE(&guard); } /* @@ -853,27 +873,28 @@ void curl_easy_cleanup(CURL *curl) #undef curl_easy_getinfo CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) { - struct Curl_easy *data = curl; - va_list arg; - void *paramp; + struct Curl_eapi_guard guard; CURLcode result; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, easy_getinfo, &result)) { + struct Curl_easy *data = curl; + va_list arg; + void *paramp; - va_start(arg, info); - paramp = va_arg(arg, void *); + va_start(arg, info); + paramp = va_arg(arg, void *); - result = Curl_getinfo(data, info, paramp); + result = Curl_getinfo(data, info, paramp); - va_end(arg); + va_end(arg); + } + CURL_EAPI_LEAVE(&guard); return result; } static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) { CURLcode result = CURLE_OK; - enum dupstring i; enum dupblob j; /* Copy src->set into dst->set first, then deal with the strings @@ -882,17 +903,17 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) #if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) dst->set.mimepostp = NULL; #endif + dst->set.str_copypostfields = NULL; + + Curl_u8_strset_init(&dst->set.strings); /* 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++) { - result = Curl_setstropt(&dst->set.str[i], src->set.str[i]); - if(result) - return result; - } + result = Curl_u8_strset_copy(&dst->set.strings, &src->set.strings); + if(result) + return result; /* duplicate all blobs */ for(j = (enum dupblob)0; j < BLOB_LAST; j++) { @@ -902,18 +923,18 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) } /* duplicate memory areas pointed to */ - i = STRING_COPYPOSTFIELDS; - if(src->set.str[i]) { + if(src->set.str_copypostfields) { if(src->set.postfieldsize == -1) - dst->set.str[i] = curlx_strdup(src->set.str[i]); + dst->set.str_copypostfields = curlx_strdup(src->set.str_copypostfields); else /* postfieldsize is curl_off_t, curlx_memdup() takes a size_t ... */ - dst->set.str[i] = curlx_memdup(src->set.str[i], - curlx_sotouz(src->set.postfieldsize)); - if(!dst->set.str[i]) + dst->set.str_copypostfields = + curlx_memdup(src->set.str_copypostfields, + curlx_sotouz(src->set.postfieldsize)); + if(!dst->set.str_copypostfields) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ - dst->set.postfields = dst->set.str[i]; + dst->set.postfields = dst->set.str_copypostfields; } #if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) @@ -942,7 +963,7 @@ static void dupeasy_meta_freeentry(void *p) /* Always FALSE. Cannot use a 0 assert here since compilers * are not in agreement if they then want a NORETURN attribute or * not. *sigh* */ - DEBUGASSERT(p == NULL); + DEBUGASSERT(!p); } /* @@ -952,117 +973,125 @@ static void dupeasy_meta_freeentry(void *p) */ CURL *curl_easy_duphandle(CURL *curl) { - struct Curl_easy *data = curl; + struct Curl_eapi_guard guard; struct Curl_easy *outcurl = NULL; - if(!GOOD_EASY_HANDLE(data)) - goto fail; - outcurl = curlx_calloc(1, sizeof(struct Curl_easy)); - if(!outcurl) - goto fail; + if(CURL_EAPI_ENTER(&guard, curl, easy_duphandle, NULL)) { + struct Curl_easy *data = curl; + const char *str; - /* - * We setup a few buffers we need. We should probably make them - * get setup on-demand in the code, as that would probably decrease - * the likeliness of us forgetting to init a buffer here in the future. - */ - 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_bufref_init(&outcurl->state.url); - Curl_bufref_init(&outcurl->state.referer); - 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 = UINT32_MAX; - outcurl->master_mid = UINT32_MAX; + outcurl = curlx_calloc(1, sizeof(struct Curl_easy)); + if(!outcurl) + goto fail; + + /* + * We setup a few buffers we need. We should probably make them + * get setup on-demand in the code, as that would probably decrease + * the likeliness of us forgetting to init a buffer here in the future. + */ + 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_bufref_init(&outcurl->state.url); + Curl_bufref_init(&outcurl->state.referer); + Curl_netrc_init(&outcurl->state.netrc); + + /* the connection pool is setup on demand */ + outcurl->state.lastconnect_id = -1; + outcurl->id = -1; + outcurl->mid = UINT32_MAX; + outcurl->master_mid = UINT32_MAX; #ifndef CURL_DISABLE_HTTP - Curl_llist_init(&outcurl->state.httphdrs, NULL); + Curl_llist_init(&outcurl->state.httphdrs, NULL); #endif - Curl_initinfo(outcurl); + Curl_initinfo(outcurl); - /* copy all userdefined values */ - if(dupset(outcurl, data)) - goto fail; + /* copy all userdefined values */ + if(dupset(outcurl, data)) + goto fail; - outcurl->progress.hide = data->progress.hide; - outcurl->progress.callback = data->progress.callback; + outcurl->progress.hide = data->progress.hide; + outcurl->progress.callback = data->progress.callback; #ifndef CURL_DISABLE_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(); - if(!outcurl->cookies) - goto fail; - outcurl->state.cookie_engine = TRUE; - } + 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(); + if(!outcurl->cookies) + goto fail; + outcurl->state.cookie_engine = TRUE; + } - if(data->state.cookielist) { - outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); - if(!outcurl->state.cookielist) - goto fail; - } + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) + goto fail; + } #endif - if(Curl_bufref_ptr(&data->state.url)) { - Curl_bufref_set(&outcurl->state.url, - Curl_bufref_dup(&data->state.url), 0, - curl_free); - if(!Curl_bufref_ptr(&outcurl->state.url)) - goto fail; - } - if(Curl_bufref_ptr(&data->state.referer)) { - Curl_bufref_set(&outcurl->state.referer, - Curl_bufref_dup(&data->state.referer), 0, - curl_free); - if(!Curl_bufref_ptr(&outcurl->state.referer)) - goto fail; - } + if(Curl_bufref_ptr(&data->state.url)) { + Curl_bufref_set(&outcurl->state.url, + Curl_bufref_dup(&data->state.url), 0, + curl_free); + if(!Curl_bufref_ptr(&outcurl->state.url)) + goto fail; + } + if(Curl_bufref_ptr(&data->state.referer)) { + Curl_bufref_set(&outcurl->state.referer, + Curl_bufref_dup(&data->state.referer), 0, + curl_free); + if(!Curl_bufref_ptr(&outcurl->state.referer)) + goto fail; + } - /* Reinitialize an SSL engine for the new handle - * note: the engine name has already been copied by dupset */ - if(outcurl->set.str[STRING_SSL_ENGINE]) { - if(Curl_ssl_set_engine(outcurl, outcurl->set.str[STRING_SSL_ENGINE])) - goto fail; - } + /* Reinitialize an SSL engine for the new handle + * note: the engine name has already been copied by dupset */ + str = CURL_EASY_STR(outcurl, STRING_SSL_ENGINE); + if(str) { + if(Curl_ssl_set_engine(outcurl, str)) + goto fail; + } #ifndef CURL_DISABLE_ALTSVC - if(data->asi) { - outcurl->asi = Curl_altsvc_init(); - if(!outcurl->asi) - goto fail; - if(outcurl->set.str[STRING_ALTSVC]) - (void)Curl_altsvc_load(outcurl->asi, outcurl->set.str[STRING_ALTSVC]); - } + if(data->asi) { + outcurl->asi = Curl_altsvc_init(); + if(!outcurl->asi) + goto fail; + str = CURL_EASY_STR(outcurl, STRING_ALTSVC); + if(str) + (void)Curl_altsvc_load(outcurl->asi, str); + } #endif #ifndef CURL_DISABLE_HSTS - if(data->hsts) { - outcurl->hsts = Curl_hsts_init(); - if(!outcurl->hsts) - goto fail; - if(outcurl->set.str[STRING_HSTS]) - (void)Curl_hsts_loadfile(outcurl, - outcurl->hsts, outcurl->set.str[STRING_HSTS]); - (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); - } + if(data->hsts) { + outcurl->hsts = Curl_hsts_init(); + if(!outcurl->hsts) + goto fail; + str = CURL_EASY_STR(outcurl, STRING_HSTS); + if(str) + (void)Curl_hsts_loadfile(outcurl, outcurl->hsts, str); + (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); + + /* Copy entries learned at runtime. (E.g. Strict-Transport-Security + headers.) */ + if(Curl_hsts_copy(outcurl->hsts, data->hsts)) + goto fail; + } #endif - outcurl->magic = CURLEASY_MAGIC_NUMBER; - - /* we reach this point and thus we are OK */ - + /* we reach this point and thus we are OK */ + outcurl->magic = CURLEASY_MAGIC_NUMBER; + } + CURL_EAPI_LEAVE(&guard); return outcurl; fail: - if(outcurl) { #ifndef CURL_DISABLE_COOKIES curlx_free(outcurl->cookies); @@ -1074,6 +1103,7 @@ CURL *curl_easy_duphandle(CURL *curl) curlx_free(outcurl); } + CURL_EAPI_LEAVE(&guard); return NULL; } @@ -1083,38 +1113,40 @@ CURL *curl_easy_duphandle(CURL *curl) */ void curl_easy_reset(CURL *curl) { - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return; + struct Curl_eapi_guard guard; - Curl_req_hard_reset(&data->req, data); - Curl_hash_clean(&data->meta_hash); + if(CURL_EAPI_ENTER(&guard, curl, easy_reset, NULL)) { + struct Curl_easy *data = curl; - /* clear all meta data */ - Curl_meta_reset(data); - /* zero out UserDefined data: */ - Curl_freeset(data); - memset(&data->set, 0, sizeof(struct UserDefined)); - Curl_init_userdefined(data); + data->state.lastconnect_id = -1; /* clear remembered connection id */ + Curl_req_hard_reset(&data->req, data); + Curl_hash_clean(&data->meta_hash); - /* zero out Progress data: */ - memset(&data->progress, 0, sizeof(struct Progress)); + /* clear all meta data */ + Curl_meta_reset(data); + /* zero out UserDefined data: */ + Curl_freeset(data); + memset(&data->set, 0, sizeof(struct UserDefined)); + Curl_init_userdefined(data); - /* zero out PureInfo data: */ - Curl_initinfo(data); + /* zero out Progress data: */ + memset(&data->progress, 0, sizeof(struct Progress)); - data->progress.hide = TRUE; - data->state.current_speed = -1; /* init to negative == impossible */ - data->state.recent_conn_id = -1; /* clear remembered connection id */ + /* zero out PureInfo data: */ + Curl_initinfo(data); - /* zero out authentication data: */ - memset(&data->state.authhost, 0, sizeof(struct auth)); - memset(&data->state.authproxy, 0, sizeof(struct auth)); + data->progress.hide = TRUE; + + /* zero out authentication 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_DIGEST_AUTH) - Curl_http_auth_cleanup_digest(data); + Curl_http_auth_cleanup_digest(data); #endif - data->master_mid = UINT32_MAX; + data->master_mid = UINT32_MAX; + } + CURL_EAPI_LEAVE(&guard); } /* @@ -1132,62 +1164,60 @@ void curl_easy_reset(CURL *curl) */ CURLcode curl_easy_pause(CURL *curl, int action) { + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - bool recursive = FALSE; - bool changed = FALSE; - struct Curl_easy *data = curl; - bool recv_paused, recv_paused_new; - bool send_paused, send_paused_new; - - if(!GOOD_EASY_HANDLE(data) || !data->conn) - /* crazy input, do not continue */ - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(Curl_is_in_callback(data)) - recursive = TRUE; - recv_paused = Curl_xfer_recv_is_paused(data); - recv_paused_new = (action & CURLPAUSE_RECV); - send_paused = Curl_xfer_send_is_paused(data); - send_paused_new = (action & CURLPAUSE_SEND); + if(CURL_EAPI_ENTER(&guard, curl, easy_pause, &result)) { + bool changed = FALSE; + struct Curl_easy *data = curl; + bool recv_paused, recv_paused_new; + bool send_paused, send_paused_new; - if((send_paused != send_paused_new) || - (send_paused_new != Curl_creader_is_paused(data))) { - changed = TRUE; - result = Curl_1st_fatal( - result, Curl_xfer_pause_send(data, send_paused_new)); - } + if(!data->conn) { + /* crazy input, do not continue */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } - if(recv_paused != recv_paused_new) { - changed = TRUE; - result = Curl_1st_fatal( - result, Curl_xfer_pause_recv(data, recv_paused_new)); - } + recv_paused = Curl_xfer_recv_is_paused(data); + recv_paused_new = (action & CURLPAUSE_RECV); + send_paused = Curl_xfer_send_is_paused(data); + send_paused_new = (action & CURLPAUSE_SEND); - /* If not completely pausing both directions now, run again in any case. */ - if(!Curl_xfer_is_blocked(data)) { - /* reset the too-slow time keeper */ - data->state.keeps_speed.tv_sec = 0; - if(data->multi) { - Curl_multi_mark_dirty(data); /* make it run */ - /* On changes, tell application to update its timers. */ - if(changed) { - if(Curl_update_timer(data->multi) && !result) - result = CURLE_ABORTED_BY_CALLBACK; - } + if((send_paused != send_paused_new) || + (send_paused_new != Curl_creader_is_paused(data))) { + changed = TRUE; + result = Curl_1st_fatal( + result, Curl_xfer_pause_send(data, send_paused_new)); } - } - if(!result && changed && !data->state.done && data->multi) - /* pause/unpausing may result in multi event changes */ - if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) - result = CURLE_ABORTED_BY_CALLBACK; + if(recv_paused != recv_paused_new) { + changed = TRUE; + result = Curl_1st_fatal( + result, Curl_xfer_pause_recv(data, recv_paused_new)); + } - 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); + /* If not completely pausing both directions, run again in any case. */ + if(!Curl_xfer_is_blocked(data)) { + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + if(data->multi) { + Curl_multi_mark_dirty(data); /* make it run */ + /* On changes, tell application to update its timers. */ + if(changed) { + if(Curl_update_timer(data->multi) && !result) + result = CURLE_ABORTED_BY_CALLBACK; + } + } + } + if(!result && changed && !data->state.done && data->multi) + /* pause/unpausing may result in multi event changes */ + if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) + result = CURLE_ABORTED_BY_CALLBACK; + } +out: + CURL_EAPI_LEAVE(&guard); return result; } @@ -1208,7 +1238,8 @@ static CURLcode easy_connection(struct Curl_easy *data, sfd = Curl_getconnectinfo(data, connp); if(sfd == CURL_SOCKET_BAD) { - failf(data, "Failed to get recent socket"); + failf(data, "Failed to get last socket used for connection #%" FMT_OFF_T, + data->state.lastconnect_id); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1220,16 +1251,11 @@ static CURLcode easy_connection(struct Curl_easy *data, * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. * Returns CURLE_OK on success, error code on error. */ -CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) +CURLcode Curl_easy_recv(struct Curl_easy *data, + void *buffer, size_t buflen, size_t *n) { CURLcode result; struct connectdata *c; - struct Curl_easy *data = curl; - - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; result = easy_connection(data, &c); if(result) @@ -1238,12 +1264,24 @@ CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); *n = 0; return Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, n); } +CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) +{ + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_recv, &result)) { + result = Curl_easy_recv(curl, buffer, buflen, n); + } + CURL_EAPI_LEAVE(&guard); + return result; +} + #ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data) { @@ -1257,7 +1295,7 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); return CURLE_OK; } @@ -1283,7 +1321,7 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, if(!data->conn) /* on first invoke, the transfer has been detached from the connection and needs to be reattached */ - Curl_attach_connection(data, c); + Curl_attach_connection(data, c, TRUE); sigpipe_ignore(data, &sigpipe_ctx); result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n); @@ -1301,16 +1339,17 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, size_t *n) { - size_t written = 0; + struct Curl_eapi_guard guard; CURLcode result; - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; - result = Curl_senddata(data, buffer, buflen, &written); - *n = written; + if(CURL_EAPI_ENTER(&guard, curl, easy_send, &result)) { + struct Curl_easy *data = curl; + size_t written = 0; + + result = Curl_senddata(data, buffer, buflen, &written); + *n = written; + } + CURL_EAPI_LEAVE(&guard); return result; } @@ -1319,16 +1358,15 @@ CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, */ CURLcode curl_easy_upkeep(CURL *curl) { - struct Curl_easy *data = curl; - /* Verify that we got an easy handle we can work with. */ - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(Curl_is_in_callback(data)) - return CURLE_RECURSIVE_API_CALL; + struct Curl_eapi_guard guard; + CURLcode result; - /* Use the common function to keep connections alive. */ - return Curl_cpool_upkeep(data); + if(CURL_EAPI_ENTER(&guard, curl, easy_upkeep, &result)) { + /* Use the common function to keep connections alive. */ + result = Curl_cpool_upkeep((struct Curl_easy *)curl); + } + CURL_EAPI_LEAVE(&guard); + return result; } CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, @@ -1336,13 +1374,15 @@ CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, const unsigned char *sdata, size_t sdata_len) { #if defined(USE_SSL) && defined(USE_SSLS_EXPORT) - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data)) - return CURLE_RECURSIVE_API_CALL; - return Curl_ssl_session_import(data, session_key, - shmac, shmac_len, sdata, sdata_len); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_import, &result)) { + result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, + shmac, shmac_len, sdata, sdata_len); + } + CURL_EAPI_LEAVE(&guard); + return result; #else (void)curl; (void)session_key; @@ -1359,12 +1399,15 @@ CURLcode curl_easy_ssls_export(CURL *curl, void *userptr) { #if defined(USE_SSL) && defined(USE_SSLS_EXPORT) - struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data)) - return CURLE_RECURSIVE_API_CALL; - return Curl_ssl_session_export(data, export_fn, userptr); + struct Curl_eapi_guard guard; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_export, &result)) { + result = Curl_ssl_session_export((struct Curl_easy *)curl, + export_fn, userptr); + } + CURL_EAPI_LEAVE(&guard); + return result; #else (void)curl; (void)export_fn; diff --git a/lib/easy_lock.h b/lib/easy_lock.h index b8f916ff659c..9f21448cbe09 100644 --- a/lib/easy_lock.h +++ b/lib/easy_lock.h @@ -71,6 +71,7 @@ static CURL_INLINE void curl_simple_lock_lock(curl_simple_lock *lock) #ifdef HAVE_BUILTIN_IA32_PAUSE __builtin_ia32_pause(); #elif defined(__aarch64__) + /* NOLINTNEXTLINE(portability-no-assembler) */ __asm__ volatile("yield" ::: "memory"); #elif defined(HAVE_SCHED_YIELD) sched_yield(); diff --git a/lib/easyoptions.c b/lib/easyoptions.c index a559362c8e83..609edcd64180 100644 --- a/lib/easyoptions.c +++ b/lib/easyoptions.c @@ -140,6 +140,10 @@ const struct curl_easyoption Curl_easyopts[] = { { "HTTPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, 0 }, { "HTTPPOST", CURLOPT_HTTPPOST, CURLOT_OBJECT, 0 }, { "HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL, CURLOT_LONG, 0 }, + { "HTTPSIG_ALGORITHM", CURLOPT_HTTPSIG_ALGORITHM, CURLOT_VALUES, 0 }, + { "HTTPSIG_HEADERS", CURLOPT_HTTPSIG_HEADERS, CURLOT_STRING, 0 }, + { "HTTPSIG_KEY", CURLOPT_HTTPSIG_KEY, CURLOT_STRING, 0 }, + { "HTTPSIG_KEYID", CURLOPT_HTTPSIG_KEYID, CURLOT_STRING, 0 }, { "HTTP_CONTENT_DECODING", CURLOPT_HTTP_CONTENT_DECODING, CURLOT_LONG, 0 }, { "HTTP_TRANSFER_DECODING", CURLOPT_HTTP_TRANSFER_DECODING, CURLOT_LONG, 0 }, @@ -393,7 +397,7 @@ int Curl_easyopts_check(void) #if UNITY_CERTVERIFY return (CURLOPT_LASTENTRY % 10000) != (901 + 1); #else - return (CURLOPT_LASTENTRY % 10000) != (328 + 1); + return (CURLOPT_LASTENTRY % 10000) != (332 + 1); #endif /* UNITY_CERTVERIFY */ } #endif diff --git a/lib/escape.c b/lib/escape.c index 4aff583de1b2..a1a714f8f592 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -196,7 +196,7 @@ void curl_free(void *p) * Curl_hexencode() * * Converts binary input to lowercase hex-encoded ASCII output. - * Null-terminated. + * null-terminated. */ void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ unsigned char *out, size_t olen) /* output buffer size */ diff --git a/lib/fake_addrinfo.c b/lib/fake_addrinfo.c index 5a89202064de..6857d6348518 100644 --- a/lib/fake_addrinfo.c +++ b/lib/fake_addrinfo.c @@ -42,6 +42,7 @@ void r_freeaddrinfo(struct addrinfo *cahead) struct context { struct ares_addrinfo *addr; + int status; }; static void async_addrinfo_cb(void *userp, int status, int timeouts, @@ -49,6 +50,7 @@ static void async_addrinfo_cb(void *userp, int status, int timeouts, { struct context *ctx = (struct context *)userp; (void)timeouts; + ctx->status = status; if(ARES_SUCCESS == status) { ctx->addr = addr; } @@ -64,7 +66,7 @@ static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead) const char *name = aihead->name; /* traverse the addrinfo list */ - for(ai = aihead->nodes; ai != NULL; ai = ai->ai_next) { + for(ai = aihead->nodes; ai; ai = ai->ai_next) { size_t ss_size; size_t namelen = name ? strlen(name) + 1 : 0; /* ignore elements with unsupported address family, @@ -127,14 +129,10 @@ static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead) return cafirst; } -/* - RETURN VALUE - - getaddrinfo() returns 0 if it succeeds, or one of the following nonzero - error codes: - - ... -*/ +/* 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, @@ -190,8 +188,10 @@ int r_getaddrinfo(const char *node, /* free the old */ ares_freeaddrinfo(ctx.addr); } + else if((ctx.status == ARES_ENOTFOUND) || (ctx.status == ARES_ENODATA)) + rc = EAI_NONAME; /* no such name */ else - rc = EAI_NONAME; /* got nothing */ + rc = EAI_AGAIN; /* failed without an authoritative answer */ /* Cleanup */ ares_destroy(channel); diff --git a/lib/fake_addrinfo.h b/lib/fake_addrinfo.h index 07d5b6da8cdd..69579da2e51f 100644 --- a/lib/fake_addrinfo.h +++ b/lib/fake_addrinfo.h @@ -43,7 +43,7 @@ # include #endif -void r_freeaddrinfo(struct addrinfo *res); +void r_freeaddrinfo(struct addrinfo *cahead); int r_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, diff --git a/lib/file.c b/lib/file.c index fff8feeb92a5..d3553b7df2d1 100644 --- a/lib/file.c +++ b/lib/file.c @@ -47,10 +47,6 @@ #include #endif -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #ifdef HAVE_DIRENT_H #include #endif @@ -80,12 +76,14 @@ struct FILEPROTO { 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! */ + bool is_dir; }; static void file_cleanup(struct FILEPROTO *file) { curlx_safefree(file->freepath); file->path = NULL; + file->is_dir = FALSE; if(file->fd != -1) { curlx_close(file->fd); file->fd = -1; @@ -130,6 +128,38 @@ static CURLcode file_done(struct Curl_easy *data, return CURLE_OK; } +static int file_stat(const char *path, curlx_struct_stat *statbuf) +{ +#ifdef _WIN32 + int result = curlx_stat(path, statbuf); + + if(result) { + size_t pathlen = strlen(path); + + /* MSVCRT's narrow stat() rejects trailing directory separators. */ + if((pathlen > 3) && + ((path[pathlen - 1] == '\\') || (path[pathlen - 1] == '/'))) { + char *trimmed = curlx_strdup(path); + + if(trimmed) { + do { + trimmed[--pathlen] = '\0'; + } while((pathlen > 3) && + ((trimmed[pathlen - 1] == '\\') || + (trimmed[pathlen - 1] == '/'))); + + result = curlx_stat(trimmed, statbuf); + curlx_free(trimmed); + } + } + } + + return result; +#else + return curlx_stat(path, statbuf); +#endif +} + /* * file_connect() gets called from Curl_protocol_connect() to allow us to * do protocol-specific actions at connect-time. We emulate a @@ -139,6 +169,9 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) { char *real_path; struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); +#ifdef _WIN32 + curlx_struct_stat statbuf; +#endif int fd; #ifdef DOS_FILESYSTEM size_t i; @@ -177,8 +210,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) On other platforms, we need the slash to indicate an absolute pathname. On Windows, absolute paths start - with a drive letter. - */ + with a drive letter. */ actual_path = real_path; if((actual_path[0] == '/') && actual_path[1] && @@ -238,7 +270,13 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) file->freepath = real_path; /* free this when done */ file->fd = fd; - if(!data->state.upload && (fd == -1)) { +#ifdef _WIN32 + if(!data->state.upload && (fd == -1) && + !file_stat(file->path, &statbuf) && S_ISDIR(statbuf.st_mode)) + file->is_dir = TRUE; +#endif + + if(!data->state.upload && (fd == -1) && !file->is_dir) { failf(data, "Could not open file %s", data->state.up.path); file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE); return CURLE_FILE_COULDNT_READ_FILE; @@ -375,6 +413,58 @@ static CURLcode file_upload(struct Curl_easy *data, return result; } +#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) +static CURLcode win32_file_list(struct Curl_easy *data, const char *path) +{ + WIN32_FIND_DATA entry; + HANDLE handle; + char *pattern; + size_t pathlen = strlen(path); + CURLcode result = CURLE_OK; + DWORD error; + + pattern = curl_maprintf("%s%s*", path, + pathlen && + (path[pathlen - 1] == '\\' || + path[pathlen - 1] == '/') ? "" : "\\"); + if(!pattern) + return CURLE_OUT_OF_MEMORY; + + handle = curlx_FindFirstFile(pattern, &entry); + curlx_free(pattern); + if(handle == INVALID_HANDLE_VALUE) + return CURLE_READ_ERROR; + + do { + if(entry.cFileName[0] != TEXT('.')) { + char *name = curlx_convert_tchar_to_UTF8(entry.cFileName); + + if(!name) { + result = CURLE_OUT_OF_MEMORY; + break; + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, + name, strlen(name)); + curlx_free(name); + if(result) + break; + + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } while(FindNextFile(handle, &entry)); + + error = GetLastError(); + if(!result && error != ERROR_NO_MORE_FILES) + result = CURLE_READ_ERROR; + + FindClose(handle); + return result; +} +#endif + /* * file_do() is the protocol-specific function for the do-phase, separated * from the connect-phase above. Other protocols merely setup the transfer in @@ -388,8 +478,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) /* 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 - */ + (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; curlx_struct_stat statbuf; @@ -411,8 +500,10 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) fd = file->fd; /* VMS: This only works reliable for STREAMLF files */ - if(curlx_fstat(fd, &statbuf) != -1) { - if(!S_ISDIR(statbuf.st_mode)) + if((file->is_dir ? file_stat(file->path, &statbuf) : + curlx_fstat(fd, &statbuf)) != -1) { + file->is_dir = (fd == -1) || S_ISDIR(statbuf.st_mode); + if(!file->is_dir) expected_size = statbuf.st_size; /* and store the modification time */ data->info.filetime = statbuf.st_mtime; @@ -439,7 +530,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) return result; result = Curl_client_write(data, CLIENTWRITE_HEADER, - accept_ranges, sizeof(accept_ranges) - 1); + accept_ranges, CURL_CSTRLEN(accept_ranges)); if(result != CURLE_OK) return result; } @@ -516,7 +607,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsSetDownloadSize(data, expected_size); if(data->state.resume_from) { - if(!S_ISDIR(statbuf.st_mode)) { + if(!file->is_dir) { if(data->state.resume_from != curl_lseek(fd, data->state.resume_from, SEEK_SET)) return CURLE_BAD_DOWNLOAD_RESUME; @@ -530,7 +621,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(result) goto out; - if(!S_ISDIR(statbuf.st_mode)) { + if(!file->is_dir) { while(!result) { ssize_t nread; /* Do not fill a whole buffer if we want less than all data */ @@ -564,7 +655,11 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) } } else { -#ifdef HAVE_OPENDIR +#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) + result = win32_file_list(data, file->path); + if(result) + goto out; +#elif defined(HAVE_OPENDIR) DIR *dir = opendir(file->path); struct dirent *entry; diff --git a/lib/formdata.c b/lib/formdata.c index 3619c15bb121..0c81d8c781c9 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -46,7 +46,7 @@ struct Curl_easy; /*************************************************************************** * - * AddHttpPost() + * httppost_add() * * Adds an HttpPost structure to the list, if parent_post is given becomes * a subpost of parent_post instead of a direct list element. @@ -54,10 +54,10 @@ struct Curl_easy; * Returns newly allocated HttpPost on success and NULL if malloc failed. * ***************************************************************************/ -static struct curl_httppost *AddHttpPost(struct FormInfo *src, - struct curl_httppost *parent_post, - struct curl_httppost **httppost, - struct curl_httppost **last_post) +static struct curl_httppost *httppost_add(struct FormInfo *src, + struct curl_httppost *parent_post, + struct curl_httppost **httppost, + struct curl_httppost **last_post) { struct curl_httppost *post; size_t namelength = src->namelength; @@ -103,7 +103,7 @@ static struct curl_httppost *AddHttpPost(struct FormInfo *src, } /* Allocate and initialize a new FormInfo structure. */ -static struct FormInfo *NewFormInfo(void) +static struct FormInfo *forminfo_new(void) { struct FormInfo *form_info = curlx_calloc(1, sizeof(struct FormInfo)); @@ -118,7 +118,7 @@ static struct FormInfo *NewFormInfo(void) } /* Replace the target field data by a dynamic copy of it. */ -static CURLcode FormInfoCopyField(struct bufref *field, size_t len) +static CURLcode forminfo_copyfield(struct bufref *field, size_t len) { const char *value = Curl_bufref_ptr(field); CURLcode result = CURLE_OK; @@ -134,12 +134,12 @@ static CURLcode FormInfoCopyField(struct bufref *field, size_t len) /*************************************************************************** * - * AddFormInfo() + * forminfo_add() * * Adds a FormInfo structure to the list presented by parent. * ***************************************************************************/ -static void AddFormInfo(struct FormInfo *form_info, struct FormInfo *parent) +static void forminfo_add(struct FormInfo *form_info, struct FormInfo *parent) { form_info->flags |= HTTPPOST_FILENAME; @@ -154,7 +154,7 @@ static void AddFormInfo(struct FormInfo *form_info, struct FormInfo *parent) static void free_formlist(struct FormInfo *ptr) { - for(; ptr != NULL; ptr = ptr->more) { + for(; ptr; ptr = ptr->more) { Curl_bufref_free(&ptr->name); Curl_bufref_free(&ptr->value); Curl_bufref_free(&ptr->contenttype); @@ -164,7 +164,7 @@ static void free_formlist(struct FormInfo *ptr) /*************************************************************************** * - * FormAdd() + * formadd_check() * * Stores a formpost parameter and builds the appropriate linked list. * @@ -184,9 +184,9 @@ static void free_formlist(struct FormInfo *ptr) * * name/value pair where only the content pointer is remembered: * curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", - * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10, + * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10L, * CURLFORM_END); - * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used) + * (if CURLFORM_CONTENTSLENGTH is missing strlen() is used) * * storing a filename (CONTENTTYPE is optional!): * curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", @@ -212,9 +212,9 @@ static void free_formlist(struct FormInfo *ptr) * ***************************************************************************/ -static CURLFORMcode FormAddCheck(struct FormInfo *first_form, - struct curl_httppost **httppost, - struct curl_httppost **last_post) +static CURLFORMcode formadd_check(struct FormInfo *first_form, + struct curl_httppost **httppost, + struct curl_httppost **last_post) { const char *prevtype = NULL; struct FormInfo *form = NULL; @@ -223,7 +223,7 @@ static CURLFORMcode FormAddCheck(struct FormInfo *first_form, /* 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) { + for(form = first_form; form; form = form->more) { const char *name = Curl_bufref_ptr(&form->name); if(((!name || !Curl_bufref_ptr(&form->value)) && !post) || @@ -263,16 +263,16 @@ static CURLFORMcode FormAddCheck(struct FormInfo *first_form, if(!(form->flags & HTTPPOST_PTRNAME)) { /* Note that there is small risk that form->name is NULL here if the app passed in a bad combo, so we check for that. */ - if(FormInfoCopyField(&form->name, form->namelength)) + if(forminfo_copyfield(&form->name, form->namelength)) return CURL_FORMADD_MEMORY; } if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | HTTPPOST_CALLBACK))) { - if(FormInfoCopyField(&form->value, (size_t)form->contentslength)) + if(forminfo_copyfield(&form->value, (size_t)form->contentslength)) return CURL_FORMADD_MEMORY; } - post = AddHttpPost(form, post, httppost, last_post); + post = httppost_add(form, post, httppost, last_post); if(!post) return CURL_FORMADD_MEMORY; @@ -297,7 +297,7 @@ static void free_chain(struct curl_httppost *c) } } -static CURLFORMcode FormAdd(struct curl_httppost **httppost, +static CURLFORMcode formadd(struct curl_httppost **httppost, struct curl_httppost **last_post, va_list params) { struct FormInfo *first_form, *curr, *form = NULL; @@ -318,7 +318,7 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, /* * We need to allocate the first struct to fill in. */ - first_form = NewFormInfo(); + first_form = forminfo_new(); if(!first_form) return CURL_FORMADD_MEMORY; @@ -436,14 +436,14 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, if(Curl_bufref_ptr(&curr->value)) { if(curr->flags & HTTPPOST_FILENAME) { if(avalue) { - form = NewFormInfo(); + form = forminfo_new(); if(!form || Curl_bufref_memdup0(&form->value, avalue, strlen(avalue))) { curlx_free(form); retval = CURL_FORMADD_MEMORY; } else { - AddFormInfo(form, curr); + forminfo_add(form, curr); curr = form; form = NULL; } @@ -512,14 +512,14 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, if(Curl_bufref_ptr(&curr->contenttype)) { if(curr->flags & HTTPPOST_FILENAME) { if(avalue) { - form = NewFormInfo(); + form = forminfo_new(); if(!form || Curl_bufref_memdup0(&form->contenttype, avalue, strlen(avalue))) { curlx_free(form); retval = CURL_FORMADD_MEMORY; } else { - AddFormInfo(form, curr); + forminfo_add(form, curr); curr = form; form = NULL; } @@ -566,7 +566,7 @@ static CURLFORMcode FormAdd(struct curl_httppost **httppost, } if(!retval) - retval = FormAddCheck(first_form, &newchain, &lastnode); + retval = formadd_check(first_form, &newchain, &lastnode); if(retval) /* On error, free allocated fields for all nodes of the FormInfo linked @@ -611,7 +611,7 @@ CURLFORMcode curl_formadd(struct curl_httppost **httppost, va_list arg; CURLFORMcode form; va_start(arg, last_post); - form = FormAdd(httppost, last_post, arg); + form = formadd(httppost, last_post, arg); va_end(arg); return form; } diff --git a/lib/ftp-int.h b/lib/ftp-int.h index 68d26f332721..8d7e14127488 100644 --- a/lib/ftp-int.h +++ b/lib/ftp-int.h @@ -113,10 +113,10 @@ struct ftp_conn { char *account; char *alternative_to_user; char *entrypath; /* the PWD reply when we logged on */ - const char *file; /* url-decoded filename (or path), points into rawpath */ + const char *file; /* URL-decoded filename (or path), points into rawpath */ char *rawpath; /* URL decoded, allocated, version of the path */ struct pathcomp *dirs; /* allocated array for path components */ - char *prevpath; /* url-decoded conn->path from the previous transfer */ + char *prevpath; /* URL-decoded conn->path from the previous transfer */ char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a and others (A/I or zero) */ char *server_os; /* The target server operating system. */ diff --git a/lib/ftp.c b/lib/ftp.c index 9d3700df5f7b..70bf654bf914 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -44,7 +44,6 @@ #include "curl_addrinfo.h" #include "curl_trc.h" #include "if2ip.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -65,6 +64,7 @@ #include "multiif.h" #include "url.h" #include "http_proxy.h" +#include "vdns/hostip.h" #include "curlx/strdup.h" #include "curlx/strerr.h" #include "curlx/strparse.h" @@ -232,12 +232,10 @@ static CURLcode ftp_parse_url_path(struct Curl_easy *data, if((pathLen > 0) && (rawPath[pathLen - 1] != '/')) fileName = rawPath; /* this is a full file path */ - /* - else: ftpc->file is not used anywhere other than for operations on - a file. In other words, never for directory operations, - so we can safely leave filename as NULL here and use it as a - argument in dir/file decisions. - */ + /* else: ftpc->file is not used anywhere other than for operations on + a file. In other words, never for directory operations, + so we can safely leave filename as NULL here and use it as a + argument in dir/file decisions. */ break; case FTPFILE_SINGLECWD: @@ -355,7 +353,6 @@ static void close_secondarysocket(struct Curl_easy *data, { (void)ftpc; CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_CSTATE(ftpc)); - Curl_conn_close(data, SECONDARYSOCKET); Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } @@ -444,8 +441,10 @@ static CURLcode ftp_cw_lc_write(struct Curl_easy *data, static const struct Curl_cwtype ftp_cw_lc = { "ftp-lineconv", NULL, + 0, Curl_cwriter_def_init, ftp_cw_lc_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct ftp_cw_lc_ctx) }; @@ -591,7 +590,7 @@ static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, static CURLcode ftp_readresp(struct Curl_easy *data, struct ftp_conn *ftpc, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *ftpcodep, /* return the ftp-code if done */ size_t *size) /* size of the response */ @@ -733,7 +732,7 @@ static CURLcode getftpresponse(struct Curl_easy *data, pp->pending_resp = FALSE; CURL_TRC_FTP(data, "getftpresponse -> result=%d, nread=%zu, ftpcode=%d", - result, *nreadp, *ftpcodep); + (int)result, *nreadp, *ftpcodep); return result; } @@ -743,7 +742,7 @@ static CURLcode ftp_state_user(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = Curl_pp_sendf(data, &ftpc->pp, "USER %s", - conn->user ? conn->user : ""); + Curl_creds_user(conn->creds)); if(!result) { ftpc->ftp_trying_alternative = FALSE; ftp_state(data, ftpc, FTP_USER); @@ -820,8 +819,7 @@ static const char *pathpiece(struct ftp_conn *ftpc, int num) ftp_state_cwd() sends the range of CWD commands to the server to change to the correct directory. It may also need to send MKD commands to create - missing ones, if that option is enabled. -*/ + missing ones, if that option is enabled. */ static CURLcode ftp_state_cwd(struct Curl_easy *data, struct ftp_conn *ftpc, struct FTP *ftp) @@ -930,11 +928,11 @@ static CURLcode ftp_port_parse_string(struct Curl_easy *data, #ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)ss; #endif - /* either ipv6 or (ipv4|domain|interface):port(-range) */ + /* either IPv6 or (ipv4|domain|interface):port(-range) */ addrlen = ip_end - string_ftpport; #ifdef USE_IPV6 if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { - /* ipv6 */ + /* IPv6 */ addrlen = strlen(string_ftpport); ip_end = NULL; /* this got no port ! */ } @@ -1024,7 +1022,7 @@ static CURLcode ftp_port_default_host(struct Curl_easy *data, struct sockaddr_in6 * const sa6 = (void *)sa; #endif char buffer[STRERROR_LEN]; - const char *r; + CURLcode result; *sslenp = sizeof(*ss); if(getsockname(conn->sock[FIRSTSOCKET], sa, sslenp)) { @@ -1035,14 +1033,14 @@ static CURLcode ftp_port_default_host(struct Curl_easy *data, switch(sa->sa_family) { #ifdef USE_IPV6 case AF_INET6: - r = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, hbuflen); + result = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, hbuflen); break; #endif default: - r = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, hbuflen); + result = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, hbuflen); break; } - if(!r) + if(result) return CURLE_FTP_PORT_FAILED; *hostp = hbuf; @@ -1084,7 +1082,7 @@ static CURLcode ftp_port_open_socket(struct Curl_easy *data, curl_socket_t *portsockp) { char buffer[STRERROR_LEN]; - int error = 0; + int sockerr = 0; const struct Curl_addrinfo *ai; CURLcode result = CURLE_FTP_PORT_FAILED; @@ -1096,14 +1094,14 @@ static CURLcode ftp_port_open_socket(struct Curl_easy *data, if(result == CURLE_OUT_OF_MEMORY) return result; result = CURLE_FTP_PORT_FAILED; - error = SOCKERRNO; + sockerr = SOCKERRNO; continue; } break; } if(!ai) { failf(data, "socket failure: %s", - curlx_strerror(error, buffer, sizeof(buffer))); + curlx_strerror(sockerr, buffer, sizeof(buffer))); return CURLE_FTP_PORT_FAILED; } *aip = ai; @@ -1132,7 +1130,7 @@ static CURLcode ftp_port_bind_socket(struct Curl_easy *data, #endif char buffer[STRERROR_LEN]; unsigned short port; - int error; + int sockerr; memcpy(sa, ai->ai_addr, ai->ai_addrlen); *sslen_io = ai->ai_addrlen; @@ -1145,13 +1143,13 @@ static CURLcode ftp_port_bind_socket(struct Curl_easy *data, sa6->sin6_port = htons(port); #endif if(bind(portsock, sa, *sslen_io)) { - error = SOCKERRNO; - if(non_local && (error == SOCKEADDRNOTAVAIL)) { + sockerr = SOCKERRNO; + if(non_local && (sockerr == 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, - curlx_strerror(error, buffer, sizeof(buffer))); + curlx_strerror(sockerr, buffer, sizeof(buffer))); *sslen_io = sizeof(*ss); if(getsockname(conn->sock[FIRSTSOCKET], sa, sslen_io)) { @@ -1163,9 +1161,9 @@ static CURLcode ftp_port_bind_socket(struct Curl_easy *data, non_local = FALSE; /* do not try this again */ continue; } - if(error != SOCKEADDRINUSE && error != SOCKEACCES) { + if(sockerr != SOCKEADDRINUSE && sockerr != SOCKEACCES) { failf(data, "bind(port=%hu) failed: %s", port, - curlx_strerror(error, buffer, sizeof(buffer))); + curlx_strerror(sockerr, buffer, sizeof(buffer))); return CURLE_FTP_PORT_FAILED; } } @@ -1188,8 +1186,7 @@ static CURLcode ftp_port_bind_socket(struct Curl_easy *data, curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_FTP_PORT_FAILED; } - CURL_TRC_FTP(data, "ftp_port_bind_socket(), socket bound to port %d", - port); + CURL_TRC_FTP(data, "ftp_port_bind_socket(), socket bound to port %d", port); return CURLE_OK; } @@ -1276,8 +1273,7 @@ static CURLcode ftp_port_send_command(struct Curl_easy *data, * EPRT |2|1080::8:800:200C:417A|5282| */ result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET ? 1 : 2, - myhost, port); + sa->sa_family == AF_INET ? 1 : 2, myhost, port); if(result) { failf(data, "Failure sending EPRT command: %s", curl_easy_strerror(result)); @@ -1337,7 +1333,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, curl_socklen_t sslen; char hbuf[NI_MAXHOST]; const char *host = NULL; - const char *string_ftpport = data->set.str[STRING_FTPPORT]; + const char *string_ftpport = CURL_EASY_STR(data, STRING_FTPPORT); struct Curl_dns_entry *dns_entry = NULL; const struct Curl_addrinfo *res = NULL; const struct Curl_addrinfo *ai = NULL; @@ -1393,13 +1389,15 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, ftp_state(data, ftpc, FTP_STOP); } else { - /* successfully set up the listen socket filter. SSL needed? */ + /* successfully set up the listen socket filter. SSL needed? + * Use the control connections origin for cert verification. */ 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); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), + conn, SECONDARYSOCKET); } conn->bits.do_more = FALSE; - Curl_pgrsTime(data, TIMER_STARTACCEPT); Curl_expire(data, (data->set.accepttimeout > 0) ? data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, EXPIRE_FTP_ACCEPT); @@ -1414,19 +1412,17 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - /* - Here's the executive summary on what to do: - - PASV is RFC959, expect: - 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) + /* Here's the executive summary on what to do: - LPSV is RFC1639, expect: - 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) + PASV is RFC959, expect: + 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) - EPSV is RFC2428, expect: - 229 Entering Extended Passive Mode (|||port|) + LPSV is RFC1639, expect: + 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) - */ + EPSV is RFC2428, expect: + 229 Entering Extended Passive Mode (|||port|) + */ static const char mode[][5] = { "EPSV", "PASV" }; int modeoff; @@ -1481,8 +1477,8 @@ static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data, to prepare the server for the upcoming PASV */ if(!ftpc->file) result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST] ? - data->set.str[STRING_CUSTOMREQUEST] : + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : (data->state.list_only ? "NLST" : "LIST")); else if(data->state.upload) result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", ftpc->file); @@ -1549,14 +1545,12 @@ static CURLcode ftp_state_list(struct Curl_easy *data, way. It has turned out that the NLST list output is not the same on all servers either... */ - /* - if FTPFILE_NOCWD was specified, we should add the path + /* if FTPFILE_NOCWD was specified, we should add the path as argument for the LIST / NLST / or custom command. Whether the server will support this, is uncertain. The other ftp_filemethods will CWD into dir/dir/ first and - then do LIST (in that case: nothing to do here) - */ + then do LIST (in that case: nothing to do here) */ const char *lstArg = NULL; int lstArglen = 0; char *cmd; @@ -1578,8 +1572,8 @@ static CURLcode ftp_state_list(struct Curl_easy *data, } cmd = curl_maprintf("%s%s%.*s", - data->set.str[STRING_CUSTOMREQUEST] ? - data->set.str[STRING_CUSTOMREQUEST] : + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : (data->state.list_only ? "NLST" : "LIST"), lstArg ? " " : "", lstArglen, lstArg ? lstArg : ""); @@ -1713,10 +1707,11 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, /* Let's read off the proper amount of bytes from the input. */ if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1946,8 +1941,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, In addition: asking for the size for 'TYPE A' transfers is not constructive since servers do not report the converted size. - Thus, skip it. - */ + Thus, skip it. */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) ftp_state(data, ftpc, FTP_RETR); @@ -1983,11 +1977,7 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, { CURLcode result = CURLE_OK; - if(conn->bits.ipv6 -#ifndef CURL_DISABLE_PROXY - && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) -#endif - ) { + if(conn->bits.ipv6 && !Curl_conn_is_tunneling(conn, FIRSTSOCKET)) { /* We cannot disable EPSV when doing IPv6, so this is instead a fail */ failf(data, "Failed EPSV attempt, exiting"); return CURLE_WEIRD_SERVER_REPLY; @@ -2019,8 +2009,8 @@ static CURLcode ftp_control_addr_dup(struct Curl_easy *data, char **newhostp) 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) - *newhostp = curlx_strdup(conn->host.name); + if(Curl_conn_is_tunneling(conn, FIRSTSOCKET)) + *newhostp = curlx_strdup(conn->origin->hostname); else #endif if(!Curl_conn_get_ip_info(data, conn, FIRSTSOCKET, &is_ipv6, &ipquad) && @@ -2059,8 +2049,6 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, { struct connectdata *conn = data->conn; CURLcode result; - struct Curl_dns_entry *dns = NULL; - unsigned short connectport; /* the local port connect() should use! */ const struct pingpong *pp = &ftpc->pp; char *newhost = NULL; unsigned short newport = 0; @@ -2075,7 +2063,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, ptr++; /* |||12345| */ sep = ptr[0]; - if((ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) { + if(sep && (ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) { const char *p = &ptr[3]; curl_off_t num; if(curlx_str_number(&p, &num, 0xffff) || (*p != sep)) { @@ -2125,7 +2113,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, /* 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, reuse %s instead", - ip[0], ip[1], ip[2], ip[3], conn->host.name); + ip[0], ip[1], ip[2], ip[3], conn->origin->hostname); result = ftp_control_addr_dup(data, &newhost); if(result) return result; @@ -2147,67 +2135,25 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, return CURLE_FTP_WEIRD_PASV_REPLY; } -#ifndef CURL_DISABLE_PROXY - if(conn->bits.proxy) { - /* This connection uses a proxy and we need to connect to the proxy again - * here. We do not want to rely on a former host lookup that might have - * expired now, instead we remake the lookup here and now! */ - struct ip_quadruple ipquad; - bool is_ipv6; - const char * const host_name = conn->bits.socksproxy ? - conn->socks_proxy.host.name : conn->http_proxy.host.name; - - result = Curl_conn_get_ip_info(data, data->conn, FIRSTSOCKET, - &is_ipv6, &ipquad); - if(result) - goto error; - - (void)Curl_resolv_blocking( - data, is_ipv6 ? CURL_DNSQ_AAAA : CURL_DNSQ_A, - host_name, ipquad.remote_port, Curl_conn_get_transport(data, conn), - &dns); - /* we connect to the proxy's port */ - connectport = (unsigned short)ipquad.remote_port; - - if(!dns) { - failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto error; - } - } - else -#endif - { - /* normal, direct, ftp connection */ - DEBUGASSERT(newhost); - - /* postponed address resolution in case of tcp fastopen */ - if(conn->bits.tcp_fastopen && !conn->bits.reuse && !newhost[0]) { - curlx_free(newhost); - result = ftp_control_addr_dup(data, &newhost); - if(result) - goto error; - } - - (void)Curl_resolv_blocking( - data, Curl_resolv_dns_queries(data, conn->ip_version), - newhost, newport, Curl_conn_get_transport(data, conn), &dns); - connectport = newport; /* we connect to the remote port */ + DEBUGASSERT(newhost); + Curl_peer_unlink(&conn->origin2); + result = Curl_peer_create(data, conn->scheme, newhost, newport, + &conn->origin2); + if(result) + goto error; - if(!dns) { - failf(data, "cannot resolve new host %s:%hu", newhost, connectport); - result = CURLE_FTP_CANT_GET_HOST; + /* If FIRSTSOCKET goes via another peer, SECONDARY needs as well, + * but with its new port. */ + if(conn->via_peer) { + Curl_peer_unlink(&conn->via_peer2); + result = Curl_peer_create(data, conn->via_peer->scheme, + conn->via_peer->hostname, newport, + &conn->via_peer2); + if(result) goto error; - } } - DEBUGASSERT(newhost); - curlx_free(conn->secondaryhostname); - conn->secondary_port = newport; - conn->secondaryhostname = newhost; - newhost = NULL; - - result = Curl_conn_setup(data, conn, SECONDARYSOCKET, dns, + result = Curl_conn_setup(data, conn, SECONDARYSOCKET, conn->bits.ftp_use_data_ssl ? CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); @@ -2227,13 +2173,10 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, #ifdef CURLVERBOSE if(data->set.verbose) { - /* Dump information about this second connection when we have issued a PASV - * command before and thus we have connected to a possibly new IP address. - */ - char buf[256]; - Curl_printable_address(dns->addr, buf, sizeof(buf)); - infof(data, "Connecting to %s (%s) port %d", - conn->secondaryhostname, buf, connectport); + /* Dump information about this second connection when we have issued + * a PASV command. */ + infof(data, "Connecting to %s port %d", + conn->origin2->hostname, conn->origin2->port); } #endif @@ -2241,7 +2184,6 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, ftp_state(data, ftpc, FTP_STOP); /* this phase is completed */ error: - Curl_dns_entry_unlink(data, &dns); curlx_free(newhost); return result; } @@ -2267,10 +2209,10 @@ static CURLcode ftp_statemach(struct Curl_easy *data, * This function shall be called when the second FTP (data) connection is * connected. * - * 'complete' can return 0 for incomplete, 1 for done and -1 for go back + * 'more' can return DOMORE_INCOMPLETE, DOMORE_DONE or DOMORE_GOBACK * (which is for when PASV is being sent to retry a failed EPSV). */ -static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) +static CURLcode ftp_do_more(struct Curl_easy *data, domore *more) { struct connectdata *conn = data->conn; struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); @@ -2285,7 +2227,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(!ftpc || !ftp) return CURLE_FAILED_INIT; - *completep = 0; /* default to stay in the state */ + *more = DOMORE_INCOMPLETE; /* default to stay in the state */ /* 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 @@ -2303,7 +2245,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) 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 */ + *more = DOMORE_GOBACK; /* go back to DOING please */ /* this is a EPSV connect failing, try PASV instead */ return ftp_epsv_disable(data, ftpc, conn); } @@ -2316,7 +2258,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) They are only done to kickstart the do_more state */ result = ftp_statemach(data, ftpc, &complete); - *completep = (int)complete; + if(complete) + *more = DOMORE_DONE; /* if we got an error or if we do not wait for a data connection return immediately */ @@ -2326,7 +2269,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) /* 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 are not actually complete */ - *completep = 0; + *more = DOMORE_INCOMPLETE; } if(ftp->transfer <= PPTRANSFER_INFO) { @@ -2348,8 +2291,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(result) return result; - *completep = 1; /* this state is now complete when the server has - connected back to us */ + *more = DOMORE_DONE; /* this state is now complete when the server has + connected back to us */ } else { result = ftp_check_ctrl_on_data_wait(data, ftpc); @@ -2368,7 +2311,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) * 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); + *more = (!ftpc->wait_data_conn && complete) ? + DOMORE_DONE : DOMORE_INCOMPLETE; } else { /* download */ @@ -2384,7 +2328,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(result) ; else if((data->state.list_only || !ftpc->file) && - !(data->set.prequote)) { + !data->set.prequote) { /* The specified path ends with a slash, and therefore we think this is a directory that is requested, use LIST. Before that, we also need to set ASCII transfer mode. */ @@ -2411,7 +2355,8 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) } result = ftp_statemach(data, ftpc, &complete); - *completep = (int)complete; + if(complete) + *more = DOMORE_DONE; } return result; } @@ -2421,7 +2366,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(!ftpc->wait_data_conn) { /* no waiting for the data connection so this is now complete */ - *completep = 1; + *more = DOMORE_DONE; CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_CSTATE(ftpc), (int)result); } @@ -2436,7 +2381,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) { if(connected) { - int completed; + domore completed; CURLcode result = ftp_do_more(data, &completed); if(result) { @@ -2901,7 +2846,7 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, if(data->req.size > data->req.maxdownload && data->req.maxdownload > 0) data->req.size = data->req.maxdownload; - else if((instate != FTP_LIST) && (data->state.prefer_ascii)) + else if((instate != FTP_LIST) && data->state.prefer_ascii) data->req.size = -1; /* for servers that understate ASCII mode file size */ @@ -2950,7 +2895,7 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data, { CURLcode result = CURLE_OK; - if(data->conn->bits.ftp_use_control_ssl) { + if(Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { /* PBSZ = PROTECTION BUFFER SIZE. The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: @@ -2975,6 +2920,20 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data, return result; } +/* A value that becomes part of an FTP control command must not carry a + control byte: a CR or LF would end the command line and let a second + command be smuggled onto the control connection. */ +static bool ftp_has_ctrl(const char *string) +{ + const unsigned char *s = (const unsigned char *)string; + while(*s) { + if(*s < 0x20) + return TRUE; + s++; + } + return FALSE; +} + /* for USER and PASS responses */ static CURLcode ftp_state_user_resp(struct Curl_easy *data, struct ftp_conn *ftpc, @@ -2986,7 +2945,8 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, 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", data->conn->passwd); + result = Curl_pp_sendf(data, &ftpc->pp, "PASS %s", + Curl_creds_passwd(data->conn->creds)); if(!result) ftp_state(data, ftpc, FTP_PASS); } @@ -2996,16 +2956,20 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *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) - ftp_state(data, ftpc, FTP_ACCT); - } - else { + const char *account = CURL_EASY_STR(data, STRING_FTP_ACCOUNT); + if(!account) { failf(data, "ACCT requested but none available"); result = CURLE_LOGIN_DENIED; } + else if(ftp_has_ctrl(account)) { + failf(data, "Control byte in FTP account"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", account); + if(!result) + ftp_state(data, ftpc, FTP_ACCT); + } } else { /* All other response codes, like: @@ -3013,14 +2977,19 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, 530 User ... access denied (the server denies to log the specified user) */ - if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && - !ftpc->ftp_trying_alternative) { + const char *alt = CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER); + if(alt && !ftpc->ftp_trying_alternative) { /* 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; - ftp_state(data, ftpc, FTP_USER); + if(ftp_has_ctrl(alt)) { + failf(data, "Control byte in FTP alternative-to-user command"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "%s", alt); + if(!result) { + ftpc->ftp_trying_alternative = TRUE; + ftp_state(data, ftpc, FTP_USER); + } } } else { @@ -3068,7 +3037,7 @@ static CURLcode ftp_pwd_resp(struct Curl_easy *data, The directory name can contain any character; embedded double-quotes should be escaped by double-quotes (the "quote-doubling" convention). - */ + */ /* scan for the first double-quote for non-standard responses */ while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') @@ -3161,8 +3130,8 @@ static CURLcode ftp_wait_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; if(ftpcode == 230) { /* 230 User logged in - already! Take as 220 if TLS required. */ - if(data->set.use_ssl <= CURLUSESSL_TRY || - conn->bits.ftp_use_control_ssl) + if(ftpc->use_ssl <= CURLUSESSL_TRY || + Curl_conn_is_ssl(conn, FIRSTSOCKET)) return ftp_state_user_resp(data, ftpc, ftpcode); } else if(ftpcode != 220) { @@ -3171,7 +3140,7 @@ static CURLcode ftp_wait_resp(struct Curl_easy *data, return CURLE_WEIRD_SERVER_REPLY; } - if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { + if(ftpc->use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { /* We do not have an SSL/TLS control connection yet, but FTPS is requested. Try an FTPS connection now */ @@ -3243,16 +3212,17 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, /* this was BLOCKING, keep it so for now */ bool done; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) { /* we failed and bail out */ return CURLE_USE_SSL_FAILED; } } + /* BLOCKING */ result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done); 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, ftpc, conn); } } @@ -3264,7 +3234,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, /* remain in this same state */ } else { - if(data->set.use_ssl > CURLUSESSL_TRY) + if(ftpc->use_ssl > CURLUSESSL_TRY) /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ result = CURLE_USE_SSL_FAILED; else @@ -3285,7 +3255,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PBSZ: result = Curl_pp_sendf(data, &ftpc->pp, "PROT %c", - data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + ftpc->use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); if(!result) ftp_state(data, ftpc, FTP_PROT); break; @@ -3293,10 +3263,10 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PROT: if(ftpcode / 100 == 2) /* We have enabled SSL for the data connection! */ - conn->bits.ftp_use_data_ssl = (data->set.use_ssl != CURLUSESSL_CONTROL); + conn->bits.ftp_use_data_ssl = (ftpc->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) + else if(ftpc->use_ssl > CURLUSESSL_CONTROL) /* we failed and bails out */ return CURLE_USE_SSL_FAILED; @@ -3343,7 +3313,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, /* Reply format is like 215 - */ + */ while(*ptr == ' ') ptr++; for(start = ptr; *ptr && *ptr != ' '; ptr++) @@ -3572,7 +3542,6 @@ static CURLcode ftp_connect(struct Curl_easy *data, result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) return result; - conn->bits.ftp_use_control_ssl = TRUE; } Curl_pp_init(pp, Curl_pgrs_now(data)); /* once per transfer */ @@ -3641,30 +3610,10 @@ static CURLcode ftp_sendquote(struct Curl_easy *data, return CURLE_OK; } -/*********************************************************************** - * - * ftp_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, - bool premature) +static CURLcode ftp_done_status(struct Curl_easy *data, + struct ftp_conn *ftpc, CURLcode status, + bool premature) { - struct connectdata *conn = data->conn; - 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; - size_t nread; - int ftpcode; - CURLcode result = CURLE_OK; - - if(!ftp || !ftpc) - return CURLE_OK; - - pp = &ftpc->pp; switch(status) { case CURLE_BAD_DOWNLOAD_RESUME: case CURLE_FTP_WEIRD_PASV_REPLY: @@ -3692,28 +3641,37 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, ftpc->ctl_valid = FALSE; ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the current path, as this connection is going */ - connclose(conn, "FTP ended with bad error code"); - result = status; /* use the already set error code */ - break; + CURL_TRC_FTP(data, "FTP ended with bad error code"); + connclose(data->conn); + return status; /* use the already set error code */ } + return CURLE_OK; +} +static void ftp_done_wildcard(struct Curl_easy *data, struct ftp_conn *ftpc) +{ if(data->state.wildcardmatch) { if(data->set.chunk_end && ftpc->file) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_end); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); freedirs(ftpc); } ftpc->known_filesize = -1; } +} +static void ftp_done_path(struct Curl_easy *data, struct ftp_conn *ftpc, + CURLcode result) +{ + struct connectdata *conn = data->conn; if(result) { /* We can limp along anyway (and should try to since we may already be in * the error path) */ ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "FTP: out of memory!"); /* mark for connection closure */ - curlx_free(ftpc->prevpath); - ftpc->prevpath = NULL; /* no path remembering */ + connclose(conn); /* mark for connection closure */ + curlx_safefree(ftpc->prevpath); /* no path remembering */ } else { /* remember working directory for connection reuse */ const char *rawPath = ftpc->rawpath; @@ -3740,39 +3698,54 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(ftpc->prevpath) infof(data, "Remembering we are in directory \"%s\"", ftpc->prevpath); } +} - /* shut down the socket to inform the server we are done */ - +static CURLcode ftp_done_secondary_socket(struct Curl_easy *data, + struct ftp_conn *ftpc, + CURLcode result) +{ + struct connectdata *conn = data->conn; 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"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "ABOR"); if(result) { failf(data, "Failure sending ABOR command: %s", curl_easy_strerror(result)); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "ABOR command failed"); /* connection closure */ + connclose(conn); /* connection closure */ } } close_secondarysocket(data, ftpc); } + return result; +} + +static CURLcode ftp_done_control_reply(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, CURLcode result, + bool premature) +{ + struct connectdata *conn = data->conn; + size_t nread; + int ftpcode; if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && - pp->pending_resp && !premature) { + ftpc->pp.pending_resp && !premature) { /* * Let's see what the server says about the transfer we performed, but * lower the timeout as sometimes this connection has died while the data * has been transferred. This happens when doing through NATs etc that * abandon old silent connections. */ - pp->response = *Curl_pgrs_now(data); /* timeout relative now */ + ftpc->pp.response = *Curl_pgrs_now(data); /* timeout relative now */ result = getftpresponse(data, &nread, &ftpcode); if(!nread && (result == CURLE_OPERATION_TIMEDOUT)) { failf(data, "control connection looks dead"); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ + connclose(conn); /* close */ } if(result) @@ -3782,7 +3755,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* we have sent ABOR and there is no reliable way to check if it was * successful or not; we have to close the connection now */ infof(data, "partial download completed, closing connection"); - connclose(conn, "Partial download with no ability to check"); + connclose(conn); return result; } @@ -3803,7 +3776,14 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, } } } + return result; +} +static CURLcode ftp_done_check_partial(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, CURLcode result, + bool premature) +{ if(result || premature) /* the response code from the transfer showed an error already so no use checking further */ @@ -3813,7 +3793,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, (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->set.crlf || data->state.prefer_ascii) && /* maybe CRLF conv */ (data->state.infilesize > data->req.writebytecount)) )) { failf(data, "Uploaded unaligned file size (%" FMT_OFF_T @@ -3837,6 +3817,35 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, result = CURLE_FTP_COULDNT_RETR_FILE; } } + return result; +} + +/*********************************************************************** + * + * ftp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + 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); + CURLcode result; + + if(!ftp || !ftpc) + return CURLE_OK; + + result = ftp_done_status(data, ftpc, status, premature); + + ftp_done_wildcard(data, ftpc); + ftp_done_path(data, ftpc, result); + result = ftp_done_secondary_socket(data, ftpc, result); + result = ftp_done_control_reply(data, ftpc, ftp, result, premature); + result = ftp_done_check_partial(data, ftpc, ftp, result, premature); /* clear these for next connection */ ftp->transfer = PPTRANSFER_BODY; @@ -3845,7 +3854,7 @@ 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, ftpc, data->set.postquote); - CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_CSTATE(ftpc), result); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_CSTATE(ftpc), (int)result); return result; } @@ -4087,11 +4096,12 @@ 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); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_bgn); userresponse = data->set.chunk_bgn( finfo, data->set.wildcardptr, (int)Curl_llist_count(&wildcard->filelist)); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); switch(userresponse) { case CURL_CHUNK_BGN_FUNC_SKIP: infof(data, "Wildcard - \"%s\" skipped by user", finfo->filename); @@ -4129,9 +4139,10 @@ static CURLcode wc_statemach(struct Curl_easy *data, case CURLWC_SKIP: { if(data->set.chunk_end) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_chunk_end); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } Curl_node_remove(Curl_llist_head(&wildcard->filelist)); wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? @@ -4289,7 +4300,7 @@ static CURLcode ftp_quit(struct Curl_easy *data, failf(data, "Failure sending QUIT command: %s", curl_easy_strerror(result)); ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(data->conn, "QUIT command failed"); /* mark for closure */ + connclose(data->conn); /* mark for closure */ ftp_state(data, ftpc, FTP_STOP); return result; } @@ -4322,8 +4333,7 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. 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 return. - */ + will try to send the QUIT command, otherwise it will return. */ ftpc->shutdown = TRUE; if(dead_connection || Curl_pp_needs_flush(data, &ftpc->pp)) ftpc->ctl_valid = FALSE; @@ -4426,16 +4436,16 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* clone connection related data that is FTP specific */ - if(data->set.str[STRING_FTP_ACCOUNT]) { - ftpc->account = curlx_strdup(data->set.str[STRING_FTP_ACCOUNT]); + if(CURL_EASY_STR(data, STRING_FTP_ACCOUNT)) { + ftpc->account = curlx_strdup(CURL_EASY_STR(data, STRING_FTP_ACCOUNT)); if(!ftpc->account) { Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } - if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]) { + if(CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER)) { ftpc->alternative_to_user = - curlx_strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); + curlx_strdup(CURL_EASY_STR(data, STRING_FTP_ALTERNATIVE_TO_USER)); if(!ftpc->alternative_to_user) { curlx_safefree(ftpc->account); Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); @@ -4454,22 +4464,31 @@ 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); + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(ftpc), + (int)result); return result; } -bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn) +bool Curl_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 */ + /* Also match ACCOUNT, ALTERNATIVE-TO-USER 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; + /* A mismatch on `use_ssl` MUST have been found in connection matching + * before we come here. This is a check on MAYBE/MUST use of STARTTLS and + * it only works on FTP. But IMAP/SMTP etc have the same `use_ssl` and + * no extra match like FTP. We lack tests in this area, so let FTP fail + * loudly here to help other cases. */ + if(nftpc->use_ssl > cftpc->use_ssl) { + DEBUGASSERT(0); + return FALSE; + } return TRUE; } diff --git a/lib/ftp.h b/lib/ftp.h index 257f5958267c..3ce02c87932f 100644 --- a/lib/ftp.h +++ b/lib/ftp.h @@ -28,7 +28,8 @@ #ifndef CURL_DISABLE_FTP extern const struct Curl_protocol Curl_protocol_ftp; -bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn); +bool Curl_ftp_conns_match(struct connectdata *needle, + struct connectdata *conn); typedef enum { FTPFILE_MULTICWD = 1, /* as defined by RFC1738 */ diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index b5d9338c1bc2..47e3d75d940a 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -196,13 +196,11 @@ void Curl_wildcard_dtor(struct WildcardData **wcp) wc->dtor = ZERO_NULL; wc->ftpwc = NULL; } - DEBUGASSERT(wc->ftpwc == NULL); + DEBUGASSERT(!wc->ftpwc); Curl_llist_destroy(&wc->filelist, NULL); - curlx_free(wc->path); - wc->path = NULL; - curlx_free(wc->pattern); - wc->pattern = NULL; + curlx_safefree(wc->path); + curlx_safefree(wc->pattern); wc->state = CURLWC_INIT; curlx_free(wc); *wcp = NULL; @@ -312,8 +310,9 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, str + parser->offsets.group : NULL; finfo->strings.perm = parser->offsets.perm ? str + parser->offsets.perm : NULL; - finfo->strings.target = parser->offsets.symlink_target ? - str + parser->offsets.symlink_target : NULL; + finfo->strings.target = parser->offsets.symlink_target && + (finfo->filetype == CURLFILETYPE_SYMLINK) ? + str + parser->offsets.symlink_target : NULL; finfo->strings.time = str + parser->offsets.time; finfo->strings.user = parser->offsets.user ? str + parser->offsets.user : NULL; @@ -324,18 +323,21 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - Curl_set_in_callback(data, TRUE); - if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { - /* discard symlink which is containing multiple " -> " */ - if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && - (strstr(finfo->strings.target, " -> "))) { + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fnmatch_data); + if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { + /* discard symlink which is containing multiple " -> " */ + if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && + strstr(finfo->strings.target, " -> ")) { + add = FALSE; + } + } + else { add = FALSE; } + CURL_CBAPI_END(&guard); } - else { - add = FALSE; - } - Curl_set_in_callback(data, FALSE); if(add) { Curl_llist_append(llist, finfo, &infop->list); @@ -940,7 +942,7 @@ static CURLcode parse_winnt(struct Curl_easy *data, parser->item_length++; if(c == ' ') { mem[parser->item_offset + parser->item_length - 1] = 0; - if(strcmp("", mem + parser->item_offset) == 0) { + if(!strcmp("", mem + parser->item_offset)) { finfo->filetype = CURLFILETYPE_DIRECTORY; finfo->size = 0; } diff --git a/lib/functypes.h b/lib/functypes.h index 887c2612ef94..7fcee834ff25 100644 --- a/lib/functypes.h +++ b/lib/functypes.h @@ -33,7 +33,7 @@ 1. For systems that run configure or cmake, the alternatives are provided here. 2. For systems with config-*.h files, define them there. -*/ + */ #ifdef USE_WINSOCK /* int recv(SOCKET, char *, int, int) */ @@ -48,7 +48,7 @@ #define SEND_TYPE_ARG3 int #define SEND_TYPE_RETV int -#elif defined(__AMIGA__) /* Any AmigaOS flavour */ +#elif defined(__AMIGA__) /* Any AmigaOS flavor */ /* long recv(long, char *, long, long); */ #define RECV_TYPE_ARG1 long diff --git a/lib/getinfo.c b/lib/getinfo.c index fab63e669a46..23f1de270c49 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -43,15 +43,9 @@ void Curl_initinfo(struct Curl_easy *data) struct Progress *pro = &data->progress; struct PureInfo *info = &data->info; - pro->t_nslookup = 0; - 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; + memset(&pro->delta, 0, sizeof(pro->delta)); + memset(&pro->total, 0, sizeof(pro->total)); + pro->startransfer_added = FALSE; info->httpcode = 0; info->httpproxycode = 0; @@ -67,11 +61,8 @@ void Curl_initinfo(struct Curl_easy *data) info->httpauthpicked = 0; info->numconnects = 0; - curlx_free(info->contenttype); - info->contenttype = NULL; - - curlx_free(info->wouldredirect); - info->wouldredirect = NULL; + curlx_safefree(info->contenttype); + curlx_safefree(info->wouldredirect); memset(&info->primary, 0, sizeof(info->primary)); info->retry_after = 0; @@ -95,7 +86,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, } break; case CURLINFO_EFFECTIVE_METHOD: { - const char *m = data->set.str[STRING_CUSTOMREQUEST]; + const char *m = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!m) { if(data->set.opt_no_body) m = "HEAD"; @@ -158,7 +149,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_RTSP_SESSION_ID: #ifndef CURL_DISABLE_RTSP - *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; + *param_charp = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); #else *param_charp = NULL; #endif @@ -439,31 +430,31 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, data->progress.ul.total_size : -1; break; case CURLINFO_TOTAL_TIME_T: - *param_offt = data->progress.timespent; + *param_offt = data->progress.total.spent_us; break; case CURLINFO_NAMELOOKUP_TIME_T: - *param_offt = data->progress.t_nslookup; + *param_offt = data->progress.total.nslookup_us; break; case CURLINFO_CONNECT_TIME_T: - *param_offt = data->progress.t_connect; + *param_offt = data->progress.total.connect_us; break; case CURLINFO_APPCONNECT_TIME_T: - *param_offt = data->progress.t_appconnect; + *param_offt = data->progress.total.appconnect_us; break; case CURLINFO_PRETRANSFER_TIME_T: - *param_offt = data->progress.t_pretransfer; + *param_offt = data->progress.total.pretransfer_us; break; case CURLINFO_POSTTRANSFER_TIME_T: - *param_offt = data->progress.t_posttransfer; + *param_offt = data->progress.total.posttransfer_us; break; case CURLINFO_STARTTRANSFER_TIME_T: - *param_offt = data->progress.t_starttransfer; + *param_offt = data->progress.total.starttransfer_us; break; case CURLINFO_QUEUE_TIME_T: - *param_offt = data->progress.t_postqueue; + *param_offt = data->progress.total.queued_us; break; case CURLINFO_REDIRECT_TIME_T: - *param_offt = data->progress.t_redirect; + *param_offt = data->progress.delta.startredirect_us; break; case CURLINFO_RETRY_AFTER: *param_offt = data->info.retry_after; @@ -472,8 +463,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->id; break; case CURLINFO_CONN_ID: - *param_offt = data->conn ? - data->conn->connection_id : data->state.recent_conn_id; + *param_offt = data->state.lastconnect_id; break; case CURLINFO_EARLYDATA_SENT_T: *param_offt = data->progress.earlydata_sent; @@ -515,22 +505,22 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, #endif switch(info) { case CURLINFO_TOTAL_TIME: - *param_doublep = DOUBLE_SECS(data->progress.timespent); + *param_doublep = DOUBLE_SECS(data->progress.total.spent_us); break; case CURLINFO_NAMELOOKUP_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_nslookup); + *param_doublep = DOUBLE_SECS(data->progress.total.nslookup_us); break; case CURLINFO_CONNECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_connect); + *param_doublep = DOUBLE_SECS(data->progress.total.connect_us); break; case CURLINFO_APPCONNECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_appconnect); + *param_doublep = DOUBLE_SECS(data->progress.total.appconnect_us); break; case CURLINFO_PRETRANSFER_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_pretransfer); + *param_doublep = DOUBLE_SECS(data->progress.total.pretransfer_us); break; case CURLINFO_STARTTRANSFER_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); + *param_doublep = DOUBLE_SECS(data->progress.total.starttransfer_us); break; case CURLINFO_SIZE_UPLOAD: *param_doublep = (double)data->progress.ul.cur_size; @@ -553,7 +543,7 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: - *param_doublep = DOUBLE_SECS(data->progress.t_redirect); + *param_doublep = DOUBLE_SECS(data->progress.delta.startredirect_us); break; default: diff --git a/lib/gopher.c b/lib/gopher.c index f087121d0784..a5d0a4b0b4cf 100644 --- a/lib/gopher.c +++ b/lib/gopher.c @@ -51,25 +51,82 @@ static CURLcode gopher_connecting(struct Curl_easy *data, bool *done) result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) - connclose(conn, "Failed TLS connection"); + connclose(conn); *done = TRUE; return result; } #endif -static CURLcode gopher_do(struct Curl_easy *data, bool *done) +/* Sends buf to the server and, optionally, writes it to the client too. */ +static CURLcode send_buf(struct Curl_easy *data, + const char *buf, + size_t buf_len, + bool client_write) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + size_t nwritten; + timediff_t timeout_ms; + int what; + + while(buf_len) { + result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten); + if(!result) { /* Which may not have written it all! */ + if(client_write) { + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten); + if(result) + break; + } + + if(nwritten > buf_len) { + DEBUGASSERT(0); + break; + } + buf_len -= nwritten; + buf += nwritten; + if(!buf_len) + break; /* but it did write it all */ + } + else + break; + + timeout_ms = Curl_timeleft_ms(data); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Do not busyloop. The entire loop thing is a workaround 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 are not + possible to send now will be sent in the doing function repeatedly + until the entire request is sent. */ + what = SOCKET_WRITABLE(sockfd, timeout_ms); + if(what < 0) { + result = CURLE_SEND_ERROR; + break; + } + else if(!what) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + } + + return result; +} + +static CURLcode gopher_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; char *gopherpath; const char *path = data->state.up.path; const char *query = data->state.up.query; const char *buf = NULL; char *buf_alloc = NULL; - size_t nwritten, buf_len; - timediff_t timeout_ms; - int what; + size_t buf_len; *done = TRUE; /* unconditionally */ @@ -103,57 +160,25 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(result) return result; buf = buf_alloc; - } - - for(; buf_len;) { - - result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten); - if(!result) { /* Which may not have written it all! */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten); - if(result) - break; - if(nwritten > buf_len) { - DEBUGASSERT(0); - break; - } - buf_len -= nwritten; - buf += nwritten; - if(!buf_len) - break; /* but it did write it all */ - } - else - break; - - timeout_ms = Curl_timeleft_ms(data); - if(timeout_ms < 0) { - result = CURLE_OPERATION_TIMEDOUT; - break; - } - if(!timeout_ms) - timeout_ms = TIMEDIFF_T_MAX; - - /* 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 are not - possible to send now will be sent in the doing function repeatedly - until the entire request is sent. - */ - what = SOCKET_WRITABLE(sockfd, timeout_ms); - if(what < 0) { - result = CURLE_SEND_ERROR; - break; - } - else if(!what) { - result = CURLE_OPERATION_TIMEDOUT; - break; + /* A decoded CR or LF would terminate the single-line gopher request and + let a crafted URL smuggle additional bytes onto the wire. REJECT_ZERO + only blocks NUL; reject CR and LF here too. A TAB is left alone as it + is the legitimate gopher type-7 selector/search separator. */ + if(memchr(buf, '\r', buf_len) || memchr(buf, '\n', buf_len)) { + curlx_free(buf_alloc); + failf(data, "Bad gopher selector, CR or LF not allowed"); + return CURLE_URL_MALFORMAT; } } + result = send_buf(data, buf, buf_len, TRUE); curlx_free(buf_alloc); if(!result) - result = Curl_xfer_send(data, "\r\n", 2, FALSE, &nwritten); + /* Send CRLF to the server now, but defer writing it to the client to + preserve the historical behavior of this file. */ + result = send_buf(data, "\r\n", 2, FALSE); if(result) { failf(data, "Failed sending Gopher request"); return result; diff --git a/lib/hash.h b/lib/hash.h index 1d7d3de8cecd..d4c73b778647 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -31,8 +31,8 @@ typedef size_t (*hash_function)(void *key, size_t slots_num); /* - Comparator function prototype. Compares two keys. -*/ + * Comparator function prototype. Compares two keys. + */ typedef size_t (*comp_function)(void *key1, size_t key1_len, void *key2, diff --git a/lib/headers.c b/lib/headers.c index 195e12b371a0..a6416b0f07b2 100644 --- a/lib/headers.c +++ b/lib/headers.c @@ -59,62 +59,84 @@ CURLHcode curl_easy_header(CURL *curl, int request, struct curl_header **hout) { - struct Curl_llist_node *e; - struct Curl_llist_node *e_pick = NULL; - struct Curl_easy *data = curl; - size_t match = 0; - size_t amount = 0; - struct Curl_header_store *hs = NULL; - struct Curl_header_store *pick = NULL; - if(!name || !hout || !data || - (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX | - CURLH_PSEUDO)) || !origin || (request < -1)) - return CURLHE_BAD_ARGUMENT; - if(!Curl_llist_count(&data->state.httphdrs)) - return CURLHE_NOHEADERS; /* no headers available */ - if(request > data->state.requests) - return CURLHE_NOREQUEST; - if(request == -1) - request = data->state.requests; - - /* we need a first round to count amount of this header */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - hs = Curl_node_elem(e); - if(curl_strequal(hs->name, name) && - (hs->type & origin) && - (hs->request == request)) { - amount++; - pick = hs; - e_pick = e; + struct Curl_eapi_guard guard; + CURLHcode hresult = CURLHE_OK; + CURLcode result; + + if(CURL_EAPI_ENTER(&guard, curl, easy_header, &result)) { + struct Curl_easy *data = curl; + struct Curl_llist_node *e; + struct Curl_llist_node *e_pick = NULL; + size_t match = 0; + size_t amount = 0; + struct Curl_header_store *hs = NULL; + struct Curl_header_store *pick = NULL; + if(!name || !hout || !data || + (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX | + CURLH_PSEUDO)) || !origin || (request < -1)) { + hresult = CURLHE_BAD_ARGUMENT; + goto out; } - } - if(!amount) - return CURLHE_MISSING; - else if(nameindex >= amount) - return CURLHE_BADINDEX; - - if(nameindex == amount - 1) - /* if the last or only occurrence is what's asked for, then we know it */ - hs = pick; - else { + if(!Curl_llist_count(&data->state.httphdrs)) { + hresult = CURLHE_NOHEADERS; /* no headers available */ + goto out; + } + if(request > data->state.requests) { + hresult = CURLHE_NOREQUEST; + goto out; + } + if(request == -1) + request = data->state.requests; + + /* we need a first round to count amount of this header */ for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { hs = Curl_node_elem(e); if(curl_strequal(hs->name, name) && (hs->type & origin) && - (hs->request == request) && - (match++ == nameindex)) { + (hs->request == request)) { + amount++; + pick = hs; e_pick = e; - break; } } - if(!e) /* this should not happen */ - return CURLHE_MISSING; + if(!amount) + hresult = CURLHE_MISSING; + else if(nameindex >= amount) + hresult = CURLHE_BADINDEX; + if(hresult) + goto out; + + if(nameindex == amount - 1) + /* if the last or only occurrence is what's asked for, then we know it */ + hs = pick; + else { + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + hs = Curl_node_elem(e); + if(curl_strequal(hs->name, name) && + (hs->type & origin) && + (hs->request == request) && + (match++ == nameindex)) { + e_pick = e; + break; + } + } + if(!e) { /* this should not happen */ + hresult = CURLHE_MISSING; + goto out; + } + } + /* this is the name we want */ + copy_header_external(hs, nameindex, amount, e_pick, + &data->state.headerout[0]); + *hout = &data->state.headerout[0]; + hresult = CURLHE_OK; } - /* this is the name we want */ - copy_header_external(hs, nameindex, amount, e_pick, - &data->state.headerout[0]); - *hout = &data->state.headerout[0]; - return CURLHE_OK; +out: + CURL_EAPI_LEAVE(&guard); + if(result) + hresult = Curl_eapi_hcode(result); + return hresult; } /* public API */ @@ -123,59 +145,68 @@ struct curl_header *curl_easy_nextheader(CURL *curl, int request, struct curl_header *prev) { - struct Curl_easy *data = curl; - struct Curl_llist_node *pick; - struct Curl_llist_node *e; - struct Curl_header_store *hs; - size_t amount = 0; - size_t index = 0; + struct Curl_eapi_guard guard; + struct curl_header *hd = NULL; + CURLcode result; - if(request > data->state.requests) - return NULL; - if(request == -1) - request = data->state.requests; + if(CURL_EAPI_ENTER(&guard, curl, easy_nextheader, &result)) { + struct Curl_easy *data = curl; + struct Curl_llist_node *pick; + struct Curl_llist_node *e; + struct Curl_header_store *hs; + size_t amount = 0; + size_t index = 0; + + if(request > data->state.requests) + goto out; + if(request == -1) + request = data->state.requests; + + if(prev) { + pick = prev->anchor; + if(!pick) + /* something is wrong */ + goto out; + pick = Curl_node_next(pick); + } + else + pick = Curl_llist_head(&data->state.httphdrs); + + if(pick) { + /* make sure it is the next header of the desired type */ + do { + hs = Curl_node_elem(pick); + if((hs->type & origin) && (hs->request == request)) + break; + pick = Curl_node_next(pick); + } while(pick); + } - if(prev) { - pick = prev->anchor; if(!pick) - /* something is wrong */ - return NULL; - pick = Curl_node_next(pick); - } - else - pick = Curl_llist_head(&data->state.httphdrs); - - if(pick) { - /* make sure it is the next header of the desired type */ - do { - hs = Curl_node_elem(pick); - if((hs->type & origin) && (hs->request == request)) - break; - pick = Curl_node_next(pick); - } while(pick); - } + /* no more headers available */ + goto out; - if(!pick) - /* no more headers available */ - return NULL; - - hs = Curl_node_elem(pick); - - /* count number of occurrences of this name within the mask and figure out - the index for the currently selected entry */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - struct Curl_header_store *check = Curl_node_elem(e); - if(curl_strequal(hs->name, check->name) && - (check->request == request) && - (check->type & origin)) - amount++; - if(e == pick) - index = amount - 1; - } + hs = Curl_node_elem(pick); + + /* count number of occurrences of this name within the mask and figure out + the index for the currently selected entry */ + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); + if(curl_strequal(hs->name, check->name) && + (check->request == request) && + (check->type & origin)) + amount++; + if(e == pick) + index = amount - 1; + } - copy_header_external(hs, index, amount, pick, - &data->state.headerout[1]); - return &data->state.headerout[1]; + copy_header_external(hs, index, amount, pick, + &data->state.headerout[1]); + hd = &data->state.headerout[1]; + } +out: + CURL_EAPI_LEAVE(&guard); + return hd; } static CURLcode namevalue(char *header, size_t hlen, unsigned int type, @@ -271,7 +302,6 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, /* 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"); @@ -286,7 +316,6 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); - data->state.prevhead = NULL; } struct hds_cw_collect_ctx { @@ -305,7 +334,7 @@ static CURLcode hds_cw_collect_write(struct Curl_easy *data, CURLH_HEADER))); CURLcode result = Curl_headers_push(data, buf, blen, htype); CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", - htype, blen, result); + htype, blen, (int)result); if(result) return result; } @@ -315,8 +344,10 @@ static CURLcode hds_cw_collect_write(struct Curl_easy *data, static const struct Curl_cwtype hds_cw_collect = { "hds-collect", NULL, + 0, Curl_cwriter_def_init, hds_cw_collect_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct hds_cw_collect_ctx) }; diff --git a/lib/hsts.c b/lib/hsts.c index 400b4423da14..73c01db01892 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -96,7 +96,7 @@ void Curl_hsts_cleanup(struct hsts **hp) static void hsts_append(struct hsts *h, struct stsentry *sts) { if(Curl_llist_count(&h->list) == MAX_HSTS_ENTRIES) { - /* It's full. Remove the first entry in the list */ + /* It is full. Remove the first entry in the list */ struct Curl_llist_node *e = Curl_llist_head(&h->list); struct stsentry *oldsts = Curl_node_elem(e); Curl_node_remove(e); @@ -121,7 +121,7 @@ static CURLcode hsts_create(struct hsts *h, struct stsentry *sts = curlx_calloc(1, sizeof(struct stsentry) + hlen); if(!sts) return CURLE_OUT_OF_MEMORY; - /* the null terminator is already there */ + /* the null-terminator is already there */ memcpy(sts->host, hostname, hlen); sts->expires = expires; sts->includeSubDomains = subdomains; @@ -130,6 +130,81 @@ static CURLcode hsts_create(struct hsts *h, return CURLE_OK; } +/* Copy all live entries from src into dst. Used by curl_easy_duphandle so the + * clone inherits entries learned at runtime. E.g. Strict-Transport-Security. + */ +CURLcode Curl_hsts_copy(struct hsts *dst, struct hsts *src) +{ + struct Curl_llist_node *e; + time_t now = time(NULL); + for(e = Curl_llist_head(&src->list); e; e = Curl_node_next(e)) { + struct stsentry *sts = Curl_node_elem(e); + if(sts->expires > now) { + CURLcode result = hsts_create(dst, sts->host, strlen(sts->host), + sts->includeSubDomains != 0, sts->expires); + if(result) + return result; + } + } + return CURLE_OK; +} + +/* + * Return the matching HSTS entry, or NULL if the given hostname is not + * currently an HSTS one. + * + * The 'subdomain' argument tells the function if subdomain matching should be + * attempted. + * + * @unittest 1660 + */ +UNITTEST struct stsentry *hsts_check(struct hsts *h, const char *hostname, + size_t hlen, bool subdomain); +UNITTEST struct stsentry *hsts_check(struct hsts *h, const char *hostname, + size_t hlen, bool subdomain) +{ + struct stsentry *bestsub = NULL; + if(h) { + time_t now = time(NULL); + struct Curl_llist_node *e; + struct Curl_llist_node *n; + size_t blen = 0; + + if((hlen > MAX_HSTS_HOSTLEN) || !hlen) + return NULL; + if(hostname[hlen - 1] == '.') + /* remove the trailing dot */ + --hlen; + + 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_node_remove(&sts->node); + hsts_free(sts); + continue; + } + ntail = strlen(sts->host); + if((subdomain && sts->includeSubDomains) && (ntail < hlen)) { + size_t offs = hlen - ntail; + if((hostname[offs - 1] == '.') && + curl_strnequal(&hostname[offs], sts->host, ntail) && + (ntail > blen)) { + /* save the tail match with the longest tail */ + bestsub = sts; + blen = ntail; + } + } + /* avoid curl_strequal because the hostname is not null-terminated */ + if((hlen == ntail) && curl_strnequal(hostname, sts->host, hlen)) + return sts; + } + } + return bestsub; +} + CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, const char *header) { @@ -148,53 +223,55 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, return CURLE_OK; do { - curlx_str_passblanks(&p); - if(curl_strnequal("max-age", p, 7)) { - bool quoted = FALSE; - int rc; - - if(gotma) - return CURLE_BAD_FUNCTION_ARGUMENT; + struct Curl_str word; + struct Curl_str val = { 0 }; + int rc; + bool assign = FALSE; - p += 7; + do { curlx_str_passblanks(&p); - if(curlx_str_single(&p, '=')) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(*p == ';') + p++; + else + break; + } while(1); + if(curlx_str_cspn(&p, &word, ";=\r\n \t")) + break; + + curlx_str_passblanks(&p); + if(!curlx_str_single(&p, '=')) { + assign = TRUE; curlx_str_passblanks(&p); - if(!curlx_str_single(&p, '\"')) - quoted = TRUE; + if(*p == '\"') { + if(curlx_str_quotedword(&p, &val, MAX_HSTS_LINE)) + break; + } + else { + if(curlx_str_cspn(&p, &val, ", ;\r\n")) + break; + } + } - rc = curlx_str_number(&p, &expires, TIME_T_MAX); + if(assign && curlx_str_casecompare(&word, "max-age")) { + const char *vp = curlx_str(&val); + if(gotma) + return CURLE_BAD_FUNCTION_ARGUMENT; + rc = curlx_str_number(&vp, &expires, TIME_T_MAX); if(rc == STRE_OVERFLOW) expires = CURL_OFF_T_MAX; else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; - if(quoted) { - if(*p != '\"') - return CURLE_BAD_FUNCTION_ARGUMENT; - p++; - } gotma = TRUE; } - else if(curl_strnequal("includesubdomains", p, 17)) { + else if(curlx_str_casecompare(&word, "includesubdomains")) { if(gotinc) return CURLE_BAD_FUNCTION_ARGUMENT; subdomains = TRUE; - p += 17; gotinc = TRUE; } - else { - /* unknown directive, do a lame attempt to skip */ - while(*p && (*p != ';')) - p++; - } - - curlx_str_passblanks(&p); - if(*p == ';') - p++; } while(*p); if(!gotma) @@ -203,7 +280,7 @@ 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, hlen, FALSE); + sts = hsts_check(h, hostname, hlen, FALSE); if(sts) { Curl_node_remove(&sts->node); hsts_free(sts); @@ -218,7 +295,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, expires += now; /* check if it already exists */ - sts = Curl_hsts(h, hostname, hlen, FALSE); + sts = hsts_check(h, hostname, hlen, FALSE); if(sts) { /* update these fields */ sts->expires = expires; @@ -230,57 +307,6 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, return CURLE_OK; } -/* - * 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, - size_t hlen, bool subdomain) -{ - struct stsentry *bestsub = NULL; - if(h) { - time_t now = time(NULL); - struct Curl_llist_node *e; - struct Curl_llist_node *n; - size_t blen = 0; - - if((hlen > MAX_HSTS_HOSTLEN) || !hlen) - return NULL; - if(hostname[hlen - 1] == '.') - /* remove the trailing dot */ - --hlen; - - 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_node_remove(&sts->node); - hsts_free(sts); - continue; - } - ntail = strlen(sts->host); - if((subdomain && sts->includeSubDomains) && (ntail < hlen)) { - size_t offs = hlen - ntail; - if((hostname[offs - 1] == '.') && - curl_strnequal(&hostname[offs], sts->host, ntail) && - (ntail > blen)) { - /* save the tail match with the longest tail */ - bestsub = sts; - blen = ntail; - } - } - /* avoid curl_strequal because the hostname is not null-terminated */ - if((hlen == ntail) && curl_strnequal(hostname, sts->host, hlen)) - return sts; - } - } - return bestsub; -} - /* * Send this HSTS entry to the write callback. */ @@ -437,10 +463,10 @@ static CURLcode hsts_add_host_expire(struct hsts *h, if(hostlen) { /* only add it if not already present */ - e = Curl_hsts(h, host, hostlen, subdomain); + e = hsts_check(h, host, hostlen, subdomain); if(!e) result = hsts_create(h, host, hostlen, subdomain, expires); - /* 'host' is not necessarily null terminated */ + /* 'host' is not necessarily null-terminated */ else if((hostlen == strlen(e->host) && curl_strnequal(host, e->host, hostlen))) { /* the same hostname, use the largest expire time and keep the strictest @@ -504,7 +530,7 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) const char *date = e.expire; if(!e.name[0] || e.expire[MAX_HSTS_DATELEN] || e.name[MAX_HSTS_HOSTLEN]) - /* bail out if no name was stored or if a null terminator is gone */ + /* bail out if no name was stored or if a null-terminator is gone */ return CURLE_BAD_FUNCTION_ARGUMENT; if(!date[0]) date = UNLIMITED; @@ -610,6 +636,11 @@ CURLcode Curl_hsts_loadfiles(struct Curl_easy *data) return result; } +bool Curl_hsts_applies(struct hsts *h, const struct Curl_peer *dest) +{ + return !!hsts_check(h, dest->hostname, strlen(dest->hostname), TRUE); +} + #if defined(DEBUGBUILD) || defined(UNITTESTS) #undef time #endif diff --git a/lib/hsts.h b/lib/hsts.h index 0e6585f11606..08215f5eaab8 100644 --- a/lib/hsts.h +++ b/lib/hsts.h @@ -25,9 +25,13 @@ ***************************************************************************/ #include "curl_setup.h" +struct hsts; + #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS) #include "llist.h" +struct Curl_peer; + #define MAX_HSTS_ENTRIES 10000 #if defined(DEBUGBUILD) || defined(UNITTESTS) @@ -50,10 +54,9 @@ struct hsts { struct hsts *Curl_hsts_init(void); void Curl_hsts_cleanup(struct hsts **hp); +CURLcode Curl_hsts_copy(struct hsts *dst, struct hsts *src); CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, const char *header); -struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, - 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, @@ -61,6 +64,9 @@ CURLcode Curl_hsts_loadfile(struct Curl_easy *data, CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h); CURLcode Curl_hsts_loadfiles(struct Curl_easy *data); + +bool Curl_hsts_applies(struct hsts *h, const struct Curl_peer *dest); + #else #define Curl_hsts_cleanup(x) #define Curl_hsts_loadcb(x, y) CURLE_OK diff --git a/lib/http.c b/lib/http.c index 9118c7e716ac..4b79b2ab9399 100644 --- a/lib/http.c +++ b/lib/http.c @@ -61,10 +61,10 @@ #include "http_ntlm.h" #include "http_negotiate.h" #include "http_aws_sigv4.h" +#include "http_httpsig.h" #include "url.h" #include "urlapi-int.h" #include "curl_share.h" -#include "hostip.h" #include "dynhds.h" #include "http.h" #include "headers.h" @@ -154,7 +154,7 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, { struct curl_slist *head; - for(head = (conn->bits.proxy && data->set.sep_headers) ? + for(head = (conn->http_proxy.peer && data->set.sep_headers) ? data->set.proxyheaders : data->set.headers; head; head = head->next) { if(curl_strnequal(head->data, thisheader, thislen) && @@ -167,15 +167,14 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, #endif /* If the header has a value, this function returns TRUE and the value is in - 'outp' with blanks trimmed off. -*/ + 'outp' with blanks trimmed off. */ static bool header_has_value(const char **headerp, struct Curl_str *outp) { bool value = !curlx_str_cspn(headerp, outp, ";:") && (!curlx_str_single(headerp, ':') || !curlx_str_single(headerp, ';')); if(value) { - curlx_str_untilnl(headerp, outp, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_cspn(headerp, outp, "\r\n"); curlx_str_trimblanks(outp); } return value; @@ -201,7 +200,7 @@ static bool http_header_is_empty(const char *header) */ static CURLcode copy_custom_value(const char *header, char **valp) { - struct Curl_str out; + struct Curl_str out = { 0 }; /* find the end of the header name */ if(header_has_value(&header, &out)) { @@ -250,34 +249,38 @@ char *Curl_copy_header_value(const char *header) * * Returns CURLcode. */ -static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) +static CURLcode http_output_basic(struct Curl_easy *data, + struct connectdata *conn, bool proxy) { size_t size = 0; char *authorization = NULL; - char **userp; - const char *user; - const char *pwd; + char **p_hd; CURLcode result; + struct Curl_creds *creds = NULL; char *out; /* credentials are unique per transfer for HTTP, do not use the ones for the connection */ if(proxy) { #ifndef CURL_DISABLE_PROXY - userp = &data->req.proxyuserpwd; - user = data->state.aptr.proxyuser; - pwd = data->state.aptr.proxypasswd; + p_hd = &data->req.hd_proxy_auth; + creds = conn->http_proxy.creds; #else + (void)conn; return CURLE_NOT_BUILT_IN; #endif } else { - userp = &data->req.userpwd; - user = data->state.aptr.user; - pwd = data->state.aptr.passwd; + p_hd = &data->req.hd_auth; + creds = data->state.creds; } - out = curl_maprintf("%s:%s", user ? user : "", pwd ? pwd : ""); + if(!creds) { + DEBUGASSERT(0); + return CURLE_FAILED_INIT; + } + + out = curl_maprintf("%s:%s", creds->user, creds->passwd); if(!out) return CURLE_OUT_OF_MEMORY; @@ -291,12 +294,12 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) goto fail; } - curlx_free(*userp); - *userp = curl_maprintf("%sAuthorization: Basic %s\r\n", - proxy ? "Proxy-" : "", - authorization); + curlx_free(*p_hd); + *p_hd = curl_maprintf("%sAuthorization: Basic %s\r\n", + proxy ? "Proxy-" : "", + authorization); curlx_free(authorization); - if(!*userp) { + if(!*p_hd) { result = CURLE_OUT_OF_MEMORY; goto fail; } @@ -320,10 +323,11 @@ static CURLcode http_output_bearer(struct Curl_easy *data) char **userp; CURLcode result = CURLE_OK; - userp = &data->req.userpwd; + DEBUGASSERT(Curl_creds_has_oauth_bearer(data->state.creds)); + userp = &data->req.hd_auth; curlx_free(*userp); *userp = curl_maprintf("Authorization: Bearer %s\r\n", - data->set.str[STRING_BEARER]); + Curl_creds_oauth_bearer(data->state.creds)); if(!*userp) { result = CURLE_OUT_OF_MEMORY; @@ -333,7 +337,6 @@ static CURLcode http_output_bearer(struct Curl_easy *data) fail: return result; } - #endif #endif @@ -343,8 +346,10 @@ static CURLcode http_output_bearer(struct Curl_easy *data) * * return TRUE if one was picked */ -static bool pickoneauth(struct auth *pick, unsigned long mask) +static bool pickoneauth(struct auth *pick, unsigned long mask, + struct Curl_creds *creds) { + bool have_user_pass = Curl_creds_has_user_or_pass(creds); bool picked; /* only deal with authentication we want */ unsigned long avail = pick->avail & pick->want & mask; @@ -352,25 +357,29 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) /* The order of these checks is highly relevant, as this will be the order of preference in case of the existence of multiple accepted types. */ - if(avail & CURLAUTH_NEGOTIATE) + if(avail & CURLAUTH_NEGOTIATE) /* available on empty creds */ pick->picked = CURLAUTH_NEGOTIATE; #ifndef CURL_DISABLE_BEARER_AUTH - else if(avail & CURLAUTH_BEARER) + else if((avail & CURLAUTH_BEARER) && Curl_creds_has_oauth_bearer(creds)) pick->picked = CURLAUTH_BEARER; #endif #ifndef CURL_DISABLE_DIGEST_AUTH - else if(avail & CURLAUTH_DIGEST) + else if((avail & CURLAUTH_DIGEST) && have_user_pass) pick->picked = CURLAUTH_DIGEST; #endif else if(avail & CURLAUTH_NTLM) pick->picked = CURLAUTH_NTLM; #ifndef CURL_DISABLE_BASIC_AUTH - else if(avail & CURLAUTH_BASIC) + else if((avail & CURLAUTH_BASIC) && have_user_pass) pick->picked = CURLAUTH_BASIC; #endif #ifndef CURL_DISABLE_AWS else if(avail & CURLAUTH_AWS_SIGV4) pick->picked = CURLAUTH_AWS_SIGV4; +#endif +#ifndef CURL_DISABLE_HTTPSIG + else if(avail & CURLAUTH_HTTPSIG) + pick->picked = CURLAUTH_HTTPSIG; #endif else { pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ @@ -459,7 +468,7 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, 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"); + streamclose(conn); data->req.size = 0; /* do not download any more than 0 bytes */ data->req.http_bodyless = TRUE; } @@ -515,7 +524,7 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode) /* * Examine the current authentication state to see if this is an error. The * idea is for this function to get called after processing all the headers - * in a response message. If we have been to asked to authenticate + * in a response message. If we have been asked to authenticate at * 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. * @@ -527,10 +536,10 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode) * 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) + if((httpcode == 401) && !data->state.creds) return TRUE; #ifndef CURL_DISABLE_PROXY - if((httpcode == 407) && !data->conn->bits.proxy_user_passwd) + if((httpcode == 407) && !data->conn->http_proxy.creds) return TRUE; #endif @@ -551,7 +560,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) CURLcode result = CURLE_OK; unsigned long authmask = ~0UL; - if(!data->set.str[STRING_BEARER]) + if(!Curl_creds_has_oauth_bearer(data->state.creds)) authmask &= (unsigned long)~CURLAUTH_BEARER; if(100 <= data->req.httpcode && data->req.httpcode <= 199) @@ -561,10 +570,10 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) if(data->state.authproblem) return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK; - if((data->state.aptr.user || data->set.str[STRING_BEARER]) && + if(data->state.creds && ((data->req.httpcode == 401) || (data->req.authneg && data->req.httpcode < 300))) { - pickhost = pickoneauth(&data->state.authhost, authmask); + pickhost = pickoneauth(&data->state.authhost, authmask, data->state.creds); if(!pickhost) data->state.authproblem = TRUE; else @@ -572,17 +581,18 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) if(data->state.authhost.picked == CURLAUTH_NTLM && (data->req.httpversion_sent > 11)) { infof(data, "Forcing HTTP/1.1 for NTLM"); - connclose(conn, "Force HTTP/1.1 connection"); + connclose(conn); 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 && + if(conn->http_proxy.creds && ((data->req.httpcode == 407) || (data->req.authneg && data->req.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy, - authmask & ~CURLAUTH_BEARER); + authmask & ~CURLAUTH_BEARER, + conn->http_proxy.creds); if(!pickproxy) data->state.authproblem = TRUE; else @@ -659,12 +669,37 @@ static CURLcode output_auth_headers(struct Curl_easy *data, } else #endif +#ifndef CURL_DISABLE_HTTPSIG + if((authstatus->picked == CURLAUTH_HTTPSIG) && !proxy) { + /* HTTPSIG uses its own configured key material rather than + data->state.creds. Do not let unrelated credentials from a + redirected URL bypass the cross-host auth boundary. */ + if(Curl_auth_allowed_to_host(data)) { + auth = "HTTPSIG"; + result = Curl_output_httpsig(data); + if(result) + return result; + } + else + authstatus->done = TRUE; + } + else +#endif #ifdef USE_SPNEGO if(authstatus->picked == CURLAUTH_NEGOTIATE) { - auth = "Negotiate"; - result = Curl_output_negotiate(data, conn, proxy); - if(result) - return result; + if( +#ifndef CURL_DISABLE_PROXY + (proxy && !Curl_checkProxyheaders(data, conn, + STRCONST("Proxy-authorization"))) || +#endif + (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")))) { + auth = "Negotiate"; + result = Curl_output_negotiate(data, conn, proxy); + if(result) + return result; + } + else + authstatus->done = TRUE; } else #endif @@ -694,14 +729,16 @@ static CURLcode output_auth_headers(struct Curl_easy *data, /* Basic */ if( #ifndef CURL_DISABLE_PROXY - (proxy && conn->bits.proxy_user_passwd && + (proxy && conn->http_proxy.creds && + Curl_creds_has_user_or_pass(conn->http_proxy.creds) && !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-authorization"))) || #endif - (!proxy && data->state.aptr.user && + (!proxy && data->state.creds && + Curl_creds_has_user_or_pass(data->state.creds) && !Curl_checkheaders(data, STRCONST("Authorization")))) { auth = "Basic"; - result = http_output_basic(data, proxy); + result = http_output_basic(data, conn, proxy); if(result) return result; } @@ -714,8 +751,7 @@ static CURLcode output_auth_headers(struct Curl_easy *data, #ifndef CURL_DISABLE_BEARER_AUTH if(authstatus->picked == CURLAUTH_BEARER) { /* Bearer */ - if(!proxy && data->set.str[STRING_BEARER] && - Curl_auth_allowed_to_host(data) && + if(!proxy && Curl_creds_has_oauth_bearer(data->state.creds) && !Curl_checkheaders(data, STRCONST("Authorization"))) { auth = "Bearer"; result = http_output_bearer(data); @@ -737,15 +773,15 @@ static CURLcode output_auth_headers(struct Curl_easy *data, data->info.httpauthpicked = authstatus->picked; infof(data, "%s auth using %s with user '%s'", proxy ? "Proxy" : "Server", auth, - proxy ? (data->state.aptr.proxyuser ? - data->state.aptr.proxyuser : "") : - (data->state.aptr.user ? - data->state.aptr.user : "")); + proxy ? (conn->http_proxy.creds ? + conn->http_proxy.creds->user : "") : + (data->state.creds ? + data->state.creds->user : "")); #else (void)proxy; infof(data, "Server auth using %s with user '%s'", - auth, data->state.aptr.user ? - data->state.aptr.user : ""); + auth, data->state.creds ? + data->state.creds->user : ""); #endif authstatus->multipass = !authstatus->done; } @@ -760,53 +796,50 @@ static CURLcode output_auth_headers(struct Curl_easy *data, return result; } -/** - * Curl_http_output_auth() setups the authentication headers for the - * host/proxy and the correct authentication - * method. data->state.authdone is set to TRUE when authentication is - * done. - * - * @param conn all information about the current connection - * @param request pointer to the request keyword - * @param path pointer to the requested path; should include query part - * @param proxytunnel boolean if this is the request setting up a "proxy - * tunnel" - * - * @returns CURLcode - */ CURLcode Curl_http_output_auth(struct Curl_easy *data, struct connectdata *conn, const char *request, Curl_HttpReq httpreq, const char *path, - bool proxytunnel) /* TRUE if this is - the request setting up - the proxy tunnel */ + const char *query, + bool is_connect) { CURLcode result = CURLE_OK; struct auth *authhost; struct auth *authproxy; + const char *path_and_query = path; + char *tmp_str = NULL; DEBUGASSERT(data); - authhost = &data->state.authhost; authproxy = &data->state.authproxy; if( #ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && conn->bits.proxy_user_passwd) || + (!conn->http_proxy.peer || !conn->http_proxy.creds) && #endif - data->state.aptr.user || #ifdef USE_SPNEGO - authhost->want & CURLAUTH_NEGOTIATE || - authproxy->want & CURLAUTH_NEGOTIATE || + !(authhost->want & CURLAUTH_NEGOTIATE) && + !(authproxy->want & CURLAUTH_NEGOTIATE) && #endif - data->set.str[STRING_BEARER]) - /* continue please */; - else { +#ifndef CURL_DISABLE_HTTPSIG + !(authhost->want & CURLAUTH_HTTPSIG) && +#endif + !data->state.creds) { + /* no authentication with no user or password */ authhost->done = TRUE; authproxy->done = TRUE; - return CURLE_OK; /* no authentication with no user or password */ + result = CURLE_OK; + goto out; + } + + if(query) { + tmp_str = curl_maprintf("%s?%s", path, query); + if(!tmp_str) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + path_and_query = tmp_str; } if(authhost->want && !authhost->picked) @@ -823,28 +856,25 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY /* Send proxy authentication header if needed */ - if(conn->bits.httpproxy && - (conn->bits.tunnel_proxy == (curl_bit)proxytunnel)) { - result = output_auth_headers(data, conn, authproxy, request, path, TRUE); + if(conn->bits.origin_is_proxy || is_connect) { + result = output_auth_headers(data, conn, authproxy, request, + path_and_query, TRUE); if(result) - return result; + goto out; } else #else - (void)proxytunnel; + (void)is_connect; #endif /* CURL_DISABLE_PROXY */ /* we have no proxy so let's pretend we are done authenticating with it */ authproxy->done = TRUE; - /* To prevent the user+password to get sent to other than the original host - due to a location-follow */ - if(Curl_auth_allowed_to_host(data) -#ifndef CURL_DISABLE_NETRC - || conn->bits.netrc -#endif - ) - result = output_auth_headers(data, conn, authhost, request, path, FALSE); + /* Either we have credentials for the origin we talk to or + performing authentication is allowed here */ + if(data->state.creds || Curl_auth_allowed_to_host(data)) + result = output_auth_headers(data, conn, authhost, request, + path_and_query, FALSE); else authhost->done = TRUE; @@ -859,27 +889,31 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, else data->req.authneg = FALSE; +out: + curlx_free(tmp_str); return result; } -#else +#else /* !CURL_DISABLE_HTTP_AUTH */ /* when disabled */ CURLcode Curl_http_output_auth(struct Curl_easy *data, struct connectdata *conn, const char *request, Curl_HttpReq httpreq, const char *path, - bool proxytunnel) + const char *query, + bool is_connect) { (void)data; (void)conn; (void)request; (void)httpreq; (void)path; - (void)proxytunnel; + (void)query; + (void)is_connect; return CURLE_OK; } -#endif +#endif /* !CURL_DISABLE_HTTP_AUTH, else */ #if defined(USE_SPNEGO) || defined(USE_NTLM) || \ !defined(CURL_DISABLE_DIGEST_AUTH) || \ @@ -1119,7 +1153,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, static void http_switch_to_get(struct Curl_easy *data, int code) { - const char *req = data->set.str[STRING_CUSTOMREQUEST]; + const char *req = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if((req || data->state.httpreq != HTTPREQ_GET) && (data->set.http_follow_mode == CURLFOLLOW_OBEYCODE)) { @@ -1134,9 +1168,9 @@ static void http_switch_to_get(struct Curl_easy *data, int code) Curl_creader_set_rewind(data, FALSE); } -#define HTTPREQ_IS_POST(data) \ - ((data)->state.httpreq == HTTPREQ_POST || \ - (data)->state.httpreq == HTTPREQ_POST_FORM || \ +#define HTTPREQ_IS_POST(data) \ + ((data)->state.httpreq == HTTPREQ_POST || \ + (data)->state.httpreq == HTTPREQ_POST_FORM || \ (data)->state.httpreq == HTTPREQ_POST_MIME) CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, @@ -1227,8 +1261,6 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, return CURLE_OUT_OF_MEMORY; } else { - bool same_origin; - CURLcode result; CURLU *u = curl_url(); if(!u) return CURLE_OUT_OF_MEMORY; @@ -1242,29 +1274,16 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, return Curl_uc_to_curlcode(uc); } - same_origin = Curl_url_same_origin(u, data->state.uh); - curl_url_cleanup(u); - #ifndef CURL_DISABLE_DIGEST_AUTH - if(!same_origin) - Curl_auth_digest_cleanup(&data->state.digest); -#endif - - if((!same_origin && !data->set.allow_auth_to_other_hosts) || - !data->set.str[STRING_USERNAME]) { - result = Curl_reset_userpwd(data); - if(result) { - curlx_free(follow_url); - return result; - } - curlx_safefree(data->state.aptr.user); - curlx_safefree(data->state.aptr.passwd); - } - result = Curl_reset_proxypwd(data); - if(result) { - curlx_free(follow_url); - return result; + { + bool same_origin = Curl_url_same_origin(u, data->state.uh); + curl_url_cleanup(u); + if(!same_origin) + Curl_auth_digest_cleanup(&data->state.digest); } +#else + curl_url_cleanup(u); +#endif } DEBUGASSERT(follow_url); @@ -1287,8 +1306,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, rewind_result = Curl_req_soft_reset(&data->req, data); infof(data, "Issue another request to this URL: '%s'", follow_url); if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && - data->set.str[STRING_CUSTOMREQUEST] && - !data->state.http_ignorecustom) { + !data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { data->state.http_ignorecustom = TRUE; infof(data, "Drop custom request method for next request"); } @@ -1427,7 +1446,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ /* pass the header */ p = &headerline[hlen]; - if(curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE)) + if(curlx_str_cspn(&p, &val, "\r\n")) return FALSE; curlx_str_trimblanks(&val); @@ -1444,7 +1463,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ !p[clen])) return TRUE; /* match! */ /* advance to the next comma */ - if(curlx_str_until(&p, &next, MAX_HTTP_RESP_HEADER_SIZE, ',') || + if(curlx_str_until(&p, &next, len, ',') || curlx_str_single(&p, ',')) break; /* no comma, get out */ @@ -1452,6 +1471,10 @@ bool Curl_compareheader(const char *headerline, /* line to check */ do curlx_str_passblanks(&p); while(!curlx_str_single(&p, ',')); + /* trailing blanks may move the parsing point past the value end, + then there is nothing left to match */ + if((size_t)(p - o) > len) + break; len -= (p - o); } } @@ -1472,7 +1495,7 @@ static void http_exp100_continue(struct Curl_easy *data, struct cr_exp100_ctx *ctx = reader->ctx; if(ctx->state > EXP100_SEND_DATA) { ctx->state = EXP100_SEND_DATA; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); + Curl_expire_clear(data, EXPIRE_100_TIMEOUT); } } @@ -1500,7 +1523,8 @@ static CURLcode cr_exp100_read(struct Curl_easy *data, "timeout %dms", data->set.expect_100_timeout)); ctx->state = EXP100_AWAITING_CONTINUE; ctx->start = *Curl_pgrs_now(data); - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + Curl_expire_set(data, EXPIRE_100_TIMEOUT, + data->set.expect_100_timeout, &ctx->start); *nread = 0; *eos = FALSE; return CURLE_OK; @@ -1532,7 +1556,7 @@ static void cr_exp100_done(struct Curl_easy *data, { struct cr_exp100_ctx *ctx = reader->ctx; ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); + Curl_expire_clear(data, EXPIRE_100_TIMEOUT); } static const struct Curl_crtype cr_exp100 = { @@ -1687,7 +1711,7 @@ CURLcode Curl_http_done(struct Curl_easy *data, return an error here */ failf(data, "Empty reply from server"); /* Mark it as closed to avoid the "left intact" message */ - streamclose(conn, "Empty reply from server"); + streamclose(conn); return CURLE_GOT_NOTHING; } @@ -1707,7 +1731,7 @@ static bool http_may_use_1_1(const struct Curl_easy *data) return FALSE; /* 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) && + if(data->state.http_neg.only_10 && (!conn || conn->httpversion_seen <= 10)) return FALSE; /* We are not restricted to use 1.0 only. */ @@ -1753,8 +1777,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, if(is_connect) proxy = HEADER_CONNECT; else - proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ? - HEADER_PROXY : HEADER_SERVER; + proxy = data->conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -1773,6 +1796,12 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, else h[0] = data->set.headers; break; + case HEADER_CONNECT_UDP: + if(data->set.sep_headers) + h[0] = data->set.proxyheaders; + else + h[0] = data->set.headers; + break; } #else (void)is_connect; @@ -1787,21 +1816,22 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, struct Curl_str name; const char *p = headers->data; const char *origp = p; + size_t hlen = strlen(origp); /* 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, ';') && + if(!curlx_str_until(&p, &name, hlen, ';') && !curlx_str_single(&p, ';') && !curlx_str_single(&p, '\0') && !memchr(curlx_str(&name), ':', curlx_strlen(&name))) blankheader = TRUE; else { p = origp; - if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') && + if(!curlx_str_until(&p, &name, hlen, ':') && !curlx_str_single(&p, ':')) { struct Curl_str val; - curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_untilnl(&p, &val, hlen); curlx_str_trimblanks(&val); if(!curlx_strlen(&val)) /* no content, do not send this */ @@ -1812,9 +1842,15 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, continue; } + /* a field name is a token and carries no surrounding whitespace, so + trim the parsed name before matching. Otherwise `Authorization :` + (space before the colon) slips past the Authorization/Cookie check + below and gets forwarded to another host on a redirect. */ + curlx_str_trimblanks(&name); + /* only send this if the contents was non-blank or done special */ - if(data->state.aptr.host && + if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ @@ -1846,9 +1882,11 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, other hosts */ !Curl_auth_allowed_to_host(data)) ; - else if(blankheader) - result = curlx_dyn_addf(req, "%.*s:\r\n", (int)curlx_strlen(&name), - curlx_str(&name)); + else if(blankheader) { + result = curlx_dyn_addn(req, curlx_str(&name), curlx_strlen(&name)); + if(!result) + result = curlx_dyn_addn(req, STRCONST(":\r\n")); + } else result = curlx_dyn_addf(req, "%s\r\n", origp); @@ -1954,9 +1992,9 @@ void Curl_http_method(struct Curl_easy *data, httpreq = HTTPREQ_PUT; /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST] && - !data->state.http_ignorecustom) { - request = data->set.str[STRING_CUSTOMREQUEST]; + if(!data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { + request = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); } else { if(data->req.no_body) @@ -1986,44 +2024,20 @@ void Curl_http_method(struct Curl_easy *data, *reqp = httpreq; } -static CURLcode http_useragent(struct Curl_easy *data) -{ - /* The User-Agent string might have been allocated already, because - it might have been used in the proxy connect, but if we have got a header - with the user-agent string specified, we erase the previously made string - here. */ - if(Curl_checkheaders(data, STRCONST("User-Agent"))) { - curlx_free(data->state.aptr.uagent); - data->state.aptr.uagent = NULL; - } - return CURLE_OK; -} - static CURLcode http_set_aptr_host(struct Curl_easy *data) { struct connectdata *conn = data->conn; - struct dynamically_allocated_data *aptr = &data->state.aptr; - const char *ptr; - - if(!data->state.this_is_a_follow) { - /* Free to avoid leaking memory on multiple requests */ - curlx_free(data->state.first_host); + const char *ptr = NULL; - data->state.first_host = curlx_strdup(conn->host.name); - if(!data->state.first_host) - return CURLE_OUT_OF_MEMORY; - - data->state.first_remote_port = conn->remote_port; - data->state.first_remote_protocol = conn->scheme->protocol; - } - curlx_safefree(aptr->host); + curlx_safefree(data->state.http_host); #ifndef CURL_DISABLE_COOKIES curlx_safefree(data->req.cookiehost); #endif - ptr = Curl_checkheaders(data, STRCONST("Host")); - if(ptr && (!data->state.this_is_a_follow || - curl_strequal(data->state.first_host, conn->host.name))) { + if(Curl_peer_equal(data->state.initial_origin, data->state.origin)) + ptr = Curl_checkheaders(data, STRCONST("Host")); + + if(ptr) { #ifndef CURL_DISABLE_COOKIES /* 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 @@ -2060,29 +2074,40 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data) #endif if(!curl_strequal("Host:", ptr)) { - aptr->host = curl_maprintf("Host:%s\r\n", &ptr[5]); - if(!aptr->host) + data->state.http_host = curl_maprintf("Host:%s", &ptr[5]); + if(!data->state.http_host) return CURLE_OUT_OF_MEMORY; } } else { - /* Use the hostname as present in the URL if it was IPv6. */ - char *host = (data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->host.name; - - if(((conn->given->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS)) && - (conn->remote_port == PORT_HTTPS)) || - ((conn->given->protocol & (CURLPROTO_HTTP | CURLPROTO_WS)) && - (conn->remote_port == PORT_HTTP))) - /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include - the port number in the host string */ - aptr->host = curl_maprintf("Host: %s\r\n", host); - else - aptr->host = curl_maprintf("Host: %s:%d\r\n", host, conn->remote_port); + /* This is the HTTP Host: header, so we want + * - for IPv6 origins: "[ipv6-address]" where the IPv6 address is + * found in origin->hostname, stripped of zoneid/scopeid. + * - the (IDN converted) origin->hostname (DNS name or IPv4) otherwise. + * Note: zoneid/scopeid only applies to local routing and has no + * meaning on the remote HTTP server (eg. would confuse it). */ + bool ipv6 = (bool)data->state.origin->ipv6; + struct dynbuf tmp; + size_t hlen; + CURLcode result; - if(!aptr->host) - /* without Host: we cannot make a nice request */ - return CURLE_OUT_OF_MEMORY; + curlx_dyn_init(&tmp, DYN_HTTP_REQUEST); + result = curlx_dyn_addn(&tmp, STRCONST("Host: ")); + if(!result && ipv6) + result = curlx_dyn_addn(&tmp, STRCONST("[")); + if(!result) + result = curlx_dyn_add(&tmp, data->state.origin->hostname); + if(!result && ipv6) + result = curlx_dyn_addn(&tmp, STRCONST("]")); + if(!result && + ((data->state.origin->port != data->state.origin->scheme->defport) || + (data->state.origin->scheme->family != conn->scheme->family))) { + result = curlx_dyn_addf(&tmp, ":%u", data->state.origin->port); + } + + data->state.http_host = result ? NULL : curlx_dyn_take(&tmp, &hlen); + curlx_dyn_free(&tmp); + return result; } return CURLE_OK; } @@ -2100,18 +2125,18 @@ static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn = data->conn; #endif - if(data->set.str[STRING_TARGET]) { - path = data->set.str[STRING_TARGET]; + if(CURL_EASY_STR(data, STRING_TARGET)) { + path = CURL_EASY_STR(data, STRING_TARGET); query = NULL; } #ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + if(conn->bits.origin_is_proxy) { /* Using a proxy but does not tunnel through it */ /* The path sent to the proxy is in fact the entire URL, but if the remote host is a IDN-name, we must make sure that the request we produce only - uses the encoded hostname! */ + uses the decoded hostname! */ /* and no fragment part */ CURLUcode uc; @@ -2120,8 +2145,9 @@ static CURLcode http_target(struct Curl_easy *data, if(!h) return CURLE_OUT_OF_MEMORY; - if(conn->host.dispname != conn->host.name) { - uc = curl_url_set(h, CURLUPART_HOST, conn->host.name, 0); + if(!data->state.origin->ipv6 && + (data->state.origin->user_hostname != data->state.origin->hostname)) { + uc = curl_url_set(h, CURLUPART_HOST, data->state.origin->hostname, 0); if(uc) { curl_url_cleanup(h); return CURLE_OUT_OF_MEMORY; @@ -2133,7 +2159,7 @@ static CURLcode http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - if(curl_strequal("http", data->state.up.scheme)) { + if(data->state.origin->scheme == &Curl_scheme_http) { /* when getting HTTP, we do not want the userinfo the URL */ uc = curl_url_set(h, CURLUPART_USER, NULL, 0); if(uc) { @@ -2146,6 +2172,19 @@ static CURLcode http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } } + else if(data->state.creds && (data->state.creds->source != CREDS_URL)) { + /* credentials not from the URL need to be set */ + uc = curl_url_set(h, CURLUPART_USER, + data->state.creds->user, CURLU_URLENCODE); + if(!uc) + uc = curl_url_set(h, CURLUPART_PASSWORD, + data->state.creds->passwd, CURLU_URLENCODE); + if(uc) { + curl_url_cleanup(h); + return Curl_uc_to_curlcode(uc); + } + } + /* Extract the URL to use in the request. */ uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); if(uc) { @@ -2156,13 +2195,13 @@ static CURLcode http_target(struct Curl_easy *data, curl_url_cleanup(h); /* target or URL */ - result = curlx_dyn_add(r, data->set.str[STRING_TARGET] ? - data->set.str[STRING_TARGET] : url); + result = curlx_dyn_add(r, CURL_EASY_STR(data, STRING_TARGET) ? + CURL_EASY_STR(data, STRING_TARGET) : url); curlx_free(url); if(result) return result; - if(curl_strequal("ftp", data->state.up.scheme) && + if((data->state.origin->scheme == &Curl_scheme_ftp) && data->set.proxy_transfer_mode) { /* when doing ftp, append ;type= if not present */ size_t len = strlen(path); @@ -2538,11 +2577,12 @@ static CURLcode http_cookies(struct Curl_easy *data, struct dynbuf *r) { CURLcode result = CURLE_OK; - char *addcookies = NULL; + const char *addcookies = NULL; bool linecap = FALSE; - if(data->set.str[STRING_COOKIE] && - !Curl_checkheaders(data, STRCONST("Cookie"))) - addcookies = data->set.str[STRING_COOKIE]; + if(CURL_EASY_STR(data, STRING_COOKIE) && + !Curl_checkheaders(data, STRCONST("Cookie")) && + Curl_auth_allowed_to_host(data)) + addcookies = CURL_EASY_STR(data, STRING_COOKIE); if(data->cookies || addcookies) { struct Curl_llist list; @@ -2551,9 +2591,9 @@ static CURLcode http_cookies(struct Curl_easy *data, if(data->cookies && data->state.cookie_engine) { bool okay; const char *host = data->req.cookiehost ? - data->req.cookiehost : data->conn->host.name; + data->req.cookiehost : data->state.origin->hostname; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - result = Curl_cookie_getlist(data, data->conn, &okay, host, &list); + result = Curl_cookie_getlist(data, &okay, host, &list); if(!result && okay) { struct Curl_llist_node *n; size_t clen = 8; /* hold the size of the generated Cookie: header */ @@ -2619,23 +2659,23 @@ static CURLcode http_range(struct Curl_easy *data, if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && !Curl_checkheaders(data, STRCONST("Range"))) { /* if a line like this was already allocated, free the previous one */ - curlx_free(data->state.aptr.rangeline); - data->state.aptr.rangeline = curl_maprintf("Range: bytes=%s\r\n", + curlx_free(data->state.rangeline); + data->state.rangeline = curl_maprintf("Range: bytes=%s\r\n", data->state.range); - if(!data->state.aptr.rangeline) + if(!data->state.rangeline) return CURLE_OUT_OF_MEMORY; } 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 */ - curlx_free(data->state.aptr.rangeline); + curlx_free(data->state.rangeline); if(data->set.set_resume_from < 0) { /* 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 = + data->state.rangeline = curl_maprintf("Content-Range: bytes 0-%" FMT_OFF_T "/" "%" FMT_OFF_T "\r\n", req_clen - 1, req_clen); } @@ -2647,7 +2687,7 @@ static CURLcode http_range(struct Curl_easy *data, curl_off_t total_len = data->req.authneg ? data->state.infilesize : (data->state.resume_from + req_clen); - data->state.aptr.rangeline = + data->state.rangeline = curl_maprintf("Content-Range: bytes %s%" FMT_OFF_T "/" "%" FMT_OFF_T "\r\n", data->state.range, total_len - 1, total_len); @@ -2655,11 +2695,11 @@ static CURLcode http_range(struct Curl_easy *data, else { /* Range was selected and then we pass the incoming range and append total size */ - data->state.aptr.rangeline = + data->state.rangeline = curl_maprintf("Content-Range: bytes %s/%" FMT_OFF_T "\r\n", data->state.range, req_clen); } - if(!data->state.aptr.rangeline) + if(!data->state.rangeline) return CURLE_OUT_OF_MEMORY; } } @@ -2692,7 +2732,7 @@ static CURLcode http_firstwrite(struct Curl_easy *data) /* The resume point is at the end of file, consider this fine even if it does not allow resume from here. */ infof(data, "The entire document is already downloaded"); - streamclose(conn, "already downloaded"); + streamclose(conn); /* Abort download */ CURL_REQ_CLEAR_RECV(data); k->done = TRUE; @@ -2720,7 +2760,7 @@ static CURLcode http_firstwrite(struct Curl_easy *data) infof(data, "Simulate an HTTP 304 response"); /* we abort the transfer before it is completed == we ruin the reuse ability. Close the connection */ - streamclose(conn, "Simulated 304 handling"); + streamclose(conn); return CURLE_OK; } } /* we have a time condition */ @@ -2737,13 +2777,16 @@ static CURLcode http_check_new_conn(struct Curl_easy *data) alpn = Curl_conn_get_alpn_negotiated(data, conn); if(alpn && !strcmp("h3", alpn)) { - DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); +#ifndef CURL_DISABLE_PROXY + if(!conn->bits.origin_is_proxy) +#endif + DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); info_version = "HTTP/3"; } else if(alpn && !strcmp("h2", alpn)) { #ifndef CURL_DISABLE_PROXY if((Curl_conn_http_version(data, conn) != 20) && - conn->bits.proxy && !conn->bits.tunnel_proxy) { + conn->bits.origin_is_proxy) { result = Curl_http2_switch(data); if(result) return result; @@ -2842,7 +2885,7 @@ typedef enum { #ifndef CURL_DISABLE_PROXY H1_HD_PROXY_AUTH, #endif - H1_HD_USER_AUTH, + H1_HD_AUTH, H1_HD_RANGE, H1_HD_USER_AGENT, H1_HD_ACCEPT, @@ -2891,33 +2934,36 @@ static CURLcode http_add_hd(struct Curl_easy *data, break; case H1_HD_HOST: - if(data->state.aptr.host) - result = curlx_dyn_add(req, data->state.aptr.host); + if(data->state.http_host) { + result = curlx_dyn_add(req, data->state.http_host); + if(!result) + result = curlx_dyn_addn(req, STRCONST("\r\n")); + } break; #ifndef CURL_DISABLE_PROXY case H1_HD_PROXY_AUTH: - if(data->req.proxyuserpwd) - result = curlx_dyn_add(req, data->req.proxyuserpwd); + if(data->req.hd_proxy_auth) + result = curlx_dyn_add(req, data->req.hd_proxy_auth); break; #endif - case H1_HD_USER_AUTH: - if(data->req.userpwd) - result = curlx_dyn_add(req, data->req.userpwd); + case H1_HD_AUTH: + if(data->req.hd_auth) + result = curlx_dyn_add(req, data->req.hd_auth); break; case H1_HD_RANGE: - if(data->state.use_range && data->state.aptr.rangeline) - result = curlx_dyn_add(req, data->state.aptr.rangeline); + if(data->state.use_range && data->state.rangeline) + result = curlx_dyn_add(req, data->state.rangeline); break; - case H1_HD_USER_AGENT: - if(data->set.str[STRING_USERAGENT] && /* User-Agent: */ - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent) - result = curlx_dyn_add(req, data->state.aptr.uagent); + case H1_HD_USER_AGENT: { + const char *ua = CURL_EASY_STR(data, STRING_USERAGENT); + if(ua && *ua && !Curl_checkheaders(data, STRCONST("User-Agent"))) + result = curlx_dyn_addf(req, "User-Agent: %s\r\n", ua); break; + } case H1_HD_ACCEPT: if(!Curl_checkheaders(data, STRCONST("Accept"))) @@ -2934,16 +2980,14 @@ static CURLcode http_add_hd(struct Curl_easy *data, #endif break; - case H1_HD_ACCEPT_ENCODING: - curlx_safefree(data->state.aptr.accept_encoding); - if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && - data->set.str[STRING_ENCODING]) - result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n", - data->set.str[STRING_ENCODING]); + case H1_HD_ACCEPT_ENCODING: { + const char *enc = CURL_EASY_STR(data, STRING_ENCODING); + if(enc && !Curl_checkheaders(data, STRCONST("Accept-Encoding"))) + result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n", enc); break; + } case H1_HD_REFERER: - curlx_safefree(data->state.aptr.ref); if(Curl_bufref_ptr(&data->state.referer) && !Curl_checkheaders(data, STRCONST("Referer"))) result = curlx_dyn_addf(req, "Referer: %s\r\n", @@ -2952,8 +2996,7 @@ static CURLcode http_add_hd(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY case H1_HD_PROXY_CONNECTION: - if(conn->bits.httpproxy && - !conn->bits.tunnel_proxy && + if(conn->bits.origin_is_proxy && !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && !Curl_checkProxyheaders(data, data->conn, STRCONST("Proxy-Connection"))) result = curlx_dyn_add(req, "Proxy-Connection: Keep-Alive\r\n"); @@ -2966,10 +3009,10 @@ static CURLcode http_add_hd(struct Curl_easy *data, #ifndef CURL_DISABLE_ALTSVC case H1_HD_ALT_USED: - if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used"))) + if(conn->bits.altused && conn->via_peer && + !Curl_checkheaders(data, STRCONST("Alt-Used"))) result = curlx_dyn_addf(req, "Alt-Used: %s:%u\r\n", - conn->conn_to_host.name, - conn->conn_to_port); + conn->via_peer->hostname, conn->via_peer->port); break; #endif @@ -3062,29 +3105,14 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* select host to send */ result = http_set_aptr_host(data); - if(!result) { - /* setup the authentication headers, how that method and host are known */ - char *pq = NULL; - if(data->state.up.query) { - pq = curl_maprintf("%s?%s", data->state.up.path, data->state.up.query); - if(!pq) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } + /* setup the authentication headers, how that method and host are known */ + if(!result) result = Curl_http_output_auth(data, data->conn, method, httpreq, - (pq ? pq : data->state.up.path), FALSE); - curlx_free(pq); - } - if(result) - goto out; - - result = http_useragent(data); - if(result) - goto out; - + data->state.up.path, + data->state.up.query, FALSE); /* Setup input reader, resume information and ranges */ - result = set_reader(data, httpreq); + if(!result) + result = set_reader(data, httpreq); if(!result) result = http_resume(data, httpreq); if(!result) @@ -3152,7 +3180,7 @@ static statusline checkhttpprefix(struct Curl_easy *data, head = head->next; } - if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len))) + if((rc != STATUS_DONE) && checkprefixmax("HTTP/", s, len)) rc = onmatch; return rc; @@ -3209,7 +3237,6 @@ static CURLcode http_header_a(struct Curl_easy *data, { #ifndef CURL_DISABLE_ALTSVC const char *v; - struct connectdata *conn = data->conn; v = (data->asi && (Curl_xfer_is_secure(data) || #ifdef DEBUGBUILD @@ -3224,8 +3251,7 @@ static CURLcode http_header_a(struct Curl_easy *data, struct SingleRequest *k = &data->req; 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)); + return Curl_altsvc_parse(data, data->asi, v, data->state.origin, id); } #else (void)data; @@ -3263,7 +3289,7 @@ static CURLcode http_header_c(struct Curl_easy *data, failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; } - streamclose(conn, "overflow content-length"); + streamclose(conn); infof(data, "Overflow Content-Length: value"); return CURLE_OK; } @@ -3290,7 +3316,7 @@ static CURLcode http_header_c(struct Curl_easy *data, } } while(1); } - v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ? + v = (!k->http_bodyless && CURL_EASY_STR(data, STRING_ENCODING)) ? HD_VAL(hd, hdlen, "Content-Encoding:") : NULL; if(v) { /* @@ -3324,7 +3350,7 @@ static CURLcode http_header_c(struct Curl_easy *data, * the connection will close when this request has been * served. */ - connclose(conn, "Connection: close used"); + connclose(conn); return CURLE_OK; } if((k->httpversion == 10) && @@ -3335,7 +3361,7 @@ static CURLcode http_header_c(struct Curl_easy *data, * pleasure. Default action for 1.0 is to close. * * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); + connkeep(conn); infof(data, "HTTP/1.0 connection set to keep alive"); return CURLE_OK; } @@ -3350,7 +3376,7 @@ static CURLcode http_header_c(struct Curl_easy *data, 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; @@ -3443,7 +3469,7 @@ static CURLcode http_header_p(struct Curl_easy *data, const char *v = HD_VAL(hd, hdlen, "Proxy-Connection:"); if(v) { struct connectdata *conn = data->conn; - if((k->httpversion == 10) && conn->bits.httpproxy && + if((k->httpversion == 10) && conn->http_proxy.peer && HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) { /* * When an HTTP/1.0 reply comes when using a proxy, the @@ -3451,16 +3477,16 @@ static CURLcode http_header_p(struct Curl_easy *data, * 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 */ + connkeep(conn); /* do not close */ infof(data, "HTTP/1.0 proxy connection set to keep alive"); } - else if((k->httpversion == 11) && conn->bits.httpproxy && + else if((k->httpversion == 11) && conn->http_proxy.peer && 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"); + connclose(conn); infof(data, "HTTP/1.1 proxy connection set close"); } return CURLE_OK; @@ -3537,7 +3563,6 @@ static CURLcode http_header_s(struct Curl_easy *data, const char *hd, size_t hdlen) { #if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_HSTS) - struct connectdata *conn = data->conn; const char *v; #else (void)data; @@ -3552,12 +3577,14 @@ static CURLcode http_header_s(struct Curl_easy *data, /* If there is a custom-set Host: name, use it here, or else use * real peer hostname. */ const char *host = data->req.cookiehost ? - data->req.cookiehost : conn->host.name; - const bool secure_context = Curl_secure_context(conn, host); + data->req.cookiehost : data->state.origin->hostname; + const unsigned char secure_context = Curl_secure_context(data, host) ? + COOKIE_SECURE : 0; CURLcode result; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - result = Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host, - data->state.up.path, secure_context); + result = Curl_cookie_add(data, data->cookies, v, host, + data->state.up.path, + COOKIE_HTTPHEADER | secure_context); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); return result; } @@ -3575,8 +3602,8 @@ static CURLcode http_header_s(struct Curl_easy *data, ) ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL; if(v) { - CURLcode result = - Curl_hsts_parse(data->hsts, conn->host.name, v); + CURLcode result = Curl_hsts_parse( + data->hsts, data->state.origin->hostname, v); if(result) { if(result == CURLE_OUT_OF_MEMORY) return result; @@ -3626,7 +3653,8 @@ static CURLcode http_header_t(struct Curl_easy *data, /* 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"); + CURL_TRC_M(data, "HTTP/1.1 transfer-encoding without chunks"); + connclose(conn); k->ignore_cl = TRUE; } return CURLE_OK; @@ -3772,7 +3800,7 @@ static CURLcode http_statusline(struct Curl_easy *data, 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"); + connclose(conn); } k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; @@ -3827,8 +3855,8 @@ static CURLcode http_size(struct Curl_easy *data) return CURLE_OK; } -static CURLcode verify_header(struct Curl_easy *data, - const char *hd, size_t hdlen) +CURLcode Curl_verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; const char *ptr = memchr(hd, 0x00, hdlen); @@ -3837,6 +3865,14 @@ static CURLcode verify_header(struct Curl_easy *data, failf(data, "Nul byte in header"); return CURLE_WEIRD_SERVER_REPLY; } + if(hdlen > 2) { + ptr = memchr(hd, '\r', hdlen - 2); + if(ptr) { + /* CR may only precede the LF, nothing else */ + failf(data, "Carriage return found in header"); + return CURLE_WEIRD_SERVER_REPLY; + } + } if(k->headerline < 2) /* the first "header" is the status-line and it has no colon */ return CURLE_OK; @@ -4062,7 +4098,7 @@ static CURLcode http_handle_send_error(struct Curl_easy *data) } else { infof(data, "Got HTTP failure 417 while sending data"); - streamclose(conn, "Stop sending data before everything sent"); + streamclose(conn); result = http_perhapsrewind(data, conn); if(result) return result; @@ -4080,7 +4116,7 @@ static CURLcode http_handle_send_error(struct Curl_easy *data) } else { infof(data, "HTTP error before end of send, stop sending"); - streamclose(conn, "Stop sending data before everything sent"); + streamclose(conn); result = Curl_req_abort_sending(data); if(result) return result; @@ -4156,7 +4192,7 @@ static CURLcode http_on_response(struct Curl_easy *data, 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"); + streamclose(conn); } http_check_auth_closure(data, conn); @@ -4191,6 +4227,17 @@ static CURLcode http_on_response(struct Curl_easy *data, goto out; } + /* final response without error, prepare to receive the body */ + result = http_firstwrite(data); + if(result) + goto out; + + /* 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); + if(result) + goto out; + /* If we requested a "no body", this is a good time to get * out and return home. */ @@ -4204,14 +4251,6 @@ static CURLcode http_on_response(struct Curl_easy *data, if((k->maxdownload == 0) && (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 */ @@ -4325,8 +4364,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, if(!fine_statusline) { /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ + compare header line against list of aliases */ statusline check = checkhttpprefix(data, hd, hdlen); if(check == STATUS_DONE) { fine_statusline = TRUE; @@ -4367,7 +4405,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, } } - result = verify_header(data, hd, hdlen); + result = Curl_verify_header(data, hd, hdlen); if(result) return result; @@ -4490,7 +4528,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* 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; - streamclose(conn, "bad HTTP: No end-of-message indicator"); + streamclose(conn); if((k->httpversion >= 10) || conn->bits.reuse) { failf(data, "Invalid status line"); return CURLE_WEIRD_SERVER_REPLY; @@ -4527,7 +4565,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* the first read "header", the status line */ statusline st = checkprotoprefix(data, conn, hd, hlen); if(st == STATUS_BAD) { - streamclose(conn, "bad HTTP: No end-of-message indicator"); + streamclose(conn); /* 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) { @@ -4876,7 +4914,6 @@ struct name_const { size_t namelen; }; -/* keep them sorted by length! */ static const struct name_const H2_NON_FIELD[] = { { STRCONST("Host") }, { STRCONST("Upgrade") }, @@ -4890,10 +4927,8 @@ static bool h2_permissible_field(struct dynhds_entry *e) { size_t i; 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 && - curl_strequal(H2_NON_FIELD[i].name, e->name)) + curl_strnequal(H2_NON_FIELD[i].name, e->name, e->namelen)) return FALSE; } return TRUE; @@ -4950,7 +4985,7 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme); } else { - scheme = Curl_xfer_is_secure(data) ? "https" : "http"; + scheme = data->state.origin->scheme->name; } } @@ -4986,7 +5021,7 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, if(e->namelen == 2 && curl_strequal("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); + "trailers", CURL_CSTRLEN("trailers")); } else if(h2_permissible_field(e)) { result = Curl_dynhds_add(h2_headers, e->name, e->namelen, diff --git a/lib/http.h b/lib/http.h index 6e33c00e9219..83b4cc6c6414 100644 --- a/lib/http.h +++ b/lib/http.h @@ -83,8 +83,6 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, 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_to_fold(struct dynbuf *bf); @@ -108,6 +106,11 @@ CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, const char *hd, size_t hdlen, bool is_eos); +/* check a received header line for forbidden bytes/format, the same checks + applied to regular response headers */ +CURLcode Curl_verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen); + /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth); @@ -134,8 +137,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, will always be unconditionally sent and thus it may not be larger than can always be afforded to send twice. - It must not be greater than 64K to work on VMS. -*/ + It must not be greater than 64K to work on VMS. */ #ifndef MAX_INITIAL_POST_SIZE #define MAX_INITIAL_POST_SIZE (64 * 1024) #endif @@ -180,8 +182,9 @@ CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, * @param request pointer to the request keyword * @param httpreq is the request type * @param path pointer to the requested path - * @param proxytunnel boolean if this is the request setting up a "proxy - * tunnel" + * @param query pointer to the requested query or NULL + * @param is_connect boolean if this is a CONNECT request + * (where httpreq is HTTPREQ_GET since there is no HTTPREQ_CONNECT) * * @returns CURLcode */ @@ -190,9 +193,8 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, const char *request, Curl_HttpReq httpreq, const char *path, - bool proxytunnel); /* TRUE if this is - the request setting up - the proxy tunnel */ + const char *query, + bool is_connect); /* Decode HTTP status code string. */ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); diff --git a/lib/http2.c b/lib/http2.c index 7be5abdd3147..0cfdf20571be 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -46,15 +46,13 @@ #include "bufref.h" #include "curlx/dynbuf.h" #include "headers.h" +#include "curl_share.h" -#if (NGHTTP2_VERSION_NUM < 0x010c00) -#error too old nghttp2 version, upgrade! +#if NGHTTP2_VERSION_NUM < 0x010f00 +#error "nghttp2 1.15.0 or greater required" #endif -#if (NGHTTP2_VERSION_NUM >= 0x010c00) #define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1 -#endif - /* buffer dimensioning: * use 16K as chunk size, as that fits H2 DATA frames well */ @@ -181,7 +179,7 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); Curl_uint32_hash_init(&ctx->streams, 63, h2_stream_hash_free); - ctx->remote_max_sid = 2147483647; + ctx->remote_max_sid = INT32_MAX; ctx->via_h1_upgrade = via_h1_upgrade; ctx->initialized = TRUE; } @@ -189,6 +187,8 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) { if(ctx && ctx->initialized) { + if(ctx->h2) + nghttp2_session_del(ctx->h2); Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); Curl_bufcp_free(&ctx->stream_bufcp); @@ -199,14 +199,6 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) curlx_free(ctx); } -static void cf_h2_ctx_close(struct cf_h2_ctx *ctx) -{ - if(ctx->h2) { - nghttp2_session_del(ctx->h2); - ctx->h2 = NULL; - } -} - static uint32_t cf_h2_initial_win_size(struct Curl_easy *data) { #if NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE @@ -221,8 +213,8 @@ static uint32_t cf_h2_initial_win_size(struct Curl_easy *data) } static size_t populate_settings(nghttp2_settings_entry *iv, - struct Curl_easy *data, - struct cf_h2_ctx *ctx) + struct Curl_easy *data, + struct cf_h2_ctx *ctx) { iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = Curl_multi_max_concurrent_streams(data->multi); @@ -232,7 +224,7 @@ static size_t populate_settings(nghttp2_settings_entry *iv, if(ctx) ctx->initial_win_size = iv[1].value; iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; - iv[2].value = data->multi->push_cb != NULL; + iv[2].value = !!data->multi->push_cb; return 3; } @@ -293,8 +285,7 @@ static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, struct Curl_easy *data) { - curl_off_t avail = Curl_rlimit_avail(&data->progress.dl.rlimit, - Curl_pgrs_now(data)); + curl_off_t avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); (void)cf; if(avail < CURL_OFF_T_MAX) { /* limit in place */ @@ -469,8 +460,7 @@ static int h2_client_new(struct Curl_cfilter *cf, return rc; /* We handle window updates ourself to enforce buffer limits */ nghttp2_option_set_no_auto_window_update(o, 1); -#if NGHTTP2_VERSION_NUM >= 0x013200 - /* with 1.50.0 */ +#if NGHTTP2_VERSION_NUM >= 0x013200 /* with 1.50.0 */ /* turn off RFC 9113 leading and trailing white spaces validation against HTTP field value. */ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); @@ -523,7 +513,8 @@ static CURLcode h2_process_pending_input(struct Curl_cfilter *cf, 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"); + CURL_TRC_M(data, "http/2: No new requests allowed"); + connclose(cf->conn); } return CURLE_OK; @@ -555,9 +546,9 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, *input_pending = FALSE; result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread); + CURL_TRC_CF(data, cf, "connisalive, recv pending input -> %d, %zu", + (int)result, nread); if(!result) { - CURL_TRC_CF(data, cf, "%zu bytes stray data read before trying " - "h2 connection", nread); result = h2_process_pending_input(cf, data); if(result) /* immediate error, considered dead */ @@ -709,11 +700,13 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *name) static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct Curl_easy *second = curl_easy_duphandle(data); + struct Curl_easy *second = curl_easy_init(); if(second) { struct h2_stream_ctx *second_stream; http2_data_setup(cf, second, &second_stream); - second->state.priority.weight = data->state.priority.weight; + second->state.weight = data->state.weight; + if(data->share) + (void)Curl_share_easy_link(second, data->share); } return second; } @@ -828,11 +821,14 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - 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); + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, multi_push_cb); + rv = data->multi->push_cb(data, newhandle, + stream->push_headers_used, &heads, + data->multi->push_userp); + CURL_CBAPI_END(&guard); + } /* free the headers again */ free_push_headers(stream); @@ -859,7 +855,7 @@ static int push_promise(struct Curl_cfilter *cf, result = http2_data_setup(cf, newhandle, &newstream); if(result) { - failf(data, "error setting up stream: %d", result); + failf(data, "error setting up stream: %d", (int)result); discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; @@ -908,7 +904,7 @@ static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, 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); + stream->id, (int)stream->xfer_result, blen); } } @@ -925,7 +921,7 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, 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); + stream->id, (int)stream->xfer_result, blen); nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); } @@ -1389,7 +1385,7 @@ static void cf_h2_header_error(struct Curl_cfilter *cf, { struct cf_h2_ctx *ctx = cf->ctx; - failf(data, "Error receiving HTTP2 header: %d(%s)", result, + failf(data, "Error receiving HTTP2 header: %d(%s)", (int)result, curl_easy_strerror(result)); if(stream) { nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, @@ -1409,7 +1405,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct h2_stream_ctx *stream; - struct Curl_easy *data_s; + struct Curl_easy *data; int32_t stream_id = frame->hd.stream_id; CURLcode result; (void)flags; @@ -1417,15 +1413,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!GOOD_EASY_HANDLE(data_s)) + data = nghttp2_session_get_stream_user_data(session, stream_id); + if(!GOOD_EASY_HANDLE(data)) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data_s); + stream = H2_STREAM_CTX(ctx, data); if(!stream) { - failf(data_s, "Internal NULL stream"); + failf(data, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1434,18 +1430,18 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { char *h; - if((namelen == (sizeof(HTTP_PSEUDO_AUTHORITY) - 1)) && + if((namelen == CURL_CSTRLEN(HTTP_PSEUDO_AUTHORITY)) && !strncmp(HTTP_PSEUDO_AUTHORITY, (const char *)name, namelen)) { /* pseudo headers are lower case */ int rc = 0; - char *check = curl_maprintf("%s:%d", cf->conn->host.name, - cf->conn->remote_port); + char *check = curl_maprintf("%s:%d", data->state.origin->hostname, + data->state.origin->port); if(!check) /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!curl_strequal(check, (const char *)value) && - ((cf->conn->remote_port != cf->conn->given->defport) || - !curl_strequal(cf->conn->host.name, (const char *)value))) { + ((data->state.origin->port != cf->conn->given->defport) || + !curl_strequal(data->state.origin->hostname, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a * PUSH_PROMISE for which the server is not authoritative as a stream @@ -1473,7 +1469,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, char **headp; if(stream->push_headers_alloc > 1000) { /* this is beyond crazy many headers, bail out */ - failf(data_s, "Too many PUSH_PROMISE headers"); + failf(data, "Too many PUSH_PROMISE headers"); free_push_headers(stream); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1497,35 +1493,35 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->bodystarted) { /* This is a trailer */ - CURL_TRC_CF(data_s, cf, "[%d] trailer: %.*s: %.*s", + CURL_TRC_CF(data, 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) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } - if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && - memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) { + if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && + !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { /* nghttp2 guarantees :status is received first and only once. */ char buffer[32]; size_t hlen; result = Curl_http_decode_status(&stream->status_code, (const char *)value, valuelen); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } hlen = curl_msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%d\r", stream->status_code); - result = Curl_headers_push(data_s, buffer, hlen, CURLH_PSEUDO); + result = Curl_headers_push(data, buffer, hlen, CURLH_PSEUDO); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; } curlx_dyn_reset(&ctx->scratch); @@ -1535,17 +1531,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, 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), + h2_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), curlx_dyn_len(&ctx->scratch), FALSE); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, 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_multi_mark_dirty(data_s); + if(CF_DATA_CURRENT(cf) != data) + Curl_multi_mark_dirty(data); - CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d", + CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d", stream->id, stream->status_code); return 0; } @@ -1562,17 +1558,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, 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), + h2_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), curlx_dyn_len(&ctx->scratch), FALSE); if(result) { - cf_h2_header_error(cf, data_s, stream, result); + cf_h2_header_error(cf, data, 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_multi_mark_dirty(data_s); + if(CF_DATA_CURRENT(cf) != data) + Curl_multi_mark_dirty(data); - CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s", stream->id, (int)namelen, name, (int)valuelen, value); return 0; /* 0 is successful */ @@ -1620,7 +1616,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session, nread = (ssize_t)n; CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) eos=%d -> %zd, %d", - stream_id, length, stream->body_eos, nread, result); + stream_id, length, stream->body_eos, nread, (int)result); if(stream->body_eos && Curl_bufq_is_empty(&stream->sendbuf)) { *data_flags = NGHTTP2_DATA_FLAG_EOF; @@ -1696,17 +1692,17 @@ static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf, if(stream->error == NGHTTP2_REFUSED_STREAM) { infof(data, "HTTP/2 stream %d refused by server, try again on a new " "connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ + connclose(cf->conn); /* do not use this anymore */ data->state.refused_stream = TRUE; return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ } else 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; - return CURLE_OK; + 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; + return CURLE_OK; } failf(data, "HTTP/2 stream %d reset by %s (error 0x%x %s)", stream->id, stream->reset_by_server ? "server" : "curl", @@ -1754,22 +1750,22 @@ static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf, result = CURLE_OK; out: - CURL_TRC_CF(data, cf, "handle_stream_close -> %d, %zu", result, *pnlen); + CURL_TRC_CF(data, cf, "handle_stream_close -> %d, %zu", (int)result, *pnlen); return result; } 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 ? - data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; + return data->set.weight ? + data->set.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 ? - data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; + return data->state.weight ? + data->state.weight : NGHTTP2_DEFAULT_WEIGHT; } /* @@ -1778,17 +1774,12 @@ static int sweight_in_effect(const struct Curl_easy *data) * struct. */ -static void h2_pri_spec(struct cf_h2_ctx *ctx, - struct Curl_easy *data, +static void h2_pri_spec(struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { - struct Curl_data_priority *prio = &data->set.priority; - 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); - data->state.priority = *prio; + int prio = data->set.weight; + nghttp2_priority_spec_init(pri_spec, 0, sweight_wanted(data), FALSE); + data->state.weight = prio; } /* @@ -1805,13 +1796,11 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, int rv = 0; 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))) { + (sweight_wanted(data) != sweight_in_effect(data))) { /* send new weight and/or dependency */ nghttp2_priority_spec pri_spec; - h2_pri_spec(ctx, data, &pri_spec); + h2_pri_spec(data, &pri_spec); CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); DEBUGASSERT(stream->id != -1); rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, @@ -1869,7 +1858,7 @@ static CURLcode stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(result && (result != CURLE_AGAIN)) CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %d, %zu", - stream->id, len, result, *pnread); + stream->id, len, (int)result, *pnread); return result; } @@ -1912,15 +1901,11 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, Curl_multi_mark_dirty(data); break; } - else if(!stream) { - DEBUGASSERT(0); - break; - } result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread); if(result) { if(result != CURLE_AGAIN) { - failf(data, "Failed receiving HTTP2 data: %d(%s)", result, + failf(data, "Failed receiving HTTP2 data: %d(%s)", (int)result, curl_easy_strerror(result)); return result; } @@ -1944,8 +1929,9 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, } if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - connclose(cf->conn, ctx->rcvd_goaway ? "server closed with GOAWAY" : - "server closed abruptly"); + CURL_TRC_CF(data, cf, "server closed %s", + ctx->rcvd_goaway ? "with GOAWAY" : "abruptly"); + connclose(cf->conn); } CURL_TRC_CF(data, cf, "[0] ingress: done"); @@ -2015,7 +2001,7 @@ static CURLcode cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %d, %zu, " "window=%d/%d, connection %d/%d", - stream->id, len, result, *pnread, + stream->id, len, (int)result, *pnread, nghttp2_session_get_stream_effective_recv_data_length( ctx->h2, stream->id), nghttp2_session_get_stream_effective_local_window_size( @@ -2098,10 +2084,11 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, if(result) goto out; - result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, &nwritten); + result = Curl_h1_req_parse_read( + &stream->h1, buf, len, NULL, + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, &nwritten); if(result) goto out; *pnwritten = nwritten; @@ -2123,7 +2110,7 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, goto out; } - h2_pri_spec(ctx, data, &pri_spec); + h2_pri_spec(data, &pri_spec); if(!nghttp2_session_check_request_allowed(ctx->h2)) CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); @@ -2200,7 +2187,7 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, out: CURL_TRC_CF(data, cf, "[%d] submit -> %d, %zu", - stream ? stream->id : -1, result, *pnwritten); + stream ? stream->id : -1, (int)result, *pnwritten); curlx_safefree(nva); *pstream = stream; Curl_dynhds_free(&h2_headers); @@ -2234,7 +2221,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(eos); result = cf_h2_body_send(cf, data, stream, buf, 0, eos, &n); CURL_TRC_CF(data, cf, "[%d] cf_body_send last CHUNK -> %d, %zu, eos=%d", - stream->id, result, n, eos); + stream->id, (int)result, n, eos); if(result) goto out; *pnwritten = len; @@ -2242,7 +2229,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, else { result = cf_h2_body_send(cf, data, stream, buf, len, eos, pnwritten); CURL_TRC_CF(data, cf, "[%d] cf_body_send(len=%zu) -> %d, %zu, eos=%d", - stream->id, len, result, *pnwritten, eos); + stream->id, len, (int)result, *pnwritten, eos); } /* Call the nghttp2 send loop and flush to write ALL buffered data, @@ -2278,7 +2265,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %d, %zu, " "eos=%d, h2 windows %d-%d (stream-conn), " "buffers %zu-%zu (stream-conn)", - stream->id, len, result, *pnwritten, + stream->id, len, (int)result, *pnwritten, stream->body_eos, nghttp2_session_get_stream_remote_window_size( ctx->h2, stream->id), @@ -2289,7 +2276,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, else { CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zu, " "connection-window=%d, nw_send_buffer(%zu)", - len, result, *pnwritten, + len, (int)result, *pnwritten, nghttp2_session_get_remote_window_size(ctx->h2), Curl_bufq_len(&ctx->outbufq)); } @@ -2322,7 +2309,7 @@ static CURLcode cf_h2_flush(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%d] flush -> %d, " "h2 windows %d-%d (stream-conn), " "buffers %zu-%zu (stream-conn)", - stream->id, result, + stream->id, (int)result, nghttp2_session_get_stream_remote_window_size( ctx->h2, stream->id), nghttp2_session_get_remote_window_size(ctx->h2), @@ -2332,7 +2319,7 @@ static CURLcode cf_h2_flush(struct Curl_cfilter *cf, else { CURL_TRC_CF(data, cf, "flush -> %d, " "connection-window=%d, nw_send_buffer(%zu)", - result, nghttp2_session_get_remote_window_size(ctx->h2), + (int)result, nghttp2_session_get_remote_window_size(ctx->h2), Curl_bufq_len(&ctx->outbufq)); } CF_DATA_RESTORE(cf, save); @@ -2424,7 +2411,9 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, failf(data, "Could not initialize nghttp2"); goto out; } - ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; + ctx->max_concurrent_streams = data->multi ? + Curl_multi_max_concurrent_streams(data->multi) : + DEFAULT_MAX_CONCURRENT_STREAMS; if(ctx->via_h1_upgrade) { /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted @@ -2551,27 +2540,11 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, result = CURLE_OK; out: - CURL_TRC_CF(data, cf, "cf_connect() -> %d, %d, ", result, *done); + CURL_TRC_CF(data, cf, "cf_connect() -> %d, %d, ", (int)result, *done); CF_DATA_RESTORE(cf, save); return result; } -static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_h2_ctx *ctx = cf->ctx; - - if(ctx) { - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - 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) { struct cf_h2_ctx *ctx = cf->ctx; @@ -2794,7 +2767,6 @@ struct Curl_cftype Curl_cft_nghttp2 = { CURL_LOG_LVL_NONE, cf_h2_destroy, cf_h2_connect, - cf_h2_close, cf_h2_shutdown, cf_h2_adjust_pollset, cf_h2_data_pending, @@ -2809,7 +2781,7 @@ struct Curl_cftype Curl_cft_nghttp2 = { static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, - int sockindex, + int8_t sockindex, bool via_h1_upgrade) { struct Curl_cfilter *cf = NULL; @@ -2869,9 +2841,7 @@ bool Curl_http2_may_switch(struct Curl_easy *data) (data->state.http_neg.wanted & CURL_HTTP_V2x) && data->state.http_neg.h2_prior_knowledge) { #ifndef CURL_DISABLE_PROXY - 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. */ + if(data->conn->bits.origin_is_proxy) { infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); return FALSE; } @@ -2921,7 +2891,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) } CURLcode Curl_http2_upgrade(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, const char *mem, size_t nread) { struct Curl_cfilter *cf; @@ -2951,7 +2921,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, result = Curl_bufq_write(&ctx->inbufq, (const unsigned char *)mem, nread, &copied); if(result) { - failf(data, "error on copying HTTP Upgrade response: %d", result); + failf(data, "error on copying HTTP Upgrade response: %d", (int)result); return CURLE_RECV_ERROR; } if(copied < nread) { diff --git a/lib/http2.h b/lib/http2.h index e38dc5745cca..c3270da1ccc5 100644 --- a/lib/http2.h +++ b/lib/http2.h @@ -54,7 +54,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data); CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data); CURLcode Curl_http2_upgrade(struct Curl_easy *data, - struct connectdata *conn, int sockindex, + struct connectdata *conn, int8_t sockindex, const char *mem, size_t nread); void *Curl_nghttp2_malloc(size_t size, void *user_data); diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 55efa120478f..b16c2cd2aaa9 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -52,7 +52,7 @@ #define TIMESTAMP_SIZE 17 -/* hex-encoded with trailing null */ +/* hex-encoded with null-terminator */ #define SHA256_HEX_LENGTH ((2 * CURL_SHA256_DIGEST_LENGTH) + 1) #define MAX_QUERY_COMPONENTS 128 @@ -223,8 +223,7 @@ static CURLcode uri_encode_path(struct Curl_str *original_path, } /* Normalize the query part. Make sure %2B is left percent encoded, and not - decoded to plus, then encoded to space. -*/ + decoded to plus, then encoded to space. */ static CURLcode normalize_query(const char *string, size_t len, struct dynbuf *db) { @@ -271,13 +270,12 @@ static bool should_urlencode(struct Curl_str *service_name) * 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 FALSE; } - return true; + return TRUE; } /* maximum length for the aws sivg4 parts */ @@ -371,7 +369,6 @@ static CURLcode merge_duplicate_headers(struct curl_slist *head) /* 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, const char *provider1, size_t plen, /* length of provider1 */ @@ -384,7 +381,7 @@ static CURLcode make_headers(struct Curl_easy *data, char date_full_hdr[DATE_FULL_HDR_LEN]; struct curl_slist *head = NULL; struct curl_slist *tmp_head = NULL; - CURLcode ret = CURLE_OUT_OF_MEMORY; + CURLcode result = CURLE_OUT_OF_MEMORY; struct curl_slist *l; bool again = TRUE; @@ -399,16 +396,10 @@ static CURLcode make_headers(struct Curl_easy *data, /* provider1 lowercase */ Curl_strntolower(&date_full_hdr[2], provider1, plen); - if(!Curl_checkheaders(data, STRCONST("Host"))) { - char *fullhost; - - if(data->state.aptr.host) { - /* remove /r/n as the separator for canonical request must be '\n' */ - size_t pos = strcspn(data->state.aptr.host, "\n\r"); - fullhost = curlx_memdup0(data->state.aptr.host, pos); - } - else - fullhost = curl_maprintf("host:%s", hostname); + if(!Curl_checkheaders(data, STRCONST("Host")) && + data->state.http_host) { + /* Host: [host]:[port] */ + char *fullhost = curlx_strdup(data->state.http_host); if(fullhost) head = Curl_slist_append_nodup(NULL, fullhost); @@ -516,8 +507,8 @@ static CURLcode make_headers(struct Curl_easy *data, } } while(again); - ret = merge_duplicate_headers(head); - if(ret) + result = merge_duplicate_headers(head); + if(result) goto fail; for(l = head; l; l = l->next) { @@ -540,11 +531,11 @@ static CURLcode make_headers(struct Curl_easy *data, goto fail; } - ret = CURLE_OK; + result = CURLE_OK; fail: curl_slist_free_all(head); - return ret; + return result; } #define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256")) @@ -618,17 +609,17 @@ static CURLcode calc_s3_payload_hash(struct Curl_easy *data, bool empty_payload = (empty_method || data->set.filesize == 0); /* The POST payload is in memory */ bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields); - CURLcode ret = CURLE_OUT_OF_MEMORY; + CURLcode result = CURLE_OUT_OF_MEMORY; if(empty_payload || post_payload) { /* Calculate a real hash when we know the request payload */ - ret = calc_payload_hash(data, sha_hash, sha_hex); - if(ret) + result = calc_payload_hash(data, sha_hash, sha_hex); + if(result) goto fail; } else { /* Fall back to s3's UNSIGNED-PAYLOAD */ - size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1; + size_t len = CURL_CSTRLEN(S3_UNSIGNED_PAYLOAD); DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */ memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len); sha_hex[len] = 0; @@ -638,9 +629,9 @@ static CURLcode calc_s3_payload_hash(struct Curl_easy *data, curl_msnprintf(header, CONTENT_SHA256_HDR_LEN, "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex); - ret = CURLE_OK; + result = CURLE_OK; fail: - return ret; + return result; } static int compare_func(const void *a, const void *b) @@ -818,138 +809,103 @@ UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq) return result; } -CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) +static CURLcode parse_sigv4_params(struct Curl_easy *data, + const char *hostname, + struct Curl_str *provider0, + struct Curl_str *provider1, + struct Curl_str *region, + struct Curl_str *service) { - CURLcode result = CURLE_OUT_OF_MEMORY; - struct connectdata *conn = data->conn; - 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; - char timestamp[TIMESTAMP_SIZE]; - 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; - const char *payload_hash = NULL; - size_t payload_hash_len = 0; - 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; - char *request_type = NULL; - char *credential_scope = NULL; - char *str_to_sign = NULL; - const char *user = data->state.aptr.user ? data->state.aptr.user : ""; - char *secret = NULL; - unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = { 0 }; - unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = { 0 }; - char *auth_headers = NULL; - - 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 */ - return CURLE_OK; - } - - /* we init those buffers here, so goto fail will free initialized dynbuf */ - 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 - * Google and Outscale use the same OSC or GOOG, - * but Amazon uses AWS and AMZ for header arguments. - * AWS is the default because most of non-amazon providers - * are still using aws:amz as a prefix. - */ - line = data->set.str[STRING_AWS_SIGV4]; + const char *line = CURL_EASY_STR(data, STRING_AWS_SIGV4); if(!line || !*line) line = "aws:amz"; /* provider0[:provider1[:region[:service]]] - No string can be longer than N bytes of non-whitespace - */ - if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) { + No string can be longer than N bytes of non-whitespace */ + 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; + return CURLE_BAD_FUNCTION_ARGUMENT; } if(curlx_str_single(&line, ':') || - curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) { - provider1 = provider0; + 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_until(&line, region, MAX_SIGV4_LEN, ':') || curlx_str_single(&line, ':') || - curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) { + curlx_str_until(&line, service, MAX_SIGV4_LEN, ':')) { /* nothing to do */ } - if(!curlx_strlen(&service)) { + if(!curlx_strlen(service)) { const char *p = hostname; - if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') || + 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; + return CURLE_URL_MALFORMAT; } infof(data, "aws_sigv4: picked service %.*s from host", - (int)curlx_strlen(&service), curlx_str(&service)); + (int)curlx_strlen(service), curlx_str(service)); - if(!curlx_strlen(®ion)) { - if(curlx_str_until(&p, ®ion, MAX_SIGV4_LEN, '.') || + if(!curlx_strlen(region)) { + if(curlx_str_until(&p, region, MAX_SIGV4_LEN, '.') || curlx_str_single(&p, '.')) { failf(data, "aws-sigv4: region missing in parameters and hostname"); - result = CURLE_URL_MALFORMAT; - goto fail; + return CURLE_URL_MALFORMAT; } infof(data, "aws_sigv4: picked region %.*s from host", - (int)curlx_strlen(®ion), curlx_str(®ion)); + (int)curlx_strlen(region), curlx_str(region)); } } - Curl_http_method(data, &method, &httpreq); + return CURLE_OK; +} - payload_hash = - parse_content_sha_hdr(data, curlx_str(&provider1), - curlx_strlen(&provider1), &payload_hash_len); +static CURLcode get_payload_hash(struct Curl_easy *data, + Curl_HttpReq httpreq, + struct Curl_str *provider0, + struct Curl_str *provider1, + struct Curl_str *service, + unsigned char *sha_hash, + char *sha_hex, + char *content_sha256_hdr, + const char **payload_hash_out, + size_t *payload_hash_len_out) +{ + *payload_hash_out = + parse_content_sha_hdr(data, curlx_str(provider1), + curlx_strlen(provider1), payload_hash_len_out); - if(!payload_hash) { + if(!*payload_hash_out) { + CURLcode result; /* 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"); + bool sign_as_s3 = curlx_str_casecompare(provider0, "aws") && + curlx_str_casecompare(service, "s3"); if(sign_as_s3) - result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1), - curlx_strlen(&provider1), sha_hash, + result = calc_s3_payload_hash(data, httpreq, curlx_str(provider1), + curlx_strlen(provider1), sha_hash, sha_hex, content_sha256_hdr); else result = calc_payload_hash(data, sha_hash, sha_hex); if(result) - goto fail; + return result; - payload_hash = sha_hex; + *payload_hash_out = sha_hex; /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */ - payload_hash_len = strlen(sha_hex); + *payload_hash_len_out = strlen(sha_hex); } + return CURLE_OK; +} + +static CURLcode get_timestamp(char *timestamp, size_t stampsize) +{ + time_t clock; + struct tm tm; + CURLcode result; #ifdef DEBUGBUILD { @@ -963,30 +919,41 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) clock = time(NULL); #endif result = curlx_gmtime(clock, &tm); - if(result) { - goto fail; - } - if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - 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; + return result; - if(*content_sha256_hdr) { - /* make_headers() needed this without the \r\n for canonicalization */ - size_t hdrlen = strlen(content_sha256_hdr); - DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr)); - memcpy(content_sha256_hdr + hdrlen, "\r\n", 3); - } + if(!strftime(timestamp, stampsize, "%Y%m%dT%H%M%SZ", &tm)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static CURLcode make_canonical_request(struct Curl_easy *data, + char *timestamp, + struct Curl_str *provider1, + struct Curl_str *service, + const char *method, + const char *payload_hash, + size_t payload_hash_len, + char **date_header_out, + char *content_sha256_hdr, + struct dynbuf *canonical_headers, + struct dynbuf *signed_headers, + char **canonical_request_out) +{ + struct dynbuf canonical_query; + struct dynbuf canonical_path; + CURLcode result; + + curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); - memcpy(date, timestamp, sizeof(date)); - date[sizeof(date) - 1] = 0; + result = make_headers(data, timestamp, + curlx_str(provider1), curlx_strlen(provider1), + date_header_out, content_sha256_hdr, + canonical_headers, signed_headers); + if(result) + goto fail; result = canon_query(data->state.up.query, &canonical_query); if(result) @@ -994,12 +961,11 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) result = canon_path(data->state.up.path, strlen(data->state.up.path), &canonical_path, - should_urlencode(&service)); + should_urlencode(service)); if(result) goto fail; - result = CURLE_OUT_OF_MEMORY; - canonical_request = + *canonical_request_out = curl_maprintf("%s\n" /* HTTPRequestMethod */ "%s\n" /* CanonicalURI */ "%s\n" /* CanonicalQueryString */ @@ -1010,37 +976,65 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) 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), + curlx_dyn_ptr(canonical_headers), + curlx_dyn_ptr(signed_headers), (int)payload_hash_len, payload_hash); - if(!canonical_request) + if(!*canonical_request_out) { + result = CURLE_OUT_OF_MEMORY; goto fail; + } + + result = CURLE_OK; +fail: + curlx_dyn_free(&canonical_query); + curlx_dyn_free(&canonical_path); + return result; +} - infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]", - canonical_request); +static CURLcode make_string_to_sign(struct Curl_easy *data, + struct Curl_str *provider0, + struct Curl_str *region, + struct Curl_str *service, + const char *date, + const char *timestamp, + const char *canonical_request, + char **request_type_out, + char **credential_scope_out, + char **str_to_sign_out) +{ + char *request_type; + char *credential_scope; + char *str_to_sign; + unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH]; + char sha_hex[SHA256_HEX_LENGTH]; request_type = curl_maprintf("%.*s4_request", - (int)curlx_strlen(&provider0), - curlx_str(&provider0)); + (int)curlx_strlen(provider0), + curlx_str(provider0)); if(!request_type) - goto fail; + return CURLE_OUT_OF_MEMORY; /* provider0 is lowercased *after* curl_maprintf() so that the buffer can be written to */ - Curl_strntolower(request_type, request_type, curlx_strlen(&provider0)); + Curl_strntolower(request_type, request_type, curlx_strlen(provider0)); credential_scope = curl_maprintf("%s/%.*s/%.*s/%s", date, - (int)curlx_strlen(®ion), - curlx_str(®ion), - (int)curlx_strlen(&service), - curlx_str(&service), + (int)curlx_strlen(region), + curlx_str(region), + (int)curlx_strlen(service), + curlx_str(service), request_type); - if(!credential_scope) - goto fail; + if(!credential_scope) { + curlx_free(request_type); + return CURLE_OUT_OF_MEMORY; + } - if(Curl_sha256it(sha_hash, (unsigned char *)canonical_request, - strlen(canonical_request))) - goto fail; + if(Curl_sha256it(sha_hash, (const unsigned char *)canonical_request, + strlen(canonical_request))) { + curlx_free(request_type); + curlx_free(credential_scope); + return CURLE_OUT_OF_MEMORY; + } sha256_to_hex(sha_hex, sha_hash); @@ -1052,36 +1046,69 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) "%s\n" /* RequestDateTime */ "%s\n" /* CredentialScope */ "%s", /* HashedCanonicalRequest in hex */ - (int)curlx_strlen(&provider0), - curlx_str(&provider0), + (int)curlx_strlen(provider0), + curlx_str(provider0), timestamp, credential_scope, sha_hex); - if(!str_to_sign) - goto fail; + if(!str_to_sign) { + curlx_free(request_type); + curlx_free(credential_scope); + return CURLE_OUT_OF_MEMORY; + } /* make provider0 part done uppercase */ - Curl_strntoupper(str_to_sign, curlx_str(&provider0), - curlx_strlen(&provider0)); + 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 = curl_maprintf("%.*s4%s", (int)curlx_strlen(&provider0), - curlx_str(&provider0), data->state.aptr.passwd ? - data->state.aptr.passwd : ""); + *request_type_out = request_type; + *credential_scope_out = credential_scope; + *str_to_sign_out = str_to_sign; + return CURLE_OK; +} + +static CURLcode sign_and_set_auth_headers(struct Curl_easy *data, + struct Curl_str *provider0, + struct Curl_str *region, + struct Curl_str *service, + const char *request_type, + const char *credential_scope, + const char *date, + const char *str_to_sign, + const char *date_header, + const char *content_sha256_hdr, + struct dynbuf *signed_headers) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + const char *passwd = Curl_creds_passwd(data->state.creds); + char *secret = NULL; + unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = { 0 }; + unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = { 0 }; + char sha_hex[SHA256_HEX_LENGTH]; + char *auth_headers = NULL; + char *user = curl_escape(Curl_creds_user(data->state.creds), 0); + if(!user) + return CURLE_OUT_OF_MEMORY; + + secret = curl_maprintf("%.*s4%s", (int)curlx_strlen(provider0), + curlx_str(provider0), passwd); if(!secret) goto fail; /* make provider0 part done uppercase */ - Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0)); + Curl_strntoupper(secret, curlx_str(provider0), curlx_strlen(provider0)); HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); HMAC_SHA256(sign0, sizeof(sign0), - curlx_str(®ion), curlx_strlen(®ion), sign1); + curlx_str(region), curlx_strlen(region), 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), - 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); + str_to_sign, strlen(str_to_sign), sign0); sha256_to_hex(sha_hex, sign0); @@ -1091,43 +1118,119 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) "Credential=%s/%s, " "SignedHeaders=%s, " "Signature=%s\r\n" + "%s" + "%s%s", + (int)curlx_strlen(provider0), + curlx_str(provider0), + user, + credential_scope, + curlx_dyn_ptr(signed_headers), + sha_hex, /* * 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) { + content_sha256_hdr, + content_sha256_hdr[0] ? "\r\n": ""); + if(!auth_headers) goto fail; - } + /* provider 0 uppercase */ - Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1], - curlx_str(&provider0), curlx_strlen(&provider0)); + Curl_strntoupper(&auth_headers[CURL_CSTRLEN("Authorization: ")], + curlx_str(provider0), curlx_strlen(provider0)); - curlx_free(data->req.userpwd); - data->req.userpwd = auth_headers; + curlx_free(data->req.hd_auth); + data->req.hd_auth = auth_headers; data->state.authhost.done = TRUE; result = CURLE_OK; fail: - curlx_dyn_free(&canonical_query); - curlx_dyn_free(&canonical_path); + curlx_free(user); + curlx_free(secret); + return result; +} + +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + struct Curl_str provider0 = { NULL, 0 }; + struct Curl_str provider1 = { NULL, 0 }; + struct Curl_str region = { NULL, 0 }; + struct Curl_str service = { NULL, 0 }; + const char *hostname = data->state.origin->hostname; + char timestamp[TIMESTAMP_SIZE]; + char date[9]; + struct dynbuf canonical_headers; + struct dynbuf signed_headers; + char *date_header = NULL; + Curl_HttpReq httpreq; + const char *method = NULL; + const char *payload_hash = NULL; + size_t payload_hash_len = 0; + 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; + char *request_type = NULL; + char *credential_scope = NULL; + char *str_to_sign = NULL; + + 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 */ + return CURLE_OK; + + /* we init those buffers here, so goto fail will free initialized dynbuf */ + curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + + result = parse_sigv4_params(data, hostname, &provider0, &provider1, + ®ion, &service); + if(!result) { + Curl_http_method(data, &method, &httpreq); + result = get_payload_hash(data, httpreq, &provider0, &provider1, &service, + sha_hash, sha_hex, content_sha256_hdr, + &payload_hash, &payload_hash_len); + } + + if(!result) + result = get_timestamp(timestamp, sizeof(timestamp)); + + if(!result) + result = make_canonical_request(data, timestamp, + &provider1, &service, + method, payload_hash, payload_hash_len, + &date_header, content_sha256_hdr, + &canonical_headers, &signed_headers, + &canonical_request); + if(!result) { + /* the timestamp might have been updated in make_canonical_request */ + memcpy(date, timestamp, sizeof(date) - 1); + date[sizeof(date) - 1] = 0; + + result = make_string_to_sign(data, &provider0, ®ion, &service, + date, timestamp, canonical_request, + &request_type, &credential_scope, + &str_to_sign); + } + if(!result) + result = sign_and_set_auth_headers(data, &provider0, ®ion, &service, + request_type, credential_scope, + date, str_to_sign, date_header, + content_sha256_hdr, &signed_headers); + curlx_dyn_free(&canonical_headers); curlx_dyn_free(&signed_headers); curlx_free(canonical_request); curlx_free(request_type); curlx_free(credential_scope); curlx_free(str_to_sign); - curlx_free(secret); curlx_free(date_header); return result; } diff --git a/lib/http_chunks.c b/lib/http_chunks.c index aa45c79e3853..95929d27ba05 100644 --- a/lib/http_chunks.c +++ b/lib/http_chunks.c @@ -27,6 +27,7 @@ #include "urldata.h" /* it includes http_chunks.h */ #include "curl_trc.h" +#include "http.h" /* for Curl_verify_header */ #include "sendf.h" /* for the client write stuff */ #include "curlx/dynbuf.h" #include "multiif.h" @@ -70,7 +71,7 @@ */ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, - bool ignore_body) + bool ignore_body, bool in_connect) { (void)data; ch->hexindex = 0; /* start at 0 */ @@ -78,6 +79,7 @@ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, ch->last_code = CHUNKE_OK; curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER); ch->ignore_body = ignore_body; + ch->in_connect = in_connect; } void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, @@ -153,7 +155,8 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, if(ch->hexindex == 0) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ - failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf); + failf(data, "chunk hex-length char not a hex digit: 0x%x", + (unsigned int)*buf); ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_ILLEGAL_HEX; return CURLE_RECV_ERROR; @@ -192,8 +195,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, case CHUNK_DATA: /* We expect 'datasize' of data. We have 'blen' right now, it can be - more or less than 'datasize'. Get the smallest piece. - */ + more or less than 'datasize'. Get the smallest piece. */ piece = blen; if(ch->datasize < (curl_off_t)blen) piece = curlx_sotouz(ch->datasize); @@ -247,6 +249,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, there was no trailer and we move on */ if(tr) { + size_t trlen; result = curlx_dyn_addn(&ch->trailer, STRCONST("\x0d\x0a")); if(result) { ch->state = CHUNK_FAILED; @@ -254,18 +257,26 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, return result; } tr = curlx_dyn_ptr(&ch->trailer); + trlen = curlx_dyn_len(&ch->trailer); + + /* a trailer is delivered to the client as a header, so it must pass + the same checks as a regular response header */ + result = Curl_verify_header(data, tr, trlen); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return result; + } + if(!data->set.http_te_skip) { - size_t trlen = curlx_dyn_len(&ch->trailer); + int hd_type = CLIENTWRITE_HEADER | CLIENTWRITE_TRAILER; + if(ch->in_connect) + hd_type |= CLIENTWRITE_CONNECT; if(cw_next) - result = Curl_cwriter_write(data, cw_next, - CLIENTWRITE_HEADER | - CLIENTWRITE_TRAILER, - tr, trlen); + result = Curl_cwriter_write(data, cw_next, hd_type, tr, trlen); else - result = Curl_client_write(data, - CLIENTWRITE_HEADER | - CLIENTWRITE_TRAILER, - tr, trlen); + result = Curl_client_write(data, hd_type, tr, trlen); + CURL_TRC_WRITE(data, "wrote trailer '%s'", tr); if(result) { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_PASSTHRU_ERROR; @@ -396,7 +407,7 @@ static CURLcode cw_chunked_init(struct Curl_easy *data, struct chunked_writer *ctx = writer->ctx; data->req.chunk = TRUE; /* chunks coming our way. */ - Curl_httpchunk_init(data, &ctx->ch, FALSE); + Curl_httpchunk_init(data, &ctx->ch, FALSE, FALSE); return CURLE_OK; } @@ -453,8 +464,10 @@ static CURLcode cw_chunked_write(struct Curl_easy *data, const struct Curl_cwtype Curl_httpchunk_unencoder = { "chunked", NULL, + 0, cw_chunked_init, cw_chunked_write, + Curl_cwriter_def_flush, cw_chunked_close, sizeof(struct chunked_writer) }; @@ -505,9 +518,12 @@ static CURLcode add_last_chunk(struct Curl_easy *data, 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); + { + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_trailer_callback); + rc = data->set.trailer_callback(&trailers, data->set.trailer_data); + CURL_CBAPI_END(&guard); + } if(rc != CURL_TRAILERFUNC_OK) { failf(data, "operation aborted by trailing headers callback"); @@ -535,7 +551,7 @@ static CURLcode add_last_chunk(struct Curl_easy *data, out: curl_slist_free_all(trailers); CURL_TRC_READ(data, "http_chunk, added last chunk with trailers " - "from client -> %d", result); + "from client -> %d", (int)result); return result; } @@ -583,7 +599,7 @@ static CURLcode add_chunk(struct Curl_easy *data, 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); + nread, (int)result); if(result) return result; } diff --git a/lib/http_chunks.h b/lib/http_chunks.h index d8e5982e6d87..0cb1f8834093 100644 --- a/lib/http_chunks.h +++ b/lib/http_chunks.h @@ -99,11 +99,12 @@ struct Curl_chunker { unsigned char hexindex; char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ BIT(ignore_body); /* never write response body data */ + BIT(in_connect); /* this is a CONNECT response body */ }; /* The following functions are defined in http_chunks.c */ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, - bool ignore_body); + bool ignore_body, bool in_connect); 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); diff --git a/lib/http_digest.c b/lib/http_digest.c index b7007071e77e..1ec29bd391a1 100644 --- a/lib/http_digest.c +++ b/lib/http_digest.c @@ -26,18 +26,46 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) #include "urldata.h" +#include "curl_trc.h" #include "strcase.h" #include "vauth/vauth.h" #include "http_digest.h" #include "curlx/strparse.h" -/* Test example headers: +/* Flush the Digest state if it was created for a different origin or with + different credentials than the ones now in use, then link the current + ones. */ +static void digest_flush_stale(struct Curl_easy *data, + struct digestdata *digest, + struct Curl_peer *peer, + struct Curl_creds *creds) +{ + bool flush = FALSE; + if(digest->origin && !Curl_peer_same_destination(peer, digest->origin)) { + CURL_TRC_M(data, "http_digest, reset on peer change to %s:%u", + peer->hostname, peer->port); + flush = TRUE; + } + else if(digest->creds && !Curl_creds_same(creds, digest->creds)) { + CURL_TRC_M(data, "http_digest, reset on creds change to %s", + creds ? creds->user : "-"); + flush = TRUE; + } + + if(flush) { + /* flush Digest state */ + Curl_auth_digest_cleanup(digest); + } -WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" -Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" + Curl_peer_link(&digest->origin, peer); + Curl_creds_link(&digest->creds, creds); +} -*/ +/* Test example headers: + WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" + Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" + */ CURLcode Curl_input_digest(struct Curl_easy *data, bool proxy, const char *header) /* rest of the *-authenticate: @@ -45,21 +73,37 @@ CURLcode Curl_input_digest(struct Curl_easy *data, { /* Point to the correct struct with this */ struct digestdata *digest; + struct Curl_peer *origin = NULL; + CURLcode result; if(proxy) { digest = &data->state.proxydigest; +#ifdef CURL_DISABLE_PROXY + Curl_auth_digest_cleanup(digest); + return CURLE_OK; /* just ignore such a header without proxy support */ +#else + origin = data->conn->http_proxy.peer; +#endif } else { digest = &data->state.digest; + origin = data->state.origin; } - if(!checkprefix("Digest", header) || !ISBLANK(header[6])) - return CURLE_BAD_CONTENT_ENCODING; + if(!checkprefix("Digest", header) || !ISBLANK(header[6])) { + Curl_auth_digest_cleanup(digest); + return CURLE_AUTH_ERROR; + } - header += strlen("Digest"); + header += CURL_CSTRLEN("Digest"); curlx_str_passblanks(&header); - return Curl_auth_decode_digest_http_message(header, digest); + /* This resets the digest struct before decoding */ + result = Curl_auth_decode_digest_http_message(header, digest); + /* Remember only the peer, the data we take in has no relation to creds + * at this time. We can use it even if creds change. */ + Curl_peer_link(&digest->origin, origin); + return result; } CURLcode Curl_output_digest(struct Curl_easy *data, @@ -67,9 +111,8 @@ CURLcode Curl_output_digest(struct Curl_easy *data, const unsigned char *request, const unsigned char *uripath) { + struct Curl_peer *origin = NULL; CURLcode result; - unsigned char *path = NULL; - const char *tmp = NULL; char *response; size_t len; bool have_chlg; @@ -79,8 +122,7 @@ CURLcode Curl_output_digest(struct Curl_easy *data, char **allocuserpwd; /* Point to the name and password for this */ - const char *userp; - const char *passwdp; + struct Curl_creds *creds = NULL; /* Point to the correct struct with this */ struct digestdata *digest; @@ -91,29 +133,24 @@ CURLcode Curl_output_digest(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; #else digest = &data->state.proxydigest; - allocuserpwd = &data->req.proxyuserpwd; - userp = data->state.aptr.proxyuser; - passwdp = data->state.aptr.proxypasswd; + origin = data->conn->http_proxy.peer; + creds = data->conn->http_proxy.creds; + allocuserpwd = &data->req.hd_proxy_auth; authp = &data->state.authproxy; #endif } else { + DEBUGASSERT(data->state.origin); digest = &data->state.digest; - allocuserpwd = &data->req.userpwd; - userp = data->state.aptr.user; - passwdp = data->state.aptr.passwd; + origin = data->state.origin; + creds = data->state.creds; + allocuserpwd = &data->req.hd_auth; authp = &data->state.authhost; } + digest_flush_stale(data, digest, origin, creds); curlx_safefree(*allocuserpwd); - /* not set means empty */ - if(!userp) - userp = ""; - - if(!passwdp) - passwdp = ""; - #ifdef USE_WINDOWS_SSPI have_chlg = !!digest->input_token; #else @@ -125,36 +162,9 @@ CURLcode Curl_output_digest(struct Curl_easy *data, return CURLE_OK; } - /* IE browsers < v7 cut off the URI part at the query part when they - evaluate the MD5 and some (IIS?) servers work with them so we may need to - do the Digest IE-style. Note that the different ways cause different MD5 - sums to get sent. - - Apache servers can be set to do the Digest IE-style automatically using - the BrowserMatch feature: - https://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie - - Further details on Digest implementation differences: - https://web.archive.org/web/2009/fngtps.com/2006/09/http-authentication - */ - - if(authp->iestyle) { - tmp = strchr((const char *)uripath, '?'); - if(tmp) { - size_t urilen = tmp - (const char *)uripath; - /* typecast is fine here since the value is always less than 32 bits */ - path = (unsigned char *)curl_maprintf("%.*s", (int)urilen, uripath); - } - } - if(!tmp) - path = (unsigned char *)curlx_strdup((const char *)uripath); - - if(!path) - return CURLE_OUT_OF_MEMORY; - - result = Curl_auth_create_digest_http_message(data, userp, passwdp, request, - path, digest, &response, &len); - curlx_free(path); + result = Curl_auth_create_digest_http_message(data, creds, request, + uripath, digest, + &response, &len); if(result) return result; diff --git a/lib/http_httpsig.c b/lib/http_httpsig.c new file mode 100644 index 000000000000..37f23a09cbe5 --- /dev/null +++ b/lib/http_httpsig.c @@ -0,0 +1,689 @@ +/*************************************************************************** + * _ _ ____ _ + * 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(CURL_DISABLE_HTTPSIG) + +#include "urldata.h" +#include "http_httpsig.h" +#include "curl_ed25519.h" +#include "curl_hmac.h" +#include "curl_sha256.h" +#include "http.h" +#include "transfer.h" +#include "curl_trc.h" +#include "slist.h" +#include "curlx/dynbuf.h" +#include "curlx/base64.h" +#include "curlx/strdup.h" +#include "curlx/strparse.h" +#include "strcase.h" + +#include + +#define HTTPSIG_MAX_SIG_BASE CURL_MAX_HTTP_HEADER +#define HTTPSIG_MAX_COMPONENTS 16 +#define HTTPSIG_MAX_RAW_SIG CURL_ED25519_SIGLEN +#define HTTPSIG_DEFAULT_LABEL "sig1" + +enum httpsig_alg { + HTTPSIG_ALG_ED25519, + HTTPSIG_ALG_HMAC_SHA256, + HTTPSIG_ALG_UNKNOWN +}; + +static const char *alg_to_str(enum httpsig_alg alg) +{ + switch(alg) { + case HTTPSIG_ALG_ED25519: + return "ed25519"; + case HTTPSIG_ALG_HMAC_SHA256: + return "hmac-sha256"; + default: + break; + } + return NULL; +} + +static enum httpsig_alg id_to_alg(uint8_t val) +{ + switch(val) { + case CURLHTTPSIG_ED25519: + return HTTPSIG_ALG_ED25519; + case CURLHTTPSIG_HMAC_SHA256: + return HTTPSIG_ALG_HMAC_SHA256; + default: + break; + } + return HTTPSIG_ALG_UNKNOWN; +} + +static CURLcode decode_hex_key(struct Curl_easy *data, + const char *hexstr, + unsigned char **keyout, + size_t *keylen) +{ + size_t len, i; + unsigned char *keybuf; + + *keyout = NULL; + *keylen = 0; + + len = strlen(hexstr); + while(len > 0 && ISNEWLINE(hexstr[len - 1])) + len--; + + if(len == 0 || (len & 1) != 0) { + failf(data, "httpsig: invalid hex key (length %zu)", len); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + if(len > CURL_MAX_INPUT_LENGTH) { + failf(data, "httpsig: hex key too long"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + keybuf = curlx_malloc(len / 2); + if(!keybuf) + return CURLE_OUT_OF_MEMORY; + + for(i = 0; i < len; i += 2) { + if(!ISXDIGIT(hexstr[i]) || !ISXDIGIT(hexstr[i + 1])) { + failf(data, "httpsig: invalid hex at position %zu ('%c%c')", + i, hexstr[i], hexstr[i + 1]); + curlx_free(keybuf); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + keybuf[i / 2] = (unsigned char)((curlx_hexval(hexstr[i]) << 4) | + curlx_hexval(hexstr[i + 1])); + } + + *keyout = keybuf; + *keylen = len / 2; + return CURLE_OK; +} + +/* @authority matches the Host header field-value when available (RFC 9421). + data->state.http_host is produced by http_set_aptr_host() before auth. */ +static CURLcode httpsig_authority(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *authority_buf) +{ + const char *h = data->state.http_host; + + if(h && curl_strnequal(h, "host:", 5)) { + const char *value = h + 5; + const char *end; + + while(ISBLANK(*value)) + value++; + if(*value) { + CURLcode result; + + end = value; + while(*end && !ISNEWLINE(*end)) + end++; + while(end > value && ISBLANK(end[-1])) + end--; + result = curlx_dyn_addn(authority_buf, value, (size_t)(end - value)); + if(result) + return result; + return CURLE_OK; + } + } + + { + const char *hostname = conn->origin->hostname; + uint16_t port = conn->origin->port; + + if((conn->given->defport != port) && port) + return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port); + return curlx_dyn_add(authority_buf, hostname); + } +} + +static CURLcode sf_append_quoted(struct dynbuf *buf, const char *str) +{ + CURLcode result = curlx_dyn_addn(buf, "\"", 1); + if(result) + return result; + while(*str) { + if(ISCNTRL(*str)) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(*str == '\\' || *str == '"') { + result = curlx_dyn_addn(buf, "\\", 1); + if(result) + return result; + } + result = curlx_dyn_addn(buf, str, 1); + if(result) + return result; + str++; + } + return curlx_dyn_addn(buf, "\"", 1); +} + +/* base64-encode raw bytes into an RFC 8941 byte sequence (:base64:) */ +static CURLcode sf_encode_byte_seq(const unsigned char *raw, size_t rawlen, + struct dynbuf *out) +{ + CURLcode result; + size_t b64len; + char *b64; + + result = curlx_base64_encode(raw, rawlen, &b64, &b64len); + if(result) + return result; + + result = curlx_dyn_addn(out, ":", 1); + if(!result) + result = curlx_dyn_addn(out, b64, b64len); + if(!result) + result = curlx_dyn_addn(out, ":", 1); + + curlx_free(b64); + return result; +} + +static CURLcode build_sig_params(struct dynbuf *params, + const char **components, size_t count, + time_t created, const char *keyid, + enum httpsig_alg alg) +{ + CURLcode result; + size_t i; + + result = curlx_dyn_addn(params, "(", 1); + if(result) + return result; + + for(i = 0; i < count; i++) { + if(i > 0) { + result = curlx_dyn_addn(params, " ", 1); + if(result) + return result; + } + result = sf_append_quoted(params, components[i]); + if(result) + return result; + } + + result = curlx_dyn_addn(params, ")", 1); + if(result) + return result; + + result = curlx_dyn_addf(params, ";created=%lld", (long long)created); + if(result) + return result; + + if(keyid && *keyid) { + result = curlx_dyn_add(params, ";keyid="); + if(result) + return result; + result = sf_append_quoted(params, keyid); + if(result) + return result; + } + + result = curlx_dyn_addf(params, ";alg=\"%s\"", alg_to_str(alg)); + return result; +} + +/* strings defined by RFC 9421 */ +#define SIG_METHOD "@method" +#define SIG_AUTHORITY "@authority" +#define SIG_PATH "@path" +#define SIG_QUERY "@query" + +/* Resolve a component identifier to its value. + * For headers, we walk the full user-supplied header list to combine + * duplicate field values with ", " per RFC 9421 Section 2.1. Each + * individual value is trimmed of leading/trailing OWS and the trailing + * \r\n. The combined result is written into the caller-provided buffer. */ +static CURLcode resolve_component(const char *name, + const char *method, + const char *authority, + const char *path, + const char *query, + struct Curl_easy *data, + struct dynbuf *valbuf, + const char **out) +{ + *out = NULL; + + if(name[0] == '@') { + if(curl_strequal(name, SIG_METHOD)) + *out = method; + else if(curl_strequal(name, SIG_AUTHORITY)) + *out = authority; + else if(curl_strequal(name, SIG_PATH)) + *out = path; + else if(curl_strequal(name, SIG_QUERY)) + *out = query; + else { + failf(data, "httpsig: unsupported derived component '%s'", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!*out) { + failf(data, "httpsig: derived component '%s' has no value", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; + } + else { + /* RFC 9421 Section 2.1: walk all user-supplied headers and combine + duplicate field values with ", " per HTTP field combination rules. */ + struct curl_slist *head; + size_t namelen = strlen(name); + bool found = FALSE; + + curlx_dyn_reset(valbuf); + + for(head = data->set.headers; head; head = head->next) { + if(curl_strnequal(head->data, name, namelen) && + Curl_headersep(head->data[namelen])) { + const char *p = strchr(head->data, ':'); + if(p) { + CURLcode result; + struct Curl_str content; + curlx_str_assign(&content, p + 1, strlen(p + 1)); + curlx_str_trimblanks(&content); + if(found) { + result = curlx_dyn_addn(valbuf, ", ", 2); + if(result) + return result; + } + result = curlx_dyn_addn(valbuf, curlx_str(&content), + curlx_strlen(&content)); + if(result) + return result; + found = TRUE; + } + } + } + + if(found) { + *out = curlx_dyn_ptr(valbuf); + return CURLE_OK; + } + failf(data, "httpsig: header '%s' not found in request", name); + return CURLE_BAD_FUNCTION_ARGUMENT; + } +} + +static CURLcode build_sig_base(struct dynbuf *base, + const char **components, size_t count, + const char *method, + const char *authority, + const char *path, + const char *query, + struct Curl_easy *data, + const char *sig_params) +{ + CURLcode result; + size_t i; + struct dynbuf hdrvalbuf; + + curlx_dyn_init(&hdrvalbuf, CURL_MAX_HTTP_HEADER); + + for(i = 0; i < count; i++) { + const char *val = NULL; + result = resolve_component(components[i], method, + authority, path, query, data, + &hdrvalbuf, &val); + if(result || !val) { + failf(data, "httpsig: cannot resolve component '%s'", components[i]); + curlx_dyn_free(&hdrvalbuf); + return result ? result : CURLE_BAD_FUNCTION_ARGUMENT; + } + + result = curlx_dyn_addf(base, "\"%s\": %s\n", components[i], val); + if(result) { + curlx_dyn_free(&hdrvalbuf); + return result; + } + } + + curlx_dyn_free(&hdrvalbuf); + result = curlx_dyn_addf(base, "\"@signature-params\": %s", sig_params); + return result; +} + +static CURLcode parse_components(struct Curl_easy *data, + const char *query, + const char **components, + size_t *ncomp_out, + char **hdrs_copy_out) +{ + const char *hdrs = CURL_EASY_STR(data, STRING_HTTPSIG_HEADERS); + size_t ncomp = 0; + + *hdrs_copy_out = NULL; + if(hdrs && *hdrs) { + char *p; + char *hdrs_copy = curlx_strdup(hdrs); + if(!hdrs_copy) + return CURLE_OUT_OF_MEMORY; + *hdrs_copy_out = hdrs_copy; + p = hdrs_copy; + while(*p && ncomp < HTTPSIG_MAX_COMPONENTS) { + char *start; + size_t tlen = 0; + const char *p2 = p; + + curlx_str_passblanks(&p2); + if(!*p2) + break; + p = start = CURL_UNCONST(p2); + while(*p && !ISBLANK(*p)) { + if((*p == '\"') || (*p == '\\')) + return CURLE_BAD_FUNCTION_ARGUMENT; + p++; + tlen++; + } + if(*p) + *p++ = '\0'; + + if(tlen && start[tlen - 1] == ':') { + /* Header field: drop the trailing ':' marker. RFC 9421 field + names are canonically lowercase (Section 2.1). */ + start[--tlen] = '\0'; + if(!tlen) { + failf(data, "httpsig: empty header component name"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + Curl_strntolower(start, start, tlen); + components[ncomp++] = start; + } + else { + /* Derived component: map the bare name to its canonical RFC 9421 + '@'-prefixed identifier (Section 2.2). */ + Curl_strntolower(start, start, tlen); + if(!strcmp(start, "method")) + components[ncomp++] = SIG_METHOD; + else if(!strcmp(start, "authority")) + components[ncomp++] = SIG_AUTHORITY; + else if(!strcmp(start, "path")) + components[ncomp++] = SIG_PATH; + else if(!strcmp(start, "query")) + components[ncomp++] = SIG_QUERY; + else { + failf(data, "httpsig: unknown component '%s'", start); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + } + if(!ncomp) { + failf(data, "httpsig: no signature components specified"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(*p) { + failf(data, "httpsig: too many signature components (max %u)", + (unsigned int)HTTPSIG_MAX_COMPONENTS); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* RFC 9421 Section 2: each covered component MUST occur only once */ + { + size_t i, j; + + for(i = 0; i < ncomp; i++) { + for(j = i + 1; j < ncomp; j++) { + if(!strcmp(components[i], components[j])) { + failf(data, "httpsig: duplicate signature component '%s'", + components[i]); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + } + } + } + else { + components[ncomp++] = SIG_METHOD; + components[ncomp++] = SIG_AUTHORITY; + components[ncomp++] = SIG_PATH; + if(query) + components[ncomp++] = SIG_QUERY; + } + + *ncomp_out = ncomp; + return CURLE_OK; +} + +static time_t httpsig_get_created(void) +{ +#ifdef DEBUGBUILD + char *force = getenv("CURL_FORCETIME"); + if(force && *force) { + char *sigts = getenv("CURL_HTTPSIG_CREATED"); + if(sigts && *sigts) { + const char *p = sigts; + curl_off_t num; + if(!curlx_str_number(&p, &num, CURL_OFF_T_MAX)) + return (time_t)num; + } + return 0; + } +#endif + return time(NULL); +} + +static CURLcode httpsig_sign_base(struct Curl_easy *data, + enum httpsig_alg alg, + const unsigned char *keybuf, + size_t keylen, + const struct dynbuf *sig_base, + unsigned char *raw_sig, + size_t *raw_sig_len) +{ + CURLcode result; + + switch(alg) { + case HTTPSIG_ALG_ED25519: + result = Curl_ed25519_sign( + keybuf, keylen, + (const unsigned char *)curlx_dyn_ptr(sig_base), + curlx_dyn_len(sig_base), + raw_sig, raw_sig_len); + break; + case HTTPSIG_ALG_HMAC_SHA256: + result = Curl_hmacit(&Curl_HMAC_SHA256, keybuf, keylen, + (const unsigned char *)curlx_dyn_ptr(sig_base), + curlx_dyn_len(sig_base), raw_sig); + if(!result) + *raw_sig_len = CURL_SHA256_DIGEST_LENGTH; + break; + default: + result = CURLE_BAD_FUNCTION_ARGUMENT; + break; + } + + if(result && result == CURLE_NOT_BUILT_IN) { + failf(data, "httpsig: algorithm '%s' not supported by TLS backend", + alg_to_str(alg)); + } + return result; +} + +CURLcode Curl_output_httpsig(struct Curl_easy *data) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + struct connectdata *conn = data->conn; + const char *path; + const char *query; + Curl_HttpReq httpreq; + const char *method = NULL; + const char *hexkey = CURL_EASY_STR(data, STRING_HTTPSIG_KEY); + const char *keyid = CURL_EASY_STR(data, STRING_HTTPSIG_KEYID); + enum httpsig_alg alg; + time_t created; + struct dynbuf sig_params; + struct dynbuf sig_base; + struct dynbuf sig_hdr; + struct dynbuf input_hdr; + struct dynbuf authority_buf; + const char *authority; + const char *components[HTTPSIG_MAX_COMPONENTS]; + size_t ncomp = 0; + unsigned char *keybuf = NULL; + size_t keylen = 0; + unsigned char raw_sig[HTTPSIG_MAX_RAW_SIG]; + size_t raw_sig_len = 0; + char *auth_headers = NULL; + char *hdrs_copy = NULL; + struct dynbuf query_dyn; + + alg = id_to_alg(data->set.httpsig_algorithm); + if(alg == HTTPSIG_ALG_UNKNOWN) { + failf(data, "httpsig: CURLOPT_HTTPSIG_ALGORITHM is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + if(!hexkey || !*hexkey) { + failf(data, "httpsig: CURLOPT_HTTPSIG_KEY is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!keyid || !*keyid) { + failf(data, "httpsig: CURLOPT_HTTPSIG_KEYID is required"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + curlx_dyn_init(&sig_params, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&sig_base, HTTPSIG_MAX_SIG_BASE); + curlx_dyn_init(&sig_hdr, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&input_hdr, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&authority_buf, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&query_dyn, CURL_MAX_HTTP_HEADER); + + if(Curl_checkheaders(data, STRCONST("Signature")) || + Curl_checkheaders(data, STRCONST("Signature-Input"))) { + /* user provides their own Signature / Signature-Input headers, consider + this done */ + goto done; + } + + result = decode_hex_key(data, hexkey, &keybuf, &keylen); + if(result) + goto fail; + + if(alg == HTTPSIG_ALG_ED25519 && keylen != 32) { + failf(data, "httpsig: ed25519 requires a 32-byte key (got %zu)", keylen); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto fail; + } + + Curl_http_method(data, &method, &httpreq); + + path = data->state.up.path; + if(!path || !*path) + path = "/"; + + query = data->state.up.query; + + result = httpsig_authority(data, conn, &authority_buf); + if(result) + goto fail; + authority = curlx_dyn_ptr(&authority_buf); + + /* Build @query value: RFC 9421 Section 2.2.7 - always starts with "?" */ + if(query && *query) + result = curlx_dyn_addf(&query_dyn, "?%s", query); + else + result = curlx_dyn_add(&query_dyn, "?"); + if(result) + goto fail; + + result = parse_components(data, query, components, &ncomp, &hdrs_copy); + if(result) + goto fail; + + created = httpsig_get_created(); + + result = build_sig_params(&sig_params, components, ncomp, + created, keyid, alg); + if(result) + goto fail; + + infof(data, "httpsig: Signature-Input params: %s", + curlx_dyn_ptr(&sig_params)); + + result = build_sig_base(&sig_base, components, ncomp, + method, authority, path, + curlx_dyn_ptr(&query_dyn), + data, curlx_dyn_ptr(&sig_params)); + if(result) + goto fail; + + infof(data, "httpsig: Signature base: [%s]", + curlx_dyn_ptr(&sig_base)); + + result = httpsig_sign_base(data, alg, keybuf, keylen, &sig_base, + raw_sig, &raw_sig_len); + if(result) + goto fail; + + result = curlx_dyn_add(&sig_hdr, HTTPSIG_DEFAULT_LABEL "="); + if(result) + goto fail; + result = sf_encode_byte_seq(raw_sig, raw_sig_len, &sig_hdr); + if(result) + goto fail; + + result = curlx_dyn_addf(&input_hdr, "%s=%s", HTTPSIG_DEFAULT_LABEL, + curlx_dyn_ptr(&sig_params)); + if(result) + goto fail; + + auth_headers = curl_maprintf("Signature-Input: %s\r\n" + "Signature: %s\r\n", + curlx_dyn_ptr(&input_hdr), + curlx_dyn_ptr(&sig_hdr)); + if(!auth_headers) + goto fail; + + infof(data, "httpsig: Signature-Input: %s", curlx_dyn_ptr(&input_hdr)); + infof(data, "httpsig: Signature: %s", curlx_dyn_ptr(&sig_hdr)); +done: + curlx_free(data->req.hd_auth); + data->req.hd_auth = auth_headers; + data->state.authhost.done = TRUE; + result = CURLE_OK; + +fail: + if(keybuf) { + memset(keybuf, 0, keylen); + curlx_free(keybuf); + } + memset(raw_sig, 0, sizeof(raw_sig)); + curlx_free(hdrs_copy); + curlx_dyn_free(&sig_params); + curlx_dyn_free(&sig_base); + curlx_dyn_free(&sig_hdr); + curlx_dyn_free(&input_hdr); + curlx_dyn_free(&authority_buf); + curlx_dyn_free(&query_dyn); + return result; +} + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ diff --git a/lib/noproxy.h b/lib/http_httpsig.h similarity index 77% rename from lib/noproxy.h rename to lib/http_httpsig.h index e16c139bb502..410755d7e0af 100644 --- a/lib/noproxy.h +++ b/lib/http_httpsig.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NOPROXY_H -#define HEADER_CURL_NOPROXY_H +#ifndef HEADER_CURL_HTTP_HTTPSIG_H +#define HEADER_CURL_HTTP_HTTPSIG_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -25,8 +25,11 @@ ***************************************************************************/ #include "curl_setup.h" -#ifndef CURL_DISABLE_PROXY -bool Curl_check_noproxy(const char *name, const char *no_proxy); -#endif +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HTTPSIG) -#endif /* HEADER_CURL_NOPROXY_H */ +#include "urldata.h" + +CURLcode Curl_output_httpsig(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */ +#endif /* HEADER_CURL_HTTP_HTTPSIG_H */ diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 74d63d6cc0bb..5e8798134f55 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -40,8 +40,11 @@ static void http_auth_nego_reset(struct connectdata *conn, { if(proxy) conn->proxy_negotiate_state = GSS_AUTHNONE; - else + else { conn->http_negotiate_state = GSS_AUTHNONE; + Curl_peer_unlink(&conn->creds_origin); + Curl_creds_unlink(&conn->creds); + } if(neg_ctx) Curl_auth_cleanup_spnego(neg_ctx); } @@ -52,10 +55,8 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, CURLcode result; size_t len; - /* Point to the username, password, service and host */ - const char *userp; - const char *passwdp; - const char *service; + /* Point to credentials and host */ + struct Curl_creds *creds = NULL; const char *host; /* Point to the correct struct with this */ @@ -64,22 +65,16 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, if(proxy) { #ifndef CURL_DISABLE_PROXY - userp = conn->http_proxy.user; - passwdp = conn->http_proxy.passwd; - service = data->set.str[STRING_PROXY_SERVICE_NAME] ? - data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP"; - host = conn->http_proxy.host.name; + creds = conn->http_proxy.creds; + host = conn->http_proxy.peer->hostname; state = conn->proxy_negotiate_state; #else return CURLE_NOT_BUILT_IN; #endif } else { - userp = conn->user; - passwdp = conn->passwd; - service = data->set.str[STRING_SERVICE_NAME] ? - data->set.str[STRING_SERVICE_NAME] : "HTTP"; - host = conn->host.name; + creds = data->state.creds; + host = data->state.origin->hostname; state = conn->http_negotiate_state; } @@ -87,15 +82,8 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, if(!neg_ctx) return CURLE_OUT_OF_MEMORY; - /* Not set means empty */ - if(!userp) - userp = ""; - - if(!passwdp) - passwdp = ""; - /* Obtain the input token, if any */ - header += strlen("Negotiate"); + header += CURL_CSTRLEN("Negotiate"); curlx_str_passblanks(&header); len = strlen(header); @@ -114,7 +102,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, } /* Supports SSL channel binding for Windows ISS extended protection */ -#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) +#ifdef USE_WINDOWS_SSPI neg_ctx->sslContext = conn->sslContext; #endif /* Check if the connection is using SSL and get the channel binding data */ @@ -135,7 +123,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, #endif /* GSS_C_CHANNEL_BOUND_FLAG */ /* Initialize the security context and decode our challenge */ - result = Curl_auth_decode_spnego_message(data, userp, passwdp, service, + result = Curl_auth_decode_spnego_message(data, creds, "HTTP", host, header, neg_ctx); #ifdef GSS_C_CHANNEL_BOUND_FLAG @@ -145,6 +133,22 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, if(result) http_auth_nego_reset(conn, neg_ctx, proxy); + if(!result && !proxy) { + /* Start it up. From this time onwards, the connection is tied + * tp the credentials used. */ + if(conn->creds_origin && + !Curl_peer_equal(conn->creds_origin, data->state.origin)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } + if(conn->creds && !Curl_creds_same(creds, conn->creds)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } + Curl_peer_link(&conn->creds_origin, data->state.origin); + Curl_creds_link(&conn->creds, creds); + } + return result; } @@ -217,13 +221,13 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, if(proxy) { #ifndef CURL_DISABLE_PROXY - curlx_free(data->req.proxyuserpwd); - data->req.proxyuserpwd = userp; + curlx_free(data->req.hd_proxy_auth); + data->req.hd_proxy_auth = userp; #endif } else { - curlx_free(data->req.userpwd); - data->req.userpwd = userp; + curlx_free(data->req.hd_auth); + data->req.hd_auth = userp; } curlx_free(base64); diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index 82b050529e00..a1de75fc079d 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -65,7 +65,7 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, if(!ntlm) return CURLE_OUT_OF_MEMORY; - header += strlen("NTLM"); + header += CURL_CSTRLEN("NTLM"); curlx_str_passblanks(&header); if(*header) { unsigned char *hdr; @@ -93,6 +93,8 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, else if(*state == NTLMSTATE_TYPE3) { infof(data, "NTLM handshake rejected"); Curl_auth_ntlm_remove(conn, proxy); + Curl_peer_unlink(&conn->creds_origin); + Curl_creds_unlink(&conn->creds); *state = NTLMSTATE_NONE; return CURLE_REMOTE_ACCESS_DENIED; } @@ -122,10 +124,8 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) server, which is for a plain host or for an HTTP proxy */ char **allocuserpwd; - /* point to the username, password, service and host */ - const char *userp; - const char *passwdp; - const char *service = NULL; + /* point to credentials and host */ + struct Curl_creds *creds = NULL; const char *hostname = NULL; /* point to the correct struct with this */ @@ -139,12 +139,9 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) if(proxy) { #ifndef CURL_DISABLE_PROXY - allocuserpwd = &data->req.proxyuserpwd; - userp = data->state.aptr.proxyuser; - passwdp = data->state.aptr.proxypasswd; - service = data->set.str[STRING_PROXY_SERVICE_NAME] ? - data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP"; - hostname = conn->http_proxy.host.name; + allocuserpwd = &data->req.hd_proxy_auth; + creds = conn->http_proxy.creds; + hostname = conn->http_proxy.peer->hostname; state = &conn->proxy_ntlm_state; authp = &data->state.authproxy; #else @@ -152,27 +149,18 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) #endif } else { - allocuserpwd = &data->req.userpwd; - userp = data->state.aptr.user; - passwdp = data->state.aptr.passwd; - service = data->set.str[STRING_SERVICE_NAME] ? - data->set.str[STRING_SERVICE_NAME] : "HTTP"; - hostname = conn->host.name; + allocuserpwd = &data->req.hd_auth; + creds = data->state.creds; + hostname = data->state.origin->hostname; state = &conn->http_ntlm_state; authp = &data->state.authhost; } + ntlm = Curl_auth_ntlm_get(conn, proxy); if(!ntlm) return CURLE_OUT_OF_MEMORY; authp->done = FALSE; - /* not set means empty */ - if(!userp) - userp = ""; - - if(!passwdp) - passwdp = ""; - #ifdef USE_WINDOWS_SSPI if(!Curl_pSecFn) { /* not thread-safe and leaks - use curl_global_init() to avoid */ @@ -180,9 +168,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) if(!Curl_pSecFn) return result; } -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS ntlm->sslContext = conn->sslContext; -#endif #endif Curl_bufref_init(&ntlmmsg); @@ -194,9 +180,23 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) switch(*state) { case NTLMSTATE_TYPE1: - default: /* for the weird cases we (re)start here */ - /* Create a type-1 message */ - result = Curl_auth_create_ntlm_type1_message(data, userp, passwdp, service, + default: /* for the weird cases we (re)start here */ + if(!proxy) { + /* Start it up. From this time onwards, the connection is tied + * tp the credentials used. */ + if(conn->creds_origin && + !Curl_peer_equal(conn->creds_origin, data->state.origin)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } + if(conn->creds && !Curl_creds_same(creds, conn->creds)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } + Curl_peer_link(&conn->creds_origin, data->state.origin); + Curl_creds_link(&conn->creds, creds); + } + result = Curl_auth_create_ntlm_type1_message(data, creds, "HTTP", hostname, ntlm, &ntlmmsg); if(!result) { DEBUGASSERT(Curl_bufref_len(&ntlmmsg) != 0); @@ -215,8 +215,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) case NTLMSTATE_TYPE2: /* We already received the type-2 message, create a type-3 message */ - result = Curl_auth_create_ntlm_type3_message(data, userp, passwdp, - ntlm, &ntlmmsg); + result = Curl_auth_create_ntlm_type3_message(data, creds, ntlm, &ntlmmsg); if(!result && Curl_bufref_len(&ntlmmsg)) { result = curlx_base64_encode(Curl_bufref_uptr(&ntlmmsg), Curl_bufref_len(&ntlmmsg), &base64, &len); diff --git a/lib/http_proxy.c b/lib/http_proxy.c index a4bdd7e36107..7774589ecd64 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -35,11 +35,12 @@ #include "cf-h2-proxy.h" #include "connect.h" #include "vauth/vauth.h" +#include "vquic/vquic.h" #include "curlx/strparse.h" static CURLcode dynhds_add_custom(struct Curl_easy *data, bool is_connect, int httpversion, - struct dynhds *hds) + bool is_udp, struct dynhds *hds) { struct connectdata *conn = data->conn; struct curl_slist *h[2]; @@ -49,11 +50,12 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, enum Curl_proxy_use proxy; - if(is_connect) + if(is_connect && !is_udp) proxy = HEADER_CONNECT; + else if(is_connect && is_udp) + proxy = HEADER_CONNECT_UDP; else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? - HEADER_PROXY : HEADER_SERVER; + proxy = conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -72,6 +74,12 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, else h[0] = data->set.headers; break; + case HEADER_CONNECT_UDP: + if(data->set.sep_headers) + h[0] = data->set.proxyheaders; + else + h[0] = data->set.headers; + break; } /* loop through one or two lists */ @@ -121,7 +129,10 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, continue; DEBUGASSERT(curlx_strlen(&name) && value); - if(data->state.aptr.host && + /* trim surrounding whitespace so a padded field name (e.g. + `Authorization :`) cannot slip past the Authorization/Cookie check */ + curlx_str_trimblanks(&name); + if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ curlx_str_casecompare(&name, "Host")) @@ -162,58 +173,50 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, return CURLE_OK; } -void Curl_http_proxy_get_destination(struct Curl_cfilter *cf, - const char **phostname, - uint16_t *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; - - *pipv6_ip = (strchr(*phostname, ':') != NULL); -} - struct cf_proxy_ctx { - int httpversion; /* HTTP version used to CONNECT */ + struct Curl_peer *peer; /* proxy */ + struct Curl_peer *tunnel_peer; /* tunnel destination */ + uint8_t proxytype; + uint8_t tunnel_transport; BIT(sub_filter_installed); }; -CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, - struct Curl_cfilter *cf, - struct Curl_easy *data, - int http_version_major) +static int proxy_http_ver_major(proxy_http_ver ver) +{ + switch(ver) { + case PROXY_HTTP_V1: + return 11; + case PROXY_HTTP_V2: + return 20; + case PROXY_HTTP_V3: + return 30; + } + return 0; +} + +static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_peer *dest, + proxy_http_ver ver) { - struct cf_proxy_ctx *ctx = cf->ctx; - const char *hostname = NULL; char *authority = NULL; - uint16_t port; - bool ipv6_ip; + const char *ua; + int httpversion = proxy_http_ver_major(ver); CURLcode result; struct httpreq *req = NULL; - Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); - - authority = curl_maprintf("%s%s%s:%u", ipv6_ip ? "[" : "", hostname, - ipv6_ip ? "]" : "", port); + authority = curl_maprintf("%s%s%s:%u", + dest->ipv6 ? "[" : "", + dest->hostname, + dest->ipv6 ? "]" : "", + dest->port); if(!authority) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT") - 1, + result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), NULL, 0, authority, strlen(authority), NULL, 0); if(result) @@ -221,41 +224,207 @@ CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, /* Setup the proxy-authorization header, if any */ result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, - req->authority, TRUE); + req->authority, NULL, TRUE); if(result) goto out; /* If user is not overriding Host: header, we add for HTTP/1.x */ - if(http_version_major == 1 && + if(ver == PROXY_HTTP_V1 && !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { result = Curl_dynhds_cadd(&req->headers, "Host", authority); if(result) goto out; } - if(data->req.proxyuserpwd) { + if(data->req.hd_proxy_auth) { result = Curl_dynhds_h1_cadd_line(&req->headers, - data->req.proxyuserpwd); + data->req.hd_proxy_auth); if(result) goto out; } + ua = CURL_EASY_STR(data, STRING_USERAGENT); 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]); + ua && *ua) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua); + if(result) + goto out; + } + + if(ver == PROXY_HTTP_V1 && + !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, httpversion, + FALSE, &req->headers); + +out: + if(result && req) { + Curl_http_req_free(req); + req = NULL; + } + curlx_free(authority); + *preq = req; + return result; +} + +static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_peer *dest, + proxy_http_ver ver) +{ + const char *proxy_scheme = "http", *ua; + const char *proxy_host = cf->conn->http_proxy.peer->hostname; + int httpversion = proxy_http_ver_major(ver); + char *authority = NULL; + char *path = NULL; + char *encoded_host = NULL; + struct httpreq *req = NULL; + bool proxy_ipv6_ip; + CURLcode result; + + if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS || + cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS2 || + cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS3) + proxy_scheme = "https"; + + proxy_ipv6_ip = cf->conn->http_proxy.peer->ipv6 != 0; + + authority = curl_maprintf("%s%s%s:%d", + proxy_ipv6_ip ? "[" : "", + proxy_host, + proxy_ipv6_ip ? "]" : "", + cf->conn->http_proxy.peer->port); + if(!authority) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(dest->ipv6) { + /* RFC 9298: colons in IPv6 addresses MUST be percent-encoded + * in the URI template (e.g. "2001:db8::1" -> "2001%3Adb8%3A%3A1") */ + const char *s = dest->hostname; + char *d; + size_t hlen = strlen(s); + encoded_host = curlx_malloc(hlen * 3 + 1); + if(!encoded_host) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + d = encoded_host; + while(*s) { + if(*s == ':') { + *d++ = '%'; + *d++ = '3'; + *d++ = 'A'; + } + else + *d++ = *s; + s++; + } + *d = '\0'; + path = curl_maprintf("/.well-known/masque/udp/%s/%u/", + encoded_host, (unsigned int)dest->port); + } + else { + path = curl_maprintf("/.well-known/masque/udp/%s/%u/", + dest->hostname, (unsigned int)dest->port); + } + + if(!path) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(ver == PROXY_HTTP_V1) { + result = Curl_http_req_make(&req, "GET", CURL_CSTRLEN("GET"), + proxy_scheme, strlen(proxy_scheme), + authority, strlen(authority), + path, strlen(path)); + if(result) + goto out; + } + else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) { + result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), + proxy_scheme, strlen(proxy_scheme), + authority, strlen(authority), + path, strlen(path)); + if(result) + goto out; + } + else { + result = CURLE_FAILED_INIT; + goto out; + } + + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, + req->authority, NULL, TRUE); + if(result) + goto out; + + /* If user is not overriding Host: header, we add for HTTP/1.x */ + if(ver == PROXY_HTTP_V1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + result = Curl_dynhds_cadd(&req->headers, "Host", authority); + if(result) + goto out; + } + + if(data->req.hd_proxy_auth) { + result = Curl_dynhds_h1_cadd_line(&req->headers, + data->req.hd_proxy_auth); if(result) goto out; } - if(http_version_major == 1 && + ua = CURL_EASY_STR(data, STRING_USERAGENT); + if(ver == PROXY_HTTP_V1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && + ua && *ua) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua); + if(result) + goto out; + } + + if(ver == PROXY_HTTP_V1 && !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); + if(ver == PROXY_HTTP_V1) { + result = Curl_dynhds_cadd(&req->headers, "Connection", "Upgrade"); + if(result) + goto out; + + result = Curl_dynhds_cadd(&req->headers, "Upgrade", "connect-udp"); + if(result) + goto out; + + result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1"); + if(result) + goto out; + } + else { + result = Curl_dynhds_cadd(&req->headers, ":Protocol", "connect-udp"); + if(result) + goto out; + + if(ver >= PROXY_HTTP_V2) { + result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1"); + if(result) + goto out; + } + } + + result = dynhds_add_custom(data, TRUE, httpversion, + TRUE, &req->headers); out: if(result && req) { @@ -263,70 +432,224 @@ CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, req = NULL; } curlx_free(authority); + curlx_free(path); + curlx_free(encoded_host); *preq = req; return result; } +CURLcode Curl_http_proxy_create_tunnel_request( + struct httpreq **preq, struct Curl_cfilter *cf, + struct Curl_easy *data, struct Curl_peer *dest, + proxy_http_ver ver, bool udp_tunnel) +{ + CURLcode result; + + if(udp_tunnel) + result = http_proxy_create_CONNECTUDP(preq, cf, data, dest, ver); + else + result = http_proxy_create_CONNECT(preq, cf, data, dest, ver); + if(result) + return result; + + if(udp_tunnel) + infof(data, "Establishing %s proxy UDP tunnel to %s:%u", + (ver == PROXY_HTTP_V2) ? "HTTP/2" : + (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", + dest->user_hostname, dest->port); + else + infof(data, "Establishing %s proxy tunnel to %s", + (ver == PROXY_HTTP_V2) ? "HTTP/2" : + (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", + (*preq)->authority); + return CURLE_OK; +} + +CURLcode Curl_http_proxy_inspect_tunnel_response( + struct Curl_cfilter *cf, struct Curl_easy *data, + struct http_resp *resp, bool udp_tunnel, + proxy_inspect_result *presult) +{ + struct dynhds_entry *capsule_protocol = NULL; + struct dynhds_entry *auth_reply = NULL; + size_t i, header_count; + CURLcode result = CURLE_OK; + + DEBUGASSERT(resp); + + header_count = Curl_dynhds_count(&resp->headers); + if(udp_tunnel) + infof(data, "CONNECT-UDP Response Status %d", resp->status); + else + infof(data, "CONNECT Response Status %d", resp->status); + infof(data, "Response Headers (%zu total):", header_count); + for(i = 0; i < header_count; i++) { + struct dynhds_entry *entry = Curl_dynhds_getn(&resp->headers, i); + if(entry) + infof(data, " %s: %s", entry->name, entry->value); + } + + if(resp->status == 401) { + auth_reply = Curl_dynhds_cget(&resp->headers, "WWW-Authenticate"); + } + else if(resp->status == 407) { + auth_reply = Curl_dynhds_cget(&resp->headers, "Proxy-Authenticate"); + } + + if(auth_reply) { + CURL_TRC_CF(data, cf, "[0] CONNECT%s: fwd auth header '%s'", + udp_tunnel ? "-UDP" : "", auth_reply->value); + result = Curl_http_input_auth(data, resp->status == 407, + auth_reply->value); + if(result) + return result; + if(data->req.newurl) { + curlx_safefree(data->req.newurl); + *presult = PROXY_INSPECT_AUTH_RETRY; + return CURLE_OK; + } + } + + if(udp_tunnel) { + if(resp->status / 100 == 2) { + capsule_protocol = Curl_dynhds_cget(&resp->headers, + "capsule-protocol"); + if(capsule_protocol) { + if(!strncmp(capsule_protocol->value, "?1", 2) && + !capsule_protocol->value[2]) { + infof(data, "CONNECT-UDP tunnel established, response %d", + resp->status); + *presult = PROXY_INSPECT_OK; + return CURLE_OK; + } + failf(data, "Failed to establish CONNECT-UDP tunnel, response %d, " + "unsupported capsule-protocol value '%s'", + resp->status, capsule_protocol->value); + *presult = PROXY_INSPECT_FAILED; + return CURLE_COULDNT_CONNECT; + } + else { + /* NOTE proxies may not set capsule protocol in the headers */ + infof(data, "CONNECT-UDP tunnel established, response %d " + "but no capsule-protocol header found", resp->status); + *presult = PROXY_INSPECT_OK; + return CURLE_OK; + } + } + else { + failf(data, "Failed to establish CONNECT-UDP tunnel, " + "response %d", resp->status); + *presult = PROXY_INSPECT_FAILED; + return CURLE_COULDNT_CONNECT; + } + } + + if(resp->status / 100 == 2) { + infof(data, "CONNECT tunnel established, response %d", resp->status); + *presult = PROXY_INSPECT_OK; + return CURLE_OK; + } + + *presult = PROXY_INSPECT_FAILED; + return CURLE_COULDNT_CONNECT; +} + static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; + bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport); + const char *tunnel_type = udp_tunnel ? "CONNECT-UDP" : "CONNECT"; if(cf->connected) { *done = TRUE; return CURLE_OK; } - CURL_TRC_CF(data, cf, "connect"); + CURL_TRC_CF(data, cf, "%s", tunnel_type); connect_sub: - result = cf->next->cft->do_connect(cf->next, data, done); - if(result || !*done) - return result; + /* in case of h3_proxy, cf->next will be NULL initially */ + if(cf->next) { + result = cf->next->cft->do_connect(cf->next, data, done); + if(result || !*done) + return result; + } *done = FALSE; if(!ctx->sub_filter_installed) { - int httpversion = 0; - const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data); + const char *alpn = NULL; + + /* in case of h3_proxy, cf->next will be NULL initially */ + if(cf->next) { + alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data); + } if(alpn) - infof(data, "CONNECT: '%s' negotiated", alpn); - else - infof(data, "CONNECT: no ALPN negotiated"); + infof(data, "%s: '%s' negotiated", tunnel_type, alpn); + else if(!alpn) { + /* No ALPN, proxytype rules. Fake ALPN */ + infof(data, "%s: no ALPN negotiated", tunnel_type); + switch(ctx->proxytype) { + case CURLPROXY_HTTP_1_0: + alpn = "http/1.0"; + break; + case CURLPROXY_HTTPS2: + alpn = "h2"; + break; + case CURLPROXY_HTTPS3: + alpn = "h3"; + break; + default: + alpn = "http/1.1"; + break; + } + } - if(alpn && !strcmp(alpn, "http/1.0")) { + if(!strcmp(alpn, "http/1.0")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0"); - result = Curl_cf_h1_proxy_insert_after(cf, data); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, 10, + udp_tunnel); if(result) goto out; - httpversion = 10; } - else if(!alpn || !strcmp(alpn, "http/1.1")) { - CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); - result = Curl_cf_h1_proxy_insert_after(cf, data); + else if(!strcmp(alpn, "http/1.1")) { + int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11; + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d", + httpversion % 10); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, + httpversion, udp_tunnel); if(result) goto out; - /* Assume that without an ALPN, we are talking to an ancient one */ - httpversion = 11; } #ifdef USE_NGHTTP2 else if(!strcmp(alpn, "h2")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); - result = Curl_cf_h2_proxy_insert_after(cf, data); + result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->tunnel_peer, + udp_tunnel); + if(result) + goto out; + } +#endif /* USE_NGHTTP2 */ +#if defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \ + defined(USE_NGTCP2) && defined(USE_OPENSSL) + else if(!strcmp(alpn, "h3")) { + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/3"); + result = Curl_cf_h3_proxy_insert_after(cf, data, ctx->peer, ctx->peer, + ctx->tunnel_peer, + ctx->tunnel_transport); if(result) goto out; - httpversion = 20; } -#endif +#endif /* USE_PROXY_HTTP3 && USE_NGHTTP3 && USE_NGTCP2 && USE_OPENSSL */ else { - failf(data, "CONNECT: negotiated ALPN '%s' not supported", alpn); + failf(data, "%s: negotiated ALPN '%s' not supported", tunnel_type, alpn); result = CURLE_COULDNT_CONNECT; goto out; } ctx->sub_filter_installed = TRUE; - ctx->httpversion = httpversion; /* after we installed the filter "below" us, we call connect * on out sub-chain again. */ @@ -334,8 +657,7 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, } else { /* subchain connected and we had already installed the protocol filter. - * This means the protocol tunnel is established, we are done. - */ + * This means the protocol tunnel is established, we are done. */ DEBUGASSERT(ctx->sub_filter_installed); result = CURLE_OK; } @@ -348,14 +670,15 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, return result; } -CURLcode Curl_cf_http_proxy_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2) +static CURLcode cf_http_proxy_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) { + struct cf_proxy_ctx *ctx = cf->ctx; switch(query) { case CF_QUERY_HOST_PORT: - *pres1 = (int)cf->conn->http_proxy.port; - *((const char **)pres2) = cf->conn->http_proxy.host.name; + *pres1 = (int)ctx->tunnel_peer->port; + *((const char **)pres2) = ctx->tunnel_peer->hostname; return CURLE_OK; case CF_QUERY_ALPN_NEGOTIATED: { const char **palpn = pres2; @@ -371,22 +694,23 @@ CURLcode Curl_cf_http_proxy_query(struct Curl_cfilter *cf, CURLE_UNKNOWN_OPTION; } -static void http_proxy_cf_destroy(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_https_proxy_ctx_free(struct cf_proxy_ctx *ctx) { - struct cf_proxy_ctx *ctx = cf->ctx; - - CURL_TRC_CF(data, cf, "destroy"); - curlx_free(ctx); + if(ctx) { + Curl_peer_unlink(&ctx->peer); + Curl_peer_unlink(&ctx->tunnel_peer); + curlx_free(ctx); + } } -static void http_proxy_cf_close(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void http_proxy_cf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) { - CURL_TRC_CF(data, cf, "close"); - cf->connected = FALSE; - if(cf->next) - cf->next->cft->do_close(cf->next, data); + struct cf_proxy_ctx *ctx = cf->ctx; + if(ctx) { + CURL_TRC_CF(data, cf, "destroy"); + cf_https_proxy_ctx_free(ctx); + } } struct Curl_cftype Curl_cft_http_proxy = { @@ -395,7 +719,6 @@ struct Curl_cftype Curl_cft_http_proxy = { 0, http_proxy_cf_destroy, http_proxy_cf_connect, - http_proxy_cf_close, Curl_cf_def_shutdown, Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, @@ -404,22 +727,34 @@ struct Curl_cftype Curl_cft_http_proxy = { Curl_cf_def_cntrl, Curl_cf_def_conn_is_alive, Curl_cf_def_conn_keep_alive, - Curl_cf_http_proxy_query, + cf_http_proxy_query, }; CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, + uint8_t proxytype) { struct Curl_cfilter *cf; struct cf_proxy_ctx *ctx = NULL; CURLcode result; (void)data; + if(!peer || !tunnel_peer) + return CURLE_FAILED_INIT; + ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } + Curl_peer_link(&ctx->peer, peer); + Curl_peer_link(&ctx->tunnel_peer, tunnel_peer); + ctx->proxytype = proxytype; + ctx->tunnel_transport = tunnel_transport; + result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); if(result) goto out; @@ -427,8 +762,18 @@ CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, Curl_conn_cf_insert_after(cf_at, cf); out: - curlx_free(ctx); + cf_https_proxy_ctx_free(ctx); return result; } +uint8_t Curl_http_proxy_transport(uint8_t proxytype) +{ + switch(proxytype) { + case CURLPROXY_HTTPS3: + return TRNSPRT_QUIC; + default: + return TRNSPRT_TCP; + } +} + #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */ diff --git a/lib/http_proxy.h b/lib/http_proxy.h index 155b222edc0d..86c908809303 100644 --- a/lib/http_proxy.h +++ b/lib/http_proxy.h @@ -32,33 +32,51 @@ enum Curl_proxy_use { HEADER_SERVER, /* direct to server */ HEADER_PROXY, /* regular request to proxy */ - HEADER_CONNECT /* sending CONNECT to a proxy */ + HEADER_CONNECT, /* sending CONNECT to a proxy */ + HEADER_CONNECT_UDP /* sending CONNECT-UDP to a proxy */ }; -void Curl_http_proxy_get_destination(struct Curl_cfilter *cf, - const char **phostname, - uint16_t *pport, bool *pipv6_ip); +/* HTTP version for proxy tunnel request creation */ +typedef enum { + PROXY_HTTP_V1 = 1, + PROXY_HTTP_V2 = 2, + PROXY_HTTP_V3 = 3 +} proxy_http_ver; -CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, - struct Curl_cfilter *cf, - struct Curl_easy *data, - int http_version_major); +/* Result from inspecting a proxy tunnel response */ +typedef enum { + PROXY_INSPECT_OK, /* Tunnel established */ + PROXY_INSPECT_FAILED, /* Tunnel failed */ + PROXY_INSPECT_AUTH_RETRY /* Retry with auth */ +} proxy_inspect_result; + +/* Create CONNECT or CONNECT-UDP request */ +CURLcode Curl_http_proxy_create_tunnel_request( + struct httpreq **preq, struct Curl_cfilter *cf, + struct Curl_easy *data, struct Curl_peer *dest, + proxy_http_ver ver, bool udp_tunnel); + +/* Inspect tunnel response for H2/H3 proxy (capsule-protocol, auth) */ +struct http_resp; +CURLcode Curl_http_proxy_inspect_tunnel_response( + struct Curl_cfilter *cf, struct Curl_easy *data, + struct http_resp *resp, bool udp_tunnel, + proxy_inspect_result *presult); /* Default proxy timeout in milliseconds */ #define PROXY_TIMEOUT (3600 * 1000) -CURLcode Curl_cf_http_proxy_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2); - CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, + uint8_t proxytype); extern struct Curl_cftype Curl_cft_http_proxy; -#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ +uint8_t Curl_http_proxy_transport(uint8_t proxytype); -#define IS_HTTPS_PROXY(t) (((t) == CURLPROXY_HTTPS) || \ - ((t) == CURLPROXY_HTTPS2)) +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP_PROXY_H */ diff --git a/lib/idn.c b/lib/idn.c index f2b954e2d03e..943a520eff5e 100644 --- a/lib/idn.c +++ b/lib/idn.c @@ -27,6 +27,7 @@ #include "curl_setup.h" #include "urldata.h" +#include "curlx/strparse.h" #include "idn.h" #ifdef USE_LIBIDN2 @@ -171,7 +172,8 @@ static CURLcode win32_idn_to_ascii(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { wchar_t punycode[IDN_MAX_LENGTH]; int chars = IdnToAscii(0, in_w, in_w_len, punycode, IDN_MAX_LENGTH); @@ -197,7 +199,8 @@ static CURLcode win32_ascii_to_idn(const char *in, char **out) /* Returned in_w_len includes the null-terminator, which then gets preserved across the calls that follow, ending up terminating the buffer returned to the caller. */ - in_w_len = MultiByteToWideChar(CP_UTF8, 0, in, -1, in_w, IDN_MAX_LENGTH); + in_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + in, -1, in_w, IDN_MAX_LENGTH); if(in_w_len) { WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */ int chars = IdnToUnicode(0, in_w, in_w_len, idn, IDN_MAX_LENGTH); @@ -222,15 +225,24 @@ static CURLcode win32_ascii_to_idn(const char *in, char **out) */ bool Curl_is_ASCII_name(const char *hostname) { - /* get an UNSIGNED local version of the pointer */ - const unsigned char *ch = (const unsigned char *)hostname; - - if(!hostname) /* bad input, consider it ASCII! */ - return TRUE; + if(hostname) { + struct Curl_str s; + s.str = hostname; + s.len = strlen(hostname); + return Curl_is_ASCII_str(&s); + } + return TRUE; +} - while(*ch) { - if(*ch++ & 0x80) - return FALSE; +bool Curl_is_ASCII_str(struct Curl_str *s) +{ + if(s && s->len) { + const unsigned char *ch = (const unsigned char *)s->str; + size_t i; + for(i = 0; i < s->len; ++i) { + if(ch[i] & 0x80) + return FALSE; + } } return TRUE; } diff --git a/lib/idn.h b/lib/idn.h index 90d8e811b1a6..c73b870a7080 100644 --- a/lib/idn.h +++ b/lib/idn.h @@ -23,7 +23,13 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ + +struct Curl_str; +struct hostname; + bool Curl_is_ASCII_name(const char *hostname); +bool Curl_is_ASCII_str(struct Curl_str *s); + CURLcode Curl_idnconvert_hostname(struct hostname *host); #if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) diff --git a/lib/if2ip.c b/lib/if2ip.c index b71254ada00e..9a9fbe7691d3 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -24,31 +24,31 @@ #include "curl_setup.h" #ifdef HAVE_NETINET_IN_H -# include +#include #endif #ifdef HAVE_ARPA_INET_H -# include +#include #endif #ifdef HAVE_NET_IF_H -# include +#include #endif #ifdef HAVE_SYS_IOCTL_H -# include +#include #endif #ifdef HAVE_NETDB_H -# include +#include #endif #ifdef HAVE_SYS_SOCKIO_H -# include +#include #endif #ifdef HAVE_IFADDRS_H -# include +#include #endif #ifdef HAVE_STROPTS_H -# include +#include #endif #ifdef __VMS -# include +#include #endif #include "curlx/inet_ntop.h" @@ -107,14 +107,14 @@ if2ip_result_t Curl_if2ip(int af, #endif if(getifaddrs(&head) >= 0) { - for(iface = head; iface != NULL; iface = iface->ifa_next) { + for(iface = head; iface; iface = iface->ifa_next) { if(iface->ifa_addr) { if(iface->ifa_addr->sa_family == af) { if(curl_strequal(iface->ifa_name, interf)) { void *addr; - const char *ip; char scope[12] = ""; char ipstr[64]; + CURLcode result; #ifdef USE_IPV6 if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -155,8 +155,8 @@ if2ip_result_t Curl_if2ip(int af, addr = &((struct sockaddr_in *)(void *)iface->ifa_addr)->sin_addr; res = IF2IP_FOUND; - ip = curlx_inet_ntop(af, addr, ipstr, sizeof(ipstr)); - curl_msnprintf(buf, buf_size, "%s%s", ip, scope); + result = curlx_inet_ntop(af, addr, ipstr, sizeof(ipstr)); + curl_msnprintf(buf, buf_size, "%s%s", result ? "" : ipstr, scope); break; } } @@ -188,7 +188,7 @@ if2ip_result_t Curl_if2ip(int af, struct sockaddr_in *s; curl_socket_t dummy; size_t len; - const char *r; + CURLcode result; #ifdef USE_IPV6 (void)remote_scope; @@ -228,10 +228,10 @@ if2ip_result_t Curl_if2ip(int af, s = (struct sockaddr_in *)(void *)&req.ifr_addr; memcpy(&in, &s->sin_addr, sizeof(in)); - r = curlx_inet_ntop(s->sin_family, &in, buf, buf_size); + result = curlx_inet_ntop(s->sin_family, &in, buf, buf_size); sclose(dummy); - if(!r) + if(result) return IF2IP_NOT_FOUND; return IF2IP_FOUND; } diff --git a/lib/if2ip.h b/lib/if2ip.h index 12fdaabd736a..dc79c383b793 100644 --- a/lib/if2ip.h +++ b/lib/if2ip.h @@ -54,7 +54,7 @@ if2ip_result_t Curl_if2ip(int af, #ifdef __INTERIX -/* Nedelcho Stanev's work-around for SFU 3.0 */ +/* Nedelcho Stanev's workaround for SFU 3.0 */ struct ifreq { #define IFNAMSIZ 16 #define IFHWADDRLEN 6 diff --git a/lib/imap.c b/lib/imap.c index 5ef2a2cb2189..62ab0a283bd1 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -57,7 +57,6 @@ #include "curlx/dynbuf.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -555,7 +554,8 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, bool ssldone = FALSE; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler */ @@ -565,7 +565,7 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, DEBUGASSERT(!imapc->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); DEBUGF(infof(data, "imap_perform_upgrade_tls, connect -> %d, %d", - result, ssldone)); + (int)result, ssldone)); if(!result && ssldone) { imapc->ssldone = ssldone; /* perform CAPA now, changes imapc->state out of IMAP_UPGRADETLS */ @@ -597,21 +597,22 @@ static CURLcode imap_perform_login(struct Curl_easy *data, /* Check we have a username and password to authenticate with and end the connect phase if we do not */ - if(!data->state.aptr.user) { + if(!conn->creds) { 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(Curl_creds_user(conn->creds), FALSE); + passwd = imap_atom(Curl_creds_passwd(conn->creds), FALSE); /* Send the LOGIN command */ result = imap_sendf(data, imapc, "LOGIN %s %s", user ? user : "", passwd ? passwd : ""); curlx_free(user); + curlx_strzero(passwd); curlx_free(passwd); if(!result) @@ -640,7 +641,8 @@ static CURLcode imap_perform_authenticate(struct Curl_easy *data, return CURLE_FAILED_INIT; if(ir) { /* Send the AUTHENTICATE command with the initial response */ - result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", mech, ir); + result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTHENTICATE command */ @@ -712,7 +714,6 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, /* Calculate the SASL login details */ result = Curl_sasl_start(&imapc->sasl, data, (bool)imapc->ir_supported, &progress); - if(!result) { if(progress == SASL_INPROGRESS) imap_state(data, imapc, IMAP_AUTHENTICATE); @@ -885,7 +886,6 @@ static CURLcode imap_perform_append(struct Curl_easy *data, result = Curl_creader_set_mime(data, postp); if(result) return result; - data->state.infilesize = Curl_creader_client_length(data); } else #endif @@ -895,9 +895,15 @@ static CURLcode imap_perform_append(struct Curl_easy *data, return result; } - /* Check we know the size of the upload */ + /* Check we know the size of the upload. This takes all readers + * into account. Especially crlf conversions which make the size + * unpredictable, e.g. -1. */ + data->state.infilesize = Curl_creader_total_length(data); if(data->state.infilesize < 0) { - failf(data, "Cannot APPEND with unknown input file size"); + if(data->set.crlf) + failf(data, "Cannot APPEND with CRLF conversion making size unknown"); + else + failf(data, "Cannot APPEND with unknown input file size"); return CURLE_UPLOAD_FAILED; } @@ -911,12 +917,12 @@ static CURLcode imap_perform_append(struct Curl_easy *data, 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} + { CURLULFLAG_ANSWERED, "Answered" }, + { CURLULFLAG_DELETED, "Deleted" }, + { CURLULFLAG_DRAFT, "Draft" }, + { CURLULFLAG_FLAGGED, "Flagged" }, + { CURLULFLAG_SEEN, "Seen" }, + { 0, NULL } }; result = CURLE_OUT_OF_MEMORY; @@ -1044,7 +1050,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, /* Extract the word */ for(wordlen = 0; line[wordlen] && !ISBLANK(line[wordlen]) && - !ISNEWLINE(line[wordlen]);) + !ISNEWLINE(line[wordlen]);) wordlen++; /* Does the server support the STARTTLS capability? */ @@ -1076,7 +1082,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, line += wordlen; } } - else if(data->set.use_ssl && !Curl_xfer_is_secure(data)) { + else if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { /* PREAUTH is not compatible with STARTTLS. */ if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) { /* Switch to TLS connection now */ @@ -1189,9 +1195,9 @@ static bool is_custom_fetch_listing_match(const char *params) return FALSE; } if(*params == ':') - return true; + return TRUE; if(*params == ',') - return true; + return TRUE; return FALSE; } @@ -1356,7 +1362,7 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, size_t len = curlx_dyn_len(&imapc->pp.recvbuf); if((len >= 18) && checkprefix("OK [UIDVALIDITY ", &line[2])) { curl_off_t value; - const char *p = &line[2] + strlen("OK [UIDVALIDITY "); + const char *p = &line[2] + CURL_CSTRLEN("OK [UIDVALIDITY "); if(!curlx_str_number(&p, &value, UINT_MAX)) { imapc->mb_uidvalidity = (unsigned int)value; imapc->mb_uidvalidity_set = TRUE; @@ -1796,7 +1802,7 @@ static CURLcode imap_parse_url_options(struct connectdata *conn, static CURLcode imap_parse_url_path(struct Curl_easy *data, struct IMAP *imap) { - /* The imap struct is already initialised in imap_connect() */ + /* The imap struct is already initialized in imap_connect() */ CURLcode result = CURLE_OK; const char *begin = &data->state.up.path[1]; /* skip leading slash */ const char *ptr = begin; @@ -1922,7 +1928,7 @@ static CURLcode imap_parse_custom_request(struct Curl_easy *data, struct IMAP *imap) { CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(custom) { /* URL decode the custom request */ @@ -2009,7 +2015,8 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, return CURLE_OK; if(status) { - connclose(conn, "IMAP done with bad status"); /* marked for closure */ + CURL_TRC_M(data, "IMAP done with bad status"); + connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } else if(!data->set.connect_only && diff --git a/lib/ldap.c b/lib/ldap.c index 16c93eeca253..2676f732bb1a 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -45,7 +45,7 @@ #ifdef USE_WIN32_LDAP /* Use Windows LDAP implementation. */ # include -/* Undefine indirect symbols conflicting with BoringSSL/AWS-LC. */ +/* Undefine indirect symbols conflicting with AWS-LC/BoringSSL. */ # undef X509_NAME # undef X509_EXTENSIONS # undef PKCS7_ISSUER_AND_SERIAL @@ -157,7 +157,7 @@ static ULONG ldap_win_bind_auth(LDAP *server, const char *user, const char *passwd, unsigned long authflags) { ULONG method = 0; - SEC_WINNT_AUTH_IDENTITY cred; + SEC_WINNT_AUTH_IDENTITY_EX cred; ULONG rc = LDAP_AUTH_METHOD_NOT_SUPPORTED; memset(&cred, 0, sizeof(cred)); @@ -230,6 +230,86 @@ static ULONG ldap_win_bind(struct Curl_easy *data, LDAP *server, } #endif /* USE_WIN32_LDAP */ +static bool ldap_value_needs_base64(const char *attr, size_t attr_len, + const BerValue *val) +{ + ber_len_t j; + + if((attr_len > 7) && curl_strequal(";binary", attr + attr_len - 7)) + return TRUE; + + /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC 2849) or + leading or trailing whitespace */ + if(val->bv_len && ((val->bv_val[0] == ':') || (val->bv_val[0] == '<') || + ISBLANK(val->bv_val[0]) || + ISBLANK(val->bv_val[val->bv_len - 1]))) + return TRUE; + + /* check for unprintable characters */ + for(j = 0; j < val->bv_len; j++) + if(!ISPRINT(val->bv_val[j])) + return TRUE; + + return FALSE; +} + +#ifdef USE_WIN32_LDAP +static BOOLEAN bypass_cert_verify(PLDAP Connection, + PCCERT_CONTEXT *ppServerCert) +{ + (void)Connection; + CertFreeCertificateContext(*ppServerCert); + /* approve any certificate since the verification is set to bypass */ + return TRUE; +} +#endif + +static CURLcode show_vals(struct Curl_easy *data, BerValue **vals, + const char *attr) +{ + CURLcode result = CURLE_OK; + int i; + size_t attr_len = strlen(attr); + + for(i = 0; vals[i] && !result; i++) { + result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, ":", 1); + + if(result) + break; + + if(ldap_value_needs_base64(attr, attr_len, vals[i])) { + char *val_b64 = NULL; + size_t val_b64_sz = 0; + + /* Binary attribute, encode to base64. */ + if(vals[i]->bv_len) + result = curlx_base64_encode((uint8_t *)vals[i]->bv_val, + vals[i]->bv_len, + &val_b64, &val_b64_sz); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); + if(!result && val_b64_sz) + result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, + val_b64_sz); + curlx_free(val_b64); + } + else { + result = Curl_client_write(data, CLIENTWRITE_BODY, " ", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, + vals[i]->bv_val, vals[i]->bv_len); + } + + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + } + return result; +} + static CURLcode ldap_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; @@ -241,21 +321,20 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) int num = 0; struct connectdata *conn = data->conn; int ldap_proto = LDAP_VERSION3; - int ldap_ssl = 0; - char *val_b64 = NULL; - size_t val_b64_sz = 0; + bool ldap_ssl = FALSE; #ifdef LDAP_OPT_NETWORK_TIMEOUT - struct timeval ldap_timeout = {10, 0}; /* 10 sec connection/search timeout */ + struct timeval ldap_timeout = { 10, 0 }; /* 10s connection/search timeout */ #endif #ifdef USE_WIN32_LDAP TCHAR *host = NULL; #else char *host = NULL; #endif - char *user = NULL; - char *passwd = NULL; + const char *user = data->state.creds ? data->state.creds->user : NULL; + const char *passwd = data->state.creds ? data->state.creds->passwd : NULL; struct ip_quadruple ipquad; bool is_ipv6; + BerElement *ber = NULL; *done = TRUE; /* unconditionally */ infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d", @@ -278,27 +357,21 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; /* Get the URL scheme (either ldap or ldaps) */ - if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) - ldap_ssl = 1; + ldap_ssl = Curl_conn_is_ssl(conn, FIRSTSOCKET); infof(data, "LDAP local: trying to establish %s connection", ldap_ssl ? "encrypted" : "cleartext"); #ifdef USE_WIN32_LDAP - host = curlx_convert_UTF8_to_tchar(conn->host.name); + host = curlx_convert_UTF8_to_tchar(conn->origin->hostname); if(!host) { result = CURLE_OUT_OF_MEMORY; goto quit; } #else - host = conn->host.name; + host = conn->origin->hostname; #endif - if(data->state.aptr.user) { - user = conn->user; - passwd = conn->passwd; - } - #ifdef USE_WIN32_LDAP if(ldap_ssl) server = ldap_sslinit(host, (curl_ldap_num_t)ipquad.remote_port, 1); @@ -307,7 +380,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) server = ldap_init(host, (curl_ldap_num_t)ipquad.remote_port); if(!server) { failf(data, "LDAP: cannot setup connect to %s:%u", - conn->host.dispname, ipquad.remote_port); + conn->origin->user_hostname, ipquad.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -317,19 +390,32 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #endif ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); + /* Do not chase referrals. */ + ldap_set_option(server, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); + if(ldap_ssl) { #ifdef HAVE_LDAP_SSL #ifdef USE_WIN32_LDAP - /* Win32 LDAP SDK does not support insecure mode without CA! */ + /* Win32 LDAP uses the Windows CA store to verify certificates */ ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); + if(!conn->ssl_config.verifypeer) { + if(conn->ssl_config.verifyhost) { + failf(data, "LDAP local: host verification cannot be enabled when " + "peer verification is disabled for Windows native LDAP"); + result = CURLE_NOT_BUILT_IN; + goto quit; + } + ldap_set_option(server, LDAP_OPT_SERVER_CERTIFICATE, + (void *)(uintptr_t)bypass_cert_verify); + } #else /* !USE_WIN32_LDAP */ int ldap_option; const char *ldap_ca = conn->ssl_config.CAfile; #ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ - if(data->set.ssl.cert_type && - !curl_strequal(data->set.ssl.cert_type, "PEM")) { + if(data->set.ssl.primary.cert_type && + !curl_strequal(data->set.ssl.primary.cert_type, "PEM")) { failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type"); result = CURLE_SSL_CERTPROBLEM; goto quit; @@ -427,33 +513,25 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) for(entryIterator = ldap_first_entry(server, ldapmsg); entryIterator; entryIterator = ldap_next_entry(server, entryIterator), num++) { - BerElement *ber = NULL; #ifdef USE_WIN32_LDAP TCHAR *attribute; #else char *attribute; #endif - int i; /* Get the DN and write it to the client */ { - char *name; + char *name = NULL; size_t name_len = 0; #ifdef USE_WIN32_LDAP TCHAR *dn = ldap_get_dn(server, entryIterator); - name = curlx_convert_tchar_to_UTF8(dn); - if(!name) { - ldap_memfree(dn); - - result = CURLE_OUT_OF_MEMORY; - - goto quit; - } + if(dn) + name = curlx_convert_tchar_to_UTF8(dn); #else char *dn = name = ldap_get_dn(server, entryIterator); #endif if(!name) - result = CURLE_FAILED_INIT; + result = dn ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; else { name_len = strlen(name); result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); @@ -473,117 +551,19 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) attribute; attribute = ldap_next_attribute(server, entryIterator, ber)) { BerValue **vals; - size_t attr_len; #ifdef USE_WIN32_LDAP char *attr = curlx_convert_tchar_to_UTF8(attribute); if(!attr) { - if(ber) - ber_free(ber, 0); - + ldap_memfree(attribute); result = CURLE_OUT_OF_MEMORY; - goto quit; } #else char *attr = attribute; #endif - attr_len = strlen(attr); - vals = ldap_get_values_len(server, entryIterator, attribute); if(vals) { - for(i = 0; (vals[i] != NULL); i++) { - result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - - if((attr_len > 7) && - curl_strequal(";binary", attr + (attr_len - 7))) { - /* Binary attribute, encode to base64. */ - if(vals[i]->bv_len) { - result = curlx_base64_encode((uint8_t *)vals[i]->bv_val, - vals[i]->bv_len, - &val_b64, &val_b64_sz); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - - if(val_b64_sz > 0) { - result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, - val_b64_sz); - curlx_free(val_b64); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - } - } - } - else { - result = Curl_client_write(data, CLIENTWRITE_BODY, vals[i]->bv_val, - vals[i]->bv_len); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - } - - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); - if(result) { - ldap_value_free_len(vals); - FREE_ON_WINLDAP(attr); - ldap_memfree(attribute); - if(ber) - ber_free(ber, 0); - - goto quit; - } - } - + result = show_vals(data, vals, attr); /* Free memory used to store values */ ldap_value_free_len(vals); } @@ -592,16 +572,21 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) FREE_ON_WINLDAP(attr); ldap_memfree(attribute); - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) goto quit; } - if(ber) + if(ber) { ber_free(ber, 0); + ber = NULL; + } } quit: + if(ber) + ber_free(ber, 0); if(ldapmsg) { ldap_msgfree(ldapmsg); LDAP_TRACE(("Received %d entries\n", num)); @@ -617,7 +602,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) /* no data to transfer */ Curl_xfer_setup_nop(data); - connclose(conn, "LDAP connection always disable reuse"); + connclose(conn); return result; } @@ -681,8 +666,8 @@ static size_t num_entries(const char *s) * Syntax: * ldap://:/???? * - * already known from 'conn->host.name'. - * already known from 'conn->remote_port'. + * already known from 'conn->origin->hostname'. + * already known from 'conn->origin->port'. * extract the rest from 'data->state.path+1'. All fields are optional. * e.g. * ldap://:/??? @@ -704,12 +689,13 @@ static curl_ldap_num_t ldap_url_parse2_low(struct Curl_easy *data, if(!data || !data->state.up.path || data->state.up.path[0] != '/' || - !curl_strnequal("LDAP", data->state.up.scheme, 4)) + ((data->state.origin->scheme != &Curl_scheme_ldap) && + (data->state.origin->scheme != &Curl_scheme_ldaps))) return LDAP_INVALID_SYNTAX; ludp->lud_scope = LDAP_SCOPE_BASE; - ludp->lud_port = conn->remote_port; - ludp->lud_host = conn->host.name; + ludp->lud_port = conn->origin->port; + ludp->lud_host = conn->origin->hostname; /* Duplicate the path */ p = path = curlx_strdup(data->state.up.path + 1); @@ -856,7 +842,7 @@ static curl_ldap_num_t ldap_url_parse2_low(struct Curl_easy *data, LDAP_TRACE(("filter '%s'\n", filter)); /* Unescape the filter */ - result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_ZERO); + result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_CTRL); if(result) { rc = LDAP_NO_MEMORY; @@ -940,9 +926,9 @@ void Curl_ldap_version(char *buf, size_t bufsz) curl_msnprintf(buf, bufsz, "WinLDAP"); #else #ifdef LDAP_OPT_X_TLS_PASSPHRASE - static const char *flavor = "/Apple"; + static const char flavor[] = "/Apple"; #else - static const char *flavor = ""; + static const char flavor[] = ""; #endif LDAPAPIInfo api; api.ldapai_info_version = LDAP_API_INFO_VERSION; diff --git a/lib/llist.c b/lib/llist.c index 3ec85e4a2ce2..6528ebaef2c3 100644 --- a/lib/llist.c +++ b/lib/llist.c @@ -200,7 +200,7 @@ void Curl_llist_destroy(struct Curl_llist *list, void *user) /* 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) +struct Curl_llist_node *Curl_llist_head(const struct Curl_llist *list) { DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); @@ -211,10 +211,9 @@ struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list) /* llist_tail() returns the last 'struct Curl_llist_node *', which might be NULL - @unittest 1300 -*/ -UNITTEST struct Curl_llist_node *llist_tail(struct Curl_llist *list); -UNITTEST struct Curl_llist_node *llist_tail(struct Curl_llist *list) + @unittest 1300 */ +UNITTEST struct Curl_llist_node *llist_tail(const struct Curl_llist *list); +UNITTEST struct Curl_llist_node *llist_tail(const struct Curl_llist *list) { DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); @@ -223,7 +222,7 @@ UNITTEST struct Curl_llist_node *llist_tail(struct Curl_llist *list) #endif /* Curl_llist_count() returns a size_t the number of nodes in the list */ -size_t Curl_llist_count(struct Curl_llist *list) +size_t Curl_llist_count(const struct Curl_llist *list) { DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); @@ -231,7 +230,7 @@ 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) +void *Curl_node_elem(const struct Curl_llist_node *n) { DEBUGASSERT(n); DEBUGASSERT(n->_init == NODEINIT); @@ -240,7 +239,7 @@ void *Curl_node_elem(struct Curl_llist_node *n) /* 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) +struct Curl_llist_node *Curl_node_next(const struct Curl_llist_node *n) { DEBUGASSERT(n); DEBUGASSERT(n->_init == NODEINIT); @@ -251,10 +250,11 @@ struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n) /* llist_node_prev() returns the previous element in a list from a given Curl_llist_node - @unittest 1300 -*/ -UNITTEST struct Curl_llist_node *llist_node_prev(struct Curl_llist_node *n); -UNITTEST struct Curl_llist_node *llist_node_prev(struct Curl_llist_node *n) + @unittest 1300 */ +UNITTEST struct Curl_llist_node *llist_node_prev( + const struct Curl_llist_node *n); +UNITTEST struct Curl_llist_node *llist_node_prev( + const struct Curl_llist_node *n) { DEBUGASSERT(n); DEBUGASSERT(n->_init == NODEINIT); @@ -262,7 +262,7 @@ UNITTEST struct Curl_llist_node *llist_node_prev(struct Curl_llist_node *n) } #endif -struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n) +struct Curl_llist *Curl_node_llist(const struct Curl_llist_node *n) { DEBUGASSERT(n); DEBUGASSERT(!n->_list || n->_init == NODEINIT); diff --git a/lib/llist.h b/lib/llist.h index 28e958d5c5e9..de4adc972f47 100644 --- a/lib/llist.h +++ b/lib/llist.h @@ -60,13 +60,13 @@ void Curl_llist_destroy(struct Curl_llist *list, void *user); /* 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); +struct Curl_llist_node *Curl_llist_head(const 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); +size_t Curl_llist_count(const 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); +void *Curl_node_elem(const struct Curl_llist_node *n); /* Remove the node from the list and return the custom data * from a Curl_llist_node. Does NOT invoke a registered `dtor`. */ @@ -74,9 +74,9 @@ void *Curl_node_take_elem(struct Curl_llist_node *e); /* Curl_node_next() returns the next element in a list from a given Curl_llist_node */ -struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n); +struct Curl_llist_node *Curl_node_next(const 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); +struct Curl_llist *Curl_node_llist(const struct Curl_llist_node *n); #endif /* HEADER_CURL_LLIST_H */ diff --git a/lib/md4.c b/lib/md4.c index 0213483ad30c..e030ffac30c2 100644 --- a/lib/md4.c +++ b/lib/md4.c @@ -158,6 +158,7 @@ static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) #elif defined(USE_GNUTLS) #include +#include typedef struct md4_ctx my_md4_ctx; @@ -175,7 +176,11 @@ static void my_md4_update(my_md4_ctx *ctx, static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) { +#if NETTLE_VERSION_MAJOR >= 4 + md4_digest(ctx, digest); +#else md4_digest(ctx, MD4_DIGEST_SIZE, digest); +#endif } #else diff --git a/lib/md5.c b/lib/md5.c index 4dd0d7c27859..f842c7ae63b4 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -40,13 +40,14 @@ #ifdef USE_MBEDTLS #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #endif #ifdef USE_GNUTLS #include +#include typedef struct md5_ctx my_md5_ctx; @@ -64,7 +65,11 @@ static void my_md5_update(void *ctx, static void my_md5_final(unsigned char *digest, void *ctx) { - md5_digest(ctx, 16, digest); +#if NETTLE_VERSION_MAJOR >= 4 + md5_digest(ctx, digest); +#else + md5_digest(ctx, MD5_DIGEST_LEN, digest); +#endif } #elif defined(USE_OPENSSL) && \ @@ -116,15 +121,16 @@ static void my_md5_final(unsigned char *digest, void *ctx) } #elif defined(USE_MBEDTLS) && \ - defined(PSA_WANT_ALG_MD5) && PSA_WANT_ALG_MD5 /* mbedTLS 4+ */ + defined(PSA_WANT_ALG_MD5) && PSA_WANT_ALG_MD5 #include typedef psa_hash_operation_t my_md5_ctx; static CURLcode my_md5_init(void *ctx) { - memset(ctx, 0, sizeof(my_md5_ctx)); - if(psa_hash_setup(ctx, PSA_ALG_MD5) != PSA_SUCCESS) + psa_hash_operation_t *pctx = (psa_hash_operation_t *)ctx; + *pctx = psa_hash_operation_init(); + if(psa_hash_setup(pctx, PSA_ALG_MD5) != PSA_SUCCESS) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -138,7 +144,7 @@ static void my_md5_update(void *ctx, static void my_md5_final(unsigned char *digest, void *ctx) { size_t actual_length; - (void)psa_hash_finish(ctx, digest, 16, &actual_length); + (void)psa_hash_finish(ctx, digest, MD5_DIGEST_LEN, &actual_length); } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ @@ -215,7 +221,7 @@ 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) + if(length == MD5_DIGEST_LEN) CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); if(ctx->hHash) CryptDestroyHash(ctx->hHash); @@ -524,7 +530,7 @@ const struct HMAC_params Curl_HMAC_MD5 = { my_md5_final, /* Hash computation end function. */ sizeof(my_md5_ctx), /* Size of hash context structure. */ 64, /* Maximum key length. */ - 16 /* Result size. */ + MD5_DIGEST_LEN /* Result size. */ }; const struct MD5_params Curl_DIGEST_MD5 = { @@ -532,7 +538,7 @@ const struct MD5_params Curl_DIGEST_MD5 = { 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 */ + MD5_DIGEST_LEN /* Result size */ }; /* diff --git a/lib/memdebug.c b/lib/memdebug.c index 6eda7a8236ba..b6af2c6d3528 100644 --- a/lib/memdebug.c +++ b/lib/memdebug.c @@ -269,7 +269,7 @@ char *curl_dbg_strdup(const char *str, int line, const char *source) char *mem; size_t len; - DEBUGASSERT(str != NULL); + DEBUGASSERT(str); if(countcheck("strdup", line, source)) return NULL; @@ -294,7 +294,7 @@ wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source) wchar_t *mem; size_t wsiz, bsiz; - DEBUGASSERT(str != NULL); + DEBUGASSERT(str); if(countcheck("wcsdup", line, source)) return NULL; @@ -510,7 +510,7 @@ int curl_dbg_fclose(FILE *file, int line, const char *source) { int res; - DEBUGASSERT(file != NULL); + DEBUGASSERT(file); if(source) curl_dbg_log("FILE %s:%d fclose(%p)\n", source, line, (void *)file); diff --git a/lib/mime.c b/lib/mime.c index b7e51aae4e96..600437576581 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -223,7 +223,7 @@ 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_formescape))) + if(strategy == MIMESTRATEGY_MAIL || (data && data->set.mime_formescape)) table = mimetable; curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); @@ -419,6 +419,11 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, return cursize; } +/* The maximum input size that does not cause an overflow. */ +#define BASE64_MAX_INPUT_SIZE \ + (((CURL_OFF_T_MAX / (MAX_ENCODED_LINE_LENGTH + 2)) * \ + MAX_ENCODED_LINE_LENGTH / 4) * 3 - 3) + static curl_off_t encoder_base64_size(curl_mimepart *part) { curl_off_t size = part->datasize; @@ -426,6 +431,10 @@ static curl_off_t encoder_base64_size(curl_mimepart *part) if(size <= 0) return size; /* Unknown size or no data. */ + /* Prevent integer overflows */ + if(size > BASE64_MAX_INPUT_SIZE) + return -1; + /* Compute base64 character count. */ size = 4 * (1 + ((size - 1) / 3)); @@ -1123,7 +1132,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, curl_mime *mime; curl_mimepart *d; const curl_mimepart *s; - CURLcode res = CURLE_OK; + CURLcode result = CURLE_OK; DEBUGASSERT(dst); @@ -1132,66 +1141,67 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, case MIMEKIND_NONE: break; case MIMEKIND_DATA: - res = curl_mime_data(dst, src->data, (size_t)src->datasize); + result = curl_mime_data(dst, src->data, (size_t)src->datasize); break; case MIMEKIND_FILE: - res = curl_mime_filedata(dst, src->data); + result = curl_mime_filedata(dst, src->data); /* Do not abort duplication if file is not readable. */ - if(res == CURLE_READ_ERROR) - res = CURLE_OK; + if(result == CURLE_READ_ERROR) + result = CURLE_OK; break; case MIMEKIND_CALLBACK: - res = curl_mime_data_cb(dst, src->datasize, src->readfunc, - src->seekfunc, src->freefunc, src->arg); + result = curl_mime_data_cb(dst, src->datasize, src->readfunc, + src->seekfunc, src->freefunc, src->arg); break; case MIMEKIND_MULTIPART: /* 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; + result = mime ? curl_mime_subparts(dst, mime) : CURLE_OUT_OF_MEMORY; /* Duplicate subparts. */ - for(s = ((curl_mime *)src->arg)->firstpart; !res && s; s = s->nextpart) { + for(s = ((curl_mime *)src->arg)->firstpart; !result && s; + s = s->nextpart) { d = curl_mime_addpart(mime); - res = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY; + result = 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? */ + result = CURLE_BAD_FUNCTION_ARGUMENT; /* Internal error? */ break; } /* Duplicate headers. */ - if(!res && src->userheaders) { + if(!result && src->userheaders) { struct curl_slist *hdrs = Curl_slist_duplicate(src->userheaders); if(!hdrs) - res = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; else { /* No one but this procedure knows about the new header list, so always take ownership. */ - res = curl_mime_headers(dst, hdrs, TRUE); - if(res) + result = curl_mime_headers(dst, hdrs, TRUE); + if(result) curl_slist_free_all(hdrs); } } - if(!res) { + if(!result) { /* Duplicate other fields. */ dst->encoder = src->encoder; - res = curl_mime_type(dst, src->mimetype); + result = curl_mime_type(dst, src->mimetype); } - if(!res) - res = curl_mime_name(dst, src->name); - if(!res) - res = curl_mime_filename(dst, src->filename); + if(!result) + result = curl_mime_name(dst, src->name); + if(!result) + result = curl_mime_filename(dst, src->filename); /* If an error occurred, rollback. */ - if(res) + if(result) Curl_mime_cleanpart(dst); - return res; + return result; } /* @@ -1201,9 +1211,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, /* Create a mime handle. */ curl_mime *curl_mime_init(void *easy) { - curl_mime *mime; - - mime = (curl_mime *)curlx_malloc(sizeof(*mime)); + curl_mime *mime = curlx_malloc(sizeof(*mime)); if(mime) { mime->parent = NULL; @@ -1240,7 +1248,7 @@ curl_mimepart *curl_mime_addpart(curl_mime *mime) if(!mime) return NULL; - part = (curl_mimepart *)curlx_malloc(sizeof(*part)); + part = curlx_malloc(sizeof(*part)); if(part) { Curl_mime_initpart(part); @@ -1692,6 +1700,62 @@ static bool content_type_match(const char *contenttype, return FALSE; } +static CURLcode add_content_disposition(struct Curl_easy *data, + curl_mimepart *part, + const char *disposition, + const char *contenttype, + enum mimestrategy strategy) +{ + if(!disposition) + if(part->filename || part->name || + (contenttype && !curl_strnequal(contenttype, "multipart/", 10))) + disposition = DISPOSITION_DEFAULT; + if(disposition && curl_strequal(disposition, "attachment") && + !part->name && !part->filename) + disposition = NULL; + if(disposition) { + CURLcode result = CURLE_OK; + char *name = NULL; + char *filename = NULL; + /* The mail (and legacy mime_formescape) strategy quotes the name and + filename with a backslash and has no in-band way to represent a CR or + LF, so one embedded in the value would split the generated header. The + form strategy percent-encodes CR/LF (see escape_string) and is safe. */ + bool backslash = (strategy == MIMESTRATEGY_MAIL) || + (data && data->set.mime_formescape); + if(backslash && + ((part->name && part->name[strcspn(part->name, "\r\n")]) || + (part->filename && part->filename[strcspn(part->filename, "\r\n")]))) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(part->name) { + name = escape_string(data, part->name, strategy); + if(!name) + return CURLE_OUT_OF_MEMORY; + } + if(part->filename) { + filename = escape_string(data, part->filename, strategy); + if(!filename) + result = CURLE_OUT_OF_MEMORY; + } + if(!result) + result = Curl_mime_add_header(&part->curlheaders, + "Content-Disposition: %s%s%s%s%s%s%s", + disposition, + name ? "; name=\"" : "", + name ? name : "", + name ? "\"" : "", + filename ? "; filename=\"" : "", + filename ? filename : "", + filename ? "\"" : ""); + curlx_safefree(name); + curlx_safefree(filename); + if(result) + return result; + } + return CURLE_OK; +} + CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, curl_mimepart *part, const char *contenttype, @@ -1750,42 +1814,10 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, /* Issue content-disposition header only if not already set by caller. */ if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) { - if(!disposition) - if(part->filename || part->name || - (contenttype && !curl_strnequal(contenttype, "multipart/", 10))) - disposition = DISPOSITION_DEFAULT; - if(disposition && curl_strequal(disposition, "attachment") && - !part->name && !part->filename) - disposition = NULL; - if(disposition) { - char *name = NULL; - char *filename = NULL; - - if(part->name) { - name = escape_string(data, part->name, strategy); - if(!name) - result = CURLE_OUT_OF_MEMORY; - } - if(!result && part->filename) { - filename = escape_string(data, part->filename, strategy); - if(!filename) - result = CURLE_OUT_OF_MEMORY; - } - if(!result) - result = Curl_mime_add_header(&part->curlheaders, - "Content-Disposition: %s%s%s%s%s%s%s", - disposition, - name ? "; name=\"" : "", - name ? name : "", - name ? "\"" : "", - filename ? "; filename=\"" : "", - filename ? filename : "", - filename ? "\"" : ""); - curlx_safefree(name); - curlx_safefree(filename); - if(result) - return result; - } + result = add_content_disposition(data, part, disposition, + contenttype, strategy); + if(result) + return result; } /* Issue Content-Type header. */ @@ -1896,7 +1928,7 @@ static CURLcode cr_mime_read(struct Curl_easy *data, /* 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); + blen, (int)ctx->error_result); *pnread = 0; *peos = FALSE; return ctx->error_result; @@ -1927,7 +1959,7 @@ static CURLcode cr_mime_read(struct Curl_easy *data, 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 + * 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); @@ -2013,8 +2045,8 @@ static CURLcode cr_mime_read(struct Curl_easy *data, } 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, result, *pnread, *peos); + ", read=%" FMT_OFF_T ") -> %d, %zu, %d", blen, + ctx->total_len, ctx->read_len, (int)result, *pnread, *peos); return result; } diff --git a/lib/mprintf.c b/lib/mprintf.c index 6eaea66b7055..d4725c905fe1 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -244,7 +244,7 @@ static int parse_flags(const char **fmtp, unsigned int *flagsp, int use_dollar, fmt += 2; } else { -#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) +#if SIZEOF_CURL_OFF_T > SIZEOF_LONG flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; @@ -267,14 +267,14 @@ static int parse_flags(const char **fmtp, unsigned int *flagsp, int use_dollar, case 'z': /* the code below generates a warning if -Wunreachable-code is used */ -#if (SIZEOF_SIZE_T > SIZEOF_LONG) +#if SIZEOF_SIZE_T > SIZEOF_LONG flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; #endif break; case 'O': -#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) +#if SIZEOF_CURL_OFF_T > SIZEOF_LONG flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; @@ -395,22 +395,26 @@ static bool parse_conversion(const char f, unsigned int *flagp, flags |= FLAGS_CHAR; break; case 'f': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; + break; + case 'F': + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; + flags |= FLAGS_UPPER; break; case 'e': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATE | FLAGS_UPPER; break; case 'g': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - type = MTYPE_DOUBLE; + type = flags & FLAGS_LONGDOUBLE ? MTYPE_LONGDOUBLE : MTYPE_DOUBLE; flags |= FLAGS_FLOATG | FLAGS_UPPER; break; default: @@ -423,7 +427,6 @@ static bool parse_conversion(const char f, unsigned int *flagp, return FALSE; } - static int parsefmt(const char *format, struct outsegment *out, struct va_input *in, @@ -619,6 +622,10 @@ static int parsefmt(const char *format, iptr->val.dnum = va_arg(arglist, double); break; + case MTYPE_LONGDOUBLE: + iptr->val.dnum = (double)va_arg(arglist, long double); + break; + default: DEBUGASSERT(NULL); /* unexpected */ break; @@ -679,7 +686,7 @@ static bool out_double(void *userp, prec = maxprec - 1; if(width > 0 && prec <= width) maxprec -= width; - while(val >= 10.0) { + while(maxprec && (val >= 10.0)) { val /= 10; maxprec--; } @@ -700,7 +707,7 @@ static bool out_double(void *userp, else if(flags & FLAGS_FLOATG) *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); else - *fptr++ = 'f'; + *fptr++ = (flags & FLAGS_UPPER) ? 'F' : 'f'; *fptr = 0; /* and a final null-termination */ @@ -713,9 +720,7 @@ static bool out_double(void *userp, #ifdef _WIN32 curlx_win32_snprintf(work, BUFFSIZE, fmt, dnum); #else - /* !checksrc! disable BANNEDFUNC 1 */ - /* !checksrc! disable LONGLINE */ - /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + /* !checksrc! disable BANNEDFUNC 2 */ snprintf(work, BUFFSIZE, fmt, dnum); #endif #ifdef CURL_HAVE_DIAG @@ -879,9 +884,9 @@ static bool out_string(void *userp, if(!str) { /* Write null string if there is space. */ - if(prec == -1 || prec >= (int)sizeof(nilstr) - 1) { + if(prec == -1 || prec >= (int)CURL_CSTRLEN(nilstr)) { str = nilstr; - len = sizeof(nilstr) - 1; + len = CURL_CSTRLEN(nilstr); /* Disable quotes around (nil) */ flags &= ~(unsigned int)FLAGS_ALT; } @@ -940,7 +945,7 @@ static bool out_pointer(void *userp, int width = p->width; int flags = p->flags; - width -= (int)(sizeof(nilstr) - 1); + width -= (int)CURL_CSTRLEN(nilstr); if(flags & FLAGS_LEFT) while(width-- > 0) OUTCHAR(' '); @@ -968,7 +973,6 @@ static bool out_pointer(void *userp, * * All output is sent to the 'stream()' callback, one byte at a time. */ - static int formatf(void *userp, /* untouched by format(), sent to the stream() function in the second argument */ /* function pointer called for each output character */ @@ -1067,6 +1071,7 @@ static int formatf(void *userp, /* untouched by format(), sent to the break; case MTYPE_DOUBLE: + case MTYPE_LONGDOUBLE: if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) return done; break; @@ -1075,9 +1080,8 @@ static int formatf(void *userp, /* untouched by format(), sent to the /* Answer the count of characters written. */ if(p.flags & FLAGS_LONGLONG) *(int64_t *)iptr->val.ptr = (int64_t)done; - else - if(p.flags & FLAGS_LONG) - *(long *)iptr->val.ptr = (long)done; + else if(p.flags & FLAGS_LONG) + *(long *)iptr->val.ptr = (long)done; else if(!(p.flags & FLAGS_SHORT)) *(int *)iptr->val.ptr = done; else diff --git a/lib/mqtt.c b/lib/mqtt.c index 84fd272e21e7..29e92ce5c133 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -276,15 +276,13 @@ static CURLcode mqtt_connect(struct Curl_easy *data) size_t start_user = 0; size_t start_pwd = 0; char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; - const size_t clen = strlen("curl"); + const size_t clen = CURL_CSTRLEN("curl"); char *packet = NULL; /* extracting username from request */ - const char *username = data->state.aptr.user ? data->state.aptr.user : ""; - const size_t ulen = strlen(username); - /* extracting password from request */ - const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : ""; - const size_t plen = strlen(passwd); + struct Curl_creds *creds = data->state.creds; + const size_t ulen = creds ? strlen(creds->user) : 0; + const size_t plen = creds ? strlen(creds->passwd) : 0; 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 */ @@ -326,7 +324,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) if(ulen) { start_pwd += 2; - rc = add_user(username, ulen, + rc = add_user(creds->user, ulen, (unsigned char *)packet, start_user, remain_pos); if(rc) { failf(data, "Username too long: [%zu]", ulen); @@ -337,7 +335,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) /* if passwd was provided, add it to the packet */ if(plen) { - rc = add_passwd(passwd, plen, packet, start_pwd, remain_pos); + rc = add_passwd(creds->passwd, plen, packet, start_pwd, remain_pos); if(rc) { failf(data, "Password too long: [%zu]", plen); result = CURLE_WEIRD_SERVER_REPLY; @@ -349,10 +347,11 @@ static CURLcode mqtt_connect(struct Curl_easy *data) result = mqtt_send(data, packet, packetlen); end: - if(packet) + if(packet) { + curlx_memzero(packet, packetlen); curlx_free(packet); - curlx_safefree(data->state.aptr.user); - curlx_safefree(data->state.aptr.passwd); + } + Curl_creds_unlink(&data->state.creds); return result; } @@ -427,7 +426,7 @@ static CURLcode mqtt_verify_connack(struct Curl_easy *data) if(ptr[0] != 0x00 || ptr[1] != 0x00) { failf(data, "Expected %02x%02x but got %02x%02x", - 0x00, 0x00, ptr[0], ptr[1]); + 0x00U, 0x00U, (unsigned char)ptr[0], (unsigned char)ptr[1]); curlx_dyn_reset(&mq->recvbuf); return CURLE_WEIRD_SERVER_REPLY; } @@ -441,7 +440,7 @@ static CURLcode mqtt_get_topic(struct Curl_easy *data, const char *path = data->state.up.path; CURLcode result = CURLE_URL_MALFORMAT; if(strlen(path) > 1) { - result = Curl_urldecode(path + 1, 0, topic, topiclen, REJECT_NADA); + result = Curl_urldecode(path + 1, 0, topic, topiclen, REJECT_CTRL); if(!result && (*topiclen > 0xffff)) { failf(data, "Too long MQTT topic"); result = CURLE_URL_MALFORMAT; @@ -605,9 +604,9 @@ static CURLcode mqtt_publish(struct Curl_easy *data) return result; } -/* return 0 on success, non-zero on error */ -static int mqtt_decode_len(size_t *lenp, const unsigned char *buf, - size_t buflen) +/* return FALSE on success, TRUE on error */ +static bool mqtt_decode_len(size_t *lenp, const unsigned char *buf, + size_t buflen) { size_t len = 0; size_t mult = 1; @@ -616,18 +615,21 @@ static int mqtt_decode_len(size_t *lenp, const unsigned char *buf, for(i = 0; (i < buflen) && (encoded & 128); i++) { if(i == 4) - return 1; /* bad size */ + return TRUE; /* bad size */ encoded = buf[i]; len += (encoded & 127) * mult; mult *= 128; } + if(encoded & 128) + /* truncated size */ + return TRUE; *lenp = len; - return 0; + return FALSE; } #if defined(DEBUGBUILD) && defined(CURLVERBOSE) -static const char *statenames[] = { +static const char * const statenames[] = { "MQTT_FIRST", "MQTT_REMAINING_LENGTH", "MQTT_CONNACK", @@ -774,7 +776,7 @@ static CURLcode mqtt_do(struct Curl_easy *data, bool *done) result = mqtt_connect(data); if(result) { - failf(data, "Error %d sending MQTT CONNECT request", result); + failf(data, "Error %d sending MQTT CONNECT request", (int)result); return result; } mqstate(data, MQTT_FIRST, MQTT_CONNACK); @@ -892,6 +894,24 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) break; } mq->npacket = 0; + /* PINGRESP and DISCONNECT must have remaining_length == 0 and + * reserved bits (low nibble) must be zero per MQTT 3.1.1 + * sections 2.2.2, 3.13.1 and 3.14.1. Reject before state + * dispatch to prevent nextstate confusion. */ + { + const unsigned char type = mq->firstbyte & 0xF0; + const unsigned char reserved = mq->firstbyte & 0x0F; + if((type == MQTT_MSG_DISCONNECT || type == MQTT_MSG_PINGRESP) && + (mq->remaining_length || reserved)) { + failf(data, + "Broker sent malformed %s " + "(remaining_length=%zu, header byte=0x%02x)", + type == MQTT_MSG_DISCONNECT ? "DISCONNECT" : "PINGRESP", + mq->remaining_length, mq->firstbyte); + result = CURLE_WEIRD_SERVER_REPLY; + break; + } + } if(mq->remaining_length) { mqstate(data, mqtt->nextstate, MQTT_NOSTATE); break; @@ -957,7 +977,7 @@ static CURLcode mqtts_connecting(struct Curl_easy *data, bool *done) result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) - connclose(conn, "Failed TLS connection"); + connclose(conn); return result; } diff --git a/lib/multi.c b/lib/multi.c index 7520253d702c..fbefa6f30d91 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -49,13 +49,11 @@ #include "bufref.h" /* initial multi->xfers table size for a full multi */ -#define CURL_XFER_TABLE_SIZE 512 +#define CURL_XFER_TABLE_SIZE 128 -/* - 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 6K memory, therefore this 3K are not significant. -*/ +/* 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 6K memory, therefore this 3K are not significant. */ #ifndef CURL_SOCKET_HASH_TABLE_SIZE #define CURL_SOCKET_HASH_TABLE_SIZE 911 #endif @@ -72,29 +70,15 @@ #define CURL_TLS_SESSION_SIZE 25 #endif -#define CURL_MULTI_HANDLE 0x000bab1e - -#ifdef DEBUGBUILD -/* On a debug build, we want to fail hard on multi handles that - * are not NULL, but no longer have the MAGIC touch. This gives - * us early warning on things only discovered by valgrind otherwise. */ -#define GOOD_MULTI_HANDLE(x) \ - (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \ - (DEBUGASSERT(!(x)), FALSE)) -#else -#define GOOD_MULTI_HANDLE(x) \ - ((x) && (x)->magic == CURL_MULTI_HANDLE) -#endif - static void move_pending_to_connect(struct Curl_multi *multi, struct Curl_easy *data); static CURLMcode add_next_timeout(const struct curltime *pnow, struct Curl_multi *multi, - struct Curl_easy *d); + struct Curl_easy *data); static void multi_timeout(struct Curl_multi *multi, - struct curltime *expire_time, - long *timeout_ms); -static void process_pending_handles(struct Curl_multi *multi); + timediff_t *pexire_offset_us, + int *timeout_ms); +static void multi_schedule_pending(struct Curl_multi *multi); static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD static void multi_xfer_tbl_dump(struct Curl_multi *multi); @@ -106,24 +90,60 @@ static const struct curltime *multi_now(struct Curl_multi *multi) return &multi->now; } -/* function pointer called once when switching TO a state */ -typedef void (*init_multistate_func)(struct Curl_easy *data); +/* function pointer called once when entering a state */ +typedef void (*mstate_enter_func)(struct Curl_easy *data, + CURLMstate from_state); + +static void mstate_enter_connect(struct Curl_easy *data, + CURLMstate from_state) +{ + (void)from_state; + Curl_init_CONNECT(data); +} -/* called in DID state, before PERFORMING state */ -static void before_perform(struct Curl_easy *data) +static void mstate_enter_did(struct Curl_easy *data, + CURLMstate from_state) { + (void)from_state; data->req.chunk = FALSE; Curl_pgrsTime(data, TIMER_PRETRANSFER); + if(!CURL_REQ_WANT_SEND(data)) + Curl_pgrsTime(data, TIMER_POSTRANSFER); +} + +static void mstate_enter_done(struct Curl_easy *data, + CURLMstate from_state) +{ + (void)from_state; + CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); } -static void init_completed(struct Curl_easy *data) +static void mstate_enter_completed(struct Curl_easy *data, + CURLMstate from_state) { - /* this is a completed transfer */ + /* we sometimes directly jump to COMPLETED, trigger things + * we then missed. */ + if(from_state < MSTATE_DID) { + Curl_pgrsTime(data, TIMER_PRETRANSFER); + Curl_pgrsTime(data, TIMER_POSTRANSFER); + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + } + Curl_pgrsCompleted(data); + if(from_state < MSTATE_DONE) + CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); + /* changing to COMPLETED means it is in process and needs to go */ + DEBUGASSERT(Curl_uint32_bset_contains(&data->multi->process, data->mid)); + Curl_uint32_bset_remove(&data->multi->process, data->mid); + Curl_uint32_bset_remove(&data->multi->pending, data->mid); /* to be sure */ + if(Curl_uint32_bset_empty(&data->multi->process)) { + /* free the transfer buffer when we have no more active transfers */ + multi_xfer_bufs_free(data->multi); + } /* 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 */ + Curl_expire_clear_all(data); /* stop all timers */ } /* always use this function to change state, to make debugging easier */ @@ -134,23 +154,23 @@ static void mstate(struct Curl_easy *data, CURLMstate state ) { CURLMstate oldstate = data->mstate; - static const init_multistate_func finit[MSTATE_LAST] = { - NULL, /* INIT */ - NULL, /* PENDING */ - NULL, /* SETUP */ - Curl_init_CONNECT, /* CONNECT */ - NULL, /* CONNECTING */ - NULL, /* PROTOCONNECT */ - NULL, /* PROTOCONNECTING */ - NULL, /* DO */ - NULL, /* DOING */ - NULL, /* DOING_MORE */ - before_perform, /* DID */ - NULL, /* PERFORMING */ - NULL, /* RATELIMITING */ - NULL, /* DONE */ - init_completed, /* COMPLETED */ - NULL /* MSGSENT */ + static const mstate_enter_func state_enter[MSTATE_LAST] = { + NULL, /* INIT */ + NULL, /* PENDING */ + NULL, /* SETUP */ + mstate_enter_connect, /* CONNECT */ + NULL, /* CONNECTING */ + NULL, /* PROTOCONNECT */ + NULL, /* PROTOCONNECTING */ + NULL, /* DO */ + NULL, /* DOING */ + NULL, /* DOING_MORE */ + mstate_enter_did, /* DID */ + NULL, /* PERFORMING */ + NULL, /* RATELIMITING */ + mstate_enter_done, /* DONE */ + mstate_enter_completed, /* COMPLETED */ + NULL /* MSGSENT */ }; if(oldstate == state) @@ -166,32 +186,8 @@ static void mstate(struct Curl_easy *data, CURLMstate state /* really switching state */ data->mstate = state; - switch(state) { - case MSTATE_DONE: - CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); - break; - case MSTATE_COMPLETED: - /* we sometimes directly jump to COMPLETED, trigger also a notification - * in that case. */ - if(oldstate < MSTATE_DONE) - CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); - /* changing to COMPLETED means it is in process and needs to go */ - DEBUGASSERT(Curl_uint32_bset_contains(&data->multi->process, data->mid)); - Curl_uint32_bset_remove(&data->multi->process, data->mid); - Curl_uint32_bset_remove(&data->multi->pending, data->mid); /* to be sure */ - - if(Curl_uint32_bset_empty(&data->multi->process)) { - /* free the transfer buffer when we have no more active transfers */ - multi_xfer_bufs_free(data->multi); - } - break; - default: - break; - } - - /* if this state has an init-function, run it */ - if(finit[state]) - finit[state](data); + if(state_enter[state]) + state_enter[state](data, oldstate); } #ifndef DEBUGBUILD @@ -208,7 +204,7 @@ static void ph_freeentry(void *p) /* Always FALSE. Cannot use a 0 assert here since compilers * are not in agreement if they then want a NORETURN attribute or * not. *sigh* */ - DEBUGASSERT(p == NULL); + DEBUGASSERT(!p); } /* @@ -224,6 +220,8 @@ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) Curl_llist_append(&multi->msglist, msg, &msg->list); } +static void multi_timeouts_init(struct Curl_easy *data); + struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, size_t ev_hashsize, /* event hash */ size_t chashsize, /* connection hash */ @@ -235,7 +233,24 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(!multi) return NULL; - multi->magic = CURL_MULTI_HANDLE; + multi->magic = CURLMULTI_MAGIC_NUMBER; + + /* Initialisation order is important here! + * easy_init() does a lazy check on curl_global_init() which sets + * up platform specific things we need. For example calling curlx_pnow() + * before this is not safe. */ + multi->admin = curl_easy_init(); + if(!multi->admin) { + curlx_free(multi); + return NULL; + } + multi->admin->multi = multi; + multi->admin->state.internal = TRUE; + + /* Now we can use curlx_* things safely */ + curlx_pnow(&multi->now); + Curl_timeouts_init(&multi->timeouts, &multi->now); + multi_timeouts_init(multi->admin); Curl_dnscache_init(&multi->dnscache, dnssize); Curl_mntfy_init(multi); @@ -256,6 +271,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->wakeup_pair[0] = CURL_SOCKET_BAD; multi->wakeup_pair[1] = CURL_SOCKET_BAD; #endif +#ifdef ENABLE_INTERNAL_WAKEUP + multi->wakeup_internal[0] = CURL_SOCKET_BAD; + multi->wakeup_internal[1] = CURL_SOCKET_BAD; +#endif if(Curl_mntfy_resize(multi) || Curl_uint32_bset_resize(&multi->process, xfer_table_size) || @@ -265,14 +284,6 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_uint32_tbl_resize(&multi->xfers, xfer_table_size)) goto error; - 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; @@ -283,7 +294,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(Curl_cshutdn_init(&multi->cshutdn, multi)) goto error; - Curl_cpool_init(&multi->cpool, multi->admin, NULL, chashsize); + Curl_cpool_init(&multi->cpool, NULL, chashsize); #ifdef USE_SSL if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) @@ -303,6 +314,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, if(Curl_wakeup_init(multi->wakeup_pair, TRUE) < 0) goto error; #endif +#ifdef ENABLE_INTERNAL_WAKEUP + if(Curl_wakeup_init(multi->wakeup_internal, TRUE) < 0) + goto error; +#endif if(Curl_probeipv6(multi)) goto error; @@ -328,7 +343,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, Curl_multi_ev_cleanup(multi); Curl_hash_destroy(&multi->proto_hash); Curl_dnscache_destroy(&multi->dnscache); - Curl_cpool_destroy(&multi->cpool); + Curl_cpool_destroy(&multi->cpool, multi->admin); Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); #ifdef USE_SSL Curl_ssl_scache_destroy(multi->ssl_scache); @@ -348,6 +363,9 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, #ifdef ENABLE_WAKEUP Curl_wakeup_destroy(multi->wakeup_pair); #endif +#ifdef ENABLE_INTERNAL_WAKEUP + Curl_wakeup_destroy(multi->wakeup_internal); +#endif curlx_free(multi); return NULL; @@ -383,7 +401,7 @@ bool Curl_is_connecting(struct Curl_easy *data) static CURLMcode multi_assess_wakeup(struct Curl_multi *multi) { -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP if(multi->socket_cb) return Curl_multi_ev_assess_xfer(multi, multi->admin); #else @@ -404,26 +422,21 @@ static CURLMcode multi_xfers_add(struct Curl_multi *multi, if(capacity < max_capacity) { /* We want `multi->xfers` to have "sufficient" free rows, so that we do - * have to reuse the `mid` from a removed easy right away. - * Since uint_tbl and uint_bset are memory efficient, - * regard less than 25% free as insufficient. - * (for low capacities, e.g. multi_easy, 4 or less). */ + * not have to reuse the `mid` from a removed easy right away. + * Check if an 8th of the capacity is still free */ uint32_t used = Curl_uint32_tbl_count(&multi->xfers); uint32_t unused = capacity - used; - uint32_t min_unused = CURLMAX(capacity >> 2, 4); - if(unused <= min_unused) { + uint32_t min_unused = CURLMAX(capacity >> 3, 4); + if(unused < min_unused) { + /* Grow by 50% of current capacity, in range of [128, 2048], + * which means the table grows max by 16kb on 64-bit arch. */ + uint32_t growth = CURLMIN(CURLMAX(capacity >> 1, 128), 2048); /* Make sure the uint arithmetic here works on the corner * cases where we are close to max_capacity or UINT_MAX */ - if((min_unused >= max_capacity) || - ((max_capacity - min_unused) <= capacity) || - ((UINT_MAX - min_unused - 63) <= capacity)) { - new_size = max_capacity; /* can not be larger than this */ - } - else { - /* make it a 64 multiple, since our bitsets frow by that and - * small (easy_multi) grows to at least 64 on first resize. */ - new_size = (((used + min_unused) + 63) / 64) * 64; - } + if((max_capacity - growth) <= capacity) + new_size = max_capacity; + else + new_size = capacity + growth; } } @@ -451,28 +464,16 @@ static CURLMcode multi_xfers_add(struct Curl_multi *multi, return CURLM_OK; } -CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) +CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, + struct Curl_easy *data) { CURLMcode mresult; - struct Curl_multi *multi = m; - struct Curl_easy *data = curl; - - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; /* Prevent users from adding same easy handle more than once and prevent adding to more than one multi stack */ if(data->multi) return CURLM_ADDED_ALREADY; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - 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 @@ -499,26 +500,28 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) if(multi_xfers_add(multi, data)) return CURLM_OUT_OF_MEMORY; - /* Initialize timeout list for this handle */ - Curl_llist_init(&data->state.timeoutlist, NULL); + /* Initialize timeouts for this handle */ + multi_timeouts_init(data); /* - * 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. + * 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. */ + /* make the Curl_easy refer back to this multi handle - before + Curl_expire() is called. */ data->multi = multi; /* set the easy handle */ multistate(data, MSTATE_INIT); + /* not yet passed INIT state */ + data->state.really_alive = FALSE; #ifdef USE_LIBPSL /* Do the same for PSL. */ @@ -558,33 +561,28 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) data->set.server_response_timeout; multi->admin->set.no_signal = data->set.no_signal; - mresult = multi_assess_wakeup(multi); - if(mresult) { - failf(data, "error enabling wakeup listening: %d", mresult); - return mresult; - } - CURL_TRC_M(data, "added to multi, mid=%u, running=%u, total=%u", data->mid, Curl_multi_xfers_running(multi), Curl_uint32_tbl_count(&multi->xfers)); return CURLM_OK; } -#if 0 -/* Debug-function, used like this: - * - * Curl_hash_print(&multi->sockhash, debug_print_sock_hash); - * - * Enable the hash print function first by editing hash.c - */ -static void debug_print_sock_hash(void *p) +CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) { - struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; + struct Curl_mapi_guard guard; + CURLMcode mresult; - curl_mfprintf(stderr, " [readers %u][writers %u]", - sh->readers, sh->writers); + if(CURL_MAPI_ENTER(&guard, m, multi_add_handle, &mresult)) { + struct Curl_easy *data = curl; + /* Verify that we got a somewhat good easy handle too */ + if(!GOOD_EASY_HANDLE(data)) + mresult = CURLM_BAD_EASY_HANDLE; + else + mresult = Curl_multi_add_handle(m, data); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } -#endif struct multi_done_ctx { BIT(premature); @@ -648,33 +646,33 @@ static void multi_done_locked(struct connectdata *conn, } data->state.done = TRUE; /* called now! */ - data->state.recent_conn_id = conn->connection_id; Curl_dnscache_prune(data); if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) { - CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s, " + CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s:%u, " "forbid=%d, close=%d, premature=%d, conn_multiplex=%d", - conn->connection_id, conn->destination, + conn->connection_id, conn->origin->user_hostname, + conn->origin->port, data->set.reuse_forbid, conn->bits.close, mdctx->premature, Curl_conn_is_multiplex(conn, FIRSTSOCKET)); - connclose(conn, "disconnecting"); - Curl_conn_terminate(data, conn, (bool)mdctx->premature); + connclose(conn); + Curl_conn_close(data, conn, (bool)mdctx->premature); } else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { - CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s was shutdown" + CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s:%u was shutdown" " by server, not reusing", conn->connection_id, - conn->destination); - connclose(conn, "server shutdown"); - Curl_conn_terminate(data, conn, (bool)mdctx->premature); + conn->origin->user_hostname, conn->origin->port); + connclose(conn); + Curl_conn_close(data, conn, (bool)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 */ - data->state.lastconnect_id = conn->connection_id; - infof(data, "Connection #%" FMT_OFF_T " to host %s left intact", - conn->connection_id, conn->destination); + infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", + conn->connection_id, conn->origin->user_hostname, + conn->origin->port); } else { /* connection was removed from the cpool and destroyed. */ @@ -743,7 +741,7 @@ static CURLcode multi_done(struct Curl_easy *data, if(conn) Curl_conn_ev_data_done(data, premature); - process_pending_handles(data->multi); /* connection / multiplex */ + multi_schedule_pending(data->multi); /* connection / multiplex */ if(!result) result = Curl_req_done(&data->req, data, premature); @@ -763,33 +761,14 @@ static CURLcode multi_done(struct Curl_easy *data, return result; } -static void close_connect_only(struct connectdata *conn, - struct Curl_easy *data, - void *userdata) -{ - (void)userdata; - (void)data; - if(conn->bits.connect_only) - connclose(conn, "Removing connect-only easy handle"); -} - -CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) +CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, + struct Curl_easy *data) { - struct Curl_multi *multi = m; - struct Curl_easy *data = curl; + CURLMcode mresult; bool premature; struct Curl_llist_node *e; - CURLMcode mresult; uint32_t mid; - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; - /* Prevent users from trying to remove same easy handle more than once */ if(!data->multi) return CURLM_OK; /* it is already removed so let's say it is fine! */ @@ -807,39 +786,37 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) return CURLM_INTERNAL_ERROR; } - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - 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(data->conn && - data->mstate > MSTATE_DO && - data->mstate < MSTATE_COMPLETED) { - /* 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"); - } - if(data->conn) { + /* 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 && (data->mstate > MSTATE_DO)) + streamclose(data->conn); + /* multi_done() clears the association between the easy handle and the connection. - Note that this ignores the return code because there is nothing really useful to do with it anyway! */ (void)multi_done(data, data->result, premature); } - /* 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 + /* The timer must be shut down before data->multi is set to NULL, else + data's splaynode would 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); + Curl_expire_clear_all(data); /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ if(!Curl_uint32_bset_contains(&multi->msgsent, data->mid)) --multi->xfers_alive; + if(data->state.really_alive) { + data->state.really_alive = FALSE; + --multi->xfers_really_alive; + if(!multi->xfers_really_alive) + (void)multi_assess_wakeup(multi); + } + Curl_wildcard_dtor(&data->wildcard); data->mstate = MSTATE_COMPLETED; @@ -850,27 +827,29 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) /* 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. This easy handle cannot find the connection - anymore once removed from the multi handle - - Better close the connection here, at once. - */ - struct connectdata *c; - curl_socket_t s; - s = Curl_getconnectinfo(data, &c); - if((s != CURL_SOCKET_BAD) && c) { - Curl_conn_terminate(data, c, TRUE); + if(data->set.connect_only) { + if(data->multi_easy) { + if(data->state.lastconnect_id != -1) { + /* Mark any connect-only connection for closure */ + struct connectdata *conn; + (void)Curl_getconnectinfo(data, &conn); + if(conn && conn->bits.connect_only) + connclose(conn); + } + } + else { + /* 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 connection anymore and it is + forbidden from reuse. This easy handle cannot find the connection + anymore once removed from the multi handle + + Better close the connection here, at once. */ + struct connectdata *conn; + (void)Curl_getconnectinfo(data, &conn); + if(conn) + Curl_conn_close(data, conn, TRUE); } - } - - if(data->state.lastconnect_id != -1) { - /* Mark any connect-only connection for closure */ - Curl_cpool_do_by_id(data, data->state.lastconnect_id, - close_connect_only, NULL); } #ifdef USE_LIBPSL @@ -903,10 +882,8 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) data->mid = UINT32_MAX; data->master_mid = UINT32_MAX; - /* NOTE NOTE NOTE - We do not touch the easy handle here! */ - process_pending_handles(multi); - + /* A pending transfer *might* be able to run now. */ + multi_schedule_pending(multi); mresult = Curl_update_timer(multi); if(mresult) return mresult; @@ -923,6 +900,22 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) return CURLM_OK; } +CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) +{ + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_remove_handle, &mresult)) { + struct Curl_easy *data = curl; + if(!GOOD_EASY_HANDLE(data)) + mresult = CURLM_BAD_EASY_HANDLE; + else + mresult = Curl_multi_remove_handle(m, data); + } + CURL_MAPI_LEAVE(&guard); + return mresult; +} + /* Return TRUE if the application asked for multiplexing */ bool Curl_multiplex_wanted(const struct Curl_multi *multi) { @@ -953,16 +946,23 @@ void Curl_detach_connection(struct Curl_easy *data) /* * Curl_attach_connection() attaches this transfer to this connection. * - * This is the only function that should assign data->conn + * This is the only function that should assign data->conn. + * `matched == TRUE` means the transfer's properties match this + * connection and it is not a temporary attach for maintenance. */ void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + bool matched) { DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(conn); DEBUGASSERT(conn->attached_xfers < UINT32_MAX); data->conn = conn; + if(matched) + data->state.lastconnect_id = conn->connection_id; + else + DEBUGASSERT(!data->mid); /* admin handle */ conn->attached_xfers++; /* all attached transfers must be from the same multi */ if(!conn->attached_multi) @@ -980,11 +980,10 @@ static CURLcode multi_adjust_pollset(struct Curl_easy *data, CURLcode result = CURLE_OK; if(ps->n) { - const struct curltime *pnow = Curl_pgrs_now(data); bool send_blocked, recv_blocked; - recv_blocked = (Curl_rlimit_avail(&data->progress.dl.rlimit, pnow) <= 0); - send_blocked = (Curl_rlimit_avail(&data->progress.ul.rlimit, pnow) <= 0); + recv_blocked = (Curl_rlimit_avail(&data->progress.dl.rlimit, NULL) <= 0); + send_blocked = (Curl_rlimit_avail(&data->progress.ul.rlimit, NULL) <= 0); if(send_blocked || recv_blocked) { int i; for(i = 0; i <= SECONDARYSOCKET; ++i) { @@ -1127,6 +1126,17 @@ static CURLcode mstate_perform_pollset(struct Curl_easy *data, return result; } +#ifdef CURLVERBOSE +static size_t multi_timeouts_count(struct expire_timers *timeouts) +{ + size_t n = 0; + uint8_t eid = timeouts->first; + for(; eid < EXPIRE_LAST; eid = timeouts->next[eid]) + ++n; + return n; +} +#endif + /* Initializes `poll_set` with the current socket poll actions needed * for transfer `data`. */ CURLMcode Curl_multi_pollset(struct Curl_easy *data, @@ -1135,12 +1145,14 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, CURLcode result = CURLE_OK; Curl_pollset_reset(ps); -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP /* The admin handle always listens on the wakeup socket when there * are transfers alive. */ if(data->multi && (data == data->multi->admin) && - data->multi->xfers_alive) { - result = Curl_pollset_add_in(data, ps, data->multi->wakeup_pair[0]); + data->multi->xfers_really_alive) { + CURL_TRC_M(data, "adding wakeup, %u xfers really alive", + data->multi->xfers_really_alive); + result = Curl_pollset_add_in(data, ps, data->multi->wakeup_internal[0]); } #endif /* If the transfer has no connection, this is fine. Happens when @@ -1156,10 +1168,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, break; case MSTATE_CONNECTING: - if(data->conn && !data->conn->bits.dns_resolved) - result = Curl_resolv_pollset(data, ps); - if(!result) - result = mstate_connecting_pollset(data, ps); + result = mstate_connecting_pollset(data, ps); break; case MSTATE_PROTOCONNECT: @@ -1192,7 +1201,8 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, break; default: - failf(data, "multi_getsock: unexpected multi state %d", data->mstate); + failf(data, "multi_getsock: unexpected multi state %d", + (int)data->mstate); DEBUGASSERT(0); break; } @@ -1201,13 +1211,13 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, if(result) { if(result == CURLE_OUT_OF_MEMORY) return CURLM_OUT_OF_MEMORY; - failf(data, "error determining pollset: %d", result); + failf(data, "error determining pollset: %d", (int)result); return CURLM_INTERNAL_ERROR; } #ifdef CURLVERBOSE if(CURL_TRC_M_is_verbose(data)) { - size_t timeout_count = Curl_llist_count(&data->state.timeoutlist); + size_t timeout_count = multi_timeouts_count(&data->state.timeouts); switch(ps->n) { case 0: CURL_TRC_M(data, "pollset[], timeouts=%zu, paused %d/%d (r/w)", @@ -1248,54 +1258,55 @@ 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. */ - int this_max_fd = -1; - struct Curl_multi *multi = m; - struct easy_pollset ps; - unsigned int i; - uint32_t mid; - (void)exc_fd_set; - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + struct Curl_mapi_guard guard; + CURLMcode mresult; - Curl_pollset_init(&ps); - if(Curl_uint32_bset_first(&multi->process, &mid)) { - do { - struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(CURL_MAPI_ENTER(&guard, m, multi_fdset, &mresult)) { + /* 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_multi *multi = m; + struct easy_pollset ps; + int this_max_fd = -1; + unsigned int i; + uint32_t mid; + (void)exc_fd_set; - if(!data) { - DEBUGASSERT(0); - continue; - } + Curl_pollset_init(&ps); + if(Curl_uint32_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - Curl_multi_pollset(data, &ps); - for(i = 0; i < ps.n; i++) { - if(!FDSET_SOCK(ps.sockets[i])) - /* pretend it does not exist */ + if(!data) { + DEBUGASSERT(0); continue; - 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((int)ps.sockets[i] > this_max_fd) - this_max_fd = (int)ps.sockets[i]; - } - } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); - } + } - Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, - read_fd_set, write_fd_set, &this_max_fd); + Curl_multi_pollset(data, &ps); + for(i = 0; i < ps.n; i++) { + if(!FDSET_SOCK(ps.sockets[i])) + /* pretend it does not exist */ + continue; + 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((int)ps.sockets[i] > this_max_fd) + this_max_fd = (int)ps.sockets[i]; + } + } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + } - *max_fd = this_max_fd; - Curl_pollset_cleanup(&ps); + Curl_cshutdn_setfds(&multi->cshutdn, read_fd_set, write_fd_set, + &this_max_fd); - return CURLM_OK; + *max_fd = this_max_fd; + Curl_pollset_cleanup(&ps); + + mresult = CURLM_OK; + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_waitfds(CURLM *m, @@ -1303,46 +1314,49 @@ CURLMcode curl_multi_waitfds(CURLM *m, unsigned int size, unsigned int *fd_count) { - struct Curl_waitfds cwfds; - CURLMcode mresult = CURLM_OK; - struct Curl_multi *multi = m; - struct easy_pollset ps; - unsigned int need = 0; - uint32_t mid; - - if(!ufds && (size || !fd_count)) - return CURLM_BAD_FUNCTION_ARGUMENT; + struct Curl_mapi_guard guard; + CURLMcode mresult; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + if(CURL_MAPI_ENTER(&guard, m, multi_waitfds, &mresult)) { + struct Curl_waitfds cwfds; + struct Curl_multi *multi = m; + struct easy_pollset ps; + unsigned int need = 0; + uint32_t mid; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + if(!ufds && (size || !fd_count)) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } - Curl_pollset_init(&ps); - Curl_waitfds_init(&cwfds, ufds, size); - if(Curl_uint32_bset_first(&multi->process, &mid)) { - do { - struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - if(!data) { - DEBUGASSERT(0); - Curl_uint32_bset_remove(&multi->process, mid); - Curl_uint32_bset_remove(&multi->dirty, mid); - continue; - } - Curl_multi_pollset(data, &ps); - need += Curl_waitfds_add_ps(&cwfds, &ps); - } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); - } + Curl_pollset_init(&ps); + Curl_waitfds_init(&cwfds, ufds, size); + mresult = CURLM_OK; + if(Curl_uint32_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); + Curl_uint32_bset_remove(&multi->process, mid); + Curl_uint32_bset_remove(&multi->dirty, mid); + continue; + } + Curl_multi_pollset(data, &ps); + need += Curl_waitfds_add_ps(&cwfds, &ps); + } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); + } - need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, &cwfds); - if(need != cwfds.n && ufds) - mresult = CURLM_OUT_OF_MEMORY; + if(need != cwfds.n && ufds) + mresult = CURLM_OUT_OF_MEMORY; - if(fd_count) - *fd_count = need; - Curl_pollset_cleanup(&ps); + if(fd_count) + *fd_count = need; + Curl_pollset_cleanup(&ps); + } +out: + CURL_MAPI_LEAVE(&guard); return mresult; } @@ -1389,7 +1403,7 @@ static CURLMcode multi_winsock_select(struct Curl_multi *multi, reset_socket_fdwrite(cpfds->pfds[i].fd); } if(mask) { - if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask) != 0) { + if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -1532,8 +1546,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, bool extrawait) /* when no socket, wait */ { size_t i; - struct curltime expire_time; - long timeout_internal; + int timeout_internal; int nevents = 0; struct easy_pollset ps; struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; @@ -1546,12 +1559,6 @@ static CURLMcode multi_wait(struct Curl_multi *multi, int wakeup_idx = -1; #endif - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(timeout_ms < 0) return CURLM_BAD_FUNCTION_ARGUMENT; @@ -1576,7 +1583,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, &cpfds)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -1617,12 +1624,12 @@ static CURLMcode multi_wait(struct Curl_multi *multi, * Use the shorter one of the internal and the caller requested timeout. * If we are called with `!extrawait` and multi_timeout() reports no * timeouts exist, do not wait. */ - multi_timeout(multi, &expire_time, &timeout_internal); - if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) - timeout_ms = (int)timeout_internal; + multi_timeout(multi, NULL, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < timeout_ms)) + timeout_ms = timeout_internal; if(data) - CURL_TRC_M(data, "multi_wait(fds=%u, timeout=%d) tinternal=%ld", + CURL_TRC_M(data, "multi_wait(fds=%u, timeout=%d) tinternal=%d", cpfds.n, timeout_ms, timeout_internal); #ifdef USE_WINSOCK @@ -1659,7 +1666,14 @@ CURLMcode curl_multi_wait(CURLM *m, int timeout_ms, int *ret) { - return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_wait, &mresult)) { + mresult = multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_poll(CURLM *m, @@ -1668,7 +1682,14 @@ CURLMcode curl_multi_poll(CURLM *m, int timeout_ms, int *ret) { - return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_poll, &mresult)) { + mresult = multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_wakeup(CURLM *m) @@ -1697,6 +1718,18 @@ CURLMcode curl_multi_wakeup(CURLM *m) return mresult; } +#ifdef ENABLE_INTERNAL_WAKEUP +void Curl_multi_wakeup_internal(struct Curl_multi *multi) +{ + /* This is expected to be invocable from another thread which + * does NOT outlive the multi handle. Check for sanity. */ + if(GOOD_MULTI_HANDLE(multi)) + Curl_wakeup_signal(multi->wakeup_internal); + else + DEBUGASSERT(0); +} +#endif + /* * multi_ischanged() is called * @@ -1707,9 +1740,13 @@ CURLMcode curl_multi_wakeup(CURLM *m) */ static bool multi_ischanged(struct Curl_multi *multi, bool clear) { - bool retval = (bool)multi->recheckstate; - if(clear) - multi->recheckstate = FALSE; + bool retval = FALSE; + DEBUGASSERT(multi); + if(multi) { + retval = (bool)multi->recheckstate; + if(clear) + multi->recheckstate = FALSE; + } return retval; } @@ -1731,24 +1768,21 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, { CURLMcode mresult; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - - mresult = curl_multi_add_handle(multi, data); + mresult = Curl_multi_add_handle(multi, data); if(!mresult) { CURLcode result; /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ - result = Curl_init_do(data, NULL); + result = Curl_init_transfer(data, NULL); if(result) { - curl_multi_remove_handle(multi, data); + Curl_multi_remove_handle(multi, data); return CURLM_INTERNAL_ERROR; } /* take this handle to the perform state right away */ multistate(data, MSTATE_PERFORMING); - Curl_attach_connection(data, conn); + Curl_attach_connection(data, conn, TRUE); CURL_REQ_SET_RECV(data); } return mresult; @@ -1773,16 +1807,16 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) * stage DO state which (wrongly) was introduced to support FTP's second * connection. * - * 'complete' can return 0 for incomplete, 1 for done and -1 for go back to - * DOING state there is more work to do! + * 'complete' can return DOMORE_INCOMPLETE, DOMORE_DONE or DOMORE_GOBACK + * (to DOING state when there is more work to do) */ -static CURLcode multi_do_more(struct Curl_easy *data, int *complete) +static CURLcode multi_do_more(struct Curl_easy *data, domore *complete) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - *complete = 0; + *complete = DOMORE_INCOMPLETE; if(conn->scheme->run->do_more) result = conn->scheme->run->do_more(data, complete); @@ -1794,44 +1828,41 @@ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) * Check whether a timeout occurred, and handle it if it did */ static bool multi_handle_timeout(struct Curl_easy *data, + const struct curltime *pnow, bool *stream_error, CURLcode *result) { timediff_t timeout_ms; - timeout_ms = Curl_timeleft_ms(data); + timeout_ms = Curl_timeleft_now_ms(data, pnow); if(timeout_ms < 0) { /* Handle timed out */ - struct curltime since; - if(Curl_is_connecting(data)) - since = data->progress.t_startsingle; - else - since = data->progress.t_startop; + timerid base_timer = Curl_is_connecting(data) ? + TIMER_STARTSINGLE : TIMER_STARTOP; + timediff_t elapsed_ms = Curl_pgrs_since_ms(data, NULL, base_timer); if(data->mstate == MSTATE_CONNECTING) failf(data, "%s timed out after %" FMT_TIMEDIFF_T " milliseconds", data->conn->bits.dns_resolved ? "Connection" : "Resolving", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since)); + elapsed_ms); else { struct SingleRequest *k = &data->req; if(k->size != -1) { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " out of %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since), - k->bytecount, k->size); + elapsed_ms, k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), &since), - k->bytecount); + elapsed_ms, k->bytecount); } } *result = CURLE_OPERATION_TIMEDOUT; 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"); + streamclose(data->conn); *stream_error = TRUE; } (void)multi_done(data, *result, TRUE); @@ -1915,11 +1946,6 @@ static CURLcode protocol_connect(struct Curl_easy *data, bool *protocol_done) return CURLE_OK; } -static void set_in_callback(struct Curl_multi *multi, bool value) -{ - multi->in_callback = value; -} - /* * posttransfer() is called immediately after a transfer ends */ @@ -1971,7 +1997,7 @@ static CURLcode mspeed_check(struct Curl_easy *data) if(data->mstate != MSTATE_RATELIMITING) { multistate(data, MSTATE_RATELIMITING); } - Curl_expire(data, CURLMAX(send_ms, recv_ms), EXPIRE_TOOFAST); + Curl_expire_set(data, EXPIRE_TOOFAST, CURLMAX(send_ms, recv_ms), pnow); Curl_multi_clear_dirty(data); CURL_TRC_M(data, "[RLIMIT] waiting %" FMT_TIMEDIFF_T "ms", CURLMAX(send_ms, recv_ms)); @@ -1986,7 +2012,7 @@ static CURLcode mspeed_check(struct Curl_easy *data) timediff_t next_ms = CURLMIN(send_ms, recv_ms); if(!next_ms) next_ms = CURLMAX(send_ms, recv_ms); - Curl_expire(data, next_ms, EXPIRE_TOOFAST); + Curl_expire_set(data, EXPIRE_TOOFAST, next_ms, pnow); CURL_TRC_M(data, "[RLIMIT] next token update in %" FMT_TIMEDIFF_T "ms", next_ms); } @@ -2000,9 +2026,9 @@ static CURLcode mspeed_check(struct Curl_easy *data) return CURLE_OK; } -static CURLMcode state_performing(struct Curl_easy *data, - bool *stream_errorp, - CURLcode *resultp) +static CURLMcode multistate_performing(struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) { char *newurl = NULL; bool retry = FALSE; @@ -2041,7 +2067,7 @@ static CURLMcode state_performing(struct Curl_easy *data, if(!ret) { infof(data, "Downgrades to HTTP/1.1"); - streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + streamclose(data->conn); 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 */ @@ -2076,7 +2102,7 @@ static CURLMcode state_performing(struct Curl_easy *data, if(!(data->conn->scheme->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); + streamclose(data->conn); multi_posttransfer(data); multi_done(data, result, TRUE); @@ -2139,23 +2165,24 @@ static CURLMcode state_performing(struct Curl_easy *data, return mresult; } -static CURLMcode state_do(struct Curl_easy *data, - bool *stream_errorp, - CURLcode *resultp) +static CURLMcode multistate_do(struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) { CURLMcode mresult = CURLM_OK; CURLcode result = CURLE_OK; if(data->set.fprereq) { + struct Curl_mapi_guard guard; int prereq_rc; /* call the prerequest callback function */ - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fprereq); 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); + CURL_CBAPI_END(&guard); 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 */ @@ -2176,8 +2203,6 @@ static CURLMcode state_do(struct Curl_easy *data, /* 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 @@ -2276,8 +2301,8 @@ static CURLMcode state_do(struct Curl_easy *data, return mresult; } -static CURLMcode state_ratelimiting(struct Curl_easy *data, - CURLcode *resultp) +static CURLMcode multistate_ratelimiting(struct Curl_easy *data, + CURLcode *resultp) { CURLcode result = CURLE_OK; CURLMcode mresult = CURLM_OK; @@ -2288,7 +2313,7 @@ static CURLMcode state_ratelimiting(struct Curl_easy *data, if(result) { if(!(data->conn->scheme->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); + streamclose(data->conn); multi_posttransfer(data); multi_done(data, result, TRUE); @@ -2301,9 +2326,9 @@ static CURLMcode state_ratelimiting(struct Curl_easy *data, return mresult; } -static CURLMcode state_connect(struct Curl_multi *multi, - struct Curl_easy *data, - CURLcode *resultp) +static CURLMcode multistate_connect(struct Curl_multi *multi, + struct Curl_easy *data, + CURLcode *resultp) { /* Connect. We want to get a connection identifier filled in. This state can be entered from SETUP and from PENDING. */ @@ -2322,7 +2347,7 @@ static CURLMcode state_connect(struct Curl_multi *multi, return mresult; } else - process_pending_handles(data->multi); + multi_schedule_pending(data->multi); if(!result) { /* after the connect has been sent off, go WAITCONNECT unless the @@ -2334,7 +2359,7 @@ static CURLMcode state_connect(struct Curl_multi *multi, if(!data->conn->bits.reuse && Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { /* new connection, can multiplex, wake pending handles */ - process_pending_handles(data->multi); + multi_schedule_pending(data->multi); } multistate(data, MSTATE_PROTOCONNECT); } @@ -2349,6 +2374,7 @@ static CURLMcode state_connect(struct Curl_multi *multi, /* returns the possibly updated result */ static CURLcode is_finished(struct Curl_multi *multi, struct Curl_easy *data, + const struct curltime *pnow, bool stream_error, CURLcode result) { @@ -2363,7 +2389,7 @@ static CURLcode is_finished(struct Curl_multi *multi, connection detach and termination happens only here */ /* Check if we can move pending requests to send pipe */ - process_pending_handles(multi); /* connection */ + multi_schedule_pending(multi); /* connection */ if(data->conn) { if(stream_error) { @@ -2375,7 +2401,7 @@ static CURLcode is_finished(struct Curl_multi *multi, We do not have to do this in every case block above where a failure is detected */ Curl_detach_connection(data); - Curl_conn_terminate(data, conn, dead_connection); + Curl_conn_close(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { @@ -2389,11 +2415,11 @@ static CURLcode is_finished(struct Curl_multi *multi, } /* if there is still a connection to use, call the progress function */ else if(data->conn && Curl_conn_is_connected(data->conn, FIRSTSOCKET)) { - result = Curl_pgrsUpdate(data); + result = Curl_pgrsUpdateX(data, pnow); if(result) { /* aborted due to progress callback return code must close the connection */ - streamclose(data->conn, "Aborted by callback"); + streamclose(data->conn); /* if not yet in DONE state, go there, otherwise COMPLETED */ multistate(data, (data->mstate < MSTATE_DONE) ? @@ -2410,20 +2436,16 @@ static void handle_completed(struct Curl_multi *multi, CURLcode result) { if(data->master_mid != UINT32_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); + /* A sub transfer, not for msgsent to application. Is anyone still + * interested in processing its results? */ + if(data->sub_xfer_done) { + struct Curl_easy *master = Curl_multi_get_easy(multi, data->master_mid); + + CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid); + if(master) + data->sub_xfer_done(data, master, 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); + CURL_TRC_M(data, "master easy %u already gone.", data->master_mid); } } else { @@ -2444,290 +2466,397 @@ static void handle_completed(struct Curl_multi *multi, Curl_uint32_bset_remove(&multi->dirty, data->mid); Curl_uint32_bset_remove(&multi->pending, data->mid); Curl_uint32_bset_add(&multi->msgsent, data->mid); + if(data->state.really_alive) { + data->state.really_alive = FALSE; + --multi->xfers_really_alive; + if(!multi->xfers_really_alive) + (void)multi_assess_wakeup(multi); + } --multi->xfers_alive; if(!multi->xfers_alive) multi_assess_wakeup(multi); } -static CURLMcode multi_runsingle(struct Curl_multi *multi, - struct Curl_easy *data, - struct Curl_sigpipe_ctx *sigpipe_ctx) +static CURLMcode multistate_init(struct Curl_easy *data, CURLcode *result) { - bool connected; - bool protocol_connected = FALSE; - bool dophase_done = FALSE; - CURLMcode mresult; - CURLcode result = CURLE_OK; - int control; + if(!data->state.really_alive) { + data->state.really_alive = TRUE; + ++data->multi->xfers_really_alive; + if(data->multi->xfers_really_alive == 1) { + CURLMcode mresult = multi_assess_wakeup(data->multi); + if(mresult) { + failf(data, "error enabling wakeup listening: %d", mresult); + return mresult; + } + } + } - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_EASY_HANDLE; + *result = Curl_pretransfer(data); + if(*result) + return CURLM_OK; - if(multi->dead) { - /* a multi-level callback returned error before, meaning every individual - transfer now has failed */ - result = CURLE_ABORTED_BY_CALLBACK; - multi_posttransfer(data); - multi_done(data, result, FALSE); - multistate(data, MSTATE_COMPLETED); - } + /* after init, go SETUP */ + multistate(data, MSTATE_SETUP); + Curl_pgrsTime(data, TIMER_STARTOP); + return CURLM_CALL_MULTI_PERFORM; +} - multi_warn_debug(multi, data); +static CURLMcode multistate_setup(struct Curl_easy *data) +{ + const struct curltime *pnow = Curl_pgrs_now(data); + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, *pnow); + if(data->set.timeout) + Curl_expire_set(data, EXPIRE_TIMEOUT, data->set.timeout, pnow); + 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_set(data, EXPIRE_CONNECTTIMEOUT, + data->set.connecttimeout, pnow); - /* transfer runs now, clear the dirty bit. This may be set - * again during processing, triggering a re-run later. */ - Curl_uint32_bset_remove(&multi->dirty, data->mid); + multistate(data, MSTATE_CONNECT); + return CURLM_CALL_MULTI_PERFORM; +} - if(data == multi->admin) { -#ifdef USE_RESOLV_THREADED - Curl_async_thrdd_multi_process(multi); -#endif - Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx); +static CURLMcode multistate_connecting(struct Curl_easy *data, + bool *stream_error, + CURLcode *result) +{ + bool connected; + + if(!data->conn) { + DEBUGASSERT(0); + *result = CURLE_FAILED_INIT; return CURLM_OK; } - - sigpipe_apply(data, sigpipe_ctx); - do { + if(!Curl_xfer_recv_is_paused(data)) { + *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 */ + multi_schedule_pending(data->multi); + } + multistate(data, MSTATE_PROTOCONNECT); + return CURLM_CALL_MULTI_PERFORM; + } + else if(*result) { + /* failure detected */ + CURL_TRC_M(data, "connect failed -> %d", (int)*result); + multi_posttransfer(data); + multi_done(data, *result, TRUE); + *stream_error = TRUE; + return CURLM_OK; + } + } + return CURLM_OK; +} + +static CURLMcode multistate_protoconnect(struct Curl_easy *data, + bool *stream_error, + CURLcode *result) +{ + bool protocol_connected = FALSE; + + if(!*result && data->conn->bits.reuse) { + /* ftp seems to hang when protoconnect on reused connection since we + * handle PROTOCONNECT in general inside the filters, it seems wrong to + * restart this on a reused connection. + */ + multistate(data, MSTATE_DO); + return CURLM_CALL_MULTI_PERFORM; + } + if(!*result) + *result = protocol_connect(data, &protocol_connected); + if(!*result && !protocol_connected) { + /* switch to waiting state */ + multistate(data, MSTATE_PROTOCONNECTING); + return CURLM_CALL_MULTI_PERFORM; + } + else if(!*result) { + /* protocol connect has completed, go WAITDO or DO */ + multistate(data, MSTATE_DO); + return CURLM_CALL_MULTI_PERFORM; + } + + /* failure detected */ + multi_posttransfer(data); + multi_done(data, *result, TRUE); + *stream_error = TRUE; + return CURLM_OK; +} + +static CURLMcode multistate_protoconnecting(struct Curl_easy *data, + bool *stream_error, + CURLcode *result) +{ + bool protocol_connected = FALSE; + + /* protocol-specific connect phase */ + *result = protocol_connecting(data, &protocol_connected); + if(!*result && protocol_connected) { + /* after the connect has completed, go WAITDO or DO */ + multistate(data, MSTATE_DO); + return CURLM_CALL_MULTI_PERFORM; + } + else if(*result) { + /* failure detected */ + multi_posttransfer(data); + multi_done(data, *result, TRUE); + *stream_error = TRUE; + } + return CURLM_OK; +} + +static CURLMcode multistate_doing(struct Curl_easy *data, + bool *stream_error, + CURLcode *result) +{ + bool dophase_done = FALSE; + + /* we continue DOING until the DO phase is complete */ + DEBUGASSERT(data->conn); + *result = protocol_doing(data, &dophase_done); + if(!*result) { + if(dophase_done) { + /* after DO, go DO_DONE or DO_MORE */ + multistate(data, data->conn->bits.do_more ? + MSTATE_DOING_MORE : MSTATE_DID); + return CURLM_CALL_MULTI_PERFORM; + } /* dophase_done */ + } + else { + /* failure detected */ + multi_posttransfer(data); + multi_done(data, *result, FALSE); + *stream_error = TRUE; + } + return CURLM_OK; +} + +static CURLMcode multistate_doing_more(struct Curl_easy *data, + bool *stream_error, + CURLcode *result) +{ + domore control; + + /* + * When we are connected, DOING MORE and then go DID + */ + DEBUGASSERT(data->conn); + *result = multi_do_more(data, &control); + + if(!*result) { + if(control != DOMORE_INCOMPLETE) { + /* if DONE, advance to DO_DONE + if GOBACK, go back to DOING */ + multistate(data, control == DOMORE_DONE ? MSTATE_DID : MSTATE_DOING); + return CURLM_CALL_MULTI_PERFORM; + } + /* else + stay in DO_MORE */ + } + else { + /* failure detected */ + multi_posttransfer(data); + multi_done(data, *result, FALSE); + *stream_error = TRUE; + } + return CURLM_OK; +} + +static CURLMcode multistate_did(struct Curl_multi *multi, + struct Curl_easy *data) +{ + DEBUGASSERT(data->conn); + if(data->conn->bits.multiplex) + /* Check if we can move pending requests to send pipe */ + multi_schedule_pending(multi); /* multiplexed */ + + /* 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(CONN_SOCK_IDX_VALID(data->conn->recv_idx) || + CONN_SOCK_IDX_VALID(data->conn->send_idx)) { + multistate(data, MSTATE_PERFORMING); + /* Do not return CURLM_CALL_MULTI_PERFORM to give other transfers + * a chance to send off their requests. + * Note: Some SFTP handlers do not seem to like this. + * Restrict it to HTTP families. */ + return ((multi->xfers_alive > 1) && + (data->conn->scheme->protocol & PROTO_FAMILY_HTTP)) ? + CURLM_OK : CURLM_CALL_MULTI_PERFORM; + } + else { +#ifndef CURL_DISABLE_FTP + if(data->state.wildcardmatch && + ((data->conn->scheme->flags & PROTOPT_WILDCARD) == 0)) { + data->wildcard->state = CURLWC_DONE; + } +#endif + multistate(data, MSTATE_DONE); + return CURLM_CALL_MULTI_PERFORM; + } +} + +static CURLMcode multistate_done(struct Curl_easy *data, CURLcode *presult) +{ + if(data->conn) { + CURLcode result; + + /* post-transfer command */ + result = multi_done(data, *presult, FALSE); + + /* allow a previously set error code take precedence */ + if(!(*presult)) + *presult = result; + } + +#ifndef CURL_DISABLE_FTP + if(data->state.wildcardmatch) { + if(data->wildcard->state != CURLWC_DONE) { + /* if a wildcard is set and we are not ending -> lets start again + with MSTATE_INIT */ + multistate(data, MSTATE_INIT); + return CURLM_CALL_MULTI_PERFORM; + } + } +#endif + /* 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); + return CURLM_CALL_MULTI_PERFORM; +} + +static CURLMcode multi_runsingle(struct Curl_multi *multi, + struct Curl_easy *data, + struct Curl_sigpipe_ctx *sigpipe_ctx) +{ + CURLMcode mresult = CURLM_OK; + CURLcode result = CURLE_OK; + const struct curltime *pnow = NULL; + + if(multi->dead) { + /* a multi-level callback returned error before, meaning every individual + transfer now has failed */ + result = CURLE_ABORTED_BY_CALLBACK; + multi_posttransfer(data); + multi_done(data, result, FALSE); + multistate(data, MSTATE_COMPLETED); + } + + multi_warn_debug(multi, data); + + /* transfer runs now, clear the dirty bit. This may be set + * again during processing, triggering a re-run later. */ + Curl_uint32_bset_remove(&multi->dirty, data->mid); + + if(data == multi->admin) { +#ifdef ENABLE_INTERNAL_WAKEUP + /* Consume any pending wakeup signals before processing. + * This is necessary for event based processing. See #21547 */ + (void)Curl_wakeup_consume(multi->wakeup_internal, TRUE); +#endif +#ifdef USE_RESOLV_THREADED + Curl_async_thrdd_multi_process(multi); +#endif + Curl_cshutdn_perform(&multi->cshutdn, sigpipe_ctx); + goto out; + } + + sigpipe_apply(data, sigpipe_ctx); + do { /* A "stream" here is a logical stream if the protocol can handle that (HTTP/2), or the full connection for older protocols */ bool stream_error = FALSE; mresult = CURLM_OK; + pnow = NULL; if(multi_ischanged(multi, TRUE)) { CURL_TRC_M(data, "multi changed, check CONNECT_PEND queue"); - process_pending_handles(multi); /* multiplexed */ + multi_schedule_pending(multi); /* multiplexed */ } if(data->mstate > MSTATE_CONNECT && data->mstate < MSTATE_COMPLETED) { /* Make sure we set the connection's current owner */ DEBUGASSERT(data->conn); - if(!data->conn) - return CURLM_INTERNAL_ERROR; + if(!data->conn) { + mresult = CURLM_INTERNAL_ERROR; + goto out; + } } /* 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, &stream_error, &result)) - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED)) { + pnow = Curl_pgrs_now(data); + if(multi_handle_timeout(data, pnow, &stream_error, &result)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; + pnow = NULL; + } switch(data->mstate) { case MSTATE_INIT: /* Transitional state. init this transfer. A handle never comes back to this state. */ - result = Curl_pretransfer(data); - if(result) - break; - - /* after init, go SETUP */ - multistate(data, MSTATE_SETUP); - Curl_pgrsTime(data, TIMER_STARTOP); - FALLTHROUGH(); + mresult = multistate_init(data, &result); + break; case MSTATE_SETUP: /* Transitional state. Setup things for a new transfer. The handle can come back to this state on a redirect. */ - 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); - - multistate(data, MSTATE_CONNECT); - FALLTHROUGH(); + mresult = multistate_setup(data); + break; case MSTATE_CONNECT: - mresult = state_connect(multi, data, &result); + mresult = multistate_connect(multi, data, &result); break; case MSTATE_CONNECTING: /* awaiting a completion of an asynch TCP connect */ - if(!data->conn) { - DEBUGASSERT(0); - result = CURLE_FAILED_INIT; - break; - } - else if(!Curl_xfer_recv_is_paused(data)) { - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected); - if(connected && !result) { - if(!data->conn->bits.reuse && - Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { - /* new connection, can multiplex, wake pending handles */ - process_pending_handles(data->multi); - } - mresult = CURLM_CALL_MULTI_PERFORM; - multistate(data, MSTATE_PROTOCONNECT); - } - else if(result) { - /* failure detected */ - CURL_TRC_M(data, "connect failed -> %d", result); - multi_posttransfer(data); - multi_done(data, result, TRUE); - stream_error = TRUE; - break; - } - } + mresult = multistate_connecting(data, &stream_error, &result); break; case MSTATE_PROTOCONNECT: - 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. - */ - multistate(data, MSTATE_DO); - mresult = CURLM_CALL_MULTI_PERFORM; - break; - } - if(!result) - result = protocol_connect(data, &protocol_connected); - if(!result && !protocol_connected) { - /* switch to waiting state */ - multistate(data, MSTATE_PROTOCONNECTING); - mresult = CURLM_CALL_MULTI_PERFORM; - } - else if(!result) { - /* protocol connect has completed, go WAITDO or DO */ - multistate(data, MSTATE_DO); - mresult = CURLM_CALL_MULTI_PERFORM; - } - else { - /* failure detected */ - multi_posttransfer(data); - multi_done(data, result, TRUE); - stream_error = TRUE; - } + mresult = multistate_protoconnect(data, &stream_error, &result); break; case MSTATE_PROTOCONNECTING: /* protocol-specific connect phase */ - result = protocol_connecting(data, &protocol_connected); - if(!result && protocol_connected) { - /* after the connect has completed, go WAITDO or DO */ - multistate(data, MSTATE_DO); - mresult = CURLM_CALL_MULTI_PERFORM; - } - else if(result) { - /* failure detected */ - multi_posttransfer(data); - multi_done(data, result, TRUE); - stream_error = TRUE; - } + mresult = multistate_protoconnecting(data, &stream_error, &result); break; case MSTATE_DO: - mresult = state_do(data, &stream_error, &result); + mresult = multistate_do(data, &stream_error, &result); break; case MSTATE_DOING: /* we continue DOING until the DO phase is complete */ - DEBUGASSERT(data->conn); - result = protocol_doing(data, &dophase_done); - if(!result) { - if(dophase_done) { - /* after DO, go DO_DONE or DO_MORE */ - multistate(data, data->conn->bits.do_more ? - MSTATE_DOING_MORE : MSTATE_DID); - mresult = CURLM_CALL_MULTI_PERFORM; - } /* dophase_done */ - } - else { - /* failure detected */ - multi_posttransfer(data); - multi_done(data, result, FALSE); - stream_error = TRUE; - } + mresult = multistate_doing(data, &stream_error, &result); break; case MSTATE_DOING_MORE: /* * When we are connected, DOING MORE and then go DID */ - DEBUGASSERT(data->conn); - result = multi_do_more(data, &control); - - if(!result) { - if(control) { - /* if positive, advance to DO_DONE - if negative, go back to DOING */ - multistate(data, control == 1 ? MSTATE_DID : MSTATE_DOING); - mresult = CURLM_CALL_MULTI_PERFORM; - } - /* else - stay in DO_MORE */ - } - else { - /* failure detected */ - multi_posttransfer(data); - multi_done(data, result, FALSE); - stream_error = TRUE; - } + mresult = multistate_doing_more(data, &stream_error, &result); break; case MSTATE_DID: - DEBUGASSERT(data->conn); - if(data->conn->bits.multiplex) - /* Check if we can move pending requests to send pipe */ - process_pending_handles(multi); /* multiplexed */ - - /* 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(CONN_SOCK_IDX_VALID(data->conn->recv_idx) || - CONN_SOCK_IDX_VALID(data->conn->send_idx)) - multistate(data, MSTATE_PERFORMING); - else { -#ifndef CURL_DISABLE_FTP - if(data->state.wildcardmatch && - ((data->conn->scheme->flags & PROTOPT_WILDCARD) == 0)) { - data->wildcard->state = CURLWC_DONE; - } -#endif - multistate(data, MSTATE_DONE); - } - mresult = CURLM_CALL_MULTI_PERFORM; + mresult = multistate_did(multi, data); break; case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ - mresult = state_ratelimiting(data, &result); + mresult = multistate_ratelimiting(data, &result); break; case MSTATE_PERFORMING: - mresult = state_performing(data, &stream_error, &result); + mresult = multistate_performing(data, &stream_error, &result); break; case MSTATE_DONE: - /* this state is highly transient, so run another loop after this */ - mresult = CURLM_CALL_MULTI_PERFORM; - - if(data->conn) { - CURLcode res; - - /* post-transfer command */ - res = multi_done(data, result, FALSE); - - /* allow a previously set error code take precedence */ - if(!result) - result = res; - } - -#ifndef CURL_DISABLE_FTP - if(data->state.wildcardmatch) { - if(data->wildcard->state != CURLWC_DONE) { - /* if a wildcard is set and we are not ending -> lets start again - with MSTATE_INIT */ - multistate(data, MSTATE_INIT); - break; - } - } -#endif - /* 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); + mresult = multistate_done(data, &result); break; case MSTATE_COMPLETED: @@ -2739,7 +2868,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; default: - return CURLM_INTERNAL_ERROR; + mresult = CURLM_INTERNAL_ERROR; + goto out; } if(data->mstate >= MSTATE_CONNECT && @@ -2752,22 +2882,26 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, * (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, &stream_error, &result); + pnow = Curl_pgrs_now(data); + multi_handle_timeout(data, pnow, &stream_error, &result); } statemachine_end: - - result = is_finished(multi, data, stream_error, result); + if(!pnow) + pnow = Curl_pgrs_now(data); + result = is_finished(multi, data, pnow, stream_error, result); if(result) mresult = CURLM_CALL_MULTI_PERFORM; if(MSTATE_COMPLETED == data->mstate) { handle_completed(multi, data, result); - return CURLM_OK; + mresult = CURLM_OK; + goto out; } } while((mresult == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); +out: data->result = result; return mresult; } @@ -2780,12 +2914,6 @@ static CURLMcode multi_perform(struct Curl_multi *multi, uint32_t mid; struct Curl_sigpipe_ctx sigpipe_ctx; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - sigpipe_init(&sigpipe_ctx); if(Curl_uint32_bset_first(&multi->process, &mid)) { @@ -2808,7 +2936,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, sigpipe_restore(&sigpipe_ctx); if(multi_ischanged(multi, TRUE)) - process_pending_handles(multi); + multi_schedule_pending(multi); if(!returncode && CURL_MNTFY_HAS_ENTRIES(multi)) returncode = Curl_mntfy_dispatch_all(multi); @@ -2823,28 +2951,27 @@ static CURLMcode multi_perform(struct Curl_multi *multi, * then and then we risk this loop to remove timers that actually have not * been handled! */ - if(multi->timetree) { - struct Curl_tree *t = NULL; - do { - multi->timetree = Curl_splaygetbest(&start, multi->timetree, &t); - if(t) { - /* the removed may have another timeout in queue */ - struct Curl_easy *data = Curl_splayget(t); - (void)add_next_timeout(&start, multi, data); - if(data->mstate == MSTATE_PENDING) { - bool stream_unused; - CURLcode result_unused; - if(multi_handle_timeout(data, &stream_unused, &result_unused)) { - infof(data, "PENDING handle timeout"); - move_pending_to_connect(multi, data); - } - } + while(Curl_timeouts_remove_expired(&multi->timeouts, &start, &mid)) { + /* the removed may have another timeout in queue */ + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); + continue; + } + (void)add_next_timeout(&start, multi, data); + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, multi_now(multi), + &stream_unused, &result_unused)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); } - } while(t); + } } if(running_handles) { - unsigned int running = Curl_multi_xfers_running(multi); + uint32_t running = Curl_multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -2856,32 +2983,35 @@ static CURLMcode multi_perform(struct Curl_multi *multi, CURLMcode curl_multi_perform(CURLM *m, int *running_handles) { - struct Curl_multi *multi = m; - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + struct Curl_mapi_guard guard; + CURLMcode mresult; - return multi_perform(multi, running_handles); + if(CURL_MAPI_ENTER(&guard, m, multi_perform, &mresult)) { + mresult = multi_perform(m, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_cleanup(CURLM *m) { - struct Curl_multi *multi = m; - if(GOOD_MULTI_HANDLE(multi)) { + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_cleanup, &mresult)) { + struct Curl_multi *multi = m; void *entry; uint32_t mid; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; /* First remove all remaining easy handles, * close internal ones. admin handle is special */ if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { do { struct Curl_easy *data = entry; - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_HANDLE; + if(!GOOD_EASY_HANDLE(data)) { + mresult = CURLM_BAD_HANDLE; + goto out; + } #ifdef DEBUGBUILD if(mid != data->mid) { @@ -2914,7 +3044,7 @@ CURLMcode curl_multi_cleanup(CURLM *m) #ifdef USE_RESOLV_THREADED Curl_async_thrdd_multi_destroy(multi, !multi->quick_exit); #endif - Curl_cpool_destroy(&multi->cpool); + Curl_cpool_destroy(&multi->cpool, multi->admin); Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); if(multi->admin) { CURL_TRC_M(multi->admin, "multi_cleanup, closing admin handle, done"); @@ -2939,6 +3069,9 @@ CURLMcode curl_multi_cleanup(CURLM *m) #ifdef ENABLE_WAKEUP Curl_wakeup_destroy(multi->wakeup_pair); #endif +#ifdef ENABLE_INTERNAL_WAKEUP + Curl_wakeup_destroy(multi->wakeup_internal); +#endif multi_xfer_bufs_free(multi); Curl_mntfy_cleanup(multi); @@ -2953,11 +3086,14 @@ CURLMcode curl_multi_cleanup(CURLM *m) Curl_uint32_bset_destroy(&multi->pending); Curl_uint32_bset_destroy(&multi->msgsent); Curl_uint32_tbl_destroy(&multi->xfers); + curlx_memzero(multi, sizeof(*multi)); curlx_free(multi); - return CURLM_OK; + mresult = CURLM_OK; } - return CURLM_BAD_HANDLE; +out: + CURL_MAPI_LEAVE(&guard); + return mresult; } /* @@ -2972,30 +3108,32 @@ CURLMcode curl_multi_cleanup(CURLM *m) CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) { - struct Curl_message *msg; - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMsg *msg_result = NULL; *msgs_in_queue = 0; /* default to none */ + if(CURL_MAPI_ENTER(&guard, m, multi_info_read, NULL)) { + struct Curl_multi *multi = m; + if(Curl_llist_count(&multi->msglist)) { + /* there is one or more messages in the list */ + struct Curl_llist_node *e; + struct Curl_message *msg; - 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; - - /* extract the head of the list to return */ - e = Curl_llist_head(&multi->msglist); + /* extract the head of the list to return */ + e = Curl_llist_head(&multi->msglist); - msg = Curl_node_elem(e); + msg = Curl_node_elem(e); - /* remove the extracted entry */ - Curl_node_remove(e); + /* remove the extracted entry */ + Curl_node_remove(e); - *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); + *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); - return &msg->extmsg; + msg_result = &msg->extmsg; + } } - return NULL; + CURL_MAPI_LEAVE(&guard); + return msg_result; } void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) @@ -3009,57 +3147,41 @@ void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) } } +static void multi_timeouts_init(struct Curl_easy *data) +{ + data->state.timeouts.first = EXPIRE_LAST; + data->state.timeouts.splaynode.registered = FALSE; +} + /* - * add_next_timeout() - * * Each Curl_easy has a list of timeouts. The add_next_timeout() is called * when it has been removed from the splay tree because the timeout has * expired. This function is then to advance in the list to pick the next * timeout to use (skip the already expired ones) and add this node back to * the splay tree again. * - * The splay tree only has each sessionhandle as a single node and the nearest + * The splay tree only has each Curl_easy as a single node and the nearest * timeout is used to sort it on. */ static CURLMcode add_next_timeout(const struct curltime *pnow, struct Curl_multi *multi, - struct Curl_easy *d) + struct Curl_easy *data) { - struct curltime *tv = &d->state.expiretime; - struct Curl_llist *list = &d->state.timeoutlist; - struct Curl_llist_node *e; + struct expire_timers *timeouts = &data->state.timeouts; + timediff_t now_us = Curl_timeouts_offset_us(&multi->timeouts, pnow); - /* 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 = 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_ptimediff_us(&node->time, pnow); - if(diff <= 0) - /* remove outdated entry */ - Curl_node_remove(e); - else - /* the list is sorted so get out on the first mismatch */ + while(timeouts->first < EXPIRE_LAST) { + if(timeouts->offset_us[timeouts->first] <= now_us) /* already expired */ + timeouts->first = timeouts->next[timeouts->first]; + else /* timeouts are sorted, first is first in the future now */ break; - e = n; } - e = Curl_llist_head(list); - if(!e) { - /* clear the expire times within the handles that we remove from the - splay tree */ - tv->tv_sec = 0; - 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)); + if(timeouts->first < EXPIRE_LAST) { /* 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); + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); } return CURLM_OK; } @@ -3068,29 +3190,25 @@ static void multi_mark_expired_as_dirty(struct Curl_multi *multi, const struct curltime *ts) { struct Curl_easy *data = NULL; - struct Curl_tree *t = NULL; + uint32_t mid; /* * The loop following here will go on as long as there are expire-times left * to process (compared to `ts`) in the splay and 'data' will be * re-assigned for every expired handle we deal with. */ - while(1) { + while(Curl_timeouts_remove_expired(&multi->timeouts, ts, &mid)) { /* 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(ts, multi->timetree, &t); - if(!t) - return; - - data = Curl_splayget(t); /* assign this for next loop */ - if(!data) + data = Curl_multi_get_easy(multi, mid); + if(!data) { + DEBUGASSERT(0); continue; + } #ifdef CURLVERBOSE if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - struct time_node *n = Curl_node_elem(e); - CURL_TRC_TIMER(data, n->eid, "has expired"); + if(data->state.timeouts.first < EXPIRE_LAST) { + CURL_TRC_TIMER(data, data->state.timeouts.first, "has expired"); } } #endif @@ -3175,7 +3293,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, 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->last_expire_ts, 0, sizeof(multi->last_expire_ts)); + multi->last_expire_offset_us = 0; /* Applications may set `socket_cb` *after* having added transfers * first. *Then* kick off processing with a @@ -3205,13 +3323,13 @@ static CURLMcode multi_socket(struct Curl_multi *multi, sigpipe_restore(&pipe_ctx); if(multi_ischanged(multi, TRUE)) - process_pending_handles(multi); + multi_schedule_pending(multi); if(!mresult && CURL_MNTFY_HAS_ENTRIES(multi)) mresult = Curl_mntfy_dispatch_all(multi); if(running_handles) { - unsigned int running = Curl_multi_xfers_running(multi); + uint32_t running = Curl_multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -3223,126 +3341,125 @@ static CURLMcode multi_socket(struct Curl_multi *multi, #undef curl_multi_setopt CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) { + struct Curl_mapi_guard guard; CURLMcode mresult = CURLM_OK; - va_list param; - unsigned long uarg; - struct Curl_multi *multi = m; - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + if(CURL_MAPI_ENTER(&guard, m, multi_setopt, &mresult)) { + struct Curl_multi *multi = m; + va_list param; + unsigned long uarg; - va_start(param, option); + va_start(param, option); - switch(option) { - case CURLMOPT_SOCKETFUNCTION: - multi->socket_cb = va_arg(param, curl_socket_callback); - break; - case CURLMOPT_SOCKETDATA: - multi->socket_userp = va_arg(param, void *); - break; - case CURLMOPT_PUSHFUNCTION: - multi->push_cb = va_arg(param, curl_push_callback); - break; - case CURLMOPT_PUSHDATA: - multi->push_userp = va_arg(param, void *); - break; - case CURLMOPT_PIPELINING: - multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; - break; - case CURLMOPT_TIMERFUNCTION: - multi->timer_cb = va_arg(param, curl_multi_timer_callback); - break; - case CURLMOPT_TIMERDATA: - multi->timer_userp = va_arg(param, void *); - break; - case CURLMOPT_MAXCONNECTS: - uarg = va_arg(param, unsigned long); - if(uarg <= UINT_MAX) - multi->maxconnects = (unsigned int)uarg; - break; - case CURLMOPT_MAX_HOST_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - case CURLMOPT_MAX_TOTAL_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - /* options formerly used for pipelining */ - case CURLMOPT_MAX_PIPELINE_LENGTH: - break; - case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: - break; - case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: - break; - case CURLMOPT_PIPELINING_SITE_BL: - break; - case CURLMOPT_PIPELINING_SERVER_BL: - break; - case CURLMOPT_MAX_CONCURRENT_STREAMS: { - long streams = va_arg(param, long); - if((streams < 1) || (streams > INT_MAX)) - streams = 100; - multi->max_concurrent_streams = (unsigned int)streams; - break; - } - case CURLMOPT_NETWORK_CHANGED: { - long val = va_arg(param, long); - if(val & CURLMNWC_CLEAR_ALL) - /* In the beginning, all values available to set were 1 by mistake. We - converted this to mean "all", thus setting all the bits - automatically */ - val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS; - if(val & CURLMNWC_CLEAR_DNS) { - Curl_dnscache_clear(multi->admin); + switch(option) { + case CURLMOPT_SOCKETFUNCTION: + multi->socket_cb = va_arg(param, curl_socket_callback); + break; + case CURLMOPT_SOCKETDATA: + multi->socket_userp = va_arg(param, void *); + break; + case CURLMOPT_PUSHFUNCTION: + multi->push_cb = va_arg(param, curl_push_callback); + break; + case CURLMOPT_PUSHDATA: + multi->push_userp = va_arg(param, void *); + break; + case CURLMOPT_PIPELINING: + multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; + break; + case CURLMOPT_TIMERFUNCTION: + multi->timer_cb = va_arg(param, curl_multi_timer_callback); + break; + case CURLMOPT_TIMERDATA: + multi->timer_userp = va_arg(param, void *); + break; + case CURLMOPT_MAXCONNECTS: + uarg = va_arg(param, unsigned long); + if(uarg <= UINT_MAX) + multi->maxconnects = (unsigned int)uarg; + break; + case CURLMOPT_MAX_HOST_CONNECTIONS: + if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + break; + case CURLMOPT_MAX_TOTAL_CONNECTIONS: + if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + break; + /* options formerly used for pipelining */ + case CURLMOPT_MAX_PIPELINE_LENGTH: + break; + case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: + break; + case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: + break; + case CURLMOPT_PIPELINING_SITE_BL: + break; + case CURLMOPT_PIPELINING_SERVER_BL: + break; + case CURLMOPT_MAX_CONCURRENT_STREAMS: { + long streams = va_arg(param, long); + if((streams < 1) || (streams > INT_MAX)) + streams = 100; + multi->max_concurrent_streams = (unsigned int)streams; + break; } - if(val & CURLMNWC_CLEAR_CONNS) { - Curl_cpool_nw_changed(multi->admin); + case CURLMOPT_NETWORK_CHANGED: { + long val = va_arg(param, long); + if(val & CURLMNWC_CLEAR_ALL) + /* In the beginning, all values available to set were 1 by mistake. We + converted this to mean "all", thus setting all the bits + automatically */ + val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS; + if(val & CURLMNWC_CLEAR_DNS) { + Curl_dnscache_clear(multi->admin); + } + if(val & CURLMNWC_CLEAR_CONNS) { + Curl_cpool_nw_changed(&multi->cpool, multi->admin); + } + break; } - break; - } - case CURLMOPT_NOTIFYFUNCTION: - multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback); - break; - case CURLMOPT_NOTIFYDATA: - multi->ntfy.ntfy_cb_data = va_arg(param, void *); - break; - case CURLMOPT_RESOLVE_THREADS_MAX: + case CURLMOPT_NOTIFYFUNCTION: + multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback); + break; + case CURLMOPT_NOTIFYDATA: + multi->ntfy.ntfy_cb_data = va_arg(param, void *); + break; + case CURLMOPT_RESOLVE_THREADS_MAX: #ifdef USE_RESOLV_THREADED - uarg = va_arg(param, long); - if((uarg <= 0) || (uarg > UINT32_MAX)) - mresult = CURLM_BAD_FUNCTION_ARGUMENT; - else { - CURLcode result = Curl_async_thrdd_multi_set_props( - multi, 0, (uint32_t)uarg, 2000); - switch(result) { - case CURLE_OK: - mresult = CURLM_OK; - break; - case CURLE_BAD_FUNCTION_ARGUMENT: + uarg = va_arg(param, long); + if((uarg <= 0) || (uarg > UINT32_MAX)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; - break; - case CURLE_OUT_OF_MEMORY: - mresult = CURLM_OUT_OF_MEMORY; - break; - default: - mresult = CURLM_INTERNAL_ERROR; - break; + else { + CURLcode result = Curl_async_thrdd_multi_set_props( + multi, 0, (uint32_t)uarg, 2000); + switch(result) { + case CURLE_OK: + mresult = CURLM_OK; + break; + case CURLE_BAD_FUNCTION_ARGUMENT: + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + break; + case CURLE_OUT_OF_MEMORY: + mresult = CURLM_OUT_OF_MEMORY; + break; + default: + mresult = CURLM_INTERNAL_ERROR; + break; + } } - } #endif - break; - case CURLMOPT_QUICK_EXIT: - multi->quick_exit = va_arg(param, long) ? 1 : 0; - break; - default: - mresult = CURLM_UNKNOWN_OPTION; - break; + break; + case CURLMOPT_QUICK_EXIT: + multi->quick_exit = va_arg(param, long) ? 1 : 0; + break; + default: + mresult = CURLM_UNKNOWN_OPTION; + break; + } + va_end(param); } - va_end(param); + CURL_MAPI_LEAVE(&guard); return mresult; } @@ -3351,33 +3468,39 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, FALSE, s, 0, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket, &mresult)) { + mresult = multi_socket(m, FALSE, s, 0, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s, int ev_bitmask, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, FALSE, s, ev_bitmask, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket_action, &mresult)) { + mresult = multi_socket(m, FALSE, s, ev_bitmask, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles) { - struct Curl_multi *multi = m; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; - if(multi->in_ntfy_callback) - return CURLM_RECURSIVE_API_CALL; - return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); + struct Curl_mapi_guard guard; + CURLMcode mresult; + + if(CURL_MAPI_ENTER(&guard, m, multi_socket_all, &mresult)) { + mresult = multi_socket(m, TRUE, CURL_SOCKET_BAD, 0, running_handles); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } static bool multi_has_dirties(struct Curl_multi *multi) @@ -3402,81 +3525,57 @@ static bool multi_has_dirties(struct Curl_multi *multi) } static void multi_timeout(struct Curl_multi *multi, - struct curltime *expire_time, - long *timeout_ms) + timediff_t *pexire_offset_us, + int *timeout_ms) { - static const struct curltime tv_zero = { 0, 0 }; - VERBOSE(struct Curl_easy *data = NULL); - if(multi->dead) { + if(pexire_offset_us) + *pexire_offset_us = 0; *timeout_ms = 0; return; } if(multi_has_dirties(multi)) { - *expire_time = *multi_now(multi); + if(pexire_offset_us) + *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, + multi_now(multi)); *timeout_ms = 0; return; } - else if(multi->timetree) { - const struct curltime *pnow = multi_now(multi); - /* splay the lowest to the bottom */ - multi->timetree = Curl_splay(&tv_zero, multi->timetree); - /* 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_ptimediff_us(&multi->timetree->key, pnow) > 0) { - /* some time left before expiration */ - timediff_t diff_ms = - curlx_timediff_ceil_ms(multi->timetree->key, *pnow); - VERBOSE(data = Curl_splayget(multi->timetree)); - /* this should be safe even on 32-bit archs, as we do not use that - overly long timeouts */ - *timeout_ms = (long)diff_ms; - } - else { - if(multi->timetree) - VERBOSE(data = Curl_splayget(multi->timetree)); - /* 0 means immediately */ - *timeout_ms = 0; - } - } else { - *expire_time = tv_zero; - *timeout_ms = -1; - } + const struct curltime *pnow = multi_now(multi); + uint32_t mid; + *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, pnow, + pexire_offset_us, &mid); #ifdef CURLVERBOSE - if(CURL_TRC_TIMER_is_verbose(data)) { - struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist); - if(e) { - struct time_node *n = Curl_node_elem(e); - CURL_TRC_TIMER(data, n->eid, "gives multi timeout in %ldms", - *timeout_ms); + if(mid != UINT32_MAX) { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(data && CURL_TRC_TIMER_is_verbose(data) && + (data->state.timeouts.first < EXPIRE_LAST)) { + CURL_TRC_TIMER(data, data->state.timeouts.first, + "gives multi timeout in %dms", *timeout_ms); + } } - } #endif + } } 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; + struct Curl_mapi_guard guard; + CURLMcode mresult; - if(multi->in_callback) - return CURLM_RECURSIVE_API_CALL; + if(CURL_MAPI_ENTER(&guard, m, multi_timeout, &mresult)) { + int itimeout_ms; - multi_timeout(multi, &expire_time, timeout_ms); - return CURLM_OK; + multi_timeout(m, NULL, &itimeout_ms); + *timeout_ms = (long)itimeout_ms; + mresult = CURLM_OK; + } + CURL_MAPI_LEAVE(&guard); + return mresult; } /* @@ -3485,14 +3584,14 @@ CURLMcode curl_multi_timeout(CURLM *m, */ CURLMcode Curl_update_timer(struct Curl_multi *multi) { - struct curltime expire_ts; - long timeout_ms; + timediff_t timeouts_offset_us = 0; + int timeout_ms; int rc; bool set_value = FALSE; if(!multi->timer_cb || multi->dead) return CURLM_OK; - multi_timeout(multi, &expire_ts, &timeout_ms); + multi_timeout(multi, &timeouts_offset_us, &timeout_ms); if(timeout_ms < 0 && multi->last_timeout_ms < 0) { /* nothing to do */ @@ -3504,14 +3603,14 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) set_value = TRUE; } else if(multi->last_timeout_ms < 0) { - CURL_TRC_M(multi->admin, "[TIMER] set %ldms, none before", timeout_ms); + CURL_TRC_M(multi->admin, "[TIMER] set %dms, none before", timeout_ms); set_value = TRUE; } - else if(curlx_ptimediff_us(&multi->last_expire_ts, &expire_ts)) { + else if(multi->last_expire_offset_us != timeouts_offset_us) { /* 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. */ - CURL_TRC_M(multi->admin, "[TIMER] set %ldms, replace previous", + CURL_TRC_M(multi->admin, "[TIMER] set %dms, replace previous", timeout_ms); set_value = TRUE; } @@ -3522,11 +3621,13 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) } if(set_value) { - multi->last_expire_ts = expire_ts; + struct Curl_mapi_guard guard; + + multi->last_expire_offset_us = timeouts_offset_us; multi->last_timeout_ms = timeout_ms; - set_in_callback(multi, TRUE); + CURL_CBAPI_MULTI_START(&guard, multi, multi_timer_cb); rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); - set_in_callback(multi, FALSE); + CURL_CBAPI_MULTI_END(&guard); if(rc == -1) { multi->dead = TRUE; return CURLM_ABORTED_BY_CALLBACK; @@ -3535,200 +3636,215 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) return CURLM_OK; } +#ifdef DEBUGBUILD +static bool multi_timeouts_check(struct Curl_easy *data) +{ + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t id; + int i = 0; + for(id = timeouts->first; id < EXPIRE_LAST; id = timeouts->next[id]) { + if(++i >= EXPIRE_LAST) { + failf(data, "expire timeouts looped: %d iterations and no end", i); + return FALSE; + } + if(id == timeouts->next[id]) { + failf(data, "expire timeouts wrong: %d points to itself", (int)id); + return FALSE; + } + if((timeouts->next[id] < EXPIRE_LAST) && + (timeouts->offset_us[id] > timeouts->offset_us[timeouts->next[id]])) { + failf(data, "expire timeouts not sorted: %d happens after %d but " + "is listed before", (int)id, (int)timeouts->next[id]); + return FALSE; + } + } + return TRUE; +} +#endif + /* - * multi_deltimeout() - * * Remove a given timestamp from the list of timeouts. */ -static void multi_deltimeout(struct Curl_easy *data, expire_id eid) +static void multi_clear_timeout(struct Curl_easy *data, expire_id eid) { - struct Curl_llist_node *e; - struct Curl_llist *timeoutlist = &data->state.timeoutlist; - /* find and remove the specific node from the list */ - 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_node_remove(e); + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t orig_first = timeouts->first; + uint8_t *anchor = &timeouts->first; + uint8_t id = (uint8_t)eid; + + if((unsigned)eid >= EXPIRE_LAST) { + DEBUGASSERT(0); + return; + } + + while(*anchor < EXPIRE_LAST) { + if(*anchor == id) { + *anchor = timeouts->next[id]; + break; + } + anchor = &timeouts->next[*anchor]; + } + DEBUGASSERT(multi_timeouts_check(data)); + if(Curl_timeouts_has(data)) { + struct Curl_multi *multi = data->multi; + + if(!multi) { + DEBUGASSERT(0); return; } + if((timeouts->first >= EXPIRE_LAST) || /* no more timeouts */ + (timeouts->first != orig_first)) { /* active timeout changed */ + Curl_timeouts_remove(&multi->timeouts, data); + } + if((timeouts->first < EXPIRE_LAST) && !Curl_timeouts_has(data)) { + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); + } } } /* - * multi_addtimeout() - * * Add a timestamp to the list of timeouts. Keep the list sorted so that head * of list is always the timeout nearest in time. - * */ -static CURLMcode multi_addtimeout(struct Curl_easy *data, - struct curltime *stamp, - expire_id eid) +static CURLMcode multi_set_timeout(struct Curl_easy *data, + const struct curltime *stamp, + expire_id eid) { - struct Curl_llist_node *e; - struct time_node *node; - struct Curl_llist_node *prev = NULL; - size_t n; - struct Curl_llist *timeoutlist = &data->state.timeoutlist; - - node = &data->state.expires[eid]; - - /* copy the timestamp and id */ - memcpy(&node->time, stamp, sizeof(*stamp)); - node->eid = eid; /* also marks it as in use */ - - n = Curl_llist_count(timeoutlist); - if(n) { - /* find the correct spot in the list */ - for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { - struct time_node *check = Curl_node_elem(e); - timediff_t diff = curlx_ptimediff_ms(&check->time, &node->time); - if(diff > 0) - break; - prev = e; - } + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t *anchor = &timeouts->first; + uint8_t id = (uint8_t)eid; + + if((unsigned)eid >= EXPIRE_LAST) { + DEBUGASSERT(0); + return CURLM_BAD_FUNCTION_ARGUMENT; } - /* else - this is the first timeout on the list */ + /* remove from list, store time and re-insert */ + multi_clear_timeout(data, eid); + timeouts->offset_us[id] = + Curl_timeouts_offset_us(&data->multi->timeouts, stamp); - Curl_llist_insert_next(timeoutlist, prev, node, &node->list); - CURL_TRC_TIMER(data, eid, "set for %" FMT_TIMEDIFF_T "ns", - curlx_ptimediff_us(&node->time, Curl_pgrs_now(data))); + while(*anchor < EXPIRE_LAST) { + if(timeouts->offset_us[*anchor] > timeouts->offset_us[id]) + break; + anchor = &timeouts->next[*anchor]; + } + timeouts->next[eid] = *anchor; + timeouts->next[eid] = *anchor; + *anchor = id; + DEBUGASSERT(multi_timeouts_check(data)); + CURL_TRC_TIMER(data, eid, "set for %" FMT_TIMEDIFF_T "us", + curlx_ptimediff_us(stamp, Curl_pgrs_now(data))); return CURLM_OK; } -void Curl_expire_ex(struct Curl_easy *data, - timediff_t milli, expire_id id) +/* + * 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_set(struct Curl_easy *data, + expire_id eid, timediff_t ms, + const struct curltime *pnow) { struct Curl_multi *multi = data->multi; - struct curltime *curr_expire = &data->state.expiretime; + struct expire_timers *timeouts = &data->state.timeouts; + uint8_t prev_id = timeouts->first; struct curltime set; /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) return; - - DEBUGASSERT(id < EXPIRE_LAST); - - set = *Curl_pgrs_now(data); - set.tv_sec += (time_t)(milli / 1000); /* may be a 64 to 32-bit conversion */ - set.tv_usec += (int)(milli % 1000) * 1000; - + DEBUGASSERT(eid < EXPIRE_LAST); + if(ms > INT_MAX) + /* Cap ridiculous timeouts, 31-bit ms is still 3.5 weeks. When the time + goes to the user, it must fit in this size. */ + ms = INT_MAX; + + set = *pnow; + set.tv_sec += (time_t)(ms / 1000); /* may be a 64 to 32-bit conversion */ + set.tv_usec += (int)(ms % 1000) * 1000; if(set.tv_usec >= 1000000) { set.tv_sec++; set.tv_usec -= 1000000; } - /* Remove any timer with the same id */ - multi_deltimeout(data, id); - - /* 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); + /* Add the timeout, will replace any previous value for this timer. */ + multi_set_timeout(data, &set, eid); + DEBUGASSERT(timeouts->first < EXPIRE_LAST); - 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 = curlx_ptimediff_ms(&set, curr_expire); - int rc; - - if(diff > 0) { - /* The current splay tree entry is sooner than this new expiry time. - We do not need to update our splay tree entry. */ + if(Curl_timeouts_has(data)) { + /* data has already a timeout registered. If the first timer + * was NOT the one we just set AND is still the first one, + * nothing changed from the timeouts point of view. The + * set timer triggers after the one already registered. Leave. */ + if((prev_id != eid) && (prev_id == timeouts->first)) return; - } - /* Since this is an updated time, we must remove the previous entry from - the splay tree first and then re-add the new value */ - rc = Curl_splayremove(multi->timetree, &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error removing splay node = %d", rc); + /* Since this is an updated time, we must remove data from + * timeouts and then add it again. */ + Curl_timeouts_remove(&multi->timeouts, data); } - /* Indicate that we are in the splay tree and insert the new timer expiry - value since it is our local minimum. */ - *curr_expire = set; - Curl_splayset(&data->state.timenode, data); - multi->timetree = Curl_splayinsert(curr_expire, multi->timetree, - &data->state.timenode); + /* Insert the new timer expiry since it is our local minimum. */ + Curl_timeouts_add(&multi->timeouts, data, + timeouts->offset_us[timeouts->first]); } -/* - * 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(struct Curl_easy *data, + timediff_t milli, expire_id eid) { - Curl_expire_ex(data, milli, id); + Curl_expire_set(data, eid, milli, Curl_pgrs_now(data)); } /* - * Curl_expire_done() - * * Removes the expire timer. Marks it as done. - * */ -void Curl_expire_done(struct Curl_easy *data, expire_id id) +void Curl_expire_clear(struct Curl_easy *data, expire_id eid) { /* remove the timer, if there */ - multi_deltimeout(data, id); - CURL_TRC_TIMER(data, id, "cleared"); + multi_clear_timeout(data, eid); + CURL_TRC_TIMER(data, eid, "cleared"); } /* - * Curl_expire_clear() - * * Clear ALL timeout values for this handle. */ -void Curl_expire_clear(struct Curl_easy *data) +void Curl_expire_clear_all(struct Curl_easy *data) { struct Curl_multi *multi = data->multi; - struct curltime *nowp = &data->state.expiretime; /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) return; - if(nowp->tv_sec || nowp->tv_usec) { + if(Curl_timeouts_remove(&multi->timeouts, data)) { /* Since this is an cleared time, we must remove the previous entry from the splay tree */ - struct Curl_llist *list = &data->state.timeoutlist; - int rc; - - rc = Curl_splayremove(multi->timetree, &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error clearing splay node = %d", rc); - - /* clear the timeout list too */ - Curl_llist_destroy(list, NULL); + multi_timeouts_init(data); if(data->id >= 0) CURL_TRC_M(data, "[TIMEOUT] all cleared"); - nowp->tv_sec = 0; - nowp->tv_usec = 0; } } CURLMcode curl_multi_assign(CURLM *m, curl_socket_t sockfd, void *sockp) { - struct Curl_multi *multi = m; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; + struct Curl_mapi_guard guard; + CURLMcode mresult; - return Curl_multi_ev_assign(multi, sockfd, sockp); + if(CURL_MAPI_ENTER(&guard, m, multi_assign, &mresult)) { + mresult = Curl_multi_ev_assign(m, sockfd, sockp); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } static void move_pending_to_connect(struct Curl_multi *multi, @@ -3744,7 +3860,7 @@ static void move_pending_to_connect(struct Curl_multi *multi, Curl_multi_mark_dirty(data); /* make it run */ } -/* process_pending_handles() moves a handle from PENDING back into the process +/* multi_schedule_pending() moves a handle from PENDING back into the process list and change state to CONNECT. We do not move all transfers because that can be a significant amount. @@ -3756,9 +3872,8 @@ static void move_pending_to_connect(struct Curl_multi *multi, 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) + more pipewait rechecks than others. */ +static void multi_schedule_pending(struct Curl_multi *multi) { uint32_t mid = multi->last_pending_mid; @@ -3794,17 +3909,6 @@ static void process_pending_handles(struct Curl_multi *multi) } } -void Curl_set_in_callback(struct Curl_easy *data, bool value) -{ - if(data && data->multi) - data->multi->in_callback = value; -} - -bool Curl_is_in_callback(struct Curl_easy *data) -{ - return data && data->multi && data->multi->in_callback; -} - unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) { DEBUGASSERT(multi); @@ -3813,24 +3917,31 @@ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) CURL **curl_multi_get_handles(CURLM *m) { - struct Curl_multi *multi = m; - void *entry; - size_t count = Curl_uint32_tbl_count(&multi->xfers); - CURL **a = curlx_malloc(sizeof(struct Curl_easy *) * (count + 1)); - if(a) { - unsigned int i = 0; - uint32_t mid; + struct Curl_mapi_guard guard; + CURL **a = NULL; - if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { - do { - struct Curl_easy *data = entry; - DEBUGASSERT(i < count); - if(!data->state.internal) - a[i++] = data; - } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry)); + if(CURL_MAPI_ENTER(&guard, m, multi_get_handles, NULL)) { + struct Curl_multi *multi = m; + void *entry; + size_t count = Curl_uint32_tbl_count(&multi->xfers); + + a = curlx_malloc(sizeof(struct Curl_easy *) * (count + 1)); + if(a) { + unsigned int i = 0; + uint32_t mid; + + if(Curl_uint32_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + DEBUGASSERT(i < count); + if(!data->state.internal) + a[i++] = data; + } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry)); + } + a[i] = NULL; /* last entry is a NULL */ } - a[i] = NULL; /* last entry is a NULL */ } + CURL_MAPI_LEAVE(&guard); return a; } @@ -3838,40 +3949,49 @@ CURLMcode curl_multi_get_offt(CURLM *m, CURLMinfo_offt info, curl_off_t *pvalue) { - struct Curl_multi *multi = m; - uint32_t n; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - if(!pvalue) - return CURLM_BAD_FUNCTION_ARGUMENT; + if(CURL_MAPI_ENTER(&guard, m, multi_get_offt, &mresult)) { + struct Curl_multi *multi = m; + uint32_t n; - switch(info) { - case CURLMINFO_XFERS_CURRENT: - n = Curl_uint32_tbl_count(&multi->xfers); - if(n && multi->admin) - --n; - *pvalue = (curl_off_t)n; - return CURLM_OK; - case CURLMINFO_XFERS_RUNNING: - n = Curl_uint32_bset_count(&multi->process); - if(n && Curl_uint32_bset_contains(&multi->process, multi->admin->mid)) - --n; - *pvalue = (curl_off_t)n; - return CURLM_OK; - case CURLMINFO_XFERS_PENDING: - *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->pending); - return CURLM_OK; - case CURLMINFO_XFERS_DONE: - *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->msgsent); - return CURLM_OK; - case CURLMINFO_XFERS_ADDED: - *pvalue = multi->xfers_total_ever; - return CURLM_OK; - default: - *pvalue = -1; - return CURLM_UNKNOWN_OPTION; + if(!pvalue) { + mresult = CURLM_BAD_FUNCTION_ARGUMENT; + goto out; + } + + switch(info) { + case CURLMINFO_XFERS_CURRENT: + n = Curl_uint32_tbl_count(&multi->xfers); + if(n && multi->admin) + --n; + *pvalue = (curl_off_t)n; + break; + case CURLMINFO_XFERS_RUNNING: + n = Curl_uint32_bset_count(&multi->process); + if(n && Curl_uint32_bset_contains(&multi->process, multi->admin->mid)) + --n; + *pvalue = (curl_off_t)n; + break; + case CURLMINFO_XFERS_PENDING: + *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->pending); + break; + case CURLMINFO_XFERS_DONE: + *pvalue = (curl_off_t)Curl_uint32_bset_count(&multi->msgsent); + break; + case CURLMINFO_XFERS_ADDED: + *pvalue = multi->xfers_total_ever; + break; + default: + *pvalue = -1; + mresult = CURLM_UNKNOWN_OPTION; + break; + } } +out: + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, @@ -3897,8 +4017,7 @@ CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, if(data->multi->xfer_buf && data->set.buffer_size > data->multi->xfer_buf_len) { /* not large enough, get a new one */ - curlx_free(data->multi->xfer_buf); - data->multi->xfer_buf = NULL; + curlx_safefree(data->multi->xfer_buf); data->multi->xfer_buf_len = 0; } @@ -3950,8 +4069,7 @@ CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, if(data->multi->xfer_ulbuf && data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { /* not large enough, get a new one */ - curlx_free(data->multi->xfer_ulbuf); - data->multi->xfer_ulbuf = NULL; + curlx_safefree(data->multi->xfer_ulbuf); data->multi->xfer_ulbuf_len = 0; } @@ -3985,11 +4103,12 @@ 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; + /* When a SHARE gets destroyed and has a connection pool, we get + * call with share->admin which does not have a multi handle. */ + *pbuf = curlx_malloc(blen); + return *pbuf ? CURLE_OK : CURLE_OUT_OF_MEMORY; } if(data->multi->xfer_sockbuf_borrowed) { failf(data, "attempt to borrow xfer_sockbuf when already borrowed"); @@ -3998,8 +4117,7 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) { /* not large enough, get a new one */ - curlx_free(data->multi->xfer_sockbuf); - data->multi->xfer_sockbuf = NULL; + curlx_safefree(data->multi->xfer_sockbuf); data->multi->xfer_sockbuf_len = 0; } @@ -4019,11 +4137,16 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) { - (void)buf; DEBUGASSERT(data); - DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); - data->multi->xfer_sockbuf_borrowed = FALSE; + if(!data->multi) { + /* When a SHARE gets destroyed and has a connection pool, we get + * call with share->admin which does not have a multi handle. */ + curlx_free(buf); + } + else { + DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + data->multi->xfer_sockbuf_borrowed = FALSE; + } } static void multi_xfer_bufs_free(struct Curl_multi *multi) @@ -4052,11 +4175,30 @@ struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, return NULL; } -unsigned int Curl_multi_xfers_running(struct Curl_multi *multi) +bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data) { + return Curl_uint32_tbl_get(&multi->xfers, data->mid) == data; +} + +uint32_t Curl_multi_xfers_running(struct Curl_multi *multi) +{ + if(!multi) { + DEBUGASSERT(0); + return 0; + } return multi->xfers_alive; } +uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi) +{ + if(!multi || !multi->admin) { + DEBUGASSERT(0); + return 0; + } + /* Discount the admin handle */ + return Curl_uint32_tbl_count(&multi->xfers) - 1; +} + void Curl_multi_mark_dirty(struct Curl_easy *data) { if(data->multi && data->mid != UINT32_MAX) @@ -4071,20 +4213,26 @@ void Curl_multi_clear_dirty(struct Curl_easy *data) CURLMcode curl_multi_notify_enable(CURLM *m, unsigned int notification) { - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - return Curl_mntfy_enable(multi, notification); + if(CURL_MAPI_ENTER(&guard, m, multi_notify_enable, &mresult)) { + mresult = Curl_mntfy_enable(m, notification); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } CURLMcode curl_multi_notify_disable(CURLM *m, unsigned int notification) { - struct Curl_multi *multi = m; + struct Curl_mapi_guard guard; + CURLMcode mresult = CURLM_OK; - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - return Curl_mntfy_disable(multi, notification); + if(CURL_MAPI_ENTER(&guard, m, multi_notify_disable, &mresult)) { + mresult = Curl_mntfy_disable(m, notification); + } + CURL_MAPI_LEAVE(&guard); + return mresult; } #ifdef DEBUGBUILD diff --git a/lib/multi_ev.c b/lib/multi_ev.c index 937e7ce48d32..3b7eeb0dc3a6 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -34,11 +34,9 @@ #include "uint-spbset.h" #include "multihandle.h" - -static void mev_in_callback(struct Curl_multi *multi, bool value) -{ - multi->in_callback = value; -} +#ifdef DEBUGBUILD +#define SH_ENTRY_MAGIC 0x570091d +#endif /* Information about a socket for which we inform the libcurl application * what to supervise (CURL_POLL_IN/CURL_POLL_OUT/CURL_POLL_REMOVE) @@ -51,6 +49,9 @@ struct mev_sh_entry { * libcurl application to watch out for */ unsigned int readers; /* this many transfers want to read */ unsigned int writers; /* this many transfers want to write */ +#ifdef DEBUGBUILD + unsigned int magic; +#endif BIT(announced); /* this socket has been passed to the socket callback at least once */ }; @@ -75,6 +76,9 @@ static void mev_sh_entry_dtor(void *freethis) { struct mev_sh_entry *entry = (struct mev_sh_entry *)freethis; Curl_uint32_spbset_destroy(&entry->xfers); +#ifdef DEBUGBUILD + entry->magic = 0; +#endif curlx_free(entry); } @@ -113,7 +117,9 @@ static struct mev_sh_entry *mev_sh_entry_add(struct Curl_hash *sh, mev_sh_entry_dtor(check); return NULL; /* major failure */ } - +#ifdef DEBUGBUILD + check->magic = SH_ENTRY_MAGIC; +#endif return check; /* things are good in sockhash land */ } @@ -195,13 +201,17 @@ static CURLMcode mev_forget_socket(struct Curl_multi *multi, /* We managed this socket before, tell the socket callback to forget it. */ if(entry->announced && multi->socket_cb) { + struct Curl_mapi_guard guard; + NOVERBOSE((void)cause); CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", cause, s); - mev_in_callback(multi, TRUE); + CURL_CBAPI_MULTI_START(&guard, multi, multi_socket_cb); rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, multi->socket_userp, entry->user_data); - mev_in_callback(multi, FALSE); - entry->announced = FALSE; + CURL_CBAPI_END(&guard); + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(entry) + entry->announced = FALSE; } mev_sh_entry_kill(multi, s); @@ -223,6 +233,7 @@ static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, /* we should only be called when the callback exists */ DEBUGASSERT(multi->socket_cb); + DEBUGASSERT(entry->magic == SH_ENTRY_MAGIC); if(!multi->socket_cb) return CURLM_OK; @@ -268,16 +279,25 @@ static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, 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); - entry->announced = TRUE; + { + struct Curl_mapi_guard guard; + CURL_CBAPI_MULTI_START(&guard, multi, multi_socket_cb); + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->user_data); + CURL_CBAPI_MULTI_END(&guard); + } if(rc == -1) { multi->dead = TRUE; return CURLM_ABORTED_BY_CALLBACK; } - entry->action = (unsigned int)comboaction; + /* curl_easy_pause() is documented as callable from any callback; it + * re-enters mev_assess() which may free this 'entry'. Re-fetch. */ + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(entry) { + DEBUGASSERT(entry->magic == SH_ENTRY_MAGIC); + entry->announced = TRUE; + entry->action = (unsigned int)comboaction; + } return CURLM_OK; } @@ -486,9 +506,9 @@ static CURLMcode mev_assess(struct Curl_multi *multi, Curl_pollset_init(&ps); if(conn) { - CURLcode r = Curl_conn_adjust_pollset(data, conn, &ps); - if(r) { - mresult = (r == CURLE_OUT_OF_MEMORY) ? + CURLcode result = Curl_conn_adjust_pollset(data, conn, &ps); + if(result) { + mresult = (result == CURLE_OUT_OF_MEMORY) ? CURLM_OUT_OF_MEMORY : CURLM_INTERNAL_ERROR; goto out; } diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c index 1319aaec0745..48c37239519a 100644 --- a/lib/multi_ntfy.c +++ b/lib/multi_ntfy.c @@ -179,8 +179,13 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) { - DEBUGASSERT(!multi->in_ntfy_callback); - multi->in_ntfy_callback = TRUE; + struct Curl_mapi_guard guard; + + if(!multi) + return CURLM_BAD_FUNCTION_ARGUMENT; + + CURL_CBAPI_MULTI_START(&guard, multi, multi_ntfy_cb); + while(multi->ntfy.head && !multi->ntfy.failure) { struct mntfy_chunk *chunk = multi->ntfy.head; /* this may cause new notifications to be added! */ @@ -194,7 +199,8 @@ CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) multi->ntfy.head = chunk->next; mnfty_chunk_destroy(chunk); } - multi->in_ntfy_callback = FALSE; + + CURL_CBAPI_MULTI_END(&guard); if(multi->ntfy.failure) { CURLMcode mresult = multi->ntfy.failure; diff --git a/lib/multihandle.h b/lib/multihandle.h index c5cdfbe82e42..2a8fba158300 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -23,18 +23,20 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "api.h" #include "llist.h" #include "hash.h" #include "conncache.h" #include "cshutdn.h" -#include "dnscache.h" #include "multi_ev.h" #include "multi_ntfy.h" #include "psl.h" #include "socketpair.h" +#include "splay.h" #include "uint-bset.h" #include "uint-spbset.h" #include "uint-table.h" +#include "vdns/dnscache.h" struct connectdata; struct Curl_easy; @@ -73,19 +75,27 @@ typedef enum { #if !defined(CURL_DISABLE_SOCKETPAIR) && !defined(USE_WINSOCK) #define ENABLE_WAKEUP #endif +#if !defined(CURL_DISABLE_SOCKETPAIR) && \ + defined(USE_RESOLV_THREADED) && \ + !defined(USE_WINSOCK) +#define ENABLE_INTERNAL_WAKEUP +#endif /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) /* 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; + /* First a simple identifier to more easily detect if a user mixes up + this multi handle with an easy handle. + Set this to CURLMULTI_MAGIC_NUMBER. */ + uint32_t magic; + uint32_t xfers_alive; /* amount of added transfers that have + not yet reached COMPLETE state */ + uint32_t xfers_really_alive; /* amount of added transfers that have + passed INIT state but are not COMPLETE yet */ + uint32_t max_concurrent_streams; - unsigned int xfers_alive; /* amount of added transfers that have - not yet reached COMPLETE state */ - curl_off_t xfers_total_ever; /* total of added transfers, ever. */ struct uint32_tbl xfers; /* transfers added to this multi */ /* Each transfer's mid may be present in at most one of these */ struct uint32_bset process; /* transfer being processed */ @@ -93,8 +103,12 @@ struct Curl_multi { struct uint32_bset pending; /* transfers in waiting (conn limit etc.) */ struct uint32_bset msgsent; /* transfers done with message for application */ + struct Curl_mapi_stack callstack; /* multi api calls ongoing */ + struct Curl_llist msglist; /* a list of messages from completed transfers */ + curl_off_t xfers_total_ever; /* total of added transfers, ever. */ + struct Curl_easy *admin; /* internal easy handle for admin operations. gets assigned `mid` 0 on multi init */ @@ -119,9 +133,8 @@ struct Curl_multi { /* current time for transfers running in this multi handle */ struct curltime now; - /* timetree points to the splay-tree of time nodes to figure out expire - times of all currently set timers */ - struct Curl_tree *timetree; + /* expiration times for all attached easy handles */ + struct Curl_timeouts timeouts; /* buffer used for transfer data, lazy initialized */ char *xfer_buf; /* the actual buffer */ @@ -148,6 +161,7 @@ struct Curl_multi { struct cshutdn cshutdn; /* connection shutdown handling */ struct cpool cpool; /* connection pool (bundles) */ + timediff_t last_expire_offset_us; /* times offset of last expiry */ size_t max_host_connections; /* if >0, a fixed limit of the maximum number of connections per host */ @@ -157,8 +171,7 @@ struct Curl_multi { /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - long last_timeout_ms; /* the last timeout value set via timer_cb */ - struct curltime last_expire_ts; /* timestamp of last expiry */ + int last_timeout_ms; /* the last timeout value set via timer_cb */ #ifdef USE_WINSOCK WSAEVENT wsa_event; /* Winsock event used for waits */ @@ -166,9 +179,14 @@ struct Curl_multi { #ifdef ENABLE_WAKEUP curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for wakeup 0 is used for read, 1 is used - for write */ + for write. Used by curl_multi_wakeup() */ +#endif +#ifdef ENABLE_INTERNAL_WAKEUP + curl_socket_t wakeup_internal[2]; /* eventfd()/pipe()/socketpair() used for + wakeup 0 is used for read, 1 is used + for write. Used for internal wakeups, + e.g. threaded resolver. */ #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 */ @@ -180,8 +198,6 @@ struct Curl_multi { BIT(ipv6_works); BIT(multiplexing); /* multiplexing wanted */ BIT(recheckstate); /* see Curl_multi_connchanged */ - BIT(in_callback); /* true while executing a callback */ - BIT(in_ntfy_callback); /* true while dispatching notifications */ #ifdef USE_OPENSSL BIT(ssl_seeded); #endif diff --git a/lib/multiif.h b/lib/multiif.h index 039db269e001..d3ad8fffb334 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -27,23 +27,28 @@ * Prototypes for library-wide functions provided by multi.c */ -void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id); -void Curl_expire_ex(struct Curl_easy *data, - timediff_t milli, expire_id id); -void Curl_expire_clear(struct Curl_easy *data); -void Curl_expire_done(struct Curl_easy *data, expire_id id); +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id eid); +void Curl_expire_set(struct Curl_easy *data, + expire_id eid, timediff_t ms, + const struct curltime *pnow); +void Curl_expire_clear(struct Curl_easy *data, expire_id eid); +void Curl_expire_clear_all(struct Curl_easy *data); CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn, + bool matched); 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 *data); CURLcode Curl_preconnect(struct Curl_easy *data); bool Curl_is_connecting(struct Curl_easy *data); void Curl_multi_connchanged(struct Curl_multi *multi); +CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, + struct Curl_easy *data); +CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, + struct Curl_easy *data); + /* Internal version of curl_multi_init() accepts size parameters for the socket, connection and dns hashes */ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, @@ -148,13 +153,20 @@ 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. + * Returns NULL if not found or not a GOOD_EASY_HANDLE() */ struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, uint32_t mid); +/* TRUE if multi knows about data via its `mid` */ +bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data); + +/* Get the # of transfers attached to the multi, without the internal + * admin handle. */ +uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi); + /* Get the # of transfers current in process/pending. */ -unsigned int Curl_multi_xfers_running(struct Curl_multi *multi); +uint32_t Curl_multi_xfers_running(struct Curl_multi *multi); /* Mark a transfer as dirty, e.g. to be rerun at earliest convenience. * A cheap operation, can be done many times repeatedly. */ @@ -164,4 +176,8 @@ void Curl_multi_clear_dirty(struct Curl_easy *data); void Curl_multi_set_now(struct Curl_multi *multi); +#ifdef ENABLE_INTERNAL_WAKEUP +void Curl_multi_wakeup_internal(struct Curl_multi *multi); +#endif + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/lib/netrc.c b/lib/netrc.c index 72d8feee7d94..7c8e1fb2ab3f 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -36,88 +36,154 @@ #endif #include "netrc.h" +#include "urldata.h" +#include "creds.h" +#include "curl_trc.h" #include "strcase.h" #include "curl_get_line.h" #include "curlx/fopen.h" #include "curlx/strparse.h" -/* Get user and password from .netrc when given a machine name */ -enum host_lookup_state { - NOTHING, - HOSTFOUND, /* the 'machine' keyword was found */ - HOSTVALID, /* this is "our" machine! */ - MACDEF -}; - -enum found_state { - NONE, - LOGIN, - PASSWORD -}; - -#define FOUND_LOGIN 1 -#define FOUND_PASSWORD 2 +/* .netrc is not really a standard. The GNU definition can be found here: + * https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html + * This gives grammar like: + * + * LITERAL := \S+ | QUOTED + * QUOTED := "(\\[rnt\]|[^"])*" + * ANYTHING := . + * EMPTY_LINE := \r*\n\r*\n + * MACHINE := machine # case-insensitive + * LOGIN := login # case-insensitive + * PASSWD := password # case-insensitive + * ACCOUNT := account # case-insensitive + * MACDEF := macdef # case-insensitive + * DEFAULT := default # case-insensitive + * + * MACRO := MACDEF ANYTHING* EMPTY_LINE + * JUNK := LITERAL + * LKEY := ( LOGIN | PASSWD | ACCOUNT ) LITERAL + * MENTRY := MACHINE LITERAL LKEY* + * DENTRY := DEFAULT LKEY* + * NETRC := (MENTRY | DENTRY | MACRO | JUNK )* EOF + * + * Tokens are separated by whitespace or newlines. which have otherwise + * no special meaning, apart from the empty line ending a MACRO. + * + * Parsing is not strict, unmatched LITERALs are ignored + */ #define MAX_NETRC_LINE 16384 #define MAX_NETRC_FILE (128 * 1024) #define MAX_NETRC_TOKEN 4096 +#define NETRC_DEBUG 0 + /* 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) +#define curl2netrc(r) \ + ((!(r)) ? NETRC_OK : (((r) == CURLE_OUT_OF_MEMORY) ? \ + NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR)) + +typedef enum { + NETRC_TOK_EOF, + NETRC_TOK_LITERAL, + NETRC_TOK_MACHINE, + NETRC_TOK_DEFAULT, + NETRC_TOK_ACCOUNT, + NETRC_TOK_LOGIN, + NETRC_TOK_PASSWD, + NETRC_TOK_MACDEF, + NETRC_TOK_JUNK +} curl_netrc_token; + +struct netrc_lexer { + struct Curl_easy *data; + const char *content; + const char *pos; + struct dynbuf literal; + curl_netrc_token token; + bool pushed; +}; -static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) +#if NETRC_DEBUG +static const char *netrc_tokenstr(curl_netrc_token token) { - NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */ - FILE *file = curlx_fopen(filename, FOPEN_READTEXT); + switch(token) { + case NETRC_TOK_EOF: + return "[EOF]"; + case NETRC_TOK_LITERAL: + return "[LITERAL]"; + case NETRC_TOK_MACHINE: + return "[MACHINE]"; + case NETRC_TOK_DEFAULT: + return "[DEFAULT]"; + case NETRC_TOK_ACCOUNT: + return "[ACCOUNT]"; + case NETRC_TOK_LOGIN: + return "[LOGIN]"; + case NETRC_TOK_PASSWD: + return "[PASSWORD]"; + case NETRC_TOK_MACDEF: + return "[MACDEF]"; + case NETRC_TOK_JUNK: + return "[JUNK]"; + default: + return "[???]"; + } +} +#endif - if(file) { - curlx_struct_stat stat; - if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) { - CURLcode result = CURLE_OK; - bool eof; - struct dynbuf linebuf; - curlx_dyn_init(&linebuf, MAX_NETRC_LINE); - ret = NETRC_OK; - do { - const char *line; - /* Curl_get_line always returns lines ending with a newline */ - result = Curl_get_line(&linebuf, file, &eof); - if(!result) { - line = curlx_dyn_ptr(&linebuf); - /* skip comments on load */ - curlx_str_passblanks(&line); - if(*line == '#') - continue; - result = curlx_dyn_add(filebuf, line); - } - if(result) { - curlx_dyn_free(filebuf); - ret = curl2netrc(result); - break; - } - } while(!eof); - curlx_dyn_free(&linebuf); +static void netrc_lexer_init(struct netrc_lexer *lexer, + struct Curl_easy *data, + const char *content) +{ + curlx_dyn_init(&lexer->literal, MAX_NETRC_TOKEN); + lexer->data = data; + lexer->content = lexer->pos = content; +} + +static void netrc_lexer_cleanup(struct netrc_lexer *lexer) +{ + lexer->content = lexer->pos = NULL; + lexer->data = NULL; + curlx_dyn_free(&lexer->literal); +} + +static void netrc_skip_blanks(struct netrc_lexer *lexer) +{ + const char *s = lexer->pos; + while(*s) { + curlx_str_passblanks(&s); + while(*s == '\r') + ++s; + if(*s == '\n') { + ++s; } - curlx_fclose(file); + else + break; } - return ret; + lexer->pos = s; } -/* bundled parser state to keep function signatures compact */ -struct netrc_state { - char *login; - char *password; - enum host_lookup_state state; - enum found_state keyword; - NETRCcode retcode; - unsigned char found; /* FOUND_LOGIN | FOUND_PASSWORD bits */ - bool our_login; - bool done; - bool specific_login; -}; +static void netrc_skip_to_empty_line(struct netrc_lexer *lexer) +{ + const char *s = lexer->pos; + while(*s) { + if(*s == '\r') + ++s; + else if(*s == '\n') { + ++s; + while(*s == '\r') + ++s; + if(*s == '\n') + goto out; + } + else + ++s; + } +out: + lexer->pos = s; +} /* * Parse a quoted token starting after the opening '"'. Handles \n, \r, \t @@ -125,365 +191,379 @@ struct netrc_state { * * Returns NETRC_OK or error. */ -static NETRCcode netrc_quoted_token(const char **tok_endp, - struct dynbuf *token) +static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer) { - bool escape = FALSE; NETRCcode rc = NETRC_SYNTAX_ERROR; - const char *tok_end = *tok_endp; - tok_end++; /* pass the leading quote */ - while(*tok_end) { - CURLcode result; - char s = *tok_end; + const char *s = lexer->pos; + bool escape = FALSE; + CURLcode result; + + DEBUGASSERT(*s == '\"'); + ++s; /* pass the leading quote */ + while(*s) { + char c = *s; if(escape) { escape = FALSE; - switch(s) { + switch(c) { case 'n': - s = '\n'; + c = '\n'; break; case 'r': - s = '\r'; + c = '\r'; break; case 't': - s = '\t'; + c = '\t'; break; } } - else if(s == '\\') { + else if(c == '\\') { escape = TRUE; - tok_end++; + ++s; continue; } - else if(s == '\"') { - tok_end++; /* pass the ending quote */ + else if(c == '\"') { + ++s; /* pass the ending quote */ rc = NETRC_OK; - break; + goto out; } - result = curlx_dyn_addn(token, &s, 1); + result = curlx_dyn_addn(&lexer->literal, &c, 1); if(result) { - *tok_endp = tok_end; - return curl2netrc(result); + rc = curl2netrc(result); + goto out; } - tok_end++; + ++s; } - *tok_endp = tok_end; +out: + lexer->pos = s; return rc; } -/* - * Gets the next token from the netrc buffer at *tokp. Writes the token into - * the 'token' dynbuf. Advances *tok_endp past the consumed token in the input - * buffer. Updates *statep for MACDEF newline handling. Sets *lineend = TRUE - * when the line is exhausted. - * - * Returns NETRC_OK or an error code. - */ -static NETRCcode netrc_get_token(const char **tokp, - const char **tok_endp, - struct dynbuf *token, - enum host_lookup_state *statep, - bool *lineend) +static void netrc_lexer_push(struct netrc_lexer *lexer) { - const char *tok = *tokp; - const char *tok_end; - - *lineend = FALSE; - curlx_dyn_reset(token); - curlx_str_passblanks(&tok); - - /* tok is first non-space letter */ - if(*statep == MACDEF) { - if((*tok == '\n') || (*tok == '\r')) - *statep = NOTHING; /* end of macro definition */ - *lineend = TRUE; - *tokp = tok; - return NETRC_OK; - } + lexer->pushed = TRUE; +} - if(!*tok || (*tok == '\n')) { - /* end of line */ - *lineend = TRUE; - *tokp = tok; - return NETRC_OK; +static NETRCcode netrc_lexer_next(struct netrc_lexer *lexer, + bool want_literal) +{ + const char *s = lexer->pos, *start; + NETRCcode rc = NETRC_OK; + size_t slen; + CURLcode result; + + if(lexer->pushed) { + lexer->pushed = FALSE; + goto out; } - tok_end = tok; - if(*tok == '\"') { - /* quoted string */ - NETRCcode ret = netrc_quoted_token(&tok_end, token); - if(ret) - return ret; - } - else { + curlx_dyn_reset(&lexer->literal); + netrc_skip_blanks(lexer); + s = lexer->pos; + + switch(*s) { + case 0: + lexer->token = NETRC_TOK_EOF; + break; + case '\"': + rc = netrc_lexer_quoted(lexer); + lexer->token = NETRC_TOK_LITERAL; + s = lexer->pos; + break; + default: /* unquoted token */ - size_t len = 0; - CURLcode result; - while(*tok_end > ' ') { - tok_end++; - len++; + start = s; + while(*s && !ISBLANK(*s) && !ISNEWLINE(*s)) + ++s; + slen = s - start; + if(!slen) { + rc = NETRC_SYNTAX_ERROR; + } + if(want_literal) { + lexer->token = NETRC_TOK_LITERAL; + result = curlx_dyn_addn(&lexer->literal, start, slen); + rc = curl2netrc(result); + } + else if((slen == 7) && curl_strnequal(start, "machine", slen)) { + lexer->token = NETRC_TOK_MACHINE; + } + else if((slen == 7) && curl_strnequal(start, "default", slen)) { + lexer->token = NETRC_TOK_DEFAULT; + } + else if((slen == 7) && curl_strnequal(start, "account", slen)) { + lexer->token = NETRC_TOK_ACCOUNT; + } + else if((slen == 5) && curl_strnequal(start, "login", slen)) { + lexer->token = NETRC_TOK_LOGIN; + } + else if((slen == 8) && curl_strnequal(start, "password", slen)) { + lexer->token = NETRC_TOK_PASSWD; } - if(!len) - return NETRC_SYNTAX_ERROR; - result = curlx_dyn_addn(token, tok, len); - if(result) - return curl2netrc(result); + else if((slen == 6) && curl_strnequal(start, "macdef", slen)) { + lexer->token = NETRC_TOK_MACDEF; + } + else { + lexer->token = NETRC_TOK_JUNK; + } + break; } - *tok_endp = tok_end; +out: +#if NETRC_DEBUG + CURL_TRC_M(lexer->data, "[NETRC] token %s '%s', rc=%d", + netrc_tokenstr(lexer->token), + curlx_dyn_ptr(&lexer->literal), rc); +#endif + lexer->pos = s; + return rc; +} - if(curlx_dyn_len(token)) - *tokp = curlx_dyn_ptr(token); - else - /* set it to blank to avoid NULL */ - *tokp = ""; +struct netrc_scanner { + struct netrc_lexer lexer; + const char *hostname; /* non-NULL, machine to scan for */ + const char *user; /* maybe NULL, login to scan for */ + char *login; + char *passwd; + struct Curl_creds *creds; + bool matches_host; + bool found; +}; - return NETRC_OK; +static void netrc_scan_reset(struct netrc_scanner *sc) +{ + curlx_safefree(sc->login); + curlx_safefree(sc->passwd); + sc->matches_host = FALSE; } -/* - * Reset parser for a new machine entry. Frees password and optionally login - * if it was not user-specified. - */ -static void netrc_new_machine(struct netrc_state *ns) +static void netrc_scan_init(struct netrc_scanner *sc, + struct Curl_easy *data, + const char *content, + const char *hostname, + const char *user) { - ns->keyword = NONE; - ns->found = 0; - ns->our_login = FALSE; - curlx_safefree(ns->password); - if(!ns->specific_login) - curlx_safefree(ns->login); + memset(sc, 0, sizeof(*sc)); + netrc_lexer_init(&sc->lexer, data, content); + sc->hostname = hostname; + sc->user = (user && user[0]) ? user : NULL; + netrc_scan_reset(sc); } -/* - * Process a parsed token through the HOSTVALID state machine branch. This - * handles login/password values and keyword transitions for the matched host. - * - * Returns NETRC_OK or an error code. - */ -static NETRCcode netrc_hostvalid(struct netrc_state *ns, const char *tok) +static void netrc_scan_cleanup(struct netrc_scanner *sc) { - if(ns->keyword == LOGIN) { - if(ns->specific_login) - ns->our_login = !Curl_timestrcmp(ns->login, tok); - else { - ns->our_login = TRUE; - curlx_free(ns->login); - ns->login = curlx_strdup(tok); - if(!ns->login) - return NETRC_OUT_OF_MEMORY; - } - ns->found |= FOUND_LOGIN; - ns->keyword = NONE; - } - else if(ns->keyword == PASSWORD) { - curlx_free(ns->password); - ns->password = curlx_strdup(tok); - if(!ns->password) - return NETRC_OUT_OF_MEMORY; - ns->found |= FOUND_PASSWORD; - ns->keyword = NONE; - } - else if(curl_strequal("login", tok)) - ns->keyword = LOGIN; - else if(curl_strequal("password", tok)) - ns->keyword = PASSWORD; - else if(curl_strequal("machine", tok)) { - /* a new machine here */ - - if(ns->found & FOUND_PASSWORD && - /* a password was provided for this host */ - - ((!ns->specific_login || ns->our_login) || - /* either there was no specific login to search for, or this - is the specific one we wanted */ - (ns->specific_login && !(ns->found & FOUND_LOGIN)))) { - /* or we look for a specific login, but that was not specified */ - - ns->done = TRUE; - return NETRC_OK; - } - - ns->state = HOSTFOUND; - netrc_new_machine(ns); - } - else if(curl_strequal("default", tok)) { - ns->state = HOSTVALID; - ns->retcode = NETRC_OK; - netrc_new_machine(ns); - } - if((ns->found == (FOUND_PASSWORD | FOUND_LOGIN)) && ns->our_login) - ns->done = TRUE; - return NETRC_OK; + netrc_scan_reset(sc); + sc->hostname = NULL; + sc->user = NULL; + Curl_creds_unlink(&sc->creds); + netrc_lexer_cleanup(&sc->lexer); } -/* - * Process one parsed token through the netrc state - * machine. Updates the parser state in *ns. - * Returns NETRC_OK or an error code. - */ -static NETRCcode netrc_handle_token(struct netrc_state *ns, - const char *tok, - const char *host) +static NETRCcode netrc_scan_literal(struct netrc_scanner *sc, + char **pdest) { - switch(ns->state) { - case NOTHING: - if(curl_strequal("macdef", tok)) - ns->state = MACDEF; - else if(curl_strequal("machine", tok)) { - ns->state = HOSTFOUND; - netrc_new_machine(ns); - } - else if(curl_strequal("default", tok)) { - ns->state = HOSTVALID; - ns->retcode = NETRC_OK; - } - break; - case MACDEF: - if(!*tok) - ns->state = NOTHING; - break; - case HOSTFOUND: - if(curl_strequal(host, tok)) { - ns->state = HOSTVALID; - ns->retcode = NETRC_OK; + NETRCcode rc = netrc_lexer_next(&sc->lexer, TRUE); + if(!rc) { + if(sc->lexer.token == NETRC_TOK_LITERAL) { + if(pdest && sc->matches_host) { + curlx_free(*pdest); + *pdest = curlx_strdup(curlx_dyn_ptr(&sc->lexer.literal)); + if(!*pdest) + rc = NETRC_OUT_OF_MEMORY; + } } else - ns->state = NOTHING; - break; - case HOSTVALID: - return netrc_hostvalid(ns, tok); + netrc_lexer_push(&sc->lexer); } - return NETRC_OK; + return rc; } -/* - * Finalize the parse result: fill in defaults and free - * resources on error. - */ -static NETRCcode netrc_finalize(struct netrc_state *ns, - char **loginp, - char **passwordp, - struct store_netrc *store) +static NETRCcode netrc_scan_end_entry(struct netrc_scanner *sc) { - NETRCcode retcode = ns->retcode; - if(!retcode) { - if(!ns->password && ns->our_login) { - /* success without a password, set a blank one */ - ns->password = curlx_strdup(""); - if(!ns->password) - retcode = NETRC_OUT_OF_MEMORY; + NETRCcode rc = NETRC_OK; +#if NETRC_DEBUG + CURL_TRC_M(sc->lexer.data, + "[NETRC] entry matches_host=%d, login='%s', passwd='%s'", + sc->matches_host, sc->login, sc->passwd); +#endif + if(sc->matches_host) { + if(sc->login) { + if(sc->user) { + if(Curl_timestrcmp(sc->user, sc->login)) + goto out; + /* We look for a specific user, + * entry is only interesting with password */ + sc->found = !!sc->passwd; + } + else { + sc->found = TRUE; + } + } + else if(sc->passwd) { + /* found a passwd that applies to any user */ + sc->found = TRUE; + } + else { + /* entry has nothing interesting */ + } + if(sc->found) { +#if NETRC_DEBUG + CURL_TRC_M(sc->lexer.data, "[NETRC] entry match found"); +#endif + if(Curl_creds_create(sc->user ? sc->user : sc->login, sc->passwd, + NULL, NULL, NULL, CREDS_NETRC, &sc->creds)) + rc = NETRC_OUT_OF_MEMORY; } - else if(!ns->login && !ns->password) - /* a default with no credentials */ - retcode = NETRC_NO_MATCH; - } - if(!retcode) { - /* success */ - if(!ns->specific_login) - *loginp = ns->login; - - /* netrc_finalize() can return a password even when specific_login is set - but our_login is false (e.g., host matched but the requested login - never matched). See test 685. */ - *passwordp = ns->password; - } - else { - curlx_dyn_free(&store->filebuf); - store->loaded = FALSE; - if(!ns->specific_login) - curlx_free(ns->login); - curlx_free(ns->password); } - return retcode; +out: + netrc_scan_reset(sc); + return rc; } -/* - * Returns zero on success. - */ -static NETRCcode parsenetrc(struct store_netrc *store, - const char *host, - char **loginp, - char **passwordp, - const char *netrcfile) +static NETRCcode netrc_scan(struct Curl_easy *data, + const char *content, + const char *hostname, + const char *user, + struct Curl_creds **pcreds) { - const char *netrcbuffer; - struct dynbuf token; - struct dynbuf *filebuf = &store->filebuf; - struct netrc_state ns; - - memset(&ns, 0, sizeof(ns)); - ns.retcode = NETRC_NO_MATCH; - ns.login = *loginp; - ns.specific_login = !!ns.login; - - DEBUGASSERT(!*passwordp); - curlx_dyn_init(&token, MAX_NETRC_TOKEN); - - if(!store->loaded) { - NETRCcode ret = file2memory(netrcfile, filebuf); - if(ret) - return ret; - store->loaded = TRUE; - } - - netrcbuffer = curlx_dyn_ptr(filebuf); - - while(!ns.done) { - const char *tok = netrcbuffer; - while(tok && !ns.done) { - const char *tok_end; - bool lineend; - NETRCcode ret; - - ret = netrc_get_token(&tok, &tok_end, &token, &ns.state, &lineend); - if(ret) { - ns.retcode = ret; - goto out; - } - if(lineend) + struct netrc_scanner sc; + NETRCcode rc = NETRC_OK; + + Curl_creds_unlink(pcreds); + netrc_scan_init(&sc, data, content, hostname, user); + + while(!rc && !sc.found) { + rc = netrc_lexer_next(&sc.lexer, FALSE); + if(!rc) { + /* Does this token end any previous entry? */ + switch(sc.lexer.token) { + case NETRC_TOK_EOF: + case NETRC_TOK_MACHINE: + case NETRC_TOK_DEFAULT: + case NETRC_TOK_MACDEF: + rc = netrc_scan_end_entry(&sc); + if(rc || sc.found) + goto out; break; + default: + break; + } - ret = netrc_handle_token(&ns, tok, host); - if(ret) { - ns.retcode = ret; + switch(sc.lexer.token) { + case NETRC_TOK_EOF: goto out; - } - /* tok_end cannot point to a null byte here since lines are always - newline terminated */ - DEBUGASSERT(*tok_end); - tok = ++tok_end; - } - if(!ns.done) { - const char *nl = NULL; - if(tok) - nl = strchr(tok, '\n'); - if(!nl) + case NETRC_TOK_MACHINE: + rc = netrc_lexer_next(&sc.lexer, TRUE); + if(!rc) { + if(sc.lexer.token == NETRC_TOK_LITERAL) { + sc.matches_host = curl_strequal( + sc.hostname, curlx_dyn_ptr(&sc.lexer.literal)); + } + else { + sc.matches_host = FALSE; + netrc_lexer_push(&sc.lexer); + } + } + break; + case NETRC_TOK_DEFAULT: + sc.matches_host = TRUE; + break; + case NETRC_TOK_ACCOUNT: + rc = netrc_scan_literal(&sc, NULL); /* ignore, not used */ + break; + case NETRC_TOK_LOGIN: + rc = netrc_scan_literal(&sc, &sc.login); break; - /* point to next line */ - netrcbuffer = &nl[1]; + case NETRC_TOK_PASSWD: + rc = netrc_scan_literal(&sc, &sc.passwd); + break; + case NETRC_TOK_MACDEF: + netrc_skip_to_empty_line(&sc.lexer); + break; + case NETRC_TOK_LITERAL: + case NETRC_TOK_JUNK: + default: + /* skip this */ + break; + } } - } /* while !done */ + } out: - curlx_dyn_free(&token); - return netrc_finalize(&ns, loginp, passwordp, store); + if(!rc) { + if(sc.creds) + Curl_creds_link(pcreds, sc.creds); + else + rc = NETRC_NO_MATCH; + } + netrc_scan_cleanup(&sc); + return rc; } -const char *Curl_netrc_strerror(NETRCcode ret) +static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) { - 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"; + NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */ + FILE *file = curlx_fopen(filename, FOPEN_READTEXT); + + curlx_dyn_reset(filebuf); + if(file) { + curlx_struct_stat stat; + if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) { + CURLcode result = CURLE_OK; + bool eof; + struct dynbuf linebuf; + curlx_dyn_init(&linebuf, MAX_NETRC_LINE); + ret = NETRC_OK; + do { + const char *line; + /* Curl_get_line always returns lines ending with a newline */ + result = Curl_get_line(&linebuf, file, &eof); + if(!result) { + line = curlx_dyn_ptr(&linebuf); + /* skip comments on load */ + curlx_str_passblanks(&line); + if(*line == '#') + continue; + result = curlx_dyn_add(filebuf, line); + } + if(result) { + curlx_dyn_free(filebuf); + ret = curl2netrc(result); + break; + } + } while(!eof); + curlx_dyn_free(&linebuf); + } + curlx_fclose(file); } - /* never reached */ + return ret; +} + +static NETRCcode netrc_scan_file(struct Curl_easy *data, + struct store_netrc *store, + const char *hostname, + const char *user, + const char *netrcfile, + struct Curl_creds **pcreds) +{ + struct dynbuf *filebuf = &store->filebuf; + + if(!store->loaded || strcmp(netrcfile, store->filename)) { + NETRCcode ret; + store->loaded = FALSE; + ret = file2memory(netrcfile, filebuf); + if(ret) { + CURL_TRC_M(data, "[NETRC] could not load '%s'", netrcfile); + return ret; + } + curlx_free(store->filename); + store->filename = curlx_strdup(netrcfile); + if(!store->filename) { + curlx_dyn_reset(&store->filebuf); + return NETRC_OUT_OF_MEMORY; + } + store->loaded = TRUE; + } + + return netrc_scan(data, curlx_dyn_ptr(filebuf), hostname, user, pcreds); } /* @@ -492,16 +572,22 @@ const char *Curl_netrc_strerror(NETRCcode ret) * *loginp and *passwordp MUST be allocated if they are not NULL when passed * in. */ -NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, - char **loginp, char **passwordp, - const char *netrcfile) +NETRCcode Curl_netrc_scan(struct Curl_easy *data, + struct store_netrc *store, + const char *hostname, + const char *user, + const char *netrcfile, + struct Curl_creds **pcreds) { NETRCcode retcode = NETRC_OK; - char *filealloc = NULL; + CURL_TRC_M(data, "[NETRC] scanning '%s' for host '%s' user '%s'", + netrcfile, hostname, user); + Curl_creds_unlink(pcreds); if(!netrcfile) { char *home = NULL; char *homea = NULL; + char *filealloc = NULL; #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) char pwbuf[1024]; #endif @@ -543,10 +629,12 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR); if(!filealloc) { curlx_free(homea); - return NETRC_OUT_OF_MEMORY; + retcode = NETRC_OUT_OF_MEMORY; + goto out; } } - retcode = parsenetrc(store, host, loginp, passwordp, filealloc); + retcode = netrc_scan_file( + data, store, hostname, user, filealloc, pcreds); curlx_free(filealloc); #ifdef _WIN32 if(retcode == NETRC_FILE_MISSING) { @@ -556,14 +644,20 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, curlx_free(homea); return NETRC_OUT_OF_MEMORY; } - retcode = parsenetrc(store, host, loginp, passwordp, filealloc); + retcode = netrc_scan_file( + data, store, hostname, user, filealloc, pcreds); curlx_free(filealloc); } #endif curlx_free(homea); } else - retcode = parsenetrc(store, host, loginp, passwordp, netrcfile); + retcode = netrc_scan_file( + data, store, hostname, user, netrcfile, pcreds); + +out: + if(retcode) + Curl_creds_unlink(pcreds); return retcode; } @@ -571,10 +665,30 @@ void Curl_netrc_init(struct store_netrc *store) { curlx_dyn_init(&store->filebuf, MAX_NETRC_FILE); store->loaded = FALSE; + store->filename = NULL; } void Curl_netrc_cleanup(struct store_netrc *store) { curlx_dyn_free(&store->filebuf); + curlx_safefree(store->filename); store->loaded = FALSE; } -#endif + +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 */ +} + +#endif /* !CURL_DISABLE_NETRC */ diff --git a/lib/netrc.h b/lib/netrc.h index 90318c2bd645..6be9b8331621 100644 --- a/lib/netrc.h +++ b/lib/netrc.h @@ -29,6 +29,9 @@ #include "curlx/dynbuf.h" +struct Curl_easy; +struct Curl_creds; + struct store_netrc { struct dynbuf filebuf; char *filename; @@ -48,14 +51,14 @@ const char *Curl_netrc_strerror(NETRCcode ret); void Curl_netrc_init(struct store_netrc *store); void Curl_netrc_cleanup(struct store_netrc *store); -NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, - char **loginp, char **passwordp, - const char *netrcfile); -/* Assume: (*passwordp)[0]=0, host[0] != 0. - * If (*loginp)[0] = 0, search for login and password within a machine - * section in the netrc. - * If (*loginp)[0] != 0, search for password within machine and login. - */ +/* Scan a netrc file for credentials matching hostname + * and optional user. */ +NETRCcode Curl_netrc_scan(struct Curl_easy *data, + struct store_netrc *store, + const char *hostname, + const char *user, + const char *netrcfile, + struct Curl_creds **pcreds); #else /* disabled */ #define Curl_netrc_init(x) diff --git a/lib/noproxy.c b/lib/noproxy.c deleted file mode 100644 index 05c59a0f1aa6..000000000000 --- a/lib/noproxy.c +++ /dev/null @@ -1,266 +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 CURL_DISABLE_PROXY - -#include "curlx/inet_pton.h" -#include "noproxy.h" -#include "curlx/strparse.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#ifdef HAVE_ARPA_INET_H -#include -#endif - -/* - * cidr4_match() returns TRUE if the given IPv4 address is within the - * specified CIDR address range. - * - * @unittest 1614 - */ -UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ - const char *network, /* 1.2.3.4 address */ - unsigned int bits); -UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ - const char *network, /* 1.2.3.4 address */ - unsigned int bits) -{ - unsigned int address = 0; - unsigned int check = 0; - - if(bits > 32) - /* strange input */ - return FALSE; - - if(curlx_inet_pton(AF_INET, ipv4, &address) != 1) - return FALSE; - if(curlx_inet_pton(AF_INET, network, &check) != 1) - return FALSE; - - if(bits && (bits != 32)) { - unsigned int mask = 0xffffffff << (32 - bits); - unsigned int haddr = htonl(address); - unsigned int hcheck = htonl(check); -#if 0 - curl_mfprintf(stderr, "Host %s (%x) network %s (%x) " - "bits %u mask %x => %x\n", - ipv4, haddr, network, hcheck, bits, mask, - (haddr ^ hcheck) & mask); -#endif - if((haddr ^ hcheck) & mask) - return FALSE; - return TRUE; - } - return address == check; -} - -/* @unittest 1614 */ -UNITTEST bool cidr6_match(const char *ipv6, const char *network, - unsigned int bits); -UNITTEST bool cidr6_match(const char *ipv6, const char *network, - unsigned int bits) -{ -#ifdef USE_IPV6 - unsigned int bytes; - unsigned int rest; - unsigned char address[16]; - unsigned char check[16]; - - if(!bits) - bits = 128; - - bytes = bits / 8; - rest = bits & 0x07; - if((bytes > 16) || ((bytes == 16) && rest)) - return FALSE; - if(curlx_inet_pton(AF_INET6, ipv6, address) != 1) - return FALSE; - if(curlx_inet_pton(AF_INET6, network, check) != 1) - return FALSE; - if(bytes && memcmp(address, check, bytes)) - return FALSE; - if(rest && ((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) - return FALSE; - - return TRUE; -#else - (void)ipv6; - (void)network; - (void)bits; - return FALSE; -#endif -} - -enum nametype { - TYPE_HOST, - TYPE_IPV4, - TYPE_IPV6 -}; - -static bool match_host(const char *token, size_t tokenlen, - const char *name, size_t namelen) -{ - bool match = FALSE; - - /* ignore trailing dots in the token to check */ - if(token[tokenlen - 1] == '.') - tokenlen--; - - if(tokenlen && (*token == '.')) { - /* ignore leading token dot as well */ - token++; - tokenlen--; - } - /* A: example.com matches 'example.com' - B: www.example.com matches 'example.com' - C: nonexample.com DOES NOT match 'example.com' - */ - if(tokenlen == namelen) - /* case A, exact match */ - match = curl_strnequal(token, name, namelen); - else if(tokenlen < namelen) { - /* case B, tailmatch domain */ - match = (name[namelen - tokenlen - 1] == '.') && - curl_strnequal(token, name + (namelen - tokenlen), tokenlen); - } - /* case C passes through, not a match */ - return match; -} - -static bool match_ip(int type, const char *token, size_t tokenlen, - const char *name) -{ - char *slash; - unsigned int bits = 0; - char checkip[128]; - if(tokenlen >= sizeof(checkip)) - /* this cannot match */ - return FALSE; - /* copy the check name to a temp buffer */ - memcpy(checkip, token, tokenlen); - checkip[tokenlen] = 0; - - slash = strchr(checkip, '/'); - /* if the slash is part of this token, use it */ - if(slash) { - curl_off_t value; - const char *p = &slash[1]; - if(curlx_str_number(&p, &value, 128) || *p) - return FALSE; - /* a too large value is rejected in the cidr function below */ - bits = (unsigned int)value; - *slash = 0; /* null-terminate there */ - } - if(type == TYPE_IPV6) - return cidr6_match(name, checkip, bits); - else - return cidr4_match(name, checkip, bits); -} - -/**************************************************************** - * 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) -{ - /* - * 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') - return FALSE; - - /* no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - */ - if(no_proxy && no_proxy[0]) { - const char *p = no_proxy; - size_t namelen; - char address[16]; - enum nametype type = TYPE_HOST; - if(!strcmp("*", no_proxy)) - return TRUE; - - /* NO_PROXY was specified and it was not only an asterisk */ - - /* Check if name is an IP address; if not, assume it being a hostname. */ - namelen = strlen(name); - if(curlx_inet_pton(AF_INET, name, &address) == 1) - type = TYPE_IPV4; -#ifdef USE_IPV6 - else if(curlx_inet_pton(AF_INET6, name, &address) == 1) - type = TYPE_IPV6; -#endif - else { - /* ignore trailing dots in the hostname */ - if(name[namelen - 1] == '.') - namelen--; - } - - while(*p) { - const char *token; - size_t tokenlen = 0; - - /* pass blanks */ - curlx_str_passblanks(&p); - - token = p; - /* pass over the pattern */ - while(*p && !ISBLANK(*p) && (*p != ',')) { - p++; - tokenlen++; - } - - if(tokenlen) { - bool match = FALSE; - if(type == TYPE_HOST) - match = match_host(token, tokenlen, name, namelen); - else - match = match_ip(type, token, tokenlen, name); - - if(match) - return TRUE; - } - - /* pass blanks after pattern */ - 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 was not only an asterisk */ - - return FALSE; -} - -#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/openldap.c b/lib/openldap.c index 48bf5b746d12..887336e5ca7a 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -164,7 +164,7 @@ static CURLcode oldap_map_error(int rc, CURLcode result) static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) { CURLcode result = CURLE_OK; - int rc = LDAP_URL_ERR_BADURL; + int rc; static const char * const url_errs[] = { "success", "out of memory", @@ -180,9 +180,16 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) }; *ludp = NULL; - if(!data->state.up.user && !data->state.up.password && - !data->state.up.options) + /* `ldap_url_parse() seems to be terrible with urls + * that have user/pass/options in it. So when we have options or + * creds from the url, fail without calling the function. + * Yes, this is super-weird code and I do not like it. */ + if((data->state.creds && (data->state.creds->source == CREDS_URL)) || + data->state.up.options) + rc = LDAP_URL_ERR_BADURL; + else rc = ldap_url_parse(Curl_bufref_ptr(&data->state.url), ludp); + if(rc != LDAP_URL_SUCCESS) { const char *msg = "url parsing problem"; @@ -345,9 +352,9 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) passwd.bv_val = NULL; passwd.bv_len = 0; - if(data->state.aptr.user) { - binddn = conn->user; - passwd.bv_val = conn->passwd; + if(conn->creds) { + binddn = Curl_creds_user(conn->creds); + passwd.bv_val = CURL_UNCONST(Curl_creds_passwd(conn->creds)); passwd.bv_len = strlen(passwd.bv_val); } @@ -355,7 +362,7 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) return oldap_map_error(rc, - data->state.aptr.user ? + data->state.creds ? CURLE_LOGIN_DENIED : CURLE_LDAP_CANNOT_BIND); oldap_state(data, li, newstate); return CURLE_OK; @@ -499,7 +506,7 @@ static Sockbuf_IO ldapsb_tls = { static bool ssl_installed(struct connectdata *conn) { struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); - return li && li->recv != NULL; + return li && li->recv; } static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) @@ -605,7 +612,7 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) if(result) goto out; - li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + li->proto = ldap_pvt_url_scheme2proto(data->state.origin->scheme->name); /* Initialize the SASL storage */ Curl_sasl_init(&li->sasl, data, &saslldap); @@ -614,11 +621,12 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) if(result) goto out; - hosturl = curl_maprintf("%s://%s:%d", + hosturl = curl_maprintf("%s://%s:%u", conn->scheme->name, - (data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->host.name, - conn->remote_port); + conn->origin->ipv6 ? + conn->origin->user_hostname : + conn->origin->hostname, + conn->origin->port); if(!hosturl) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -783,8 +791,19 @@ 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) - oldap_state(data, li, OLDAP_STOP); + if(!result) { + switch(progress) { + case SASL_DONE: + oldap_state(data, li, OLDAP_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + break; + default: + break; + } + } } if(li->servercred) @@ -900,7 +919,8 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) result = oldap_perform_bind(data, OLDAP_BIND); break; } - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) break; FALLTHROUGH(); @@ -911,7 +931,7 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) else if(ssl_installed(conn)) { if(li->sasl.prefmech != SASL_AUTH_NONE) result = oldap_perform_mechs(data); - else if(data->state.aptr.user) + else if(data->state.creds) result = oldap_perform_bind(data, OLDAP_BIND); else { /* Version 3 supported: no bind required */ @@ -1080,7 +1100,7 @@ static CURLcode client_write(struct Curl_easy *data, return result; } -static CURLcode oldap_recv(struct Curl_easy *data, int sockindex, char *buf, +static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -1177,7 +1197,7 @@ static CURLcode oldap_recv(struct Curl_easy *data, int sockindex, char *buf, binary = bv.bv_len > 7 && curl_strnequal(bv.bv_val + bv.bv_len - 7, ";binary", 7); - for(i = 0; bvals[i].bv_val != NULL; i++) { + for(i = 0; bvals[i].bv_val; i++) { bool binval = FALSE; result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, @@ -1186,9 +1206,11 @@ static CURLcode oldap_recv(struct Curl_easy *data, int sockindex, char *buf, break; if(!binary) { - /* check for leading or trailing whitespace */ + /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC + 2849) or leading or trailing whitespace */ if(bvals[i].bv_len && - (ISBLANK(bvals[i].bv_val[0]) || + ((bvals[i].bv_val[0] == ':') || (bvals[i].bv_val[0] == '<') || + ISBLANK(bvals[i].bv_val[0]) || ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1]))) binval = TRUE; else { diff --git a/lib/optiontable.pl b/lib/optiontable.pl index db0659cbb039..de6fb55151f2 100755 --- a/lib/optiontable.pl +++ b/lib/optiontable.pl @@ -39,7 +39,7 @@ HEAD ; -my $lastnum=0; +my $lastnum = 0; my %opt; my %type; @@ -47,7 +47,7 @@ my %alias; sub add { - my($optstr, $typestr, $num)=@_; + my($optstr, $typestr, $num) = @_; my $name; # remove all spaces from the type $typestr =~ s/ //g; @@ -59,7 +59,7 @@ sub add { } if($optstr =~ /^CURLOPT_(.*)/) { - $name=$1; + $name = $1; } $ext =~ s/CURLOPTTYPE_//; $ext =~ s/CBPOINT/CBPTR/; @@ -89,7 +89,7 @@ sub add { $fl .= $1; # the end - my @p=split(/, */, $fl); + my @p = split(/, */, $fl); add($p[0], $p[1], $p[2]); undef $fl; } @@ -106,14 +106,14 @@ sub add { } if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { - my($opt, $type, $num)=($1,$2,$3); + my($opt, $type, $num) = ($1, $2, $3); add($opt, $type, $num); } # alias for an older option # old = new if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { - my ($o, $n)=($1, $2); + my ($o, $n) = ($1, $2); # skip obsolete ones if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { $o =~ s/^CURLOPT_//; diff --git a/lib/parsedate.c b/lib/parsedate.c index ce358bd465ab..ec9f21296b5e 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -27,59 +27,57 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" -/* - A brief summary of the date string formats this parser groks: - - RFC 2616 3.3.1 +/* A brief summary of the date string formats this parser groks: - Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 - Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 - Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + RFC 2616 3.3.1 - we support dates without week day name: + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format - 06 Nov 1994 08:49:37 GMT - 06-Nov-94 08:49:37 GMT - Nov 6 08:49:37 1994 + we support dates without week day name: - without the time zone: + 06 Nov 1994 08:49:37 GMT + 06-Nov-94 08:49:37 GMT + Nov 6 08:49:37 1994 - 06 Nov 1994 08:49:37 - 06-Nov-94 08:49:37 + without the time zone: - weird order: + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 - 1994 Nov 6 08:49:37 (GNU date fails) - GMT 08:49:37 06-Nov-94 Sunday - 94 6 Nov 08:49:37 (GNU date fails) + weird order: - time left out: + 1994 Nov 6 08:49:37 (GNU date fails) + GMT 08:49:37 06-Nov-94 Sunday + 94 6 Nov 08:49:37 (GNU date fails) - 1994 Nov 6 - 06-Nov-94 - Sun Nov 6 94 + time left out: - unusual separators: + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 - 1994.Nov.6 - Sun/Nov/6/94/GMT + unusual separators: - commonly used time zone names: + 1994.Nov.6 + Sun/Nov/6/94/GMT - Sun, 06 Nov 1994 08:49:37 CET - 06 Nov 1994 08:49:37 EST + commonly used time zone names: - time zones specified using RFC822 style: + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST - Sun, 12 Sep 2004 15:05:58 -0700 - Sat, 11 Sep 2004 21:32:11 +0200 + time zones specified using RFC822 style: - compact numerical date strings: + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 - 20040912 15:05:58 -0700 - 20040911 +0200 + compact numerical date strings: -*/ + 20040912 15:05:58 -0700 + 20040911 +0200 + */ #if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \ !defined(CURL_DISABLE_FILE) || defined(USE_GNUTLS) @@ -98,8 +96,12 @@ const char * const Curl_month[] = { #ifndef CURL_DISABLE_PARSEDATE +#if SIZEOF_TIME_T < 5 #define PARSEDATE_LATER 1 +#endif +#if SIZEOF_TIME_T < 5 || defined(HAVE_TIME_T_UNSIGNED) #define PARSEDATE_SOONER 2 +#endif static const char * const weekday[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" @@ -189,8 +191,7 @@ static const struct tzinfo tz[] = { /* returns: -1 no day 0 monday - 6 sunday -*/ - + */ static int checkday(const char *check, size_t len) { int i; diff --git a/lib/peer.c b/lib/peer.c new file mode 100644 index 000000000000..4a7a12d8cf06 --- /dev/null +++ b/lib/peer.c @@ -0,0 +1,732 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + * + ***************************************************************************/ +/* + * IDN conversions + */ +#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 HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_IPHLPAPI_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#if defined(HAVE_IF_NAMETOINDEX) && defined(USE_WINSOCK) +#if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 5) +#include /* workaround for old mingw-w64 missing to include it */ +#endif +#include +#endif + +#include "curl_addrinfo.h" +#include "curl_trc.h" +#include "protocol.h" +#include "http_proxy.h" +#include "idn.h" +#include "curlx/strdup.h" +#include "curlx/strparse.h" +#include "peer.h" +#include "urldata.h" +#include "url.h" +#include "urlapi-int.h" +#include "vtls/vtls.h" + +struct peer_parse { + const struct Curl_scheme *scheme; + struct Curl_str host_user; + struct Curl_str host; + struct Curl_str zoneid; + char *tmp_host_user; + char *tmp_host; + char *tmp_zoneid; + uint32_t scopeid; + uint16_t port; + bool ipv6; + bool unix_socket; + bool abstract_uds; +}; + +static void peer_parse_clear(struct peer_parse *pp) +{ + curlx_free(pp->tmp_host_user); + curlx_free(pp->tmp_host); + curlx_free(pp->tmp_zoneid); + memset(pp, 0, sizeof(*pp)); +} + +static CURLcode peer_create(struct peer_parse *pp, + struct Curl_peer **ppeer) +{ + struct Curl_peer *peer = NULL; + CURLcode result = CURLE_OK; + size_t zone_alen = 0, host_alen = 0; + + if(!pp || !pp->scheme) + return CURLE_FAILED_INIT; + if(!pp->host.len && !(pp->scheme->flags & PROTOPT_NONETWORK)) + return CURLE_FAILED_INIT; + + if((pp->host.str != pp->host_user.str) || + (pp->host.len != pp->host_user.len)) { + host_alen = pp->host.len + 1; + } + zone_alen = pp->zoneid.len ? (pp->zoneid.len + 1) : 0; + + /* null-terminator already part of struct */ + peer = curlx_calloc(1, sizeof(*peer) + + pp->host_user.len + host_alen + zone_alen); + if(!peer) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + peer->refcount = 1; + peer->scheme = pp->scheme; + peer->hostname = peer->user_hostname; + peer->port = pp->port; + peer->scopeid = pp->scopeid; + peer->ipv6 = pp->ipv6; + peer->unix_socket = pp->unix_socket; + peer->abstract_uds = pp->abstract_uds; + + if(pp->host_user.len) + memcpy(peer->user_hostname, pp->host_user.str, pp->host_user.len); + + if(host_alen) { + peer->hostname = peer->user_hostname + pp->host_user.len + 1; + memcpy(peer->hostname, pp->host.str, pp->host.len); + } + + if(zone_alen) { + peer->zoneid = peer->user_hostname + pp->host_user.len + 1 + host_alen; + memcpy(peer->zoneid, pp->zoneid.str, pp->zoneid.len); +#ifdef USE_IPV6 + /* Determine scope_id if not already provided */ + if(!peer->scopeid) { + const char *p = peer->zoneid; + curl_off_t scope; + if(!curlx_str_number(&p, &scope, UINT_MAX)) { + /* A plain number, use it directly as a scope id. */ + peer->scopeid = (uint32_t)scope; + } +#ifdef HAVE_IF_NAMETOINDEX + else { + /* Zone identifier is not numeric */ + unsigned int idx = 0; + idx = if_nametoindex(peer->zoneid); + if(idx) { + peer->scopeid = (uint32_t)idx; + } + else { + /* Do we want to return an error here? */ + } + } +#endif /* HAVE_IF_NAMETOINDEX */ + } +#endif /* USE_IPV6 */ + } + +out: + if(!result) + *ppeer = peer; + else + Curl_peer_unlink(&peer); + return result; +} + +static CURLcode peer_parse_host(struct Curl_easy *data, + struct peer_parse *pp, + bool scan_for_ipv6) +{ + if(!pp || !pp->host_user.str || !pp->host_user.len) + return CURLE_FAILED_INIT; + + if(pp->host_user.str[0] == '[') { + const char *s = pp->host_user.str + 1; + struct Curl_str tmp; + if(curlx_str_until(&s, &tmp, pp->host_user.len - 1, ']')) + return CURLE_URL_MALFORMAT; + + if(!Curl_looks_like_ipv6(tmp.str, tmp.len, TRUE, + &pp->host, &pp->zoneid)) { + failf(data, "Invalid IPv6 address format in '%.*s'", + (int)pp->host_user.len, pp->host_user.str); + return CURLE_URL_MALFORMAT; + } + pp->ipv6 = TRUE; + } + else { +#ifdef USE_IDN + if(!Curl_is_ASCII_str(&pp->host_user)) { + CURLcode result; + if(!pp->tmp_host_user) { + /* need a null-terminated string for IDN */ + pp->tmp_host_user = curlx_memdup0(pp->host_user.str, + pp->host_user.len); + if(!pp->tmp_host_user) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_idn_decode(pp->tmp_host_user, &pp->tmp_host); + if(result) + return result; + pp->host.str = pp->tmp_host; + pp->host.len = strlen(pp->host.str); + } + else +#endif + if(scan_for_ipv6 && + Curl_looks_like_ipv6(pp->host_user.str, pp->host_user.len, TRUE, + &pp->host, &pp->zoneid)) { + if(pp->host_user.len < MAX_IPADR_LEN) { + char tmp[MAX_IPADR_LEN]; + memcpy(tmp, pp->host_user.str, pp->host_user.len); + tmp[pp->host_user.len] = 0; + pp->ipv6 = !Curl_is_ipv4addr(tmp); + } + else + pp->ipv6 = TRUE; + } + else + pp->host = pp->host_user; + } + return CURLE_OK; +} + +CURLcode Curl_peer_create(struct Curl_easy *data, + const struct Curl_scheme *scheme, + const char *hostname, + uint16_t port, + struct Curl_peer **ppeer) +{ + struct peer_parse pp; + CURLcode result; + + Curl_peer_unlink(ppeer); + memset(&pp, 0, sizeof(pp)); + pp.scheme = scheme; + pp.host_user.str = hostname; + pp.host_user.len = strlen(hostname); + pp.port = port; + + result = peer_parse_host(data, &pp, TRUE); + if(!result) + result = peer_create(&pp, ppeer); + + peer_parse_clear(&pp); + return result; +} + +#ifdef USE_UNIX_SOCKETS +CURLcode Curl_peer_uds_create(const struct Curl_scheme *scheme, + const char *path, + bool abstract_unix_socket, + struct Curl_peer **ppeer) +{ + struct peer_parse pp; + size_t pathlen = path ? strlen(path) : 0; + CURLcode result = CURLE_OK; + + Curl_peer_unlink(ppeer); + memset(&pp, 0, sizeof(pp)); + if(!scheme) + return CURLE_FAILED_INIT; + if(!pathlen) + return CURLE_FAILED_INIT; + + pp.scheme = scheme; + pp.host_user.str = pp.host.str = path; + pp.host_user.len = pp.host.len = pathlen; + pp.unix_socket = TRUE; + pp.abstract_uds = abstract_unix_socket; + + result = peer_create(&pp, ppeer); + peer_parse_clear(&pp); + return result; +} +#endif /* USE_UNIX_SOCKETS */ + +void Curl_peer_link(struct Curl_peer **pdest, struct Curl_peer *src) +{ + if(*pdest != src) { + Curl_peer_unlink(pdest); + *pdest = src; + if(src) { + DEBUGASSERT(src->refcount < UINT32_MAX); + src->refcount++; + } + } +} + +void Curl_peer_unlink(struct Curl_peer **ppeer) +{ + if(*ppeer) { + struct Curl_peer *peer = *ppeer; + + DEBUGASSERT(peer->refcount); + *ppeer = NULL; + if(peer->refcount) + peer->refcount--; + if(!peer->refcount) { + curlx_free(peer); + } + } +} + +bool Curl_peer_equal(struct Curl_peer *p1, struct Curl_peer *p2) +{ + return (p1 == p2) || + (p1 && p2 && + (p1->scheme == p2->scheme) && + Curl_peer_same_destination(p1, p2)); +} + +static bool peer_same_hostname(struct Curl_peer *p1, struct Curl_peer *p2) +{ + /* UNIX domain socket paths must be compared case-sensitive, + * as many filesystem are like that. */ + return (p1->unix_socket == p2->unix_socket) && + (p1->abstract_uds == p2->abstract_uds) && + (p1->ipv6 == p2->ipv6) && + (p1->unix_socket ? + !strcmp(p1->hostname, p2->hostname) : + curl_strequal(p1->hostname, p2->hostname)); +} + +bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) +{ + return (p1 == p2) || + (p1 && p2 && + (p1->port == p2->port) && + peer_same_hostname(p1, p2) && + (p1->scopeid == p2->scopeid) && + (p1->scopeid || curl_strequal(p1->zoneid, p2->zoneid))); +} + +CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, + uint16_t port_override, + uint32_t scopeid_override, + struct Curl_peer **ppeer) +{ + struct peer_parse pp; + char *zoneid = NULL, *scheme = NULL, *hostname = NULL; + CURLUcode uc; + CURLcode result; + + Curl_peer_unlink(ppeer); + memset(&pp, 0, sizeof(pp)); + + uc = curl_url_get(uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + pp.scheme = Curl_get_scheme(scheme); + if(!pp.scheme) { + failf(data, "Protocol \"%s\" not supported%s", scheme, + data->state.this_is_a_follow ? " (in redirect)" : ""); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + + uc = curl_url_get(uh, CURLUPART_HOST, &hostname, 0); + if(uc) { + if((uc == CURLUE_NO_HOST) && (pp.scheme->flags & PROTOPT_NONETWORK)) + ; /* acceptable */ + else { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else if(strlen(hostname) > MAX_URL_LEN) { + failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); + result = CURLE_URL_MALFORMAT; + goto out; + } + + pp.host_user.str = hostname ? hostname : ""; + pp.host_user.len = strlen(pp.host_user.str); + if(pp.host_user.len) { + result = peer_parse_host(data, &pp, FALSE); + if(result) + goto out; + } + else + pp.host = pp.host_user; + + if(port_override) { + /* if set, we use this instead of the port possibly given in the URL */ + char portbuf[16]; + curl_msnprintf(portbuf, sizeof(portbuf), "%d", port_override); + uc = curl_url_set(uh, CURLUPART_PORT, portbuf, 0); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + else + pp.port = port_override; + } + else { + uc = Curl_url_get_port(uh, &pp.port); + if(uc) { + if(uc == CURLUE_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + else if(!(pp.scheme->flags & PROTOPT_NONETWORK)) { + result = CURLE_URL_MALFORMAT; + goto out; + } + /* no port ok when not a network scheme */ + } + } + + if(scopeid_override) + /* Override any scope id from an URL zone. */ + pp.scopeid = scopeid_override; + else { + if(curl_url_get(uh, CURLUPART_ZONEID, &zoneid, 0) == + CURLUE_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + if(zoneid) { + pp.zoneid.str = zoneid; + pp.zoneid.len = strlen(zoneid); + } + } + + result = peer_create(&pp, ppeer); + if(result) + failf(data, "Error %d creating peer for %s:%u", + (int)result, pp.host_user.str, pp.port); + +out: + peer_parse_clear(&pp); + curlx_free(scheme); + curlx_free(hostname); + curlx_free(zoneid); + return result; +} + +/* Parse a "host:port" string to connect to into a peer. + * IPv6 addresses might appear in brackets or without them. */ +CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, + const struct Curl_peer *dest, + const char *connect_to, + struct Curl_peer **ppeer) +{ + struct peer_parse pp; + const char *portstr = NULL; + CURLcode result; + + Curl_peer_unlink(ppeer); + memset(&pp, 0, sizeof(pp)); + if(!connect_to || !*connect_to) + return CURLE_FAILED_INIT; + + pp.scheme = dest->scheme; + + /* detect and extract RFC6874-style IPv6-addresses */ + if(connect_to[0] == '[') { + const char *s = strchr(connect_to + 1, ']'); + if(!s) { + failf(data, "Invalid IPv6 address format in '%s'", connect_to); + result = CURLE_SETOPT_OPTION_SYNTAX; + goto out; + } + portstr = strchr(s, ':'); + pp.host_user.str = connect_to; + pp.host_user.len = s - pp.host_user.str + 1; + pp.ipv6 = TRUE; + } + else { + portstr = strchr(connect_to, ':'); + pp.host_user.str = connect_to; + pp.host_user.len = portstr ? + (size_t)(portstr - connect_to) : strlen(connect_to); + } + + if(!pp.host_user.len) { /* no hostname found, only port switch */ + pp.host_user.str = dest->user_hostname; + pp.host_user.len = strlen(dest->user_hostname); + } + + result = peer_parse_host(data, &pp, FALSE); + if(result) + goto out; + + if(portstr && portstr[1]) { + const char *p = portstr + 1; + curl_off_t portparse; + if(curlx_str_number(&p, &portparse, 0xffff)) { + failf(data, "No valid port number in '%s'", connect_to); + result = CURLE_SETOPT_OPTION_SYNTAX; + goto out; + } + pp.port = (uint16_t)portparse; /* we know it will fit */ + } + else + pp.port = dest->port; + +#ifndef USE_IPV6 + if(pp.ipv6) { + failf(data, "Use of IPv6 in *_CONNECT_TO without IPv6 support built-in"); + result = CURLE_NOT_BUILT_IN; + goto out; + } +#endif + + result = peer_create(&pp, ppeer); + CURL_TRC_M(data, "connect-to peer_create2 -> %d", (int)result); + +out: + CURL_TRC_M(data, "parse connect_to peer: %s -> %d", connect_to, (int)result); + peer_parse_clear(&pp); + return result; +} + +#ifndef CURL_DISABLE_PROXY + +#ifdef USE_UNIX_SOCKETS +#define UNIX_SOCKET_PREFIX "localhost" +#endif + +CURLcode Curl_scheme_to_proxytype(struct Curl_easy *data, + const char *scheme, + uint8_t *proxytype, const char *url) +{ + if(!scheme) + return CURLE_OK; + + if(curl_strequal("https", scheme)) { + if(*proxytype != CURLPROXY_HTTPS2 && *proxytype != CURLPROXY_HTTPS3) + *proxytype = CURLPROXY_HTTPS; + } + else if(curl_strequal("socks5h", scheme)) + *proxytype = CURLPROXY_SOCKS5_HOSTNAME; + else if(curl_strequal("socks5", scheme)) + *proxytype = CURLPROXY_SOCKS5; + else if(curl_strequal("socks4a", scheme)) + *proxytype = CURLPROXY_SOCKS4A; + else if(curl_strequal("socks4", scheme) || curl_strequal("socks", scheme)) + *proxytype = CURLPROXY_SOCKS4; + else if(curl_strequal("http", scheme)) { + if(*proxytype != CURLPROXY_HTTP_1_0) + *proxytype = CURLPROXY_HTTP; + } + else { + /* Any other xxx:// reject! */ + failf(data, "Unsupported proxy scheme for \'%s\'", url); + return CURLE_COULDNT_CONNECT; + } + return CURLE_OK; +} + +CURLcode Curl_peer_from_proxy_url(CURLU *uh, + struct Curl_easy *data, + const char *url, + uint8_t proxytype, + struct Curl_peer **ppeer, + uint8_t *pproxytype) +{ + struct peer_parse pp; + char *scheme = NULL; + char *portptr = NULL; +#ifdef USE_UNIX_SOCKETS + bool is_socks = FALSE; +#endif + CURLUcode uc; + CURLcode result = CURLE_OK; + + Curl_peer_unlink(ppeer); + memset(&pp, 0, sizeof(pp)); + pp.port = CURL_DEFAULT_PROXY_PORT; + uc = curl_url_get(uh, CURLUPART_SCHEME, &scheme, + CURLU_NON_SUPPORT_SCHEME | CURLU_NO_GUESS_SCHEME); + if(uc) { + if(uc == CURLUE_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + /* URL came without scheme, the passed `proxytype` determines it */ + switch(proxytype) { + case CURLPROXY_HTTP: + case CURLPROXY_HTTP_1_0: + pp.scheme = &Curl_scheme_http; + break; + case CURLPROXY_HTTPS: + case CURLPROXY_HTTPS2: + case CURLPROXY_HTTPS3: + pp.scheme = &Curl_scheme_https; + break; + case CURLPROXY_SOCKS4: + pp.scheme = &Curl_scheme_socks4; + break; + case CURLPROXY_SOCKS4A: + pp.scheme = &Curl_scheme_socks4a; + break; + case CURLPROXY_SOCKS5: + pp.scheme = &Curl_scheme_socks5; + break; + case CURLPROXY_SOCKS5_HOSTNAME: + pp.scheme = &Curl_scheme_socks5h; + break; + default: + failf(data, "Unsupported proxy type %u for \'%s\'", proxytype, url); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto out; + } + } + else { + pp.scheme = Curl_get_scheme(scheme); + result = Curl_scheme_to_proxytype(data, scheme, &proxytype, url); + if(result) + goto out; + } + DEBUGASSERT(pp.scheme); + + if(CURL_PROXY_IS_HTTPS(proxytype) && + !Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY)) { + failf(data, "Unsupported proxy \'%s\', libcurl is built without the " + "HTTPS-proxy support.", url); + result = CURLE_NOT_BUILT_IN; + goto out; + } + + switch(pp.scheme->family) { + case CURLPROTO_SOCKS: +#ifdef USE_UNIX_SOCKETS + is_socks = TRUE; +#endif + break; + case CURLPROTO_HTTP: + break; + default: + failf(data, "Unsupported proxy protocol for \'%s\'", url); + result = CURLE_COULDNT_CONNECT; + goto out; + } + + uc = curl_url_get(uh, CURLUPART_PORT, &portptr, CURLU_NO_DEFAULT_PORT); + if(uc == CURLUE_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + if(portptr) { + curl_off_t num; + const char *p = portptr; + if(!curlx_str_number(&p, &num, UINT16_MAX)) + pp.port = (uint16_t)num; + /* Should we not error out when the port number is invalid? */ + curlx_free(portptr); + } + else { + /* No port in URL, take the set one or the scheme's default */ + if(data->set.proxyport) + pp.port = data->set.proxyport; + else + pp.port = pp.scheme->defport; + } + + /* now, clone the proxy hostname */ + uc = curl_url_get(uh, CURLUPART_HOST, &pp.tmp_host_user, CURLU_URLDECODE); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + pp.host_user.str = pp.tmp_host_user; + pp.host_user.len = strlen(pp.tmp_host_user); + +#ifdef USE_UNIX_SOCKETS + if(is_socks && curl_strequal(UNIX_SOCKET_PREFIX, pp.tmp_host_user)) { + uc = curl_url_get(uh, CURLUPART_PATH, &pp.tmp_host, CURLU_URLDECODE); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + /* path will be "/", if no path was found */ + if(strcmp("/", pp.tmp_host)) { + pp.host.str = pp.tmp_host; + pp.host.len = strlen(pp.tmp_host); + pp.unix_socket = TRUE; + } + else { + pp.host = pp.host_user; + } + } +#endif /* USE_UNIX_SOCKETS */ + + if(!pp.host.len) { + result = peer_parse_host(data, &pp, FALSE); + if(result) + goto out; + } + + uc = curl_url_get(uh, CURLUPART_ZONEID, &pp.tmp_zoneid, 0); + if(uc == CURLUE_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + if(pp.tmp_zoneid) { + pp.zoneid.str = pp.tmp_zoneid; + pp.zoneid.len = strlen(pp.tmp_zoneid); + } + + *pproxytype = proxytype; + result = peer_create(&pp, ppeer); + +out: + peer_parse_clear(&pp); + curlx_free(scheme); +#ifdef DEBUGBUILD + if(!result) + DEBUGASSERT(*ppeer); +#endif + return result; +} + +#endif /* !CURL_DISABLE_PROXY */ diff --git a/lib/peer.h b/lib/peer.h new file mode 100644 index 000000000000..a0a3054cd844 --- /dev/null +++ b/lib/peer.h @@ -0,0 +1,108 @@ +#ifndef HEADER_CURL_PEER_H +#define HEADER_CURL_PEER_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_scheme; + +/* if peer hostname starts with this, the peer is a unix domain socket + * path, e.g. the remainder after 'localhost'. */ +#define CURL_PEER_UDS_PREFIX "localhost/" + +struct Curl_peer { + const struct Curl_scheme *scheme; /* url scheme */ + char *hostname; /* normalized hostname (IDN decoded when supported) */ + char *zoneid; /* NULL or IPv6 zone identifier */ + uint32_t refcount; /* created with 1, freed when dropping to 0 */ + uint32_t scopeid; /* != 0, IPv6 scope to use */ + uint16_t port; + BIT(unix_socket); /* hostname is a UDS path without the prefix */ + BIT(abstract_uds); /* only TRUE when `unix_socket` also TRUE */ + BIT(ipv6); /* hostname is an IPv6 address stripped of '[]' */ + char user_hostname[1]; /* hostname supplied by user/url */ +}; + +/* Create a new peer: + * - `peer->user_hostname` is the passed `hostname` + * - `peer->hostname` is the normalized `hostname` via + * + IDN conversion if it has non-ASCII characters + * + stripping of surrounding '[]' for URL formatted IPv6 addresses + * + the path alone in case of a unix domain socket, e.g. hostname + * starts with CURL_PEER_UDS_PREFIX and is longer + * Scans for IPv6 addresses even without surrounding '[]'. + * - `zoneid` IPv6 zone identifier or NULL + * - `scopeid` IPv6 scopeid of zoneid, when known. + */ +CURLcode Curl_peer_create(struct Curl_easy *data, + const struct Curl_scheme *scheme, + const char *hostname, + uint16_t port, + struct Curl_peer **ppeer); + +#ifdef USE_UNIX_SOCKETS +CURLcode Curl_peer_uds_create(const struct Curl_scheme *scheme, + const char *path, + bool abstract_unix_socket, + struct Curl_peer **ppeer); +#endif + +/* Unlink any peer in `*pdest`, assign src, increase src + * refcount when not NULL. */ +void Curl_peer_link(struct Curl_peer **pdest, struct Curl_peer *src); + +/* Drop a reference, peer may be passed as NULL */ +void Curl_peer_unlink(struct Curl_peer **ppeer); + +/* TRUE if both peers are NULL or have completely same properties. */ +bool Curl_peer_equal(struct Curl_peer *p1, struct Curl_peer *p2); + +/* TRUE if both peers are NULL or have same properties except the scheme. */ +bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2); + +CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, + uint16_t port_override, + uint32_t scopeid_override, + struct Curl_peer **ppeer); + +CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, + const struct Curl_peer *dest, + const char *connect_to, + struct Curl_peer **ppeer); + +#ifndef CURL_DISABLE_PROXY + +CURLcode Curl_scheme_to_proxytype(struct Curl_easy *data, + const char *scheme, + uint8_t *proxytype, + const char *url); + +CURLcode Curl_peer_from_proxy_url(CURLU *uh, + struct Curl_easy *data, + const char *url, + uint8_t proxytype, + struct Curl_peer **ppeer, + uint8_t *pproxytype); +#endif /* !CURL_DISABLE_PROXY */ + +#endif /* HEADER_CURL_PEER_H */ diff --git a/lib/pingpong.c b/lib/pingpong.c index 44e424418d88..b365772fb71a 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -120,13 +120,13 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, /* initialize stuff to prepare for reading a fresh new response */ void Curl_pp_init(struct pingpong *pp, const struct curltime *pnow) { - DEBUGASSERT(!pp->initialised); + DEBUGASSERT(!pp->initialized); pp->nread_resp = 0; pp->response = *pnow; /* start response time-out */ pp->pending_resp = TRUE; curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); - pp->initialised = TRUE; + pp->initialized = TRUE; } /*********************************************************************** @@ -152,7 +152,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, DEBUGASSERT(pp->sendleft == 0); DEBUGASSERT(pp->sendsize == 0); - DEBUGASSERT(pp->sendthis == NULL); + DEBUGASSERT(!pp->sendthis); if(!conn) /* cannot send without a connection! */ @@ -222,7 +222,7 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, } static CURLcode pingpong_read(struct Curl_easy *data, - int sockindex, + int8_t sockindex, char *buffer, size_t buflen, size_t *nread) @@ -236,7 +236,7 @@ static CURLcode pingpong_read(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size) /* size of the response */ @@ -292,6 +292,13 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, the line is not really terminated until the LF comes */ size_t length = nl - line + 1; + if(memchr(line, 0, length)) { + /* The response line is passed on as a "header" below, so reject an + embedded nul the same way verify_header() does for HTTP. */ + failf(data, "Nul byte in server response line"); + return CURLE_WEIRD_SERVER_REPLY; + } + /* output debug output if that is requested */ Curl_debug(data, CURLINFO_HEADER_IN, line, length); @@ -389,7 +396,7 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, CURLcode Curl_pp_disconnect(struct pingpong *pp) { - if(pp->initialised) { + if(pp->initialized) { curlx_dyn_free(&pp->sendbuf); curlx_dyn_free(&pp->recvbuf); memset(pp, 0, sizeof(*pp)); diff --git a/lib/pingpong.h b/lib/pingpong.h index 864f2c68350e..09dca414ebea 100644 --- a/lib/pingpong.h +++ b/lib/pingpong.h @@ -64,7 +64,7 @@ struct pingpong { CURLcode (*statemachine)(struct Curl_easy *data, struct connectdata *conn); bool (*endofresp)(struct Curl_easy *data, struct connectdata *conn, const char *ptr, size_t len, int *code); - BIT(initialised); + BIT(initialized); BIT(pending_resp); /* set TRUE when a server response is pending or in progress, and is cleared once the last response is read */ @@ -132,7 +132,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size); /* size of the response */ diff --git a/lib/pop3.c b/lib/pop3.c index 317c04bbe363..b32663ce1473 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -58,7 +58,6 @@ #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -231,7 +230,7 @@ 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() */ + /* The POP3 struct is already initialized in pop3_connect() */ struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); const char *path = &data->state.up.path[1]; /* skip leading path */ @@ -251,7 +250,7 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!pop3) return CURLE_FAILED_INIT; @@ -485,7 +484,8 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, return CURLE_FAILED_INIT; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler */ @@ -495,7 +495,7 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, DEBUGASSERT(!pop3c->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); DEBUGF(infof(data, "pop3_perform_upgrade_tls, connect -> %d, %d", - result, ssldone)); + (int)result, ssldone)); if(!result && ssldone) { pop3c->ssldone = ssldone; /* perform CAPA now, changes pop3c->state out of POP3_UPGRADETLS */ @@ -527,7 +527,7 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, /* Check we have a username and password to authenticate with and end the connect phase if we do not */ - if(!data->state.aptr.user) { + if(!conn->creds) { pop3_state(data, POP3_STOP); return result; @@ -535,7 +535,7 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, /* Send the USER command */ result = Curl_pp_sendf(data, &pop3c->pp, "USER %s", - conn->user ? conn->user : ""); + Curl_creds_user(conn->creds)); if(!result) pop3_state(data, POP3_USER); @@ -564,7 +564,7 @@ static CURLcode pop3_perform_apop(struct Curl_easy *data, /* Check we have a username and password to authenticate with and end the connect phase if we do not */ - if(!data->state.aptr.user) { + if(!data->state.creds) { pop3_state(data, POP3_STOP); return result; @@ -578,17 +578,18 @@ static CURLcode pop3_perform_apop(struct Curl_easy *data, Curl_MD5_update(ctxt, (const unsigned char *)pop3c->apoptimestamp, curlx_uztoui(strlen(pop3c->apoptimestamp))); - Curl_MD5_update(ctxt, (const unsigned char *)conn->passwd, - curlx_uztoui(strlen(conn->passwd))); + Curl_MD5_update(ctxt, (const unsigned char *)Curl_creds_passwd(conn->creds), + curlx_uztoui(strlen(Curl_creds_passwd(conn->creds)))); /* Finalise the digest */ Curl_MD5_final(ctxt, digest); - /* Convert the calculated 16 octet digest into a 32 byte hex string */ + /* Convert the calculated 16 octet digest into a 32-byte hex string */ for(i = 0; i < MD5_DIGEST_LEN; i++) curl_msnprintf(&secret[2 * i], 3, "%02x", digest[i]); - result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", conn->user, secret); + result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", + Curl_creds_user(conn->creds), secret); if(!result) pop3_state(data, POP3_APOP); @@ -618,7 +619,8 @@ static CURLcode pop3_perform_auth(struct Curl_easy *data, if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTH command */ @@ -1038,7 +1040,8 @@ static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code, } else /* Send the PASS command */ - result = Curl_pp_sendf(data, &pop3c->pp, "PASS %s", conn->passwd); + result = Curl_pp_sendf(data, &pop3c->pp, "PASS %s", + Curl_creds_passwd(conn->creds)); if(!result) pop3_state(data, POP3_PASS); @@ -1403,7 +1406,7 @@ static const struct SASLproto saslpop3 = { pop3_continue_auth, /* Send authentication continuation */ pop3_cancel_auth, /* Send authentication cancellation */ pop3_get_message, /* Get SASL response message */ - 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - CRLF */ '*', /* Code received when continuation is expected */ '+', /* Code to receive upon authentication success */ SASL_AUTH_DEFAULT, /* Default mechanisms */ @@ -1437,7 +1440,7 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) pop3c->preftype = POP3_TYPE_ANY; Curl_sasl_init(&pop3c->sasl, data, &saslpop3); - /* Initialise the pingpong layer */ + /* Initialize the pingpong layer */ Curl_pp_init(pp, Curl_pgrs_now(data)); /* Parse the URL options */ @@ -1474,7 +1477,8 @@ static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, return CURLE_OK; if(status) { - connclose(data->conn, "POP3 done with bad status"); + CURL_TRC_M(data, "POP3 done with bad status"); + connclose(data->conn); result = status; /* use the already set error code */ } diff --git a/lib/progress.c b/lib/progress.c index 919c151e75e4..fcb8bf24229b 100644 --- a/lib/progress.c +++ b/lib/progress.c @@ -33,8 +33,7 @@ #ifndef CURL_DISABLE_PROGRESS_METER /* Provide a string that is 7 letters long (plus the zero byte). - @unittest 1636 -*/ + @unittest 1636 */ UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds); UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds) { @@ -83,8 +82,7 @@ UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds) but never longer than 6 columns (+ one zero byte). Add suffix k, M, G when suitable... - @unittest 1636 -*/ + @unittest 1636 */ UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen); UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen) { @@ -93,7 +91,7 @@ UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen) if(bytes < 100000) curl_msnprintf(max6, mlen, "%6" CURL_FORMAT_CURL_OFF_T, bytes); else { - const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; + static const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; int k = 0; curl_off_t nbytes; curl_off_t rest; @@ -165,21 +163,18 @@ UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data, /* since low speed limit is enabled, set the expire timer to make this connection's speed get checked again in a second */ - Curl_expire(data, 1000, EXPIRE_SPEEDCHECK); + Curl_expire_set(data, EXPIRE_SPEEDCHECK, 1000, pnow); return CURLE_OK; } const struct curltime *Curl_pgrs_now(struct Curl_easy *data) { - struct curltime *pnow = data->multi ? - &data->multi->now : &data->progress.now; - curlx_pnow(pnow); - return pnow; + curlx_pnow(&data->progress.now); + return &data->progress.now; } -/* - New proposed interface, 9th of February 2000: +/* New proposed interface, 9th of February 2000: pgrsStartNow() - sets start time pgrsSetDownloadSize(x) - known expected download size @@ -188,12 +183,12 @@ const struct curltime *Curl_pgrs_now(struct Curl_easy *data) pgrsSetUploadCounter() - amount of data currently uploaded pgrsUpdate() - show progress pgrsDone() - transfer complete -*/ + */ int Curl_pgrsDone(struct Curl_easy *data) { int rc; - data->progress.lastshow = 0; + data->progress.delta.lastshow_us = -1; rc = Curl_pgrsUpdate(data); /* the final (forced) update */ if(rc) return rc; @@ -240,6 +235,29 @@ void Curl_pgrsSendPause(struct Curl_easy *data, bool enable) } } +#ifdef CURLVERBOSE +static const char * const pgrs_timer_names[] = { + "PGRS-NONE", + "PGRS-STARTOP", + "PGRS-STARTSINGLE", + "PGRS-POSTQUEUE", + "PGRS-NAMELOOKUP", + "PGRS-CONNECT", + "PGRS-APPCONNECT", + "PGRS-PRETRANSFER", + "PGRS-STARTTRANSFER", + "PGRS-POSTRANSFER", + "PGRS-REDIRECT", +}; + +static const char *pgrs_timer_name(timerid timer) +{ + if((size_t)timer < CURL_ARRAYSIZE(pgrs_timer_names)) + return pgrs_timer_names[(size_t)timer]; + return "?"; +} +#endif /* CURLVERBOSE */ + /* * Curl_pgrsTimeWas(). Store the timestamp time at the given label. */ @@ -255,66 +273,70 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, break; 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; + data->progress.delta.startop_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + data->progress.delta.startqueue_us = data->progress.delta.startop_us; break; case TIMER_STARTSINGLE: /* This is set at the start of each single transfer */ - data->progress.t_startsingle = timestamp; - data->progress.is_t_startransfer_set = FALSE; + data->progress.delta.startsingle_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + data->progress.startransfer_added = FALSE; break; case TIMER_POSTQUEUE: /* Queue time is accumulative from all involved redirects */ - data->progress.t_postqueue += - curlx_ptimediff_us(×tamp, &data->progress.t_startqueue); - break; - case TIMER_STARTACCEPT: - data->progress.t_acceptdata = timestamp; + data->progress.total.queued_us += + curlx_ptimediff_us(×tamp, &data->progress.start) - + data->progress.delta.startqueue_us; break; case TIMER_NAMELOOKUP: - delta = &data->progress.t_nslookup; + delta = &data->progress.total.nslookup_us; break; case TIMER_CONNECT: - delta = &data->progress.t_connect; + delta = &data->progress.total.connect_us; break; case TIMER_APPCONNECT: - delta = &data->progress.t_appconnect; + delta = &data->progress.total.appconnect_us; break; case TIMER_PRETRANSFER: - delta = &data->progress.t_pretransfer; + delta = &data->progress.total.pretransfer_us; break; case TIMER_STARTTRANSFER: - delta = &data->progress.t_starttransfer; /* prevent updating t_starttransfer unless: * 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. */ - if(data->progress.is_t_startransfer_set) { + if(data->progress.startransfer_added) { + CURL_TRC_M(data, "[%s] ignored", pgrs_timer_name(timer)); return; } - else { - data->progress.is_t_startransfer_set = TRUE; - break; - } + data->progress.startransfer_added = TRUE; + delta = &data->progress.total.starttransfer_us; + break; case TIMER_POSTRANSFER: - delta = &data->progress.t_posttransfer; + delta = &data->progress.total.posttransfer_us; break; case TIMER_REDIRECT: - data->progress.t_redirect = curlx_ptimediff_us(×tamp, - &data->progress.start); - data->progress.t_startqueue = timestamp; + data->progress.delta.startredirect_us = + curlx_ptimediff_us(×tamp, &data->progress.start); + /* transfer starts queueing again */ + data->progress.delta.startqueue_us = + data->progress.delta.startredirect_us; break; } if(delta) { - timediff_t us = curlx_ptimediff_us(×tamp, - &data->progress.t_startsingle); + timediff_t us = curlx_ptimediff_us(×tamp, &data->progress.start) - + data->progress.delta.startsingle_us; if(us < 1) us = 1; /* make sure at least one microsecond passed */ *delta += us; + CURL_TRC_M(data, "[%s] added %" FMT_TIMEDIFF_T "us", + pgrs_timer_name(timer), us); } + else + CURL_TRC_M(data, "[%s] set", pgrs_timer_name(timer)); } /* @@ -328,13 +350,17 @@ void Curl_pgrsTime(struct Curl_easy *data, timerid timer) Curl_pgrsTimeWas(data, timer, *Curl_pgrs_now(data)); } -void Curl_pgrsStartNow(struct Curl_easy *data) +void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow) { struct Progress *p = &data->progress; + if(!pnow) + pnow = Curl_pgrs_now(data); p->speeder_c = 0; /* reset the progress meter display */ - p->start = *Curl_pgrs_now(data); - p->is_t_startransfer_set = FALSE; + p->start = *pnow; + memset(&p->delta, 0, sizeof(p->delta)); + memset(&p->total, 0, sizeof(p->total)); + p->startransfer_added = FALSE; p->dl.cur_size = 0; p->ul.cur_size = 0; /* the sizes are unknown at start */ @@ -365,7 +391,7 @@ void Curl_pgrs_download_inc(struct Curl_easy *data, size_t delta) { if(delta) { data->progress.dl.cur_size += delta; - Curl_rlimit_drain(&data->progress.dl.rlimit, delta, Curl_pgrs_now(data)); + Curl_rlimit_drain(&data->progress.dl.rlimit, delta, NULL); } } @@ -373,7 +399,7 @@ void Curl_pgrs_upload_inc(struct Curl_easy *data, size_t delta) { if(delta) { data->progress.ul.cur_size += delta; - Curl_rlimit_drain(&data->progress.ul.rlimit, delta, Curl_pgrs_now(data)); + Curl_rlimit_drain(&data->progress.ul.rlimit, delta, NULL); } } @@ -434,21 +460,22 @@ static bool progress_calc(struct Curl_easy *data, { struct Progress * const p = &data->progress; int i_next, i_oldest, i_latest; - timediff_t duration_us; + timediff_t duration_us, elapsed_us; curl_off_t amount; /* The time spent so far (from the start) in microseconds */ - p->timespent = curlx_ptimediff_us(pnow, &p->start); - p->dl.speed = trspeed(p->dl.cur_size, p->timespent); - p->ul.speed = trspeed(p->ul.cur_size, p->timespent); + elapsed_us = curlx_ptimediff_us(pnow, &p->start); + p->total.spent_us = elapsed_us; + p->dl.speed = trspeed(p->dl.cur_size, p->total.spent_us); + p->ul.speed = trspeed(p->ul.cur_size, p->total.spent_us); if(!p->speeder_c) { /* no previous record exists */ p->speed_amount[0] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[0] = *pnow; + p->speed_time[0] = elapsed_us; p->speeder_c++; /* use the overall average at the start */ p->current_speed = p->ul.speed + p->dl.speed; - p->lastshow = pnow->tv_sec; + p->delta.lastshow_us = elapsed_us; return TRUE; } /* We have at least one record now. Where to put the next and @@ -458,11 +485,11 @@ static bool progress_calc(struct Curl_easy *data, /* Make a new record only when some time has passed. * Too frequent calls otherwise ruin the history. */ - if(curlx_ptimediff_ms(pnow, &p->speed_time[i_latest]) >= 1000) { + if((elapsed_us - p->speed_time[i_latest]) >= (1000 * 1000)) { p->speeder_c++; i_latest = i_next; p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[i_latest] = *pnow; + p->speed_time[i_latest] = elapsed_us; } else if(data->req.done) { /* When a transfer is done, and we did not have a current speed @@ -471,7 +498,7 @@ static bool progress_calc(struct Curl_easy *data, * reported speed since it no longer measures a full second. */ if(!p->current_speed) { p->speed_amount[i_latest] = p->dl.cur_size + p->ul.cur_size; - p->speed_time[i_latest] = *pnow; + p->speed_time[i_latest] = elapsed_us; } } else { @@ -485,8 +512,7 @@ static bool progress_calc(struct Curl_easy *data, /* How much we transferred between oldest and current records */ amount = p->speed_amount[i_latest] - p->speed_amount[i_oldest]; /* How long this took */ - duration_us = curlx_ptimediff_us(&p->speed_time[i_latest], - &p->speed_time[i_oldest]); + duration_us = p->speed_time[i_latest] - p->speed_time[i_oldest]; if(duration_us <= 0) duration_us = 1; @@ -500,9 +526,10 @@ static bool progress_calc(struct Curl_easy *data, p->current_speed = amount * 1000000 / duration_us; } - if((p->lastshow == pnow->tv_sec) && !data->req.done) + if((p->delta.lastshow_us >= 0) && !data->req.done && + ((elapsed_us - p->delta.lastshow_us) < (1000 * 1000))) return FALSE; - p->lastshow = pnow->tv_sec; + p->delta.lastshow_us = elapsed_us; return TRUE; } @@ -547,7 +574,7 @@ static void progress_meter(struct Curl_easy *data) char time_left[8]; char time_total[8]; char time_spent[8]; - curl_off_t cur_secs = (curl_off_t)p->timespent / 1000000; /* seconds */ + curl_off_t cur_secs = (curl_off_t)p->total.spent_us / 1000000; if(!p->headers_out) { if(data->state.resume_from) { @@ -634,13 +661,14 @@ static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress) int rc; if(data->set.fxferinfo) { /* There is a callback set, call that */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fxferinfo); rc = data->set.fxferinfo(data->set.progress_client, data->progress.dl.total_size, data->progress.dl.cur_size, data->progress.ul.total_size, data->progress.ul.cur_size); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURL_PROGRESSFUNC_CONTINUE) { if(rc) { failf(data, "Callback aborted"); @@ -651,13 +679,14 @@ static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress) } else if(data->set.fprogress) { /* The older deprecated callback is set, call that */ - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_fprogress); rc = data->set.fprogress(data->set.progress_client, (double)data->progress.dl.total_size, (double)data->progress.dl.cur_size, (double)data->progress.ul.total_size, (double)data->progress.ul.cur_size); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURL_PROGRESSFUNC_CONTINUE) { if(rc) { failf(data, "Callback aborted"); @@ -686,16 +715,28 @@ CURLcode Curl_pgrsUpdate(struct Curl_easy *data) return pgrs_update(data, Curl_pgrs_now(data)); } -CURLcode Curl_pgrsCheck(struct Curl_easy *data) +CURLcode Curl_pgrsUpdateX(struct Curl_easy *data, + const struct curltime *pnow) +{ + return pgrs_update(data, pnow); +} + +CURLcode Curl_pgrsCheckX(struct Curl_easy *data, + const struct curltime *pnow) { CURLcode result; - result = pgrs_update(data, Curl_pgrs_now(data)); + result = pgrs_update(data, pnow); if(!result && !data->req.done) - result = pgrs_speedcheck(data, Curl_pgrs_now(data)); + result = pgrs_speedcheck(data, pnow); return result; } +CURLcode Curl_pgrsCheck(struct Curl_easy *data) +{ + return Curl_pgrsCheckX(data, Curl_pgrs_now(data)); +} + /* * Update all progress, do not do progress meter/callbacks. */ @@ -703,3 +744,28 @@ void Curl_pgrsUpdate_nometer(struct Curl_easy *data) { (void)progress_calc(data, Curl_pgrs_now(data)); } + +void Curl_pgrsCompleted(struct Curl_easy *data) +{ + struct Progress * const p = &data->progress; + p->total.spent_us = curlx_ptimediff_us(Curl_pgrs_now(data), &p->start); +} + +timediff_t Curl_pgrs_since_ms(struct Curl_easy *data, + const struct curltime *pnow, + timerid timer) +{ + if(!pnow) + pnow = Curl_pgrs_now(data); + switch(timer) { + case TIMER_STARTOP: + return (curlx_ptimediff_us(pnow, &data->progress.start) - + data->progress.delta.startop_us) / 1000; + case TIMER_STARTSINGLE: + return (curlx_ptimediff_us(pnow, &data->progress.start) - + data->progress.delta.startsingle_us) / 1000; + default: + DEBUGASSERT(0); + return 0; + } +} diff --git a/lib/progress.h b/lib/progress.h index 7d419ecb8ab0..19bb09517cbd 100644 --- a/lib/progress.h +++ b/lib/progress.h @@ -38,7 +38,6 @@ typedef enum { TIMER_PRETRANSFER, TIMER_STARTTRANSFER, TIMER_POSTRANSFER, - TIMER_STARTACCEPT, TIMER_REDIRECT, TIMER_LAST /* must be last */ } timerid; @@ -47,7 +46,7 @@ typedef enum { const struct curltime *Curl_pgrs_now(struct Curl_easy *data); int Curl_pgrsDone(struct Curl_easy *data); -void Curl_pgrsStartNow(struct Curl_easy *data); +void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); CURLcode Curl_pgrs_deliver_check(struct Curl_easy *data, size_t delta); @@ -58,10 +57,12 @@ void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); /* perform progress update, invoking callbacks at intervals */ CURLcode Curl_pgrsUpdate(struct Curl_easy *data); +CURLcode Curl_pgrsUpdateX(struct Curl_easy *data, const struct curltime *pnow); /* perform progress update, no callbacks invoked */ void Curl_pgrsUpdate_nometer(struct Curl_easy *data); /* perform progress update with callbacks and speed checks */ CURLcode Curl_pgrsCheck(struct Curl_easy *data); +CURLcode Curl_pgrsCheckX(struct Curl_easy *data, const struct curltime *pnow); /* Inform progress/speedcheck about receive/send pausing */ void Curl_pgrsRecvPause(struct Curl_easy *data, bool enable); @@ -83,4 +84,10 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent); +void Curl_pgrsCompleted(struct Curl_easy *data); + +timediff_t Curl_pgrs_since_ms(struct Curl_easy *data, + const struct curltime *pnow, + timerid timer); + #endif /* HEADER_CURL_PROGRESS_H */ diff --git a/lib/protocol.c b/lib/protocol.c index c8d43251cf7e..3bfcd88ebe46 100644 --- a/lib/protocol.c +++ b/lib/protocol.c @@ -153,7 +153,8 @@ const struct Curl_scheme Curl_scheme_https = { CURLPROTO_HTTPS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */ - PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE, + PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE | + PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTPS, /* defport */ }; @@ -361,6 +362,51 @@ const struct Curl_scheme Curl_scheme_smtps = { PORT_SMTPS, /* defport */ }; +const struct Curl_scheme Curl_scheme_socks = { + "socks", /* scheme */ + ZERO_NULL, + CURLPROTO_SOCKS, /* protocol */ + CURLPROTO_SOCKS, /* family */ + PROTOPT_NO_TRANSFER, /* flags */ + PORT_SOCKS, /* defport */ +}; + +const struct Curl_scheme Curl_scheme_socks4 = { + "socks4", /* scheme */ + ZERO_NULL, + CURLPROTO_SOCKS, /* protocol */ + CURLPROTO_SOCKS, /* family */ + PROTOPT_NO_TRANSFER, /* flags */ + PORT_SOCKS, /* defport */ +}; + +const struct Curl_scheme Curl_scheme_socks4a = { + "socks4a", /* scheme */ + ZERO_NULL, + CURLPROTO_SOCKS, /* protocol */ + CURLPROTO_SOCKS, /* family */ + PROTOPT_NO_TRANSFER, /* flags */ + PORT_SOCKS, /* defport */ +}; + +const struct Curl_scheme Curl_scheme_socks5 = { + "socks5", /* scheme */ + ZERO_NULL, + CURLPROTO_SOCKS, /* protocol */ + CURLPROTO_SOCKS, /* family */ + PROTOPT_NO_TRANSFER, /* flags */ + PORT_SOCKS, /* defport */ +}; + +const struct Curl_scheme Curl_scheme_socks5h = { + "socks5h", /* scheme */ + ZERO_NULL, + CURLPROTO_SOCKS, /* protocol */ + CURLPROTO_SOCKS, /* family */ + PROTOPT_NO_TRANSFER, /* flags */ + PORT_SOCKS, /* defport */ +}; + const struct Curl_scheme Curl_scheme_telnet = { "telnet", /* scheme */ #ifdef CURL_DISABLE_TELNET @@ -397,7 +443,7 @@ const struct Curl_scheme Curl_scheme_ws = { CURLPROTO_WS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL, + PROTOPT_USERPWDCTRL | PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTP /* defport */ }; @@ -412,71 +458,202 @@ const struct Curl_scheme Curl_scheme_wss = { CURLPROTO_WSS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL, + PROTOPT_USERPWDCTRL | PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTPS /* defport */ }; +static const struct Curl_scheme *two_letter_scheme(const char *scheme) +{ + if((Curl_raw_tolower(scheme[0]) == 'w') && + (Curl_raw_tolower(scheme[1]) == 's')) + return &Curl_scheme_ws; + return NULL; +} + +static const struct Curl_scheme *three_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + if(s0 == 'f') { + if(s1 == 't' && s2 == 'p') + return &Curl_scheme_ftp; + } + else if(s0 == 'w') { + if(s1 == 's' && s2 == 's') + return &Curl_scheme_wss; + } + else if(s0 == 's') { + if(s1 == 'c' && s2 == 'p') + return &Curl_scheme_scp; + if(s1 == 'm' && s2 == 'b') + return &Curl_scheme_smb; + } + return NULL; +} + +static const struct Curl_scheme *four_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + char s3 = Curl_raw_tolower(scheme[3]); + if(s3 == 'p') { + if(s0 == 'h') { + if(s1 == 't' && s2 == 't') + return &Curl_scheme_http; + } + else if(s0 == 'i') { + if(s1 == 'm' && s2 == 'a') + return &Curl_scheme_imap; + } + else if(s0 == 'l') { + if(s1 == 'd' && s2 == 'a') + return &Curl_scheme_ldap; + } + else if(s0 == 'r') { + if(s1 == 't' && s2 == 's') + return &Curl_scheme_rtsp; + } + else if(s0 == 't') { + if(s1 == 'f' && s2 == 't') + return &Curl_scheme_tftp; + } + else if(s0 == 's') { + if(s1 == 'f' && s2 == 't') + return &Curl_scheme_sftp; + if(s1 == 'm' && s2 == 't') + return &Curl_scheme_smtp; + } + } + else if(s0 == 'f') { + if(s1 == 't' && s2 == 'p' && s3 == 's') + return &Curl_scheme_ftps; + if(s1 == 'i' && s2 == 'l' && s3 == 'e') + return &Curl_scheme_file; + } + else if(s0 == 'm') { + if(s1 == 'q' && s2 == 't' && s3 == 't') + return &Curl_scheme_mqtt; + } + else if(s0 == 'p') { + if(s1 == 'o' && s2 == 'p' && s3 == '3') + return &Curl_scheme_pop3; + } + else if(s0 == 'd') { + if(s1 == 'i' && s2 == 'c' && s3 == 't') + return &Curl_scheme_dict; + } + else if(s0 == 's') { + if(s1 == 'm' && s2 == 'b' && s3 == 's') + return &Curl_scheme_smbs; + } + return NULL; +} + +static const struct Curl_scheme *five_letter_scheme(const char *scheme) +{ + char s4 = Curl_raw_tolower(scheme[4]); + if(s4 == 's') { + char s0 = Curl_raw_tolower(scheme[0]); + char s1 = Curl_raw_tolower(scheme[1]); + char s2 = Curl_raw_tolower(scheme[2]); + char s3 = Curl_raw_tolower(scheme[3]); + if(s3 == 'p') { + switch(s0) { + case 'h': + if(s1 == 't' && s2 == 't') + return &Curl_scheme_https; + break; + case 'l': + if(s1 == 'd' && s2 == 'a') + return &Curl_scheme_ldaps; + break; + case 'i': + if(s1 == 'm' && s2 == 'a') + return &Curl_scheme_imaps; + break; + case 's': + if(s1 == 'm' && s2 == 't') + return &Curl_scheme_smtps; + break; + default: + break; + } + } + else if(s0 == 'p') { + if(s1 == 'o' && s2 == 'p' && s3 == '3') + return &Curl_scheme_pop3s; + } + else if(s0 == 'm') { + if(s1 == 'q' && s2 == 't' && s3 == 't') + return &Curl_scheme_mqtts; + } + else if(s0 == 's') { + if(s1 == 'o' && s2 == 'c' && s3 == 'k') + return &Curl_scheme_socks; + } + } + return NULL; +} + +static const struct Curl_scheme *six_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + switch(s0) { + case 's': + if(curl_strnequal("ocks4", &scheme[1], 5)) + return &Curl_scheme_socks4; + if(curl_strnequal("ocks5", &scheme[1], 5)) + return &Curl_scheme_socks5; + break; + case 'g': + if(curl_strnequal("opher", &scheme[1], 5)) + return &Curl_scheme_gopher; + break; + case 't': + if(curl_strnequal("elnet", &scheme[1], 5)) + return &Curl_scheme_telnet; + break; + } + return NULL; +} + +static const struct Curl_scheme *seven_letter_scheme(const char *scheme) +{ + char s0 = Curl_raw_tolower(scheme[0]); + if(s0 == 's') { + if(curl_strnequal("ocks4a", &scheme[1], 6)) + return &Curl_scheme_socks4a; + if(curl_strnequal("ocks5h", &scheme[1], 6)) + return &Curl_scheme_socks5h; + } + else if(s0 == 'g') { + if(curl_strnequal("ophers", &scheme[1], 6)) + return &Curl_scheme_gophers; + } + return NULL; +} + /* Returns a struct scheme pointer if the name is a known scheme. Check the ->run struct field for non-NULL to figure out if an implementation is present. */ const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len) { - /* table generated by schemetable.c: - 1. gcc schemetable.c && ./a.out - 2. check how small the table gets - 3. tweak the hash algorithm, then rerun from 1 - 4. when the table is good enough - 5. copy the table into this source code - 6. make sure this function uses the same hash function that worked for - schemetable.c - */ - static const struct Curl_scheme * const all_schemes[47] = { - &Curl_scheme_mqtt, - &Curl_scheme_smtp, - &Curl_scheme_tftp, - &Curl_scheme_imap, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - &Curl_scheme_ldaps, - &Curl_scheme_dict, NULL, - &Curl_scheme_file, NULL, - &Curl_scheme_pop3s, - &Curl_scheme_ftp, - &Curl_scheme_scp, - &Curl_scheme_mqtts, - &Curl_scheme_imaps, - &Curl_scheme_ldap, - &Curl_scheme_http, - &Curl_scheme_smb, NULL, NULL, - &Curl_scheme_telnet, - &Curl_scheme_https, - &Curl_scheme_gopher, - &Curl_scheme_rtsp, NULL, NULL, - &Curl_scheme_wss, NULL, - &Curl_scheme_gophers, - &Curl_scheme_smtps, - &Curl_scheme_pop3, - &Curl_scheme_ws, NULL, NULL, - &Curl_scheme_sftp, - &Curl_scheme_ftps, NULL, - &Curl_scheme_smbs, NULL, + typedef const struct Curl_scheme *(*letterfunc)(const char *ptr); + static const letterfunc parse[] = { + two_letter_scheme, + three_letter_scheme, + four_letter_scheme, + five_letter_scheme, + six_letter_scheme, + seven_letter_scheme }; - if(len && (len <= 7)) { - const char *s = scheme; - size_t l = len; - const struct Curl_scheme *h; - unsigned int c = 792; - while(l) { - c <<= 4; - c += (unsigned int)Curl_raw_tolower(*s); - s++; - l--; - } + if(len < 2 || len > 7) + return NULL; - h = all_schemes[c % 47]; - if(h && curl_strnequal(scheme, h->name, len) && !h->name[len]) - return h; - } - return NULL; + return parse[len - 2](scheme); } const struct Curl_scheme *Curl_get_scheme(const char *scheme) diff --git a/lib/protocol.h b/lib/protocol.h index f8254096e235..031bb834e417 100644 --- a/lib/protocol.h +++ b/lib/protocol.h @@ -51,6 +51,7 @@ struct easy_pollset; #define PORT_SMTPS 465 /* sometimes called SSMTP */ #define PORT_RTSP 554 #define PORT_GOPHER 70 +#define PORT_SOCKS 1080 #define PORT_MQTT 1883 #define PORT_MQTTS 8883 @@ -62,6 +63,7 @@ struct easy_pollset; #define CURLPROTO_WS (1L << 30) #define CURLPROTO_WSS ((curl_prot_t)1 << 31) #define CURLPROTO_MQTTS (1LL << 32) +#define CURLPROTO_SOCKS (1LL << 33) #define CURLPROTO_64ALL ((uint64_t)0xffffffffffffffff) @@ -103,6 +105,12 @@ typedef enum { FOLLOW_REDIR /* a full true redirect */ } followtype; +typedef enum { + DOMORE_GOBACK = -1, + DOMORE_INCOMPLETE = 0, + DOMORE_DONE = 1 +} domore; + /* * Specific protocol handler, an implementation of one or more URI schemes. */ @@ -118,9 +126,13 @@ struct Curl_protocol { /* If the curl_do() function is better made in two halves, this * curl_do_more() function will be called afterwards, if set. For example - * for doing the FTP stuff after the PASV/PORT command. + * for doing the FTP stuff after the PASV/PORT command. The second + * argument is an output parameter that MUST be set to one of the + * DOMORE_* values: DOMORE_INCOMPLETE if more do_more work remains, + * DOMORE_DONE when the second phase is complete, or DOMORE_GOBACK + * to return to the regular DO/DOING handling. */ - CURLcode (*do_more)(struct Curl_easy *, int *); + CURLcode (*do_more)(struct Curl_easy *, domore *); /* 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. @@ -197,8 +209,7 @@ struct Curl_protocol { #define PROTOPT_CLOSEACTION (1 << 2) /* need action before socket close */ /* some protocols will have to call the underlying functions without regard to what exact state the socket signals. IE even if the socket says "readable", - the send function might need to be called while uploading, or vice versa. -*/ + the send function might need to be called while uploading, or vice versa. */ #define PROTOPT_DIRLOCK (1 << 3) #define PROTOPT_NONETWORK (1 << 4) /* protocol does not use the network! */ #define PROTOPT_NEEDSPWD (1 << 5) /* needs a password, and if none is set it @@ -224,6 +235,9 @@ struct Curl_protocol { SSL connection in the same family without having PROTOPT_SSL. */ #define PROTOPT_CONN_REUSE (1 << 16) /* this protocol can reuse connections */ +#define PROTOPT_NO_TRANSFER (1 << 17) /* this protocol is not for transfers */ +#define PROTOPT_HTTP_PROXY_TUNNEL (1 << 18) /* Using this protocol with a + * HTTP proxy requires tunneling */ /* Everything about a URI scheme. */ struct Curl_scheme { @@ -268,6 +282,11 @@ extern const struct Curl_scheme Curl_scheme_smb; extern const struct Curl_scheme Curl_scheme_smbs; extern const struct Curl_scheme Curl_scheme_smtp; extern const struct Curl_scheme Curl_scheme_smtps; +extern const struct Curl_scheme Curl_scheme_socks; +extern const struct Curl_scheme Curl_scheme_socks4; +extern const struct Curl_scheme Curl_scheme_socks4a; +extern const struct Curl_scheme Curl_scheme_socks5; +extern const struct Curl_scheme Curl_scheme_socks5h; extern const struct Curl_scheme Curl_scheme_telnet; extern const struct Curl_scheme Curl_scheme_tftp; extern const struct Curl_scheme Curl_scheme_ws; diff --git a/lib/proxy.c b/lib/proxy.c new file mode 100644 index 000000000000..5d7c02579ddd --- /dev/null +++ b/lib/proxy.c @@ -0,0 +1,679 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 CURL_DISABLE_PROXY + +#include "urldata.h" +#include "curl_trc.h" +#include "protocol.h" +#include "proxy.h" +#include "http_proxy.h" +#include "strcase.h" +#include "url.h" +#include "vauth/vauth.h" +#include "curlx/inet_pton.h" +#include "curlx/strparse.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +/* + * cidr4_match() returns TRUE if the given IPv4 address is within the + * specified CIDR address range. + * + * @unittest 1614 + */ +UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits); +UNITTEST bool cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits) +{ + unsigned int address = 0; + unsigned int check = 0; + + if(bits > 32) + /* strange input */ + return FALSE; + + if(curlx_inet_pton(AF_INET, ipv4, &address) != 1) + return FALSE; + if(curlx_inet_pton(AF_INET, network, &check) != 1) + return FALSE; + + if(bits && (bits != 32)) { + unsigned int mask = 0xffffffff << (32 - bits); + unsigned int haddr = htonl(address); + unsigned int hcheck = htonl(check); +#if 0 + curl_mfprintf(stderr, "Host %s (%x) network %s (%x) " + "bits %u mask %x => %x\n", + ipv4, haddr, network, hcheck, bits, mask, + (haddr ^ hcheck) & mask); +#endif + if((haddr ^ hcheck) & mask) + return FALSE; + return TRUE; + } + return address == check; +} + +/* @unittest 1614 */ +UNITTEST bool cidr6_match(const char *ipv6, const char *network, + unsigned int bits); +UNITTEST bool cidr6_match(const char *ipv6, const char *network, + unsigned int bits) +{ +#ifdef USE_IPV6 + unsigned int bytes; + unsigned int rest; + unsigned char address[16]; + unsigned char check[16]; + + if(!bits) + bits = 128; + + bytes = bits / 8; + rest = bits & 0x07; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; + if(curlx_inet_pton(AF_INET6, ipv6, address) != 1) + return FALSE; + if(curlx_inet_pton(AF_INET6, network, check) != 1) + return FALSE; + if(bytes && memcmp(address, check, bytes)) + return FALSE; + if(rest && ((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) + return FALSE; + + return TRUE; +#else + (void)ipv6; + (void)network; + (void)bits; + return FALSE; +#endif +} + +enum nametype { + TYPE_HOST, + TYPE_IPV4, + TYPE_IPV6 +}; + +static bool match_host(const char *token, size_t tokenlen, + const char *name, size_t namelen) +{ + bool match = FALSE; + + /* ignore trailing dots in the token to check */ + if(token[tokenlen - 1] == '.') + tokenlen--; + + if(tokenlen && (*token == '.')) { + /* ignore leading token dot as well */ + token++; + tokenlen--; + } + /* A: example.com matches 'example.com' + B: www.example.com matches 'example.com' + C: nonexample.com DOES NOT match 'example.com' + */ + if(tokenlen == namelen) + /* case A, exact match */ + match = curl_strnequal(token, name, namelen); + else if(tokenlen < namelen) { + /* case B, tailmatch domain */ + match = (name[namelen - tokenlen - 1] == '.') && + curl_strnequal(token, name + (namelen - tokenlen), tokenlen); + } + /* case C passes through, not a match */ + return match; +} + +static bool match_ip(int type, const char *token, size_t tokenlen, + const char *name) +{ + char *slash; + unsigned int bits = 0; + char checkip[128]; + if(tokenlen >= sizeof(checkip)) + /* this cannot match */ + return FALSE; + /* copy the check name to a temp buffer */ + memcpy(checkip, token, tokenlen); + checkip[tokenlen] = 0; + + slash = strchr(checkip, '/'); + /* if the slash is part of this token, use it */ + if(slash) { + curl_off_t value; + const char *p = &slash[1]; + if(curlx_str_number(&p, &value, 128) || *p) + return FALSE; + /* a too large value is rejected in the cidr function below */ + bits = (unsigned int)value; + *slash = 0; /* null-terminate there */ + } + if(type == TYPE_IPV6) + return cidr6_match(name, checkip, bits); + else + return cidr4_match(name, checkip, bits); +} + +/**************************************************************** + * Checks if the host is in the noproxy list. returns TRUE if it matches and + * therefore the proxy should NOT be used. + ****************************************************************/ +/* @unittest 1614 */ +UNITTEST bool proxy_check_noproxy(const char *name, const char *no_proxy); +UNITTEST bool proxy_check_noproxy(const char *name, const char *no_proxy) +{ + /* + * 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') + return FALSE; + + /* no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + */ + if(no_proxy && no_proxy[0]) { + const char *p = no_proxy; + size_t namelen; + char address[16]; + enum nametype type = TYPE_HOST; + if(!strcmp("*", no_proxy)) + return TRUE; + + /* NO_PROXY was specified and it was not only an asterisk */ + + /* Check if name is an IP address; if not, assume it being a hostname. */ + namelen = strlen(name); + if(curlx_inet_pton(AF_INET, name, &address) == 1) + type = TYPE_IPV4; +#ifdef USE_IPV6 + else if(curlx_inet_pton(AF_INET6, name, &address) == 1) + type = TYPE_IPV6; +#endif + else { + /* ignore trailing dots in the hostname */ + if(name[namelen - 1] == '.') + namelen--; + } + + while(*p) { + const char *token; + size_t tokenlen = 0; + + /* pass blanks */ + curlx_str_passblanks(&p); + + token = p; + /* pass over the pattern */ + while(*p && !ISBLANK(*p) && (*p != ',')) { + p++; + tokenlen++; + } + + if(tokenlen) { + bool match = FALSE; + if(type == TYPE_HOST) + match = match_host(token, tokenlen, name, namelen); + else + match = match_ip(type, token, tokenlen, name); + + if(match) + return TRUE; + } + + /* pass blanks after pattern */ + 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 was not only an asterisk */ + + return FALSE; +} + +#ifndef CURL_DISABLE_HTTP + +/**************************************************************** + * Detect what (if any) proxy to use. Remember that this selects a host + * name and is not limited to HTTP proxies only. + * The returned pointer must be freed by the caller. + ****************************************************************/ +static char *proxy_detect_proxy(struct Curl_easy *data, + const struct Curl_scheme *scheme) +{ + /* If proxy was not specified, we check for default proxy environment + * variables, to enable i.e Lynx compliance: + * + * http_proxy=http://some.server.dom:port/ + * https_proxy=http://some.server.dom:port/ + * ftp_proxy=http://some.server.dom:port/ + * no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + * all_proxy=http://some.server.dom:port/ + * (seems to exist for the CERN www lib. Probably + * the first to check for.) + * + * For compatibility, the all-uppercase versions of these variables are + * checked if the lowercase versions do not exist. + */ + const char *env_name = NULL; + char *proxy = NULL; + char name_buf[20]; + + /* Try scheme specific env var first, unless http(s). + * lowercase first, then uppercase. */ + if((scheme != &Curl_scheme_https) && (scheme != &Curl_scheme_http)) { + curl_msnprintf(name_buf, sizeof(name_buf), "%s_proxy", scheme->name); + env_name = name_buf; + proxy = curl_getenv(env_name); + if(!proxy) { + Curl_strntoupper(name_buf, name_buf, sizeof(name_buf)); + proxy = curl_getenv(env_name); + } + } + + if(!proxy && + ((scheme == &Curl_scheme_https) || (scheme == &Curl_scheme_wss))) { + /* Not found, check 'https' env vars, also for 'wss'. + * Again, first lowercase then uppercase. */ + env_name = "https_proxy"; + proxy = curl_getenv(env_name); + if(!proxy) { + env_name = "HTTPS_PROXY"; + proxy = curl_getenv(env_name); + } + } + else if(!proxy && + ((scheme == &Curl_scheme_http) || (scheme == &Curl_scheme_ws))) { + /* Not found, check 'http' env vars, also for 'ws'. + * We do NOT try the uppercase version 'HTTP_PROXY' because of + * security reasons: + * + * When curl is used in a webserver application + * environment (cgi or php), this environment variable can + * be controlled by the web server user by setting the + * http header 'Proxy:' to some value. + * + * This can cause 'internal' http/ftp requests to be + * arbitrarily redirected by any external attacker. + */ + env_name = "http_proxy"; + proxy = curl_getenv(env_name); + } + + if(!proxy) { + /* still not found, last resort checks. */ + env_name = "all_proxy"; + proxy = curl_getenv(env_name); + if(!proxy) { + env_name = "ALL_PROXY"; + proxy = curl_getenv(env_name); + } + } + + if(proxy) + infof(data, "Uses proxy env variable %s == '%s'", env_name, proxy); + + return proxy; +} +#endif /* CURL_DISABLE_HTTP */ + +/* + * If this is supposed to use a proxy, we need to figure out the proxy + * 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, + const char *proxy, + bool for_pre_proxy, + struct proxy_info *proxyinfo) +{ + char *proxyuser = NULL; + char *proxypasswd = NULL; + char *scheme = NULL; + CURLcode result = CURLE_OK; + /* Set the start proxy type for URL scheme guessing */ + uint8_t proxytype = for_pre_proxy ? CURLPROXY_SOCKS4 : data->set.proxytype; + CURLU *uhp = curl_url(); + CURLUcode uc; + + if(!uhp) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* When parsing the proxy, allowing non-supported schemes since we have + these made up ones for proxies. Guess scheme for URLs without it. */ + uc = curl_url_set(uhp, CURLUPART_URL, proxy, + CURLU_NON_SUPPORT_SCHEME | CURLU_GUESS_SCHEME); + if(!uc) { + /* parsed okay as a URL - only update proxytype when scheme was explicit */ + uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + if(!uc) { + result = Curl_scheme_to_proxytype(data, scheme, &proxytype, proxy); + if(result) + goto error; + } + else if(uc != CURLUE_NO_SCHEME) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* else: no explicit scheme, keep the configured proxytype */ + } + else { + failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy, + curl_url_strerror(uc)); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + + result = Curl_peer_from_proxy_url(uhp, data, proxy, proxytype, + &proxyinfo->peer, &proxytype); + if(result) + goto error; + + switch(proxytype) { + case CURLPROXY_HTTP: + case CURLPROXY_HTTP_1_0: + case CURLPROXY_HTTPS: + case CURLPROXY_HTTPS2: + case CURLPROXY_HTTPS3: + if(for_pre_proxy) { + failf(data, "Unsupported pre-proxy type for \'%s\'", proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + break; + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + break; + default: + failf(data, "Unsupported proxy type %u for \'%s\'", proxytype, proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + + /* Is there a username and password given in this proxy URL? */ + uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_USER)) { + result = Curl_uc_to_curlcode(uc); + goto error; + } + uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_PASSWORD)) { + result = Curl_uc_to_curlcode(uc); + goto error; + } + + if(proxyuser || proxypasswd) { + result = Curl_creds_create(proxyuser, proxypasswd, NULL, NULL, + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME), + CREDS_URL, &proxyinfo->creds); + if(result) + goto error; + } + else if(!for_pre_proxy && + (CURL_EASY_STR(data, STRING_PROXYUSERNAME) || + CURL_EASY_STR(data, STRING_PROXYPASSWORD) || + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME))) { + /* No user/passwd in URL, if this is not a pre-proxy, the + * CURLOPT_PROXY* settings apply. */ + result = Curl_creds_create(CURL_EASY_STR(data, STRING_PROXYUSERNAME), + CURL_EASY_STR(data, STRING_PROXYPASSWORD), + NULL, NULL, + CURL_EASY_STR(data, STRING_PROXY_SERVICE_NAME), + CREDS_OPTION, &proxyinfo->creds); + } + else + Curl_creds_unlink(&proxyinfo->creds); + + proxyinfo->proxytype = proxytype; + +error: + curlx_free(scheme); + curlx_free(proxyuser); + curlx_free(proxypasswd); + curl_url_cleanup(uhp); +#ifdef DEBUGBUILD + if(!result) { + DEBUGASSERT(proxyinfo); + DEBUGASSERT(proxyinfo->peer); + } +#endif + return result; +} + +/* Is transfer's origin exempted from proxy use? */ +static bool proxy_do_not_proxy(struct Curl_easy *data) +{ + const char *no_proxy; + char *env_no_proxy = NULL; + bool do_not_proxy; + + /* no proxying if the transfer does not use the network */ + if(data->state.origin->scheme->flags & PROTOPT_NONETWORK) + return TRUE; + + no_proxy = CURL_EASY_STR(data, STRING_NOPROXY); + if(!no_proxy) { + const char *p = "no_proxy"; + env_no_proxy = curl_getenv(p); + if(!env_no_proxy) { + p = "NO_PROXY"; + env_no_proxy = curl_getenv(p); + } + if(env_no_proxy) + infof(data, "Uses proxy env variable %s == '%s'", p, env_no_proxy); + no_proxy = env_no_proxy; + } + + do_not_proxy = proxy_check_noproxy(data->state.origin->hostname, no_proxy); + curlx_safefree(env_no_proxy); + return do_not_proxy; +} + +CURLcode Curl_proxy_init_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + char *proxy = NULL; + char *pre_proxy = NULL; + const char *str = NULL; + bool do_env_detect = TRUE; + CURLcode result = CURLE_OK; + + /* Enforce no proxy use unless we decide to use one */ + conn->bits.origin_is_proxy = FALSE; + DEBUGASSERT(!conn->socks_proxy.peer); + DEBUGASSERT(!conn->http_proxy.peer); + + if(proxy_do_not_proxy(data)) + goto out; + + /************************************************************* + * Detect what (if any) proxy to use + *************************************************************/ + /* the empty config strings disable proxy use and env detects */ + str = CURL_EASY_STR(data, STRING_PROXY); + if(str) { + if(*str) { + proxy = curlx_strdup(str); + /* if global proxy is set, this is it */ + if(!proxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else + do_env_detect = FALSE; + } + + str = CURL_EASY_STR(data, STRING_PRE_PROXY); + if(str) { + if(*str) { + pre_proxy = curlx_strdup(str); + /* if global socks proxy is set, this is it */ + if(!pre_proxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + else + do_env_detect = FALSE; + } + +#ifndef CURL_DISABLE_HTTP + /* None configured, detect possible proxy from environment. */ + if(!proxy && !pre_proxy && do_env_detect) + proxy = proxy_detect_proxy(data, conn->scheme); +#else + (void)do_env_detect; +#endif /* CURL_DISABLE_HTTP */ + + if(!proxy && !pre_proxy) + goto out; + + if(pre_proxy) { + result = parse_proxy(data, pre_proxy, TRUE, &conn->socks_proxy); + if(result) + goto out; + } + + if(proxy) { + result = parse_proxy(data, proxy, FALSE, &conn->http_proxy); + if(result) + goto out; + + switch(conn->http_proxy.proxytype) { + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + /* Whoops, it is not an HTTP proxy */ + if(pre_proxy) { + /* and we already have a SOCKS pre-proxy. Cannot have both */ + failf(data, "Having a SOCKS pre-proxy and proxy is not " + "supported with \'%s\'", proxy); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto out; + } + /* switch */ + conn->socks_proxy = conn->http_proxy; + memset(&conn->http_proxy, 0, sizeof(conn->http_proxy)); + break; + default: + /* all other types are HTTP */ + break; + } + } + + if(conn->socks_proxy.peer) { + DEBUGASSERT(!CURL_PROXY_IS_ANY_HTTP(conn->socks_proxy.proxytype)); + } + +#ifdef CURL_DISABLE_HTTP + if(conn->http_proxy.peer) { + /* asking for an HTTP proxy is a bit funny when HTTP is disabled... */ + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + +#else /* CURL_DISABLE_HTTP */ + if(conn->http_proxy.peer) { + const struct Curl_scheme *scheme = data->state.origin->scheme; + bool tunnel_proxy = (bool)data->set.tunnel_thru_httpproxy; + DEBUGASSERT(CURL_PROXY_IS_ANY_HTTP(conn->http_proxy.proxytype)); + + if(!tunnel_proxy) { + /* Decide if we tunnel through proxy automatically */ + if(conn->via_peer) { + /* With connect-to, we always tunnel */ + tunnel_proxy = TRUE; + } + else if(scheme->flags & PROTOPT_SSL) { + /* If the transfer is supposed to be secure, we tunnel */ + tunnel_proxy = TRUE; + } + else if(scheme->flags & PROTOPT_HTTP_PROXY_TUNNEL) { + /* transfer scheme required tunneling */ + tunnel_proxy = TRUE; + } + else if(!(scheme->protocol & PROTO_FAMILY_HTTP) && + !(scheme->flags & PROTOPT_PROXY_AS_HTTP)) { + /* Cannot delegate transfer URL to HTTP proxy */ + tunnel_proxy = TRUE; + } + } + + if(!tunnel_proxy) { + /* HTTP proxy used in forwarding mode. This means the connection + * is really to the proxy and NOT the origin of the transfer. */ + DEBUGASSERT(!conn->via_peer); + Curl_peer_link(&conn->origin, conn->http_proxy.peer); + conn->scheme = conn->http_proxy.peer->scheme; + conn->bits.origin_is_proxy = TRUE; + } + +#ifndef CURL_DISABLE_DIGEST_AUTH + if(!Curl_safecmp(data->state.envproxy, proxy)) { + /* proxy changed */ + Curl_auth_digest_cleanup(&data->state.proxydigest); + curlx_free(data->state.envproxy); + data->state.envproxy = curlx_strdup(proxy); + } +#endif + } +#endif /* !CURL_DISABLE_HTTP */ + +out: + curlx_free(pre_proxy); + curlx_free(proxy); + return result; +} + +#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/proxy.h b/lib/proxy.h new file mode 100644 index 000000000000..307ee003e6fa --- /dev/null +++ b/lib/proxy.h @@ -0,0 +1,59 @@ +#ifndef HEADER_CURL_PROXY_H +#define HEADER_CURL_PROXY_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" + +#ifndef CURL_DISABLE_PROXY + +struct Curl_easy; +struct Curl_peer; +struct Curl_creds; +struct connectdata; + +struct proxy_info { + struct Curl_peer *peer; /* proxy to this peer */ + struct Curl_creds *creds; /* use these credentials, maybe NULL */ + uint8_t proxytype; /* what kind of proxy that is in use */ +}; + +#define CURL_PROXY_IS_HTTPS(t) \ + (((t) == CURLPROXY_HTTPS) || \ + ((t) == CURLPROXY_HTTPS2) || \ + ((t) == CURLPROXY_HTTPS3)) + +#define CURL_PROXY_IS_HTTP(t) \ + (((t) == CURLPROXY_HTTP) || \ + ((t) == CURLPROXY_HTTP_1_0)) + +#define CURL_PROXY_IS_ANY_HTTP(t) \ + (CURL_PROXY_IS_HTTP(t) || \ + CURL_PROXY_IS_HTTPS(t)) + +CURLcode Curl_proxy_init_conn(struct Curl_easy *data, + struct connectdata *conn); + +#endif /* !CURL_DISABLE_PROXY */ + +#endif /* HEADER_CURL_PROXY_H */ diff --git a/lib/psl.c b/lib/psl.c index e2488aea2215..1e3efbb0fadc 100644 --- a/lib/psl.c +++ b/lib/psl.c @@ -29,6 +29,10 @@ #include "progress.h" #include "curl_share.h" +#if !defined(PSL_VERSION_NUMBER) || PSL_VERSION_NUMBER < 0x001000 +#error "libpsl 0.16.0 or greater required" +#endif + void Curl_psl_destroy(struct PslCache *pslcache) { if(pslcache->psl) { @@ -51,7 +55,8 @@ const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) Curl_share_lock(easy, CURL_LOCK_DATA_PSL, CURL_LOCK_ACCESS_SHARED); now_sec = Curl_pgrs_now(easy)->tv_sec; if(!pslcache->psl || pslcache->expires <= now_sec) { - /* Let a chance to other threads to do the job: avoids deadlock. */ + /* Release the shared lock so another thread can refresh the cache and + avoid deadlock. */ Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); /* Update cache: this needs an exclusive lock. */ @@ -65,17 +70,14 @@ const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) bool dynamic = FALSE; time_t expires = TIME_T_MAX; -#if defined(PSL_VERSION_NUMBER) && PSL_VERSION_NUMBER >= 0x001000 psl = psl_latest(NULL); - dynamic = psl != NULL; + dynamic = !!psl; /* Take care of possible time computation overflow. */ expires = (now_sec < TIME_T_MAX - PSL_TTL) ? (now_sec + PSL_TTL) : TIME_T_MAX; /* Only get the built-in PSL if we do not already have the "latest". */ if(!psl && !pslcache->dynamic) -#endif - psl = psl_builtin(); if(psl) { diff --git a/lib/rand.c b/lib/rand.c index dd82750ba6b4..d0527aa3c032 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -33,23 +33,7 @@ #include "rand.h" #include "escape.h" -#ifdef _WIN32 -#include -#ifndef STATUS_SUCCESS -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#endif - -CURLcode Curl_win32_random(unsigned char *entropy, size_t length) -{ - memset(entropy, 0, length); - - if(BCryptGenRandom(NULL, entropy, (ULONG)length, - BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) - return CURLE_FAILED_INIT; - - return CURLE_OK; -} -#endif +#include "curlx/winapi.h" #ifndef USE_SSL /* ---- possibly non-cryptographic version following ---- */ @@ -64,7 +48,7 @@ static CURLcode weak_random(struct Curl_easy *data, #ifdef _WIN32 (void)data; { - CURLcode result = Curl_win32_random(entropy, length); + CURLcode result = curlx_win32_random(entropy, length); if(result != CURLE_NOT_BUILT_IN) return result; } @@ -186,7 +170,7 @@ CURLcode Curl_rand_bytes(struct Curl_easy *data, /* * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random - * hexadecimal digits PLUS a null-terminating byte. It must be an odd number + * hexadecimal digits PLUS a null-terminator byte. It must be an odd number * size. */ @@ -214,7 +198,7 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num) /* * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random - * alphanumerical chars PLUS a null-terminating byte. + * alphanumerical chars PLUS a null-terminator byte. */ static const char alnum[] = @@ -224,7 +208,7 @@ 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; + const unsigned int alnumspace = CURL_CSTRLEN(alnum); unsigned int r; DEBUGASSERT(num > 1); diff --git a/lib/rand.h b/lib/rand.h index afccd0aac13b..452c1b67ef90 100644 --- a/lib/rand.h +++ b/lib/rand.h @@ -37,22 +37,16 @@ CURLcode Curl_rand_bytes(struct Curl_easy *data, /* * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random - * hexadecimal digits PLUS a null-terminating byte. It must be an odd number + * hexadecimal digits PLUS a null-terminator byte. It must be an odd number * size. */ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num); /* * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random - * alphanumerical chars PLUS a null-terminating byte. + * alphanumerical chars PLUS a null-terminator 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); -#endif - #endif /* HEADER_CURL_RAND_H */ diff --git a/lib/ratelimit.c b/lib/ratelimit.c index dc013757e912..a5efc7c11a38 100644 --- a/lib/ratelimit.c +++ b/lib/ratelimit.c @@ -141,12 +141,14 @@ static void rlimit_tune_steps(struct Curl_rlimit *r, r->step_us = CURL_US_PER_SEC + ((timediff_t)mstep_inc * 1000); r->rate_per_step += rate_inc; r->tokens = r->rate_per_step; + if(r->burst_per_step) { + curl_off_t burst_inc = ((r->burst_per_step * mstep_inc) / 1000); + if(burst_inc) + r->burst_per_step += burst_inc; + } } } } - - if(r->burst_per_step) - r->burst_per_step = r->rate_per_step; } void Curl_rlimit_init(struct Curl_rlimit *r, @@ -198,9 +200,15 @@ bool Curl_rlimit_is_blocked(struct Curl_rlimit *r) int64_t Curl_rlimit_avail(struct Curl_rlimit *r, const struct curltime *pts) { + struct curltime ts; + if(r->blocked) return 0; else if(r->rate_per_step) { + if(!pts) { + curlx_pnow(&ts); + pts = &ts; + } rlimit_update(r, pts); return r->tokens; } @@ -208,13 +216,18 @@ int64_t Curl_rlimit_avail(struct Curl_rlimit *r, return INT64_MAX; } -void Curl_rlimit_drain(struct Curl_rlimit *r, - size_t tokens, +void Curl_rlimit_drain(struct Curl_rlimit *r, size_t tokens, const struct curltime *pts) { + struct curltime ts; + if(r->blocked || !r->rate_per_step) return; + if(!pts) { + curlx_pnow(&ts); + pts = &ts; + } rlimit_update(r, pts); #if 8 <= SIZEOF_SIZE_T if(tokens > INT64_MAX) { diff --git a/lib/ratelimit.h b/lib/ratelimit.h index 3c3e38b89587..3548f55608ff 100644 --- a/lib/ratelimit.h +++ b/lib/ratelimit.h @@ -90,14 +90,15 @@ bool Curl_rlimit_active(struct Curl_rlimit *r); bool Curl_rlimit_is_blocked(struct Curl_rlimit *r); int64_t Curl_rlimit_per_step(struct Curl_rlimit *r); -/* Return how many tokens are available to spend, may be negative */ +/* Return how many tokens are available to spend, may be negative. + * Pass timestamp or NULL. */ int64_t Curl_rlimit_avail(struct Curl_rlimit *r, const struct curltime *pts); /* Drain tokens from the ratelimit, give an estimate of how many tokens - * remain to be drained in the future (-1 for unknown). */ -void Curl_rlimit_drain(struct Curl_rlimit *r, - size_t tokens, + * remain to be drained in the future (-1 for unknown). + * Pass timestamp or NULL. */ +void Curl_rlimit_drain(struct Curl_rlimit *r, size_t tokens, const struct curltime *pts); /* Block/unblock ratelimiting. A blocked ratelimit has 0 tokens available. */ diff --git a/lib/request.c b/lib/request.c index c414383dc068..51672bfe00cb 100644 --- a/lib/request.c +++ b/lib/request.c @@ -26,7 +26,6 @@ #include "urldata.h" #include "cfilters.h" #include "curlx/dynbuf.h" -#include "doh.h" #include "progress.h" #include "request.h" #include "sendf.h" @@ -65,9 +64,9 @@ CURLcode Curl_req_soft_reset(struct SingleRequest *req, req->httpversion = 0; req->sendbuf_hds_len = 0; - curlx_safefree(req->userpwd); + curlx_safefree(req->hd_auth); #ifndef CURL_DISABLE_PROXY - curlx_safefree(req->proxyuserpwd); + curlx_safefree(req->hd_proxy_auth); #endif result = Curl_client_start(data); @@ -115,9 +114,9 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) struct curltime t0 = { 0, 0 }; curlx_safefree(req->newurl); - curlx_safefree(req->userpwd); + curlx_safefree(req->hd_auth); #ifndef CURL_DISABLE_PROXY - curlx_safefree(req->proxyuserpwd); + curlx_safefree(req->hd_proxy_auth); #endif #ifndef CURL_DISABLE_COOKIES curlx_safefree(req->cookiehost); @@ -175,9 +174,9 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) { curlx_safefree(req->newurl); - curlx_safefree(req->userpwd); + curlx_safefree(req->hd_auth); #ifndef CURL_DISABLE_PROXY - curlx_safefree(req->proxyuserpwd); + curlx_safefree(req->hd_proxy_auth); #endif if(req->sendbuf_init) Curl_bufq_free(&req->sendbuf); @@ -271,7 +270,8 @@ static CURLcode req_set_upload_done(struct Curl_easy *data) data->req.upload_done = TRUE; CURL_REQ_CLEAR_SEND(data); - Curl_pgrsTime(data, TIMER_POSTRANSFER); + if(data->mstate >= MSTATE_DID) + Curl_pgrsTime(data, TIMER_POSTRANSFER); Curl_creader_done(data, data->req.upload_aborted); if(data->req.upload_aborted) { @@ -333,7 +333,7 @@ static CURLcode req_flush(struct Curl_easy *data) 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); + "Proceeding as if everything is ok.", (int)result); result = CURLE_OK; done = TRUE; } @@ -392,7 +392,7 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req, blen = curlx_dyn_len(req); /* if the sendbuf is empty and the request without body and * the length to send fits info a sendbuf chunk, we send it directly. - * If `blen` is larger then `chunk_size`, we can not. Because we + * If `blen` is larger than `chunk_size`, we can not. Because we * might have to retry a blocked send later from sendbuf and that * would result in retry sends with a shrunken length. That is trouble. */ if(Curl_bufq_is_empty(&data->req.sendbuf) && diff --git a/lib/request.h b/lib/request.h index 6948d79be763..a7c0a93dd3b1 100644 --- a/lib/request.h +++ b/lib/request.h @@ -95,16 +95,22 @@ struct SingleRequest { first one */ int httpcode; /* error code from the 'HTTP/1.? XXX' or 'RTSP/1.? XXX' line */ - 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 */ + uint8_t httpversion_sent; /* Version in request (09, 10, 11, etc.) */ + uint8_t httpversion; /* Version in response (09, 10, 11, etc.) */ + uint8_t upgr101; /* 101 upgrade state */ + uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */ /* Client Writer stack, handles transfer- and content-encodings, protocol * checks, pausing by client callbacks. */ - struct Curl_cwriter *writer_stack; + struct { + struct Curl_cwriter *stack; + BIT(paused); + } writer; /* Client Reader stack, handles transfer- and content-encodings, protocol * checks, pausing by client callbacks. */ - struct Curl_creader *reader_stack; + struct { + struct Curl_creader *stack; + } reader; 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; @@ -112,11 +118,10 @@ struct SingleRequest { header data */ char *newurl; /* Set to the new URL to use when a redirect or a retry is wanted */ - uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */ - char *userpwd; /* auth header */ + char *hd_auth; /* Authorization header, full HTTP/1.x line */ #ifndef CURL_DISABLE_PROXY - char *proxyuserpwd; /* proxy auth header */ + char *hd_proxy_auth; /* Proxy-Authorization header, full HTTP/1.x line */ #endif #ifndef CURL_DISABLE_COOKIES char *cookiehost; diff --git a/lib/rtsp.c b/lib/rtsp.c index b08767f377cd..fabdda3d3eed 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -195,7 +195,7 @@ static CURLcode rtsp_done(struct Curl_easy *data, } static CURLcode rtsp_setup_body(struct Curl_easy *data, - Curl_RtspReq rtspreq, + unsigned char rtspreq, struct dynbuf *reqp) { CURLcode result; @@ -270,201 +270,135 @@ static CURLcode rtsp_setup_body(struct Curl_easy *data, return result; } -static CURLcode rtsp_do(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - CURLcode result = CURLE_OK; - const Curl_RtspReq rtspreq = data->set.rtspreq; - struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); - struct dynbuf req_buffer; - const unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort - of... */ - const char *p_request = NULL; - const char *p_session_id = NULL; - const char *p_accept = NULL; - const char *p_accept_encoding = NULL; - const char *p_range = NULL; - const char *p_referrer = NULL; - const char *p_stream_uri = NULL; - const char *p_transport = NULL; - const char *p_uagent = NULL; - const char *p_proxyuserpwd = NULL; - 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; - - /* Setup the first_* fields to allow auth details get sent - to this origin */ +struct rtspselect { + const char *method; + bool no_body; +}; - if(!data->state.first_host) { - data->state.first_host = curlx_strdup(conn->host.name); - if(!data->state.first_host) - return CURLE_OUT_OF_MEMORY; +static CURLcode pick_method(struct Curl_easy *data, + const unsigned char rtspreq, + const char **p) +{ + static const struct rtspselect req[] = { + { "OPTIONS", TRUE }, + { "DESCRIBE", FALSE }, + { "ANNOUNCE", TRUE }, + { "SETUP", TRUE }, + { "PLAY", TRUE }, + { "PAUSE", TRUE }, + { "TEARDOWN", TRUE }, + { "GET_PARAMETER", FALSE }, + { "SET_PARAMETER", TRUE }, + { "RECORD", TRUE }, + { "", FALSE }, /* RECEIVE: treat interleaved RTP as body */ + }; + /* this is verified already in setopt, this is just added precaution */ + DEBUGASSERT((rtspreq > RTSPREQ_NONE) && (rtspreq < RTSPREQ_LAST)); + if((rtspreq <= RTSPREQ_NONE) || (rtspreq >= RTSPREQ_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + *p = req[rtspreq - 1].method; + data->req.no_body = req[rtspreq - 1].no_body; + return CURLE_OK; +} - data->state.first_remote_port = conn->remote_port; - data->state.first_remote_protocol = conn->scheme->protocol; - } +/* Allocate and store a header string. */ +static CURLcode rtsp_header_alloc(const char *header_name, + const char *value, + char **target) +{ + if(!value) + return CURLE_OK; + curlx_free(*target); + *target = curl_maprintf("%s: %s\r\n", header_name, value); + if(!*target) + return CURLE_OUT_OF_MEMORY; + return CURLE_OK; +} - /* Setup the 'p_request' pointer to the proper p_request string - * Since all RTSP requests are included here, there is no need to - * support custom requests like HTTP. - **/ - data->req.no_body = TRUE; /* most requests do not contain a body */ - switch(rtspreq) { - default: - failf(data, "Got invalid RTSP request"); - return CURLE_BAD_FUNCTION_ARGUMENT; - case RTSPREQ_OPTIONS: - p_request = "OPTIONS"; - break; - case RTSPREQ_DESCRIBE: - p_request = "DESCRIBE"; - data->req.no_body = FALSE; - break; - case RTSPREQ_ANNOUNCE: - p_request = "ANNOUNCE"; - break; - case RTSPREQ_SETUP: - p_request = "SETUP"; - break; - case RTSPREQ_PLAY: - p_request = "PLAY"; - break; - case RTSPREQ_PAUSE: - p_request = "PAUSE"; - break; - case RTSPREQ_TEARDOWN: - p_request = "TEARDOWN"; - break; - case RTSPREQ_GET_PARAMETER: - /* GET_PARAMETER's no_body status is determined later */ - p_request = "GET_PARAMETER"; - data->req.no_body = FALSE; - break; - case RTSPREQ_SET_PARAMETER: - p_request = "SET_PARAMETER"; - break; - case RTSPREQ_RECORD: - p_request = "RECORD"; - break; - case RTSPREQ_RECEIVE: - p_request = ""; - /* Treat interleaved RTP as body */ - data->req.no_body = FALSE; - break; - case RTSPREQ_LAST: - failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } +struct rtsp_blocks { + const char *request; + const char *session_id; + const char *accept; + const char *range; + const char *stream_uri; + const char *hd_proxy_auth; + const char *hd_auth; + char *referrer; + char *accept_encoding; + char *transport; + BIT(transport_alloc); /* if 'transport' is allocated */ +}; - if(rtspreq == RTSPREQ_RECEIVE) { - Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); - goto out; - } +static CURLcode rtsp_setup_request(struct Curl_easy *data, + struct rtsp_blocks *b, + const unsigned char rtspreq) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; - p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; - if(!p_session_id && - (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS | - RTSPREQ_DESCRIBE | - RTSPREQ_SETUP))) { - failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", - p_request); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } + b->session_id = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); /* Stream URI. Default to server '*' if not specified */ - if(data->set.str[STRING_RTSP_STREAM_URI]) { - p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; - } - else { - p_stream_uri = "*"; - } + if(CURL_EASY_STR(data, STRING_RTSP_STREAM_URI)) + b->stream_uri = CURL_EASY_STR(data, STRING_RTSP_STREAM_URI); + else + b->stream_uri = "*"; /* Transport Header for SETUP requests */ - p_transport = Curl_checkheaders(data, STRCONST("Transport")); - if(rtspreq == RTSPREQ_SETUP && !p_transport) { + b->transport = Curl_checkheaders(data, STRCONST("Transport")); + if(rtspreq == RTSPREQ_SETUP && !b->transport) { /* New Transport: setting? */ - if(data->set.str[STRING_RTSP_TRANSPORT]) { - curlx_free(data->state.aptr.rtsp_transport); - data->state.aptr.rtsp_transport = - curl_maprintf("Transport: %s\r\n", - data->set.str[STRING_RTSP_TRANSPORT]); - if(!data->state.aptr.rtsp_transport) - return CURLE_OUT_OF_MEMORY; + if(CURL_EASY_STR(data, STRING_RTSP_TRANSPORT)) { + result = rtsp_header_alloc( + "Transport", CURL_EASY_STR(data, STRING_RTSP_TRANSPORT), + &b->transport); + if(result) + return result; + b->transport_alloc = TRUE; } else { failf(data, "Refusing to issue an RTSP SETUP without a Transport: header."); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; + return CURLE_BAD_FUNCTION_ARGUMENT; } - - p_transport = data->state.aptr.rtsp_transport; } /* Accept Headers for DESCRIBE requests */ if(rtspreq == RTSPREQ_DESCRIBE) { /* Accept Header */ - p_accept = Curl_checkheaders(data, STRCONST("Accept")) ? + b->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]) { - curlx_free(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = - curl_maprintf("Accept-Encoding: %s\r\n", - data->set.str[STRING_ENCODING]); - - if(!data->state.aptr.accept_encoding) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - p_accept_encoding = data->state.aptr.accept_encoding; + CURL_EASY_STR(data, STRING_ENCODING)) { + result = rtsp_header_alloc("Accept-Encoding", + CURL_EASY_STR(data, STRING_ENCODING), + &b->accept_encoding); + if(result) + return result; } } - /* The User-Agent string might have been allocated already, because - it might have been used in the proxy connect, but if we have got a header - with the user-agent string specified, we erase the previously made string - here. */ - if(Curl_checkheaders(data, STRCONST("User-Agent")) && - data->state.aptr.uagent) { - curlx_safefree(data->state.aptr.uagent); - } - else if(!Curl_checkheaders(data, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT]) { - p_uagent = data->state.aptr.uagent; - } - /* setup the authentication headers */ - result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, - p_stream_uri, FALSE); + result = Curl_http_output_auth(data, conn, b->request, HTTPREQ_GET, + b->stream_uri, NULL, FALSE); if(result) - goto out; + return result; #ifndef CURL_DISABLE_PROXY - p_proxyuserpwd = data->req.proxyuserpwd; + b->hd_proxy_auth = data->req.hd_proxy_auth; #endif - p_userpwd = data->req.userpwd; + b->hd_auth = data->req.hd_auth; /* Referrer */ - curlx_safefree(data->state.aptr.ref); if(Curl_bufref_ptr(&data->state.referer) && - !Curl_checkheaders(data, STRCONST("Referer"))) - data->state.aptr.ref = + !Curl_checkheaders(data, STRCONST("Referer"))) { + b->referrer = curl_maprintf("Referer: %s\r\n", Curl_bufref_ptr(&data->state.referer)); - - p_referrer = data->state.aptr.ref; + if(!b->referrer) + result = CURLE_OUT_OF_MEMORY; + } /* * Range Header @@ -472,18 +406,59 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * * Go ahead and use the Range stuff supplied for HTTP */ - if(data->state.use_range && - (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { + if(!result && + data->state.use_range && + ((rtspreq == RTSPREQ_PLAY) || + (rtspreq == RTSPREQ_PAUSE) || + (rtspreq == RTSPREQ_RECORD))) { /* Check to see if there is a range set in the custom headers */ if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - curlx_free(data->state.aptr.rangeline); - data->state.aptr.rangeline = curl_maprintf("Range: %s\r\n", - data->state.range); - p_range = data->state.aptr.rangeline; + result = rtsp_header_alloc("Range", + data->state.range, + &data->state.rangeline); + if(!result) + b->range = data->state.rangeline; } } + return result; +} + +#define HTTPVERSION 11 /* RTSP is close to HTTP/1.1, sort of... */ + +static CURLcode rtsp_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + const unsigned char rtspreq = data->set.rtspreq; + const char *str; + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); + struct dynbuf req_buffer; + struct rtsp_blocks block; + memset(&block, 0, sizeof(block)); + + *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; + + /* Setup the 'p_request' pointer to the proper method. */ + result = pick_method(data, rtspreq, &block.request); + if(result) + goto out; + + if(rtspreq == RTSPREQ_RECEIVE) { + Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); + goto out; + } + result = rtsp_setup_request(data, &block, rtspreq); + if(result) + goto out; /* * Sanity check the custom headers */ @@ -502,7 +477,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) curlx_dyn_addf(&req_buffer, "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ "CSeq: %u\r\n", /* CSeq */ - p_request, p_stream_uri, rtsp->CSeq_sent); + block.request, block.stream_uri, rtsp->CSeq_sent); if(result) goto out; @@ -510,8 +485,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * Rather than do a normal alloc line, keep the session_id unformatted * to make comparison easier */ - if(p_session_id) { - result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); + if(block.session_id) { + result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", block.session_id); if(result) goto out; } @@ -525,19 +500,25 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) "%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 : ""); - + block.transport ? block.transport : "", + block.accept ? block.accept : "", + block.accept_encoding ? block.accept_encoding : "", + block.range ? block.range : "", + block.referrer ? block.referrer : ""); + + str = CURL_EASY_STR(data, STRING_USERAGENT); + if(!result && str && *str && + !Curl_checkheaders(data, STRCONST("User-Agent"))) + result = curlx_dyn_addf(&req_buffer, + "User-Agent: %s\r\n", str); + + if(!result) + result = curlx_dyn_addf(&req_buffer, + "%s" /* hd_proxy_auth */ + "%s", /* hd_auth */ + block.hd_proxy_auth ? block.hd_proxy_auth : "", + block.hd_auth ? block.hd_auth : ""); if(result) goto out; @@ -547,7 +528,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) goto out; } - result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer); + result = Curl_add_custom_headers(data, FALSE, HTTPVERSION, &req_buffer); if(result) goto out; @@ -563,7 +544,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1); /* issue the request */ - result = Curl_req_send(data, &req_buffer, httpversion); + result = Curl_req_send(data, &req_buffer, HTTPVERSION); if(result) { failf(data, "Failed sending RTSP request"); goto out; @@ -579,6 +560,10 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = Curl_pgrsUpdate(data); } out: + if(block.transport_alloc) + curlx_free(block.transport); + curlx_free(block.accept_encoding); + curlx_free(block.referrer); curlx_dyn_free(&req_buffer); return result; } @@ -610,6 +595,7 @@ static CURLcode rtp_write_body_junk(struct Curl_easy *data, static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) { + struct Curl_mapi_guard guard; size_t wrote; curl_write_callback writeit; void *user_ptr; @@ -632,9 +618,9 @@ static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, user_ptr = data->set.out; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fwrite_rtp); wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(wrote == CURL_WRITEFUNC_PAUSE) { failf(data, "Cannot pause RTP"); @@ -670,7 +656,7 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, while(blen && buf[0] != '$') { if(!in_body && buf[0] == 'R' && data->set.rtspreq != RTSPREQ_RECEIVE) { - if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) { + if(!strncmp(buf, "RTSP/", (blen < 5) ? blen : 5)) { /* This could be the next response, no consume and return */ if(*pconsumed) { DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, " @@ -893,6 +879,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed); if(result) goto out; + buf += consumed; blen -= consumed; } } @@ -904,7 +891,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, * 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, + blen, rtspc->in_header, data->req.done, (int)rtspc->state, data->req.size)); if(!result && (is_eos || blen)) { result = Curl_client_write(data, CLIENTWRITE_BODY | @@ -989,7 +976,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) data->state.rtsp_CSeq_recv = rtsp->CSeq_recv = (uint32_t)CSeq; } else if(checkprefix("Session:", header)) { - const char *start, *end; + const char *start, *end, *str; size_t idlen; /* Find the first non-space letter */ @@ -1012,24 +999,24 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) end++; idlen = end - start; - if(data->set.str[STRING_RTSP_SESSION_ID]) { + str = CURL_EASY_STR(data, STRING_RTSP_SESSION_ID); + if(str) { /* 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)) { + if(strlen(str) != idlen || + strncmp(start, str, idlen)) { failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", - start, data->set.str[STRING_RTSP_SESSION_ID]); + start, str); return CURLE_RTSP_SESSION_ERROR; } } else { /* If the Session ID is not set, and we find it in a response, then set * it. - */ - - /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = curlx_memdup0(start, idlen); - if(!data->set.str[STRING_RTSP_SESSION_ID]) + * Copy the id substring into a new buffer */ + void *mem = curlx_memdup0(start, idlen); + if(!mem || + CURL_EASY_STR_SETN(data, STRING_RTSP_SESSION_ID, mem)) return CURLE_OUT_OF_MEMORY; } } diff --git a/lib/rtsp.h b/lib/rtsp.h index dd5df3ff73c5..791cbff33701 100644 --- a/lib/rtsp.h +++ b/lib/rtsp.h @@ -30,4 +30,18 @@ extern const struct Curl_protocol Curl_protocol_rtsp; #define Curl_rtsp_parseheader(x, y) CURLE_NOT_BUILT_IN #endif +#define RTSPREQ_NONE CURL_RTSPREQ_NONE +#define RTSPREQ_OPTIONS CURL_RTSPREQ_OPTIONS +#define RTSPREQ_DESCRIBE CURL_RTSPREQ_DESCRIBE +#define RTSPREQ_ANNOUNCE CURL_RTSPREQ_ANNOUNCE +#define RTSPREQ_SETUP CURL_RTSPREQ_SETUP +#define RTSPREQ_PLAY CURL_RTSPREQ_PLAY +#define RTSPREQ_PAUSE CURL_RTSPREQ_PAUSE +#define RTSPREQ_TEARDOWN CURL_RTSPREQ_TEARDOWN +#define RTSPREQ_GET_PARAMETER CURL_RTSPREQ_GET_PARAMETER +#define RTSPREQ_SET_PARAMETER CURL_RTSPREQ_SET_PARAMETER +#define RTSPREQ_RECORD CURL_RTSPREQ_RECORD +#define RTSPREQ_RECEIVE CURL_RTSPREQ_RECEIVE +#define RTSPREQ_LAST CURL_RTSPREQ_LAST + #endif /* HEADER_CURL_RTSP_H */ diff --git a/lib/select.c b/lib/select.c index 888490f2c9ac..76b56bc0426b 100644 --- a/lib/select.c +++ b/lib/select.c @@ -23,10 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL) -#error "We cannot compile without select() or poll() support." -#endif - #ifdef HAVE_SYS_SELECT_H #include #elif defined(HAVE_UNISTD_H) @@ -40,7 +36,7 @@ #include "curlx/timediff.h" #include "curlx/wait.h" -#ifndef HAVE_POLL +#ifndef HAVE_POLL /* use select() */ /* * This is a wrapper around select() to aid in Windows compatibility. A * negative timeout value makes this function wait indefinitely, unless no @@ -287,12 +283,10 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) } } - /* - Note also that Winsock ignores the first argument, so we do not worry + /* 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). - */ + value). */ r = our_select(maxfd, &fds_read, &fds_write, &fds_err, timeout_ms); if(r <= 0) { if((r == -1) && (SOCKERRNO == SOCKEINTR)) diff --git a/lib/select.h b/lib/select.h index 7552d0554865..273068b6d1dc 100644 --- a/lib/select.h +++ b/lib/select.h @@ -80,10 +80,8 @@ int Curl_socket_check(curl_socket_t readfd0, int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms); -/* - With Winsock the valid range is [0..INVALID_SOCKET-1] according to - https://learn.microsoft.com/windows/win32/winsock/socket-data-type-2 -*/ +/* With Winsock the valid range is [0..INVALID_SOCKET-1] according to + https://learn.microsoft.com/windows/win32/winsock/socket-data-type-2 */ #ifdef USE_WINSOCK #define VALID_SOCK(s) ((s) < INVALID_SOCKET) #define FDSET_SOCK(x) 1 @@ -133,7 +131,7 @@ struct easy_pollset { #define CURL_EASY_POLLSET_MAGIC 0x7a657370 #endif -/* allocate and initialise */ +/* allocate and initialize */ struct easy_pollset *Curl_pollset_create(void); /* Initialize before first use */ diff --git a/lib/sendf.c b/lib/sendf.c index 7c977d3b9ea5..abd2e3fdbfba 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -46,24 +46,25 @@ static void cl_reset_writer(struct Curl_easy *data) { - struct Curl_cwriter *writer = data->req.writer_stack; + struct Curl_cwriter *writer = data->req.writer.stack; while(writer) { - data->req.writer_stack = writer->next; + data->req.writer.stack = writer->next; writer->cwt->do_close(data, writer); curlx_free(writer); - writer = data->req.writer_stack; + writer = data->req.writer.stack; } + data->req.writer.paused = FALSE; } static void cl_reset_reader(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; data->req.reader_started = FALSE; while(reader) { - data->req.reader_stack = reader->next; + data->req.reader.stack = reader->next; reader->crt->do_close(data, reader); curlx_free(reader); - reader = data->req.reader_stack; + reader = data->req.reader.stack; } } @@ -95,7 +96,7 @@ void Curl_client_reset(struct Curl_easy *data) CURLcode Curl_client_start(struct Curl_easy *data) { if(data->req.rewind_read) { - struct Curl_creader *r = data->req.reader_stack; + struct Curl_creader *r = data->req.reader.stack; CURLcode result = CURLE_OK; CURL_TRC_READ(data, "client start, rewind readers"); @@ -103,7 +104,7 @@ CURLcode Curl_client_start(struct Curl_easy *data) result = r->crt->cntrl(data, r, CURL_CRCNTRL_REWIND); if(result) { failf(data, "rewind of client reader '%s' failed: %d", - r->crt->name, result); + r->crt->name, (int)result); return result; } r = r->next; @@ -124,16 +125,6 @@ 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) { @@ -149,6 +140,12 @@ CURLcode Curl_cwriter_def_write(struct Curl_easy *data, return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } +CURLcode Curl_cwriter_def_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + return Curl_cwriter_flush(data, writer->next); +} + void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer) { @@ -183,7 +180,8 @@ static CURLcode cw_download_write(struct Curl_easy *data, bool is_connect = !!(type & CLIENTWRITE_CONNECT); if(!ctx->started_response && - !(type & (CLIENTWRITE_INFO | CLIENTWRITE_CONNECT))) { + !(type & CLIENTWRITE_CONNECT) && + (!(type & CLIENTWRITE_INFO) || data->req.upload_done)) { Curl_pgrsTime(data, TIMER_STARTTRANSFER); ctx->started_response = TRUE; } @@ -191,9 +189,13 @@ static CURLcode cw_download_write(struct Curl_easy *data, if(!(type & CLIENTWRITE_BODY)) { if(is_connect && data->set.suppress_connect_headers) return CURLE_OK; +#ifdef DEBUGBUILD + if(is_connect && getenv("CURL_DBG_SUPPRESS_CONNECT_HDS")) + return CURLE_OK; +#endif 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); + (unsigned int)type, nbytes, (int)result); return result; } @@ -212,9 +214,9 @@ static CURLcode cw_download_write(struct Curl_easy *data, if(data->req.no_body && nbytes > 0) { /* BODY arrives although we want none, bail out */ - streamclose(data->conn, "ignoring body"); + streamclose(data->conn); CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " - "did not want a BODY", type, nbytes); + "did not want a BODY", (unsigned int)type, nbytes); data->req.download_done = TRUE; if(data->info.header_size) /* if headers have been received, this is fine */ @@ -258,7 +260,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, 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); + (unsigned int)type, nbytes, (int)result); if(result) return result; } @@ -279,7 +281,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, ", bytecount = %" FMT_OFF_T, excess_len, data->req.size, data->req.maxdownload, data->req.bytecount); - connclose(data->conn, "excess found in a read"); + connclose(data->conn); } } else if((nwrite < nbytes) && !data->req.ignorebody) { @@ -295,8 +297,10 @@ static CURLcode cw_download_write(struct Curl_easy *data, static const struct Curl_cwtype cw_download = { "protocol", NULL, + 0, Curl_cwriter_def_init, cw_download_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct cw_download_ctx) }; @@ -316,53 +320,47 @@ static CURLcode cw_raw_write(struct Curl_easy *data, static const struct Curl_cwtype cw_raw = { "raw", NULL, + 0, Curl_cwriter_def_init, cw_raw_write, + Curl_cwriter_def_flush, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; +static void cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct Curl_cwriter **anchor = &data->req.writer.stack; + + /* 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; +} + 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, + 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; + cwriter_add(data, writer); 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; + cwriter_add(data, writer); return result; } @@ -387,16 +385,32 @@ CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, DEBUGASSERT(!(type & CLIENTWRITE_INFO) || ((type & ~(CLIENTWRITE_INFO | CLIENTWRITE_EOS)) == 0)); - if(!data->req.writer_stack) { + if(!data->req.writer.stack) { result = do_init_writer_stack(data); if(result) return result; - DEBUGASSERT(data->req.writer_stack); + DEBUGASSERT(data->req.writer.stack); } - result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, len); + result = Curl_cwriter_write(data, data->req.writer.stack, type, buf, len); CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", - type, len, result); + (unsigned int)type, len, (int)result); + return result; +} + +CURLcode Curl_client_flush(struct Curl_easy *data) +{ + CURLcode result; + + if(!data->req.writer.stack) { + result = do_init_writer_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer.stack); + } + + result = Curl_cwriter_flush(data, data->req.writer.stack); + CURL_TRC_WRITE(data, "client_flush() -> %d", (int)result); return result; } @@ -442,18 +456,33 @@ 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) { + for(w = data->req.writer.stack; w; w = w->next) { if(w->phase == phase) ++n; } return n; } +static CURLcode cwriter_ensure_pause_writer(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = + Curl_cwriter_get_by_type(data, &Curl_cwt_pause); + CURLcode result = CURLE_OK; + + if(!writer) { + result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause, + CURL_CW_BEFORE_DECODE); + if(!result) + cwriter_add(data, writer); + } + return result; +} + CURLcode Curl_cwriter_add(struct Curl_easy *data, struct Curl_cwriter *writer) { CURLcode result; - struct Curl_cwriter **anchor = &data->req.writer_stack; + struct Curl_cwriter **anchor = &data->req.writer.stack; if(!*anchor) { result = do_init_writer_stack(data); @@ -461,12 +490,16 @@ CURLcode Curl_cwriter_add(struct Curl_easy *data, 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; + if(writer->cwt->flags & CURL_CW_FLAG_BLOWUP) { + /* On adding a writer that may blow up write sizes, e.g. zip bombs, + * add the pause writer. Do this first as any failure will make the + * caller destroy the writer again. */ + result = cwriter_ensure_pause_writer(data); + if(result) + return result; + } + + cwriter_add(data, writer); return CURLE_OK; } @@ -474,7 +507,7 @@ 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) { + for(writer = data->req.writer.stack; writer; writer = writer->next) { if(!strcmp(name, writer->cwt->name)) return writer; } @@ -485,31 +518,17 @@ 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) { + 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); + data->req.writer.paused = FALSE; + return Curl_cwriter_flush(data, data->req.writer.stack); } CURLcode Curl_creader_read(struct Curl_easy *data, @@ -665,9 +684,10 @@ static CURLcode cr_in_read(struct Curl_easy *data, } nread = 0; if(ctx->read_cb && blen) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_cr_in_read); nread = ctx->read_cb(buf, 1, blen, ctx->cb_user_data); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); ctx->has_used_cb = TRUE; } @@ -697,7 +717,7 @@ static CURLcode cr_in_read(struct Curl_easy *data, case CURL_READFUNC_PAUSE: if(data->conn->scheme->flags & PROTOPT_NONETWORK) { /* protocols that work without network cannot be paused. This is - actually only FILE:// now, and it cannot pause since the transfer + actually only file:// 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"); result = CURLE_READ_ERROR; @@ -731,7 +751,7 @@ static CURLcode cr_in_read(struct Curl_easy *data, } CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%" FMT_OFF_T ", read=%" FMT_OFF_T ") -> %d, nread=%zu, eos=%d", - blen, ctx->total_len, ctx->read_len, result, + blen, ctx->total_len, ctx->read_len, (int)result, *pnread, *peos); return result; } @@ -765,9 +785,10 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, return CURLE_READ_ERROR; if(data->set.seek_func) { - Curl_set_in_callback(data, TRUE); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, offset, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -779,6 +800,7 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, } /* when seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + struct Curl_mapi_guard guard; char scratch[4 * 1024]; size_t readthisamountnow = (offset - passed > (curl_off_t)sizeof(scratch)) ? @@ -786,10 +808,10 @@ static CURLcode cr_in_resume_from(struct Curl_easy *data, curlx_sotouz(offset - passed); size_t actuallyread; - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_cr_in_resume_from); actuallyread = ctx->read_cb(scratch, 1, readthisamountnow, ctx->cb_user_data); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -825,11 +847,12 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, return CURLE_OK; if(data->set.seek_func) { + struct Curl_mapi_guard guard; 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_CBAPI_START(&guard, data, easy_seek_func); + err = data->set.seek_func(data->set.seek_client, 0, SEEK_SET); + CURL_CBAPI_END(&guard); CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); if(err) { failf(data, "seek callback returned error %d", err); @@ -837,12 +860,13 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, } } else if(data->set.ioctl_func) { + struct Curl_mapi_guard guard; 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); + CURL_CBAPI_START(&guard, data, easy_ioctl_func); + err = data->set.ioctl_func(data, CURLIOCMD_RESTARTREAD, + data->set.ioctl_client); + CURL_CBAPI_END(&guard); 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); @@ -1054,7 +1078,7 @@ static CURLcode cr_lc_read(struct Curl_easy *data, out: CURL_TRC_READ(data, "cr_lc_read(len=%zu) -> %d, nread=%zu, eos=%d", - blen, result, *pnread, *peos); + blen, (int)result, *pnread, *peos); return result; } @@ -1104,17 +1128,19 @@ static CURLcode do_init_reader_stack(struct Curl_easy *data, DEBUGASSERT(r); DEBUGASSERT(r->crt); DEBUGASSERT(r->phase == CURL_CR_CLIENT); - DEBUGASSERT(!data->req.reader_stack); + DEBUGASSERT(!data->req.reader.stack); - data->req.reader_stack = r; + 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, + /* if we do not have 0 length init, and CRLF conversion is wanted, * add the reader for it */ - if(clen && (data->set.crlf + if(clen && #ifdef CURL_PREFER_LF_LINEENDS - || data->state.prefer_ascii + (data->set.crlf || data->state.prefer_ascii) +#else + data->set.crlf #endif - )) { + ) { result = cr_lc_add(data); if(result) return result; @@ -1139,7 +1165,7 @@ CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) result = do_init_reader_stack(data, r); out: CURL_TRC_READ(data, "add fread reader, len=%" FMT_OFF_T " -> %d", - len, result); + len, (int)result); return result; } @@ -1147,7 +1173,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data, struct Curl_creader *reader) { CURLcode result; - struct Curl_creader **anchor = &data->req.reader_stack; + struct Curl_creader **anchor = &data->req.reader.stack; if(!*anchor) { result = Curl_creader_set_fread(data, data->state.infilesize); @@ -1158,7 +1184,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data, /* Insert the writer as first in its phase. * Skip existing readers of lower phases. */ while(*anchor && (*anchor)->phase < reader->phase) - anchor = &((*anchor)->next); + anchor = &(*anchor)->next; reader->next = *anchor; *anchor = reader; return CURLE_OK; @@ -1190,11 +1216,11 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, DEBUGASSERT(eos); *nread = 0; - if(!data->req.reader_stack) { + if(!data->req.reader.stack) { result = Curl_creader_set_fread(data, data->state.infilesize); if(result) return result; - DEBUGASSERT(data->req.reader_stack); + DEBUGASSERT(data->req.reader.stack); } if(!data->req.reader_started) { Curl_rlimit_start(&data->progress.ul.rlimit, Curl_pgrs_now(data), -1); @@ -1202,8 +1228,7 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, } if(Curl_rlimit_active(&data->progress.ul.rlimit)) { - curl_off_t ul_avail = Curl_rlimit_avail(&data->progress.ul.rlimit, - Curl_pgrs_now(data)); + curl_off_t ul_avail = Curl_rlimit_avail(&data->progress.ul.rlimit, NULL); if(ul_avail <= 0) { result = CURLE_OK; *eos = FALSE; @@ -1212,18 +1237,18 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, if(ul_avail < (curl_off_t)blen) blen = (size_t)ul_avail; } - result = Curl_creader_read(data, data->req.reader_stack, buf, blen, + result = Curl_creader_read(data, data->req.reader.stack, buf, blen, nread, eos); out: CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", - blen, result, *nread, *eos); + blen, (int)result, *nread, *eos); return result; } bool Curl_creader_needs_rewind(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + 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"); @@ -1403,19 +1428,19 @@ CURLcode Curl_creader_set_buf(struct Curl_easy *data, cl_reset_reader(data); result = do_init_reader_stack(data, r); out: - CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, result); + CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, (int)result); return result; } curl_off_t Curl_creader_total_length(struct Curl_easy *data) { - struct Curl_creader *r = data->req.reader_stack; + 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; + 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; @@ -1423,7 +1448,7 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data) CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) { - struct Curl_creader *r = data->req.reader_stack; + 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; @@ -1431,12 +1456,12 @@ CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) CURLcode Curl_creader_unpause(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; CURLcode result = CURLE_OK; while(reader) { result = reader->crt->cntrl(data, reader, CURL_CRCNTRL_UNPAUSE); - CURL_TRC_READ(data, "unpausing %s -> %d", reader->crt->name, result); + CURL_TRC_READ(data, "unpausing %s -> %d", reader->crt->name, (int)result); if(result) break; reader = reader->next; @@ -1446,7 +1471,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data) bool Curl_creader_is_paused(struct Curl_easy *data) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; while(reader) { if(reader->crt->is_paused(data, reader)) @@ -1458,7 +1483,7 @@ bool Curl_creader_is_paused(struct Curl_easy *data) void Curl_creader_done(struct Curl_easy *data, int premature) { - struct Curl_creader *reader = data->req.reader_stack; + struct Curl_creader *reader = data->req.reader.stack; while(reader) { reader->crt->done(data, reader, premature); reader = reader->next; @@ -1469,7 +1494,7 @@ 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) { + for(r = data->req.reader.stack; r; r = r->next) { if(r->crt == crt) return r; } diff --git a/lib/sendf.h b/lib/sendf.h index 75c6e248ea47..9863c5b0dcaa 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -24,6 +24,7 @@ * ***************************************************************************/ #include "curl_setup.h" +#include "cw-out.h" /** * Type of data that is being written to the client (application) @@ -61,6 +62,8 @@ struct Curl_easy; CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, size_t len) WARN_UNUSED_RESULT; +CURLcode Curl_client_flush(struct Curl_easy *data); + /** * Free all resources related to client writing. */ @@ -102,19 +105,26 @@ 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_BEFORE_DECODE, /* after protocol, but before content decoding */ CURL_CW_CONTENT_DECODE, /* remove content-encodings */ CURL_CW_CLIENT /* data written to client */ } Curl_cwriter_phase; +/* writer may blow up size of write data, e.g. zip bombs */ +#define CURL_CW_FLAG_BLOWUP (1U << 0) + /* Client Writer Type, provides the implementation */ struct Curl_cwtype { const char *name; /* writer name. */ const char *alias; /* writer name alias, maybe NULL. */ + uint8_t flags; /* flags for writer behaviour */ 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); + CURLcode (*do_flush)(struct Curl_easy *data, + struct Curl_cwriter *writer); void (*do_close)(struct Curl_easy *data, struct Curl_cwriter *writer); size_t cwriter_size; /* sizeof() allocated struct Curl_cwriter */ @@ -172,20 +182,16 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, 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); +/* Convenience method for calling `writer->do_write()` that + * checks for NULL writer. */ +#define Curl_cwriter_write(d, w, t, b, n) \ + ((w) ? (w)->cwt->do_write((d), (w), (t), (b), (n)) : CURLE_WRITE_ERROR) -/** - * Return TRUE iff client writer is paused. - */ -bool Curl_cwriter_is_paused(struct Curl_easy *data); +#define Curl_cwriter_flush(d, w) \ + ((w) ? (w)->cwt->do_flush((d), (w)) : CURLE_WRITE_ERROR) -bool Curl_cwriter_is_content_decoding(struct Curl_easy *data); +/* TRUE if client writer is paused. */ +#define Curl_cwriter_is_paused(d) ((bool)(d)->req.writer.paused) /** * Unpause client writer and flush any buffered date to the client. @@ -201,6 +207,8 @@ CURLcode Curl_cwriter_def_init(struct Curl_easy *data, CURLcode Curl_cwriter_def_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes); +CURLcode Curl_cwriter_def_flush(struct Curl_easy *data, + struct Curl_cwriter *writer); void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer); @@ -377,7 +385,7 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data); * 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 + * of offset larger than total length * CURLE_PARTIAL_FILE if offset led to 0 total length */ CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset); diff --git a/lib/setopt.c b/lib/setopt.c index a23b5151a79f..9e61a2cd0dd3 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -41,7 +41,6 @@ #include "curl_share.h" #include "vtls/vtls.h" #include "curl_trc.h" -#include "hostip.h" #include "setopt.h" #include "altsvc.h" #include "hsts.h" @@ -79,23 +78,15 @@ static CURLcode setopt_set_timeout_ms(timediff_t *ptimeout_ms, long ms) return CURLE_OK; } -CURLcode Curl_setstropt(char **charp, const char *s) +CURLcode Curl_setstropt(struct Curl_easy *data, + enum dupstring id, const char *s) { - /* Release the previous storage at `charp' and replace by a dynamic storage - copy of `s'. Return CURLE_OK or CURLE_OUT_OF_MEMORY. */ - - curlx_safefree(*charp); - - if(s) { - if(strlen(s) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - - *charp = curlx_strdup(s); - if(!*charp) - return CURLE_OUT_OF_MEMORY; - } + size_t slen = s ? strlen(s) : 0; + DEBUGASSERT((unsigned)id <= UINT8_MAX); + if(s && (slen > CURL_MAX_INPUT_LENGTH)) + return CURLE_BAD_FUNCTION_ARGUMENT; - return CURLE_OK; + return CURL_EASY_STR_SET(data, (uint8_t)id, s, slen); } CURLcode Curl_setblobopt(struct curl_blob **blobp, @@ -108,7 +99,7 @@ CURLcode Curl_setblobopt(struct curl_blob **blobp, if(blob) { struct curl_blob *nblob; - if(!blob->len || (blob->len > CURL_MAX_INPUT_LENGTH)) + if(!blob->data || !blob->len || (blob->len > CURL_MAX_INPUT_LENGTH)) return CURLE_BAD_FUNCTION_ARGUMENT; nblob = (struct curl_blob *) curlx_malloc(sizeof(struct curl_blob) + @@ -154,40 +145,41 @@ static CURLcode setstropt_userpwd(const char *option, char **userp, curlx_free(*userp); *userp = user; + curlx_strzero(*passwdp); curlx_free(*passwdp); *passwdp = passwd; return CURLE_OK; } -static CURLcode setstropt_interface(char *option, char **devp, - char **ifacep, char **hostp) +static CURLcode setstropt_interface(struct Curl_easy *data, char *option) { 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; } - curlx_free(*devp); - *devp = dev; - - curlx_free(*ifacep); - *ifacep = iface; - curlx_free(*hostp); - *hostp = host; - - return CURLE_OK; + result = CURL_EASY_STR_SETN(data, STRING_DEVICE, dev); + dev = NULL; + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_INTERFACE, iface); + iface = NULL; + } + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_BINDHOST, host); + host = NULL; + } + curlx_free(dev); + curlx_free(iface); + curlx_free(host); + return result; } #ifdef USE_SSL @@ -240,17 +232,9 @@ static CURLcode httpauth(struct Curl_easy *data, bool proxy, 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 */ + auth &= ~CURLAUTH_DIGEST_IE; /* drop the legacy bit */ } /* switch off bits we cannot support */ @@ -334,7 +318,7 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, if(option != CURLOPT_SSLVERSION) primary = &data->set.proxy_ssl.primary; #else - if(option) {} + (void)option; /* unused */ #endif version = C_SSLVERSION_VALUE(arg); version_max = (long)C_SSLVERSION_MAX_VALUE(arg); @@ -363,66 +347,14 @@ 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 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: + if((arg <= CURL_RTSPREQ_NONE) || (arg >= CURL_RTSPREQ_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - } - data->set.rtspreq = rtspreq; + data->set.rtspreq = (unsigned char)arg; 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_bool(struct Curl_easy *data, CURLoption option, long arg) { @@ -826,7 +758,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, if((arg > ok) || (arg < 0)) /* reserve other values for future use */ infof(data, "boolean setopt(%d) got unsupported argument %ld," - " treated as %d", option, arg, enabled); + " treated as %d", (int)option, arg, enabled); return CURLE_OK; } @@ -855,9 +787,9 @@ static CURLcode setopt_long_net(struct Curl_easy *data, CURLoption option, s->dns_cache_timeout_ms = -1; break; case CURLOPT_MAXCONNECTS: - result = value_range(&arg, 1, 1, INT_MAX); + result = value_range(&arg, 0, 0, INT_MAX); if(!result) - s->maxconnects = (uint32_t)arg; + s->maxconnects = arg ? (uint32_t)arg : DEFAULT_CONNCACHE_SIZE; break; case CURLOPT_SERVER_RESPONSE_TIMEOUT: return setopt_set_timeout_sec(&s->server_response_timeout, arg); @@ -1002,17 +934,17 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, s->use_ssl = (unsigned char)arg; break; case CURLOPT_SSL_OPTIONS: - set_ssl_options(&s->ssl, &s->ssl.primary, arg); + s->ssl.primary.ssl_options = (unsigned char)(arg & 0xff); break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSL_OPTIONS: - set_ssl_options(&s->proxy_ssl, &s->proxy_ssl.primary, arg); + s->proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); break; #endif case CURLOPT_SSL_ENABLE_NPN: break; case CURLOPT_SSLENGINE_DEFAULT: - curlx_safefree(s->str[STRING_SSL_ENGINE]); + CURL_EASY_STR_CLEAR(data, STRING_SSL_ENGINE); result = Curl_ssl_set_engine_default(data); break; default: @@ -1027,23 +959,35 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, #endif /* !USE_SSL */ } +#ifndef CURL_DISABLE_PROXY +static void changeproxy(struct Curl_easy *data) +{ + Curl_auth_digest_cleanup(&data->state.proxydigest); + memset(&data->state.authproxy, 0, sizeof(data->state.authproxy)); +} + static CURLcode setopt_long_proxy(struct Curl_easy *data, CURLoption option, long arg) { -#ifndef CURL_DISABLE_PROXY struct UserDefined *s = &data->set; switch(option) { case CURLOPT_PROXYPORT: if((arg < 0) || (arg > UINT16_MAX)) return CURLE_BAD_FUNCTION_ARGUMENT; + if(arg != s->proxyport) + changeproxy(data); s->proxyport = (uint16_t)arg; break; case CURLOPT_PROXYAUTH: return httpauth(data, TRUE, (unsigned long)arg); case CURLOPT_PROXYTYPE: - if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME)) + if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_HTTPS3)) return CURLE_BAD_FUNCTION_ARGUMENT; +#ifndef USE_PROXY_HTTP3 + if(arg == CURLPROXY_HTTPS3) + return CURLE_NOT_BUILT_IN; +#endif s->proxytype = (unsigned char)arg; break; case CURLOPT_SOCKS5_AUTH: @@ -1055,13 +999,17 @@ static CURLcode setopt_long_proxy(struct Curl_easy *data, CURLoption option, return CURLE_UNKNOWN_OPTION; } return CURLE_OK; +} #else +static CURLcode setopt_long_proxy(struct Curl_easy *data, CURLoption option, + long arg) +{ (void)data; (void)option; (void)arg; return CURLE_UNKNOWN_OPTION; -#endif } +#endif static CURLcode setopt_long_http(struct Curl_easy *data, CURLoption option, long arg) @@ -1106,11 +1054,24 @@ static CURLcode setopt_long_http(struct Curl_easy *data, CURLoption option, case CURLOPT_STREAM_WEIGHT: #if defined(USE_HTTP2) || defined(USE_HTTP3) if((arg >= 1) && (arg <= 256)) - s->priority.weight = (int)arg; + s->weight = (int)arg; break; #else result = CURLE_NOT_BUILT_IN; break; +#endif +#ifndef CURL_DISABLE_HTTPSIG + case CURLOPT_HTTPSIG_ALGORITHM: + if(arg != CURLHTTPSIG_NONE && + arg != CURLHTTPSIG_ED25519 && + arg != CURLHTTPSIG_HMAC_SHA256) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->httpsig_algorithm = (uint8_t)arg; + if(arg) + s->httpauth = (uint32_t)CURLAUTH_HTTPSIG; + else + s->httpauth &= ~(uint32_t)CURLAUTH_HTTPSIG; + break; #endif default: return CURLE_UNKNOWN_OPTION; @@ -1242,9 +1203,8 @@ static CURLcode setopt_long_misc(struct Curl_easy *data, CURLoption option, case CURLOPT_POSTFIELDSIZE: if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(s->postfieldsize < arg && - s->postfields == s->str[STRING_COPYPOSTFIELDS]) { - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + if(s->postfieldsize < arg && s->str_copypostfields) { + curlx_safefree(s->str_copypostfields); s->postfields = NULL; } s->postfieldsize = arg; @@ -1276,15 +1236,23 @@ static CURLcode setopt_long_misc(struct Curl_easy *data, CURLoption option, return CURLE_OUT_OF_MEMORY; } } - else + else if(!data->share || !data->share->hsts) { + /* throw away the HSTS cache unless shared */ Curl_hsts_cleanup(&data->hsts); + /* flush all the entries */ + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + } + else + /* detach from shared HSTS cache without freeing it */ + data->hsts = NULL; break; #endif #ifndef CURL_DISABLE_ALTSVC case CURLOPT_ALTSVC_CTRL: return Curl_altsvc_ctrl(data, arg); #endif -#ifdef HAVE_GSSAPI +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) case CURLOPT_GSSAPI_DELEGATION: s->gssapi_delegation = (unsigned char)arg & (CURLGSSAPI_DELEGATION_POLICY_FLAG | CURLGSSAPI_DELEGATION_FLAG); @@ -1449,6 +1417,26 @@ static CURLcode setopt_mimepost(struct Curl_easy *data, curl_mime *mimep) #endif /* !CURL_DISABLE_MIME */ #endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP */ +static CURLcode setopt_share(struct Curl_easy *data, struct Curl_share *set) +{ + CURLcode result; + + if(data->conn) { + /* As this handle already has a connection attached, changing share now + would be complicated and error-prone */ + infof(data, "Cannot change share object while in use"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + else { + /* disconnect from old share, if any and possible */ + result = Curl_share_easy_unlink(data); + if(!result && GOOD_SHARE_HANDLE(set)) + /* use new share if it set */ + result = Curl_share_easy_link(data, set); + } + return result; +} + /* assorted pointer type arguments */ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, va_list param) @@ -1461,7 +1449,7 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, * pass CURLU to set URL */ Curl_bufref_free(&data->state.url); - curlx_safefree(s->str[STRING_SET_URL]); + CURL_EASY_STR_CLEAR(data, STRING_SET_URL); s->uh = va_arg(param, CURLU *); break; #ifndef CURL_DISABLE_HTTP @@ -1496,33 +1484,8 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, if(!s->err) s->err = stderr; break; - case CURLOPT_SHARE: { - struct Curl_share *set = va_arg(param, struct Curl_share *); - - /* disconnect from old share, if any and possible */ - result = Curl_share_easy_unlink(data); - if(result) - return result; - - /* use new share if it set */ - if(GOOD_SHARE_HANDLE(set)) { - result = Curl_share_easy_link(data, set); - if(result) - return result; - } - 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 + case CURLOPT_SHARE: + return setopt_share(data, va_arg(param, struct Curl_share *)); default: return CURLE_UNKNOWN_OPTION; @@ -1555,7 +1518,9 @@ static CURLcode cookielist(struct Curl_easy *data, const char *ptr) } else if(curl_strequal(ptr, "RELOAD")) { /* reload cookies from file */ - return Curl_cookie_loadfiles(data); + return Curl_cookie_loadfiles(data, COOKIE_NOPSL | + (data->set.cookiesession ? + COOKIE_NOSESSION : 0)); } else { if(!data->cookies) { @@ -1570,15 +1535,20 @@ static CURLcode cookielist(struct Curl_easy *data, const char *ptr) if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) return CURLE_BAD_FUNCTION_ARGUMENT; + /* Adding these cookies without the PSL check, because the PSL is not + initialized until *perform() time, and this might be called before + that */ Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); if(checkprefix("Set-Cookie:", ptr)) /* HTTP Header format line */ - result = Curl_cookie_add(data, data->cookies, TRUE, FALSE, ptr + 11, - NULL, NULL, TRUE); + result = Curl_cookie_add(data, data->cookies, ptr + 11, + NULL, NULL, + COOKIE_HTTPHEADER | COOKIE_SECURE | + COOKIE_NOPSL); else /* Netscape format line */ - result = Curl_cookie_add(data, data->cookies, FALSE, FALSE, ptr, NULL, - NULL, TRUE); + result = Curl_cookie_add(data, data->cookies, ptr, NULL, + NULL, COOKIE_SECURE | COOKIE_NOPSL); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } return result; @@ -1625,18 +1595,18 @@ static CURLcode cookiefile(struct Curl_easy *data, const char *ptr) #ifndef CURL_DISABLE_PROXY static CURLcode setproxy(struct Curl_easy *data, const char *proxy) { - if((data->set.str[STRING_PROXY] && proxy) && + const char *str = CURL_EASY_STR(data, STRING_PROXY); + if(str && proxy && /* there was one set, is this a new one? */ - !strcmp(data->set.str[STRING_PROXY], proxy)) + !strcmp(str, proxy)) return CURLE_OK; /* same one as before */ - Curl_auth_digest_cleanup(&data->state.proxydigest); - memset(&data->state.authproxy, 0, sizeof(data->state.authproxy)); - return Curl_setstropt(&data->set.str[STRING_PROXY], proxy); + changeproxy(data); + return Curl_setstropt(data, STRING_PROXY, proxy); } static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, - const char *ptr) + char *ptr) { CURLcode result = CURLE_OK; struct UserDefined *s = &data->set; @@ -1650,17 +1620,24 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, result = setstropt_userpwd(ptr, &u, &p); /* URL decode the components */ - if(!result && u) { - curlx_safefree(s->str[STRING_PROXYUSERNAME]); - result = Curl_urldecode(u, 0, &s->str[STRING_PROXYUSERNAME], NULL, - REJECT_ZERO); - } - if(!result && p) { - curlx_safefree(s->str[STRING_PROXYPASSWORD]); - result = Curl_urldecode(p, 0, &s->str[STRING_PROXYPASSWORD], NULL, - REJECT_ZERO); + if(!result) { + char *str = NULL; + CURL_EASY_STR_CLEAR(data, STRING_PROXYUSERNAME); + CURL_EASY_STR_CLEAR(data, STRING_PROXYPASSWORD); + if(u) { + result = Curl_urldecode(u, 0, &str, NULL, REJECT_ZERO); + if(!result) + result = Curl_u8_strset_setn(&s->strings, STRING_PROXYUSERNAME, str); + } + if(!result && p) { + str = NULL; + result = Curl_urldecode(p, 0, &str, NULL, REJECT_ZERO); + if(!result) + result = Curl_u8_strset_setn(&s->strings, STRING_PROXYPASSWORD, str); + } } curlx_free(u); + curlx_strzero(p); curlx_free(p); break; } @@ -1668,55 +1645,55 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, /* * authentication username to use in the operation */ - return Curl_setstropt(&s->str[STRING_PROXYUSERNAME], ptr); + return Curl_setstropt(data, STRING_PROXYUSERNAME, ptr); case CURLOPT_PROXYPASSWORD: /* * authentication password to use in the operation */ - return Curl_setstropt(&s->str[STRING_PROXYPASSWORD], ptr); + return Curl_setstropt(data, STRING_PROXYPASSWORD, ptr); case CURLOPT_NOPROXY: /* * proxy exception list */ - return Curl_setstropt(&s->str[STRING_NOPROXY], ptr); + return Curl_setstropt(data, STRING_NOPROXY, ptr); case CURLOPT_PROXY_SSLCERT: /* * String that holds filename of the SSL certificate to use for proxy */ - return Curl_setstropt(&s->str[STRING_CERT_PROXY], ptr); + return Curl_setstropt(data, STRING_CERT_PROXY, ptr); case CURLOPT_PROXY_SSLCERTTYPE: /* * String that holds file type of the SSL certificate to use for proxy */ - return Curl_setstropt(&s->str[STRING_CERT_TYPE_PROXY], ptr); + return Curl_setstropt(data, STRING_CERT_TYPE_PROXY, ptr); case CURLOPT_PROXY_SSLKEY: /* * String that holds filename of the SSL key to use for proxy */ - return Curl_setstropt(&s->str[STRING_KEY_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_PROXY, ptr); case CURLOPT_PROXY_KEYPASSWD: /* * String that holds the SSL private key password for proxy. */ - return Curl_setstropt(&s->str[STRING_KEY_PASSWD_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_PASSWD_PROXY, ptr); case CURLOPT_PROXY_SSLKEYTYPE: /* * String that holds file type of the SSL key to use for proxy */ - return Curl_setstropt(&s->str[STRING_KEY_TYPE_PROXY], ptr); + return Curl_setstropt(data, STRING_KEY_TYPE_PROXY, ptr); 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(&s->str[STRING_SSL_CIPHER_LIST_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER_LIST_PROXY, ptr); } else return CURLE_NOT_BUILT_IN; 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(&s->str[STRING_SSL_CIPHER13_LIST_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER13_LIST_PROXY, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY: @@ -1738,13 +1715,13 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, * If the proxy is set to "" or NULL we explicitly say that we do not want * to use the socks proxy. */ - return Curl_setstropt(&s->str[STRING_PRE_PROXY], ptr); + return Curl_setstropt(data, STRING_PRE_PROXY, ptr); case CURLOPT_SOCKS5_GSSAPI_SERVICE: case CURLOPT_PROXY_SERVICE_NAME: /* * Set proxy authentication service name for Kerberos 5 and SPNEGO */ - return Curl_setstropt(&s->str[STRING_PROXY_SERVICE_NAME], ptr); + return Curl_setstropt(data, STRING_PROXY_SERVICE_NAME, ptr); case CURLOPT_PROXY_PINNEDPUBLICKEY: /* * Set pinned public key for SSL connection. @@ -1752,7 +1729,7 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, */ #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - return Curl_setstropt(&s->str[STRING_SSL_PINNEDPUBLICKEY_PROXY], ptr); + return Curl_setstropt(data, STRING_SSL_PINNEDPUBLICKEY_PROXY, ptr); #endif return CURLE_NOT_BUILT_IN; @@ -1760,30 +1737,35 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, /* * Set the client IP to send through HAProxy PROXY protocol */ - result = Curl_setstropt(&s->str[STRING_HAPROXY_CLIENT_IP], ptr); + result = Curl_setstropt(data, STRING_HAPROXY_CLIENT_IP, ptr); /* enable the HAProxy protocol if an IP is provided */ - s->haproxyprotocol = !!s->str[STRING_HAPROXY_CLIENT_IP]; + s->haproxyprotocol = !!CURL_EASY_STR(data, STRING_HAPROXY_CLIENT_IP); break; case CURLOPT_PROXY_CAINFO: /* * Set CA info SSL connection for proxy. Specify filename of the * CA certificate */ - s->proxy_ssl.custom_cafile = TRUE; - return Curl_setstropt(&s->str[STRING_SSL_CAFILE_PROXY], ptr); + result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, ptr); + s->proxy_ssl.custom_cafile = + !!CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY); + return result; case CURLOPT_PROXY_CRLFILE: /* * Set CRL file info for SSL connection for proxy. Specify filename of the * CRL to check certificates revocation */ - return Curl_setstropt(&s->str[STRING_SSL_CRLFILE_PROXY], ptr); + if(Curl_ssl_supports(data, SSLSUPP_CRLFILE)) + return Curl_setstropt(data, STRING_SSL_CRLFILE_PROXY, ptr); + return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_ISSUERCERT: /* - * Set Issuer certificate file - * to check certificates issuer + * Set Issuer certificate file to check certificates issuer */ - return Curl_setstropt(&s->str[STRING_SSL_ISSUERCERT_PROXY], ptr); + if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT)) + return Curl_setstropt(data, STRING_SSL_ISSUERCERT_PROXY, ptr); + return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_CAPATH: /* * Set CA path info for SSL connection proxy. Specify directory name of the @@ -1792,8 +1774,10 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ - s->proxy_ssl.custom_capath = TRUE; - return Curl_setstropt(&s->str[STRING_SSL_CAPATH_PROXY], ptr); + result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, ptr); + s->proxy_ssl.custom_capath = + !!CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY); + return result; } #endif return CURLE_NOT_BUILT_IN; @@ -1813,68 +1797,114 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, static CURLcode setopt_copypostfields(const char *ptr, struct UserDefined *s) { CURLcode result = CURLE_OK; - if(!ptr || s->postfieldsize == -1) - result = Curl_setstropt(&s->str[STRING_COPYPOSTFIELDS], ptr); - else { - size_t pflen; - - if(s->postfieldsize < 0) + if(s->postfieldsize < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(!ptr || s->postfieldsize == -1) { + if(ptr && (strlen(ptr) > CURL_MAX_INPUT_LENGTH)) return CURLE_BAD_FUNCTION_ARGUMENT; - pflen = curlx_sotouz_range(s->postfieldsize, 0, SIZE_MAX); + curlx_safefree(s->str_copypostfields); + if(ptr) { + s->str_copypostfields = curlx_strdup(ptr); + if(!s->str_copypostfields) + return CURLE_OUT_OF_MEMORY; + } + } + else { + size_t pflen = curlx_sotouz_range(s->postfieldsize, 0, SIZE_MAX); if(pflen == SIZE_MAX) return CURLE_OUT_OF_MEMORY; 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. - */ + that postfields is used rather than read function or form data. */ char *p = curlx_memdup0(ptr, pflen); if(!p) return CURLE_OUT_OF_MEMORY; else { - curlx_free(s->str[STRING_COPYPOSTFIELDS]); - s->str[STRING_COPYPOSTFIELDS] = p; + curlx_free(s->str_copypostfields); + s->str_copypostfields = p; } } } - s->postfields = s->str[STRING_COPYPOSTFIELDS]; + s->postfields = s->str_copypostfields; s->method = HTTPREQ_POST; return result; } #endif -static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, - char *ptr) +#ifdef USE_ECH +static CURLcode setopt_ech(struct Curl_easy *data, const char *ptr) { - CURLcode result; struct UserDefined *s = &data->set; -#ifndef CURL_DISABLE_PROXY - result = setopt_cptr_proxy(data, option, ptr); - if(result != CURLE_UNKNOWN_OPTION) - return result; + CURLcode result = CURLE_OK; + + if(!ptr || !strcmp(ptr, "false")) + s->tls_ech = CURLECH_DISABLE; + else { + size_t plen = strlen(ptr); + if(plen > CURL_MAX_INPUT_LENGTH) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else { + if(!strcmp(ptr, "grease")) + s->tls_ech = CURLECH_GREASE; + else if(!strcmp(ptr, "true")) + s->tls_ech = CURLECH_ENABLE; + else if(!strcmp(ptr, "hard")) + s->tls_ech = CURLECH_HARD; + else if(plen > 4 && !strncmp(ptr, "ecl:", 4)) { + if(!s->tls_ech) + s->tls_ech = CURLECH_HARD; + result = Curl_setstropt(data, STRING_ECH_CONFIG, ptr + 4); + } + else if(plen > 3 && !strncmp(ptr, "pn:", 3)) { + if(!s->tls_ech) + s->tls_ech = CURLECH_HARD; + result = Curl_setstropt(data, STRING_ECH_PUBLIC, ptr + 3); + } + else + result = CURLE_BAD_FUNCTION_ARGUMENT; + } + } + return result; +} +#else +#define setopt_ech(x, y) CURLE_NOT_BUILT_IN #endif - result = CURLE_OK; + +#if defined(USE_SSL) || defined(USE_SSH) +/* One of the options is used for both TLS and SSH */ +static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + CURLcode result = CURLE_OK; switch(option) { + case CURLOPT_KEYPASSWD: + /* + * String that holds the SSL or SSH private key password. + */ + result = Curl_setstropt(data, STRING_KEY_PASSWD, ptr); + break; +#ifdef USE_SSL case CURLOPT_CAINFO: /* * Set CA info for SSL connection. Specify filename of the CA certificate */ - s->ssl.custom_cafile = TRUE; - return Curl_setstropt(&s->str[STRING_SSL_CAFILE], ptr); + result = Curl_setstropt(data, STRING_SSL_CAFILE, ptr); + data->set.ssl.custom_cafile = !!CURL_EASY_STR(data, STRING_SSL_CAFILE); + return result; case CURLOPT_CAPATH: /* * Set CA path info for SSL connection. Specify directory name of the CA * certificates which have been prepared using openssl c_rehash utility. */ -#ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ - s->ssl.custom_capath = TRUE; - return Curl_setstropt(&s->str[STRING_SSL_CAPATH], ptr); + result = Curl_setstropt(data, STRING_SSL_CAPATH, ptr); + data->set.ssl.custom_capath = !!CURL_EASY_STR(data, STRING_SSL_CAPATH); + return result; } -#endif return CURLE_NOT_BUILT_IN; case CURLOPT_CRLFILE: /* @@ -1882,36 +1912,115 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * to check certificates revocation */ if(Curl_ssl_supports(data, SSLSUPP_CRLFILE)) - return Curl_setstropt(&s->str[STRING_SSL_CRLFILE], ptr); + return Curl_setstropt(data, STRING_SSL_CRLFILE, ptr); return CURLE_NOT_BUILT_IN; 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(&s->str[STRING_SSL_CIPHER_LIST], ptr); + return Curl_setstropt(data, STRING_SSL_CIPHER_LIST, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_TLS13_CIPHERS: - if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) /* set preferred list of TLS 1.3 cipher suites */ - return Curl_setstropt(&s->str[STRING_SSL_CIPHER13_LIST], ptr); - } + return Curl_setstropt(data, STRING_SSL_CIPHER13_LIST, ptr); else return CURLE_NOT_BUILT_IN; case CURLOPT_RANDOM_FILE: break; case CURLOPT_EGDSOCKET: break; - case CURLOPT_REQUEST_TARGET: - return Curl_setstropt(&s->str[STRING_TARGET], ptr); -#ifndef CURL_DISABLE_NETRC - case CURLOPT_NETRC_FILE: + case CURLOPT_SSL_CTX_DATA: + /* + * Set an SSL_CTX callback parameter pointer + */ + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { + data->set.ssl.fsslctxp = ptr; + break; + } + else + return CURLE_NOT_BUILT_IN; + case CURLOPT_SSLCERT: + /* + * String that holds filename of the SSL certificate to use + */ + return Curl_setstropt(data, STRING_CERT, ptr); + case CURLOPT_SSLCERTTYPE: + /* + * String that holds file type of the SSL certificate to use + */ + return Curl_setstropt(data, STRING_CERT_TYPE, ptr); + case CURLOPT_SSLKEY: + /* + * String that holds filename of the SSL key to use + */ + return Curl_setstropt(data, STRING_KEY, ptr); + case CURLOPT_SSLKEYTYPE: + /* + * String that holds file type of the SSL key to use + */ + return Curl_setstropt(data, STRING_KEY_TYPE, ptr); + case CURLOPT_SSLENGINE: + /* + * String that holds the SSL crypto engine. + */ + if(ptr && ptr[0]) { + result = Curl_setstropt(data, STRING_SSL_ENGINE, ptr); + if(!result) { + result = Curl_ssl_set_engine(data, ptr); + } + } + break; + case CURLOPT_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT)) + return Curl_setstropt(data, STRING_SSL_ISSUERCERT, ptr); + return CURLE_NOT_BUILT_IN; + case CURLOPT_SSL_EC_CURVES: + /* + * Set accepted curves in SSL connection setup. + * Specify colon-delimited list of curve algorithm names. + */ + if(Curl_ssl_supports(data, SSLSUPP_SSL_EC_CURVES)) + return Curl_setstropt(data, STRING_SSL_EC_CURVES, ptr); + return CURLE_NOT_BUILT_IN; + case CURLOPT_SSL_SIGNATURE_ALGORITHMS: + /* + * Set accepted signature algorithms. + * Specify colon-delimited list of signature scheme names. + */ + if(Curl_ssl_supports(data, SSLSUPP_SIGNATURE_ALGORITHMS)) + return Curl_setstropt(data, STRING_SSL_SIGNATURE_ALGORITHMS, ptr); + return CURLE_NOT_BUILT_IN; + case CURLOPT_PINNEDPUBLICKEY: /* - * Use this file instead of the $HOME/.netrc file + * Set pinned public key for SSL connection. + * Specify filename of the public key in DER format. */ - return Curl_setstropt(&s->str[STRING_NETRC_FILE], ptr); + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + return Curl_setstropt(data, STRING_SSL_PINNEDPUBLICKEY, ptr); + return CURLE_NOT_BUILT_IN; + case CURLOPT_ECH: + return setopt_ech(data, ptr); +#endif + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} #endif #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) +static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data, + CURLoption option, char *ptr) +{ + CURLcode result = CURLE_OK; + struct UserDefined *s = &data->set; + + switch(option) { case CURLOPT_COPYPOSTFIELDS: return setopt_copypostfields(ptr, s); @@ -1921,10 +2030,9 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, */ s->postfields = ptr; /* Release old copied data. */ - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + curlx_safefree(s->str_copypostfields); s->method = HTTPREQ_POST; break; -#endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_MQTT */ #ifndef CURL_DISABLE_HTTP case CURLOPT_TRAILERDATA: @@ -1937,20 +2045,18 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * 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. + * and ignore any received Content-Encoding header. * */ if(ptr && !*ptr) { ptr = Curl_get_content_encodings(); - if(ptr) { - curlx_free(s->str[STRING_ENCODING]); - s->str[STRING_ENCODING] = ptr; - } + if(ptr) + result = CURL_EASY_STR_SETN(data, STRING_ENCODING, ptr); else result = CURLE_OUT_OF_MEMORY; return result; } - return Curl_setstropt(&s->str[STRING_ENCODING], ptr); + return Curl_setstropt(data, STRING_ENCODING, ptr); #ifndef CURL_DISABLE_AWS case CURLOPT_AWS_SIGV4: @@ -1958,33 +2064,51 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * String that is merged to some authentication * parameters are used by the algorithm. */ - result = Curl_setstropt(&s->str[STRING_AWS_SIGV4], ptr); + result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr); /* - * Basic been set by default it need to be unset here + * Basic has been set by default; it needs to be unset here. */ - if(s->str[STRING_AWS_SIGV4]) + if(CURL_EASY_STR(data, STRING_AWS_SIGV4)) s->httpauth = CURLAUTH_AWS_SIGV4; + else + s->httpauth &= ~(uint32_t)CURLAUTH_AWS_SIGV4; + break; +#endif +#ifndef CURL_DISABLE_HTTPSIG + case CURLOPT_HTTPSIG_KEY: + result = Curl_setstropt(data, STRING_HTTPSIG_KEY, ptr); + break; + case CURLOPT_HTTPSIG_KEYID: + result = Curl_setstropt(data, STRING_HTTPSIG_KEYID, ptr); + break; + case CURLOPT_HTTPSIG_HEADERS: + result = Curl_setstropt(data, STRING_HTTPSIG_HEADERS, ptr); break; #endif - case CURLOPT_REFERER: + case CURLOPT_REFERER: { /* * String to set in the HTTP Referer: field. */ - result = Curl_setstropt(&s->str[STRING_SET_REFERER], ptr); + struct bufref *oldref = &data->state.referer; + /* free the old after the storing the new in case the input is actually + pointing back to this */ + result = Curl_setstropt(data, STRING_SET_REFERER, ptr); + Curl_bufref_free(oldref); break; + } case CURLOPT_USERAGENT: /* * String to use in the HTTP User-Agent field */ - return Curl_setstropt(&s->str[STRING_USERAGENT], ptr); + return Curl_setstropt(data, STRING_USERAGENT, ptr); #ifndef CURL_DISABLE_COOKIES case CURLOPT_COOKIE: /* * Cookie string to send to the remote server in the request. */ - return Curl_setstropt(&s->str[STRING_COOKIE], ptr); + return Curl_setstropt(data, STRING_COOKIE, ptr); case CURLOPT_COOKIEFILE: return cookiefile(data, ptr); @@ -1993,7 +2117,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* * Set cookie filename to dump all cookies to when we are done. */ - result = Curl_setstropt(&s->str[STRING_COOKIEJAR], ptr); + result = Curl_setstropt(data, STRING_COOKIEJAR, ptr); if(!result) { /* * Activate the cookie parser. This may or may not already @@ -2013,323 +2137,248 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, #endif /* !CURL_DISABLE_COOKIES */ #endif /* !CURL_DISABLE_HTTP */ + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} +#endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_MQTT */ - case CURLOPT_CUSTOMREQUEST: +#ifdef USE_SSH +static CURLcode setopt_cptr_ssh(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + struct UserDefined *s = &data->set; + switch(option) { + case CURLOPT_SSH_PUBLIC_KEYFILE: /* - * Set a custom string to use as request + * Use this file instead of the $HOME/.ssh/id_dsa.pub file */ - return Curl_setstropt(&s->str[STRING_CUSTOMREQUEST], ptr); - - /* we do not set s->method = HTTPREQ_CUSTOM; here, we continue as if we - were using the already set type and this changes the actual request - keyword */ - case CURLOPT_SERVICE_NAME: + return Curl_setstropt(data, STRING_SSH_PUBLIC_KEY, ptr); + case CURLOPT_SSH_PRIVATE_KEYFILE: /* - * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO + * Use this file instead of the $HOME/.ssh/id_dsa file */ - return Curl_setstropt(&s->str[STRING_SERVICE_NAME], ptr); - - case CURLOPT_HEADERDATA: + return Curl_setstropt(data, STRING_SSH_PRIVATE_KEY, ptr); + case CURLOPT_SSH_KEYDATA: /* - * Custom pointer to pass the header write callback function + * Custom client data to pass to the SSH keyfunc callback */ - s->writeheader = ptr; + s->ssh_keyfunc_userp = ptr; break; - case CURLOPT_READDATA: + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: /* - * FILE pointer to read the file to be uploaded from. Or possibly used as - * argument to the read callback. + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. */ - s->in_set = ptr; + return Curl_setstropt(data, STRING_SSH_HOST_PUBLIC_KEY_MD5, ptr); + 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, STRING_SSH_HOST_PUBLIC_KEY_SHA256, ptr); + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the filename to read known hosts from. + */ + return Curl_setstropt(data, STRING_SSH_KNOWNHOSTS, ptr); +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOSTKEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + s->ssh_hostkeyfunc_userp = ptr; break; - case CURLOPT_WRITEDATA: +#endif /* USE_LIBSSH2 */ + default: + return CURLE_UNKNOWN_OPTION; + } + return CURLE_OK; +} +#endif /* USE_SSH */ + +#ifndef CURL_DISABLE_FTP +static CURLcode setopt_cptr_ftp(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + CURLcode result = CURLE_OK; + struct UserDefined *s = &data->set; + switch(option) { + case CURLOPT_FTPPORT: /* - * FILE pointer to write to. Or possibly used as argument to the write - * callback. + * Use FTP PORT, this also specifies which IP address to use */ - s->out = ptr; + result = Curl_setstropt(data, STRING_FTPPORT, ptr); + s->ftp_use_port = !!CURL_EASY_STR(data, STRING_FTPPORT); break; - case CURLOPT_DEBUGDATA: + + case CURLOPT_FTP_ACCOUNT: + return Curl_setstropt(data, STRING_FTP_ACCOUNT, ptr); + + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + return Curl_setstropt(data, STRING_FTP_ALTERNATIVE_TO_USER, ptr); + + case CURLOPT_KRBLEVEL: + return CURLE_NOT_BUILT_IN; /* removed in 8.17.0 */ + case CURLOPT_CHUNK_DATA: + s->wildcardptr = ptr; + break; + case CURLOPT_FNMATCH_DATA: + s->fnmatch_data = ptr; + break; + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} +#endif /* !CURL_DISABLE_FTP */ + +static CURLcode setopt_cptr_net(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + switch(option) { + case CURLOPT_INTERFACE: /* - * Set to a void * that should receive all error writes. This - * defaults to CURLOPT_STDERR for normal operations. + * Set what interface or address/hostname to bind the socket to when + * performing an operation and thus what from-IP your connection will use. */ + return setstropt_interface(data, ptr); + +#ifdef USE_RESOLV_ARES + case CURLOPT_DNS_SERVERS: + return Curl_setstropt(data, STRING_DNS_SERVERS, ptr); + + case CURLOPT_DNS_INTERFACE: + return Curl_setstropt(data, STRING_DNS_INTERFACE, ptr); + + case CURLOPT_DNS_LOCAL_IP4: + return Curl_setstropt(data, STRING_DNS_LOCAL_IP4, ptr); + + case CURLOPT_DNS_LOCAL_IP6: + return Curl_setstropt(data, STRING_DNS_LOCAL_IP6, ptr); +#endif +#ifdef USE_UNIX_SOCKETS + case CURLOPT_UNIX_SOCKET_PATH: + data->set.abstract_unix_socket = FALSE; + return Curl_setstropt(data, STRING_UNIX_SOCKET_PATH, ptr); + + case CURLOPT_ABSTRACT_UNIX_SOCKET: + data->set.abstract_unix_socket = TRUE; + return Curl_setstropt(data, STRING_UNIX_SOCKET_PATH, ptr); +#endif +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_URL: + { + CURLcode result = Curl_setstropt(data, STRING_DOH, ptr); + data->set.doh = !!CURL_EASY_STR(data, STRING_DOH); + return result; + } +#endif + default: + return CURLE_UNKNOWN_OPTION; + } +} + +static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + CURLcode result = CURLE_OK; + struct UserDefined *s = &data->set; + + switch(option) { + case CURLOPT_REQUEST_TARGET: + return Curl_setstropt(data, STRING_TARGET, ptr); +#ifndef CURL_DISABLE_NETRC + case CURLOPT_NETRC_FILE: + return Curl_setstropt(data, STRING_NETRC_FILE, ptr); +#endif + case CURLOPT_CUSTOMREQUEST: + return Curl_setstropt(data, STRING_CUSTOMREQUEST, ptr); + + /* we do not set s->method = HTTPREQ_CUSTOM; here, we continue as if we + were using the already set type and this changes the actual request + keyword */ + case CURLOPT_SERVICE_NAME: + return Curl_setstropt(data, STRING_SERVICE_NAME, ptr); + + case CURLOPT_HEADERDATA: + s->writeheader = ptr; + break; + case CURLOPT_READDATA: + s->in_set = ptr; + break; + case CURLOPT_WRITEDATA: + s->out = ptr; + break; + case CURLOPT_DEBUGDATA: s->debugdata = ptr; break; case CURLOPT_PROGRESSDATA: - /* - * Custom client data to pass to the progress callback - */ s->progress_client = ptr; break; case CURLOPT_SEEKDATA: - /* - * Seek control callback. Might be NULL. - */ s->seek_client = ptr; break; case CURLOPT_IOCTLDATA: - /* - * I/O control data pointer. Might be NULL. - */ s->ioctl_client = ptr; break; -#if UNITY_CERTVERIFY - case CURLOPT_UNITY_CERTVERIFY_DATA: - /* - * Unity: userdata for CURLOPT_UNITY_CERTVERIFY_FUNCTION. Might be NULL. - */ -#ifdef USE_SSL - s->ssl.primary.unity_certverify_userp = ptr; - break; -#else - return CURLE_NOT_BUILT_IN; -#endif -#endif /* UNITY_CERTVERIFY */ - case CURLOPT_SSL_CTX_DATA: - /* - * Set an SSL_CTX callback parameter pointer - */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - s->ssl.fsslctxp = ptr; - break; - } - else -#endif - return CURLE_NOT_BUILT_IN; case CURLOPT_SOCKOPTDATA: - /* - * socket callback data pointer. Might be NULL. - */ s->sockopt_client = ptr; break; case CURLOPT_OPENSOCKETDATA: - /* - * socket callback data pointer. Might be NULL. - */ s->opensocket_client = ptr; break; case CURLOPT_RESOLVER_START_DATA: - /* - * resolver start callback data pointer. Might be NULL. - */ s->resolver_start_client = ptr; break; case CURLOPT_CLOSESOCKETDATA: - /* - * socket callback data pointer. Might be NULL. - */ s->closesocket_client = ptr; break; case CURLOPT_PREREQDATA: s->prereq_userp = ptr; break; case CURLOPT_ERRORBUFFER: - /* - * Error buffer provided by the caller to get the human readable error - * string in. - */ s->errorbuffer = ptr; break; - -#ifndef CURL_DISABLE_FTP - case CURLOPT_FTPPORT: - /* - * Use FTP PORT, this also specifies which IP address to use - */ - result = Curl_setstropt(&s->str[STRING_FTPPORT], ptr); - s->ftp_use_port = !!(s->str[STRING_FTPPORT]); - break; - - case CURLOPT_FTP_ACCOUNT: - return Curl_setstropt(&s->str[STRING_FTP_ACCOUNT], ptr); - - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - return Curl_setstropt(&s->str[STRING_FTP_ALTERNATIVE_TO_USER], ptr); - - case CURLOPT_KRBLEVEL: - return CURLE_NOT_BUILT_IN; /* removed in 8.17.0 */ - case CURLOPT_CHUNK_DATA: - s->wildcardptr = ptr; - break; - case CURLOPT_FNMATCH_DATA: - s->fnmatch_data = ptr; - break; -#endif case CURLOPT_URL: - /* - * The URL to fetch. - */ - result = Curl_setstropt(&s->str[STRING_SET_URL], ptr); - Curl_bufref_set(&data->state.url, s->str[STRING_SET_URL], 0, NULL); + result = Curl_setstropt(data, STRING_SET_URL, ptr); + Curl_bufref_set(&data->state.url, + CURL_EASY_STR(data, STRING_SET_URL), 0, NULL); break; - case CURLOPT_USERPWD: - /* - * user:password to use in the operation - */ - return setstropt_userpwd(ptr, &s->str[STRING_USERNAME], - &s->str[STRING_PASSWORD]); + case CURLOPT_USERPWD: { + char *u = NULL, *p = NULL; + result = setstropt_userpwd(ptr, &u, &p); + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_USERNAME, u); + u = NULL; + } + if(!result) { + result = CURL_EASY_STR_SETN(data, STRING_PASSWORD, p); + p = NULL; + } + curlx_free(u); + curlx_free(p); + return result; + } case CURLOPT_USERNAME: - /* - * authentication username to use in the operation - */ - return Curl_setstropt(&s->str[STRING_USERNAME], ptr); + return Curl_setstropt(data, STRING_USERNAME, ptr); case CURLOPT_PASSWORD: - /* - * authentication password to use in the operation - */ - return Curl_setstropt(&s->str[STRING_PASSWORD], ptr); + return Curl_setstropt(data, STRING_PASSWORD, ptr); case CURLOPT_LOGIN_OPTIONS: - /* - * authentication options to use in the operation - */ - return Curl_setstropt(&s->str[STRING_OPTIONS], ptr); + return Curl_setstropt(data, STRING_OPTIONS, ptr); case CURLOPT_XOAUTH2_BEARER: - /* - * OAuth 2.0 bearer token to use in the operation - */ - return Curl_setstropt(&s->str[STRING_BEARER], ptr); + return Curl_setstropt(data, STRING_BEARER, ptr); case CURLOPT_RANGE: - /* - * What range of the file you want to transfer - */ - return Curl_setstropt(&s->str[STRING_SET_RANGE], ptr); - case CURLOPT_SSLCERT: - /* - * String that holds filename of the SSL certificate to use - */ - return Curl_setstropt(&s->str[STRING_CERT], ptr); - case CURLOPT_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use - */ - return Curl_setstropt(&s->str[STRING_CERT_TYPE], ptr); - case CURLOPT_SSLKEY: - /* - * String that holds filename of the SSL key to use - */ - return Curl_setstropt(&s->str[STRING_KEY], ptr); - case CURLOPT_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use - */ - return Curl_setstropt(&s->str[STRING_KEY_TYPE], ptr); - case CURLOPT_KEYPASSWD: - /* - * String that holds the SSL or SSH private key password. - */ - return Curl_setstropt(&s->str[STRING_KEY_PASSWD], ptr); - case CURLOPT_SSLENGINE: - /* - * String that holds the SSL crypto engine. - */ - if(ptr && ptr[0]) { - result = Curl_setstropt(&s->str[STRING_SSL_ENGINE], ptr); - if(!result) { - result = Curl_ssl_set_engine(data, ptr); - } - } - break; - 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. - */ - return setstropt_interface(ptr, - &s->str[STRING_DEVICE], - &s->str[STRING_INTERFACE], - &s->str[STRING_BINDHOST]); - case CURLOPT_ISSUERCERT: - /* - * Set Issuer certificate file - * to check certificates issuer - */ - if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT)) - return Curl_setstropt(&s->str[STRING_SSL_ISSUERCERT], ptr); - return CURLE_NOT_BUILT_IN; + return Curl_setstropt(data, STRING_SET_RANGE, ptr); case CURLOPT_PRIVATE: - /* - * Set private data pointer. - */ s->private_data = ptr; break; -#ifdef USE_SSL - case CURLOPT_SSL_EC_CURVES: - /* - * Set accepted curves in SSL connection setup. - * Specify colon-delimited list of curve algorithm names. - */ - if(Curl_ssl_supports(data, SSLSUPP_SSL_EC_CURVES)) - return Curl_setstropt(&s->str[STRING_SSL_EC_CURVES], ptr); - return CURLE_NOT_BUILT_IN; - case CURLOPT_SSL_SIGNATURE_ALGORITHMS: - /* - * Set accepted signature algorithms. - * Specify colon-delimited list of signature scheme names. - */ - if(Curl_ssl_supports(data, SSLSUPP_SIGNATURE_ALGORITHMS)) - return Curl_setstropt(&s->str[STRING_SSL_SIGNATURE_ALGORITHMS], ptr); - return CURLE_NOT_BUILT_IN; - case CURLOPT_PINNEDPUBLICKEY: - /* - * Set pinned public key for SSL connection. - * Specify filename of the public key in DER format. - */ - if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - return Curl_setstropt(&s->str[STRING_SSL_PINNEDPUBLICKEY], ptr); - return CURLE_NOT_BUILT_IN; -#endif -#ifdef USE_SSH - case CURLOPT_SSH_PUBLIC_KEYFILE: - /* - * Use this file instead of the $HOME/.ssh/id_dsa.pub file - */ - return Curl_setstropt(&s->str[STRING_SSH_PUBLIC_KEY], ptr); - case CURLOPT_SSH_PRIVATE_KEYFILE: - /* - * Use this file instead of the $HOME/.ssh/id_dsa file - */ - return Curl_setstropt(&s->str[STRING_SSH_PRIVATE_KEY], ptr); - case CURLOPT_SSH_KEYDATA: - /* - * Custom client data to pass to the SSH keyfunc callback - */ - s->ssh_keyfunc_userp = ptr; - break; -#if defined(USE_LIBSSH2) || defined(USE_LIBSSH) - case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: - /* - * Option to allow for the MD5 of the host public key to be checked - * for validation purposes. - */ - return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_MD5], ptr); - case CURLOPT_SSH_KNOWNHOSTS: - /* - * Store the filename to read known hosts from. - */ - return Curl_setstropt(&s->str[STRING_SSH_KNOWNHOSTS], ptr); -#endif -#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(&s->str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], ptr); - case CURLOPT_SSH_HOSTKEYDATA: - /* - * Custom client data to pass to the SSH keyfunc callback - */ - s->ssh_hostkeyfunc_userp = ptr; - break; -#endif /* USE_LIBSSH2 */ -#endif /* USE_SSH */ case CURLOPT_PROTOCOLS_STR: if(ptr) { curl_prot_t protos; @@ -2354,91 +2403,36 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, break; case CURLOPT_DEFAULT_PROTOCOL: /* Set the protocol to use when the URL does not include any protocol */ - return Curl_setstropt(&s->str[STRING_DEFAULT_PROTOCOL], ptr); + return Curl_setstropt(data, STRING_DEFAULT_PROTOCOL, ptr); #ifndef CURL_DISABLE_SMTP case CURLOPT_MAIL_FROM: /* Set the SMTP mail originator */ - return Curl_setstropt(&s->str[STRING_MAIL_FROM], ptr); + return Curl_setstropt(data, STRING_MAIL_FROM, ptr); case CURLOPT_MAIL_AUTH: /* Set the SMTP auth originator */ - return Curl_setstropt(&s->str[STRING_MAIL_AUTH], ptr); + return Curl_setstropt(data, STRING_MAIL_AUTH, ptr); #endif case CURLOPT_SASL_AUTHZID: /* Authorization identity (identity to act as) */ - return Curl_setstropt(&s->str[STRING_SASL_AUTHZID], ptr); + return Curl_setstropt(data, 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 - */ - return Curl_setstropt(&s->str[STRING_RTSP_SESSION_ID], ptr); + return Curl_setstropt(data, 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. - */ - return Curl_setstropt(&s->str[STRING_RTSP_STREAM_URI], ptr); + return Curl_setstropt(data, STRING_RTSP_STREAM_URI, ptr); case CURLOPT_RTSP_TRANSPORT: - /* - * The content of the Transport: header for the RTSP request - */ - return Curl_setstropt(&s->str[STRING_RTSP_TRANSPORT], ptr); + return Curl_setstropt(data, STRING_RTSP_TRANSPORT, ptr); case CURLOPT_INTERLEAVEDATA: s->rtp_out = ptr; break; #endif /* !CURL_DISABLE_RTSP */ -#ifdef USE_TLS_SRP case CURLOPT_TLSAUTH_USERNAME: - return Curl_setstropt(&s->str[STRING_TLSAUTH_USERNAME], ptr); case CURLOPT_TLSAUTH_PASSWORD: - return Curl_setstropt(&s->str[STRING_TLSAUTH_PASSWORD], ptr); case CURLOPT_TLSAUTH_TYPE: - if(ptr && !curl_strequal(ptr, "SRP")) - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; -#ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_USERNAME: - return Curl_setstropt(&s->str[STRING_TLSAUTH_USERNAME_PROXY], ptr); case CURLOPT_PROXY_TLSAUTH_PASSWORD: - return Curl_setstropt(&s->str[STRING_TLSAUTH_PASSWORD_PROXY], ptr); case CURLOPT_PROXY_TLSAUTH_TYPE: - if(ptr && !curl_strequal(ptr, "SRP")) - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; -#endif -#endif -#ifdef USE_RESOLV_ARES - case CURLOPT_DNS_SERVERS: - return Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr); - - case CURLOPT_DNS_INTERFACE: - return Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr); - - case CURLOPT_DNS_LOCAL_IP4: - return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr); - - case CURLOPT_DNS_LOCAL_IP6: - return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr); - -#endif -#ifdef USE_UNIX_SOCKETS - case CURLOPT_UNIX_SOCKET_PATH: - s->abstract_unix_socket = FALSE; - return Curl_setstropt(&s->str[STRING_UNIX_SOCKET_PATH], ptr); - - case CURLOPT_ABSTRACT_UNIX_SOCKET: - s->abstract_unix_socket = TRUE; - return Curl_setstropt(&s->str[STRING_UNIX_SOCKET_PATH], ptr); - -#endif - -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_URL: - result = Curl_setstropt(&s->str[STRING_DOH], ptr); - s->doh = !!(s->str[STRING_DOH]); - break; -#endif + return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_HSTS case CURLOPT_HSTSREADDATA: s->hsts_read_userp = ptr; @@ -2454,7 +2448,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, return CURLE_OUT_OF_MEMORY; } if(ptr) { - result = Curl_setstropt(&s->str[STRING_HSTS], ptr); + result = Curl_setstropt(data, STRING_HSTS, ptr); if(result) return result; /* this needs to build a list of filenames to read from, so that it can @@ -2486,51 +2480,56 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, if(!data->asi) return CURLE_OUT_OF_MEMORY; } - result = Curl_setstropt(&s->str[STRING_ALTSVC], ptr); + result = Curl_setstropt(data, STRING_ALTSVC, ptr); if(result) break; if(ptr) return Curl_altsvc_load(data->asi, ptr); break; #endif /* !CURL_DISABLE_ALTSVC */ -#ifdef USE_ECH - case CURLOPT_ECH: { - size_t plen = 0; - - if(!ptr) { - s->tls_ech = CURLECH_DISABLE; - break; - } - plen = strlen(ptr); - if(plen > CURL_MAX_INPUT_LENGTH) { - s->tls_ech = CURLECH_DISABLE; - return CURLE_BAD_FUNCTION_ARGUMENT; - } - /* set tls_ech flag value, preserving CLA_CFG bit */ - if(!strcmp(ptr, "false")) - s->tls_ech = (s->tls_ech & CURLECH_CLA_CFG) | CURLECH_DISABLE; - else if(!strcmp(ptr, "grease")) - s->tls_ech = (s->tls_ech & CURLECH_CLA_CFG) | CURLECH_GREASE; - else if(!strcmp(ptr, "true")) - s->tls_ech = (s->tls_ech & CURLECH_CLA_CFG) | CURLECH_ENABLE; - else if(!strcmp(ptr, "hard")) - s->tls_ech = (s->tls_ech & CURLECH_CLA_CFG) | CURLECH_HARD; - else if(plen > 5 && !strncmp(ptr, "ecl:", 4)) { - result = Curl_setstropt(&s->str[STRING_ECH_CONFIG], ptr + 4); - if(!result) - s->tls_ech |= CURLECH_CLA_CFG; - } - else if(plen > 4 && !strncmp(ptr, "pn:", 3)) - result = Curl_setstropt(&s->str[STRING_ECH_PUBLIC], ptr + 3); - break; - } -#endif + case CURLOPT_ECH: + return setopt_ech(data, ptr); default: return CURLE_UNKNOWN_OPTION; } return result; } +static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + typedef CURLcode (*ptrfunc)(struct Curl_easy *data, CURLoption option, + char *ptr); + /* Order by likeliness */ + static const ptrfunc setopt_call[] = { + setopt_cptr_misc, +#if defined(USE_SSL) || defined(USE_SSH) + setopt_cptr_ssl, +#endif +#ifndef CURL_DISABLE_PROXY + setopt_cptr_proxy, +#endif + setopt_cptr_net, +#ifndef CURL_DISABLE_FTP + setopt_cptr_ftp, +#endif +#ifdef USE_SSH + setopt_cptr_ssh, +#endif +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) + setopt_cptr_http_mqtt, +#endif + }; + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(setopt_call); i++) { + CURLcode result = setopt_call[i](data, option, ptr); + if(result != CURLE_UNKNOWN_OPTION) + return result; + } + return CURLE_UNKNOWN_OPTION; +} + static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, va_list param) { @@ -2756,10 +2755,9 @@ static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, if(offt < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(s->postfieldsize < offt && - s->postfields == s->str[STRING_COPYPOSTFIELDS]) { + if(s->postfieldsize < offt && s->str_copypostfields) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - curlx_safefree(s->str[STRING_COPYPOSTFIELDS]); + curlx_safefree(s->str_copypostfields); s->postfields = NULL; } s->postfieldsize = offt; @@ -2775,13 +2773,15 @@ static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, break; case CURLOPT_MAX_SEND_SPEED_LARGE: /* - * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE + * When transfer uploads are faster than CURLOPT_MAX_SEND_SPEED_LARGE * bytes per second the transfer is throttled.. */ if(offt < 0) return CURLE_BAD_FUNCTION_ARGUMENT; s->max_send_speed = offt; - Curl_rlimit_init(&data->progress.ul.rlimit, offt, offt, + /* use minimal burst rate of 32k. some protocol batch IO */ + Curl_rlimit_init(&data->progress.ul.rlimit, offt, + CURLMAX(offt, (32 * 1024)), Curl_pgrs_now(data)); break; case CURLOPT_MAX_RECV_SPEED_LARGE: @@ -2792,7 +2792,9 @@ static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, if(offt < 0) return CURLE_BAD_FUNCTION_ARGUMENT; s->max_recv_speed = offt; - Curl_rlimit_init(&data->progress.dl.rlimit, offt, offt, + /* use minimal burst rate of 32k. some protocol batch IO */ + Curl_rlimit_init(&data->progress.dl.rlimit, offt, + CURLMAX(offt, (32 * 1024)), Curl_pgrs_now(data)); break; case CURLOPT_RESUME_FROM_LARGE: @@ -2845,15 +2847,20 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, * Specify entire PEM of the CA certificate */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) - return Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob); + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { + CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob); + s->proxy_ssl.custom_cablob = !!s->blobs[BLOB_CAINFO_PROXY]; + return result; + } #endif return CURLE_NOT_BUILT_IN; case CURLOPT_PROXY_ISSUERCERT_BLOB: /* * Blob that holds Issuer certificate to check certificates issuer */ - return Curl_setblobopt(&s->blobs[BLOB_SSL_ISSUERCERT_PROXY], blob); + if(Curl_ssl_supports(data, SSLSUPP_ISSUERCERT_BLOB)) + return Curl_setblobopt(&s->blobs[BLOB_SSL_ISSUERCERT_PROXY], blob); + return CURLE_NOT_BUILT_IN; #endif case CURLOPT_SSLKEY_BLOB: /* @@ -2867,8 +2874,9 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, */ #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { - s->ssl.custom_cablob = TRUE; - return Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob); + CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob); + s->ssl.custom_cablob = !!s->blobs[BLOB_CAINFO]; + return result; } #endif return CURLE_NOT_BUILT_IN; @@ -2913,10 +2921,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_MIMEPOST: /* curl_mime * */ case CURLOPT_STDERR: /* FILE * */ case CURLOPT_SHARE: /* CURLSH * */ - case CURLOPT_STREAM_DEPENDS: /* CURL * */ - case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ case CURLOPT_CURLU: /* CURLU * */ return setopt_pointers(data, option, param); + case CURLOPT_STREAM_DEPENDS: /* CURL * */ + case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ + return CURLE_OK; default: break; } @@ -2937,23 +2946,24 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * NOTE: This is one of few API functions that are allowed to be called from * within a callback. */ - #undef curl_easy_setopt CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) { - va_list arg; + struct Curl_eapi_guard guard; CURLcode result; - struct Curl_easy *data = curl; - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, easy_setopt, &result)) { + struct Curl_easy *data = curl; + va_list arg; - va_start(arg, option); + va_start(arg, option); - result = Curl_vsetopt(data, option, arg); + result = Curl_vsetopt(data, option, arg); - va_end(arg); - if(result == CURLE_BAD_FUNCTION_ARGUMENT) - failf(data, "setopt 0x%x got bad argument", option); + va_end(arg); + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + failf(data, "setopt 0x%x got bad argument", (unsigned int)option); + } + CURL_EAPI_LEAVE(&guard); return result; } diff --git a/lib/setopt.h b/lib/setopt.h index c421f5c5e5bc..e386262f88cc 100644 --- a/lib/setopt.h +++ b/lib/setopt.h @@ -31,7 +31,8 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, #define Curl_setopt_SSLVERSION(a, b, c) CURLE_NOT_BUILT_IN #endif -CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; +CURLcode Curl_setstropt(struct Curl_easy *data, + enum dupstring id, const char *s) WARN_UNUSED_RESULT; CURLcode Curl_setblobopt(struct curl_blob **blobp, const struct curl_blob *blob) WARN_UNUSED_RESULT; CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) diff --git a/lib/sha256.c b/lib/sha256.c index d97f45f05f9b..89b96017bdc2 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -32,7 +32,7 @@ #ifdef USE_MBEDTLS #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #endif @@ -43,8 +43,8 @@ * 2. USE_WOLFSSL * 3. USE_GNUTLS * 4. USE_MBEDTLS - * 5. USE_COMMON_CRYPTO - * 6. USE_WIN32_CRYPTO + * 5. USE_WIN32_CRYPTO + * 6. USE_COMMON_CRYPTO * * This ensures that the same SSL branch gets activated throughout this source * file even if multiple backends are enabled at the same time. @@ -61,12 +61,12 @@ typedef struct ossl_sha256_ctx my_sha256_ctx; static CURLcode my_sha256_init(void *in) { my_sha256_ctx *ctx = (my_sha256_ctx *)in; - ctx->openssl_ctx = EVP_MD_CTX_create(); + ctx->openssl_ctx = EVP_MD_CTX_new(); if(!ctx->openssl_ctx) return CURLE_OUT_OF_MEMORY; if(!EVP_DigestInit_ex(ctx->openssl_ctx, EVP_sha256(), NULL)) { - EVP_MD_CTX_destroy(ctx->openssl_ctx); + EVP_MD_CTX_free(ctx->openssl_ctx); return CURLE_FAILED_INIT; } return CURLE_OK; @@ -84,7 +84,7 @@ 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); + EVP_MD_CTX_free(ctx->openssl_ctx); } #elif defined(USE_WOLFSSL) @@ -113,7 +113,8 @@ static void my_sha256_final(unsigned char *digest, void *in) } #elif defined(USE_GNUTLS) -#include +#include +#include typedef struct sha256_ctx my_sha256_ctx; @@ -132,19 +133,24 @@ static void my_sha256_update(void *ctx, static void my_sha256_final(unsigned char *digest, void *ctx) { +#if NETTLE_VERSION_MAJOR >= 4 + sha256_digest(ctx, digest); +#else sha256_digest(ctx, SHA256_DIGEST_SIZE, digest); +#endif } #elif defined(USE_MBEDTLS) && \ - defined(PSA_WANT_ALG_SHA_256) && PSA_WANT_ALG_SHA_256 /* mbedTLS 4+ */ + defined(PSA_WANT_ALG_SHA_256) && PSA_WANT_ALG_SHA_256 #include typedef psa_hash_operation_t my_sha256_ctx; static CURLcode my_sha256_init(void *ctx) { - memset(ctx, 0, sizeof(my_sha256_ctx)); - if(psa_hash_setup(ctx, PSA_ALG_SHA_256) != PSA_SUCCESS) + psa_hash_operation_t *pctx = (psa_hash_operation_t *)ctx; + *pctx = psa_hash_operation_init(); + if(psa_hash_setup(pctx, PSA_ALG_SHA_256) != PSA_SUCCESS) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -376,12 +382,11 @@ static CURLcode my_sha256_init(void *in) return CURLE_OK; } -/* - Process a block of memory though the hash +/* Process a block of memory though the hash @param md The hash state @param in The data to hash @param inlen The length of the data (octets) -*/ + */ static void my_sha256_update(void *ctx, const unsigned char *in, unsigned int len) @@ -416,12 +421,11 @@ static void my_sha256_update(void *ctx, } } -/* - Terminate the hash to get the digest +/* Terminate the hash to get the digest @param md The hash state @param out [out] The destination of the hash (32 bytes) @return 0 if successful -*/ + */ static void my_sha256_final(unsigned char *out, void *ctx) { struct sha256_state *md = ctx; diff --git a/lib/smb.c b/lib/smb.c index 6a97d2e00685..4299190ff577 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -61,7 +61,7 @@ enum smb_conn_state { /* SMB connection data, kept at connection */ struct smb_conn { enum smb_conn_state state; - char *user; + const char *user; char *domain; char *share; unsigned char challenge[8]; @@ -467,14 +467,15 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - char *slash; + const char *slash; + const char *user = Curl_creds_user(conn->creds); (void)done; if(!smbc) return CURLE_FAILED_INIT; /* Check we have a username and password to authenticate with */ - if(!data->state.aptr.user) + if(!Curl_creds_has_user(data->state.creds)) return CURLE_LOGIN_DENIED; /* Initialize the connection state */ @@ -487,20 +488,20 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) return CURLE_OUT_OF_MEMORY; /* Parse the username, domain, and password */ - slash = strchr(conn->user, '/'); + slash = strchr(user, '/'); if(!slash) - slash = strchr(conn->user, '\\'); + slash = strchr(user, '\\'); if(slash) { smbc->user = slash + 1; - smbc->domain = curlx_strdup(conn->user); + smbc->domain = curlx_strdup(user); if(!smbc->domain) return CURLE_OUT_OF_MEMORY; - smbc->domain[slash - conn->user] = 0; + smbc->domain[slash - user] = 0; } else { - smbc->user = conn->user; - smbc->domain = curlx_strdup(conn->host.name); + smbc->user = user; + smbc->domain = curlx_strdup(conn->origin->hostname); if(!smbc->domain) return CURLE_OUT_OF_MEMORY; } @@ -653,9 +654,10 @@ 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"; + static const char msg[] = "\x00\x0c\x00\x02NT LM 0.12"; - return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, 15); + return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, + sizeof(msg)); } static CURLcode smb_send_setup(struct Curl_easy *data) @@ -670,19 +672,20 @@ static CURLcode smb_send_setup(struct Curl_easy *data) unsigned char nt_hash[21]; unsigned char nt[24]; size_t byte_count; + const char *passwd = Curl_creds_passwd(conn->creds); if(!smbc || !req) return CURLE_FAILED_INIT; byte_count = sizeof(lm) + sizeof(nt) + strlen(smbc->user) + strlen(smbc->domain) + - strlen(CURL_OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ + CURL_CSTRLEN(CURL_OS) + CURL_CSTRLEN(CLIENTNAME) + 4; /* 4 null chars */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - Curl_ntlm_core_mk_lm_hash(conn->passwd, lm_hash); + Curl_ntlm_core_mk_lm_hash(passwd, lm_hash); Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); - Curl_ntlm_core_mk_nt_hash(conn->passwd, nt_hash); + Curl_ntlm_core_mk_nt_hash(passwd, nt_hash); Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); @@ -720,8 +723,9 @@ static CURLcode smb_send_tree_connect(struct Curl_easy *data, struct smb_tree_connect msg; struct connectdata *conn = data->conn; char *p = msg.bytes; - const size_t byte_count = strlen(conn->host.name) + strlen(smbc->share) + - strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ + const size_t byte_count = strlen(conn->origin->hostname) + + strlen(smbc->share) + + CURL_CSTRLEN(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; @@ -735,7 +739,7 @@ static CURLcode smb_send_tree_connect(struct Curl_easy *data, "\\\\%s\\" /* hostname */ "%s%c" /* share */ "%s", /* service */ - conn->host.name, smbc->share, 0, SERVICENAME); + conn->origin->hostname, smbc->share, 0, SERVICENAME); p++; /* count the final null-termination */ DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); msg.byte_count = smb_swap16((unsigned short)byte_count); @@ -909,7 +913,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) result = smb_send_negotiate(data, smbc, req); if(result) { - connclose(conn, "SMB: failed to send negotiate message"); + CURL_TRC_M(data, "SMB: failed to send negotiate message"); + connclose(conn); return result; } @@ -919,7 +924,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) /* Send the previous message and check for a response */ result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { - connclose(conn, "SMB: failed to communicate"); + CURL_TRC_M(data, "SMB: failed to communicate"); + connclose(conn); return result; } @@ -932,7 +938,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) case SMB_NEGOTIATE: if((smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) || h->status) { - connclose(conn, "SMB: negotiation failed"); + CURL_TRC_M(data, "SMB: negotiation failed"); + connclose(conn); return CURLE_COULDNT_CONNECT; } nrsp = msg; @@ -949,7 +956,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) smbc->session_key = smb_swap32(nrsp->session_key); result = smb_send_setup(data); if(result) { - connclose(conn, "SMB: failed to send setup message"); + CURL_TRC_M(data, "SMB: failed to send setup message"); + connclose(conn); return result; } conn_state(data, smbc, SMB_SETUP); @@ -957,7 +965,8 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) case SMB_SETUP: if(h->status) { - connclose(conn, "SMB: authentication failed"); + CURL_TRC_M(data, "SMB: authentication failed"); + connclose(conn); return CURLE_LOGIN_DENIED; } smbc->uid = smb_swap16(h->uid); @@ -1004,8 +1013,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); struct smb_header *h; enum smb_req_state next_state = SMB_DONE; - unsigned short len; - unsigned short off; + size_t len; + size_t off; CURLcode result; void *msg = NULL; const struct smb_nt_create_response *smb_m; @@ -1022,7 +1031,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) if(req->state == SMB_REQUESTING) { result = smb_send_tree_connect(data, smbc, req); if(result) { - connclose(conn, "SMB: failed to send tree connect message"); + CURL_TRC_M(data, "SMB: failed to send tree connect message"); + connclose(conn); return result; } @@ -1032,7 +1042,8 @@ 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, smbc, &msg); if(result && result != CURLE_AGAIN) { - connclose(conn, "SMB: failed to communicate"); + CURL_TRC_M(data, "SMB: failed to communicate"); + connclose(conn); return result; } @@ -1177,7 +1188,8 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) } if(result) { - connclose(conn, "SMB: failed to send message"); + CURL_TRC_M(data, "SMB: failed to send message"); + connclose(conn); return result; } @@ -1228,4 +1240,4 @@ const struct Curl_protocol Curl_protocol_smb = { ZERO_NULL, /* follow */ }; -#endif /* CURL_ENABLE_SMB && USE_CURL_NTLM_CORE && SIZEOF_CURL_OFF_T > 4 */ +#endif /* CURL_ENABLE_SMB && USE_CURL_NTLM_CORE */ diff --git a/lib/smtp.c b/lib/smtp.c index b5c425cd7c18..50e2af400a10 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -60,7 +60,6 @@ #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "escape.h" @@ -182,7 +181,7 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn, static CURLcode smtp_parse_url_path(struct Curl_easy *data, struct smtp_conn *smtpc) { - /* The SMTP struct is already initialised in smtp_connect() */ + /* The SMTP struct is already initialized in smtp_connect() */ const char *path = &data->state.up.path[1]; /* skip leading path */ char localhost[HOSTNAME_MAX + 1]; @@ -208,7 +207,7 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data, struct SMTP *smtp) { CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + const char *custom = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); /* URL decode the custom request */ if(custom) @@ -250,16 +249,26 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data, * calling function deems it to be) then the input will be returned in * the address part with the hostname being NULL. */ -static CURLcode smtp_parse_address(const char *fqma, char **address, - struct hostname *host, const char **suffix) +static CURLcode smtp_parse_address(struct Curl_easy *data, const char *fqma, + char **address, struct hostname *host, + const char **suffix) { CURLcode result = CURLE_OK; size_t length; char *addressend; + char *dup; + + /* A CR or LF in the address ends up verbatim in the MAIL FROM/RCPT TO + command line, so a crafted address could smuggle further SMTP commands + onto the wire. Reject it before the command is built. */ + if(strpbrk(fqma, "\r\n")) { + failf(data, "Refusing to send email address with a CR or LF"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } /* Duplicate the fully qualified email address so we can manipulate it, ensuring it does not contain the delimiters if specified */ - char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); + dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); if(!dup) return CURLE_OUT_OF_MEMORY; @@ -347,7 +356,7 @@ static CURLcode cr_eob_read(struct Curl_easy *data, /* Get more and convert it when needed */ result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); CURL_TRC_SMTP(data, "cr_eob_read, next_read(len=%zu) -> %d, %zu eos=%d", - blen, result, nread, eos); + blen, (int)result, nread, eos); if(result) return result; @@ -432,7 +441,7 @@ static CURLcode cr_eob_read(struct Curl_easy *data, } *peos = (bool)ctx->eos; DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zu, %d", - blen, result, *pnread, *peos)); + blen, (int)result, *pnread, *peos)); return result; } @@ -608,7 +617,7 @@ static void smtp_state(struct Curl_easy *data, * * smtp_perform_ehlo() * - * Sends the EHLO command to not only initialise communication with the ESMTP + * Sends the EHLO command to not only initialize 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, @@ -635,7 +644,7 @@ static CURLcode smtp_perform_ehlo(struct Curl_easy *data, * * smtp_perform_helo() * - * Sends the HELO command to initialise communication with the SMTP server. + * Sends the HELO command to initialize communication with the SMTP server. */ static CURLcode smtp_perform_helo(struct Curl_easy *data, struct smtp_conn *smtpc) @@ -689,7 +698,8 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS); if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler and SMTP state */ @@ -699,7 +709,7 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, DEBUGASSERT(!smtpc->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d", - result, ssldone)); + (int)result, ssldone)); if(!result && ssldone) { smtpc->ssldone = ssldone; /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */ @@ -735,7 +745,8 @@ static CURLcode smtp_perform_auth(struct Curl_easy *data, if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", + mech, *ir ? ir : "="); } else { /* Send the AUTH command */ @@ -841,7 +852,7 @@ static CURLcode smtp_perform_command(struct Curl_easy *data, /* 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, + result = smtp_parse_address(data, smtp->rcpt->data, &address, &host, &suffix); if(result) return result; @@ -867,7 +878,7 @@ 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 = (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, &smtpc->pp, @@ -901,6 +912,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, char *from = NULL; char *auth = NULL; char *size = NULL; + const char *str; CURLcode result = CURLE_OK; /* We notify the server we are sending UTF-8 data if a) it supports the @@ -910,15 +922,15 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, bool utf8 = FALSE; /* Calculate the FROM parameter */ - if(data->set.str[STRING_MAIL_FROM]) { + str = CURL_EASY_STR(data, STRING_MAIL_FROM); + if(str) { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; const char *suffix = ""; /* 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, &suffix); + result = smtp_parse_address(data, str, &address, &host, &suffix); if(result) goto out; @@ -951,16 +963,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, } /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { - if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { + str = CURL_EASY_STR(data, STRING_MAIL_AUTH); + if(str && smtpc->sasl.authused) { + if(str[0] != '\0') { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; const char *suffix = ""; /* 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, &suffix); + result = smtp_parse_address(data, str, &address, &host, &suffix); if(result) goto out; @@ -1096,7 +1108,7 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, /* 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, + result = smtp_parse_address(data, smtp->rcpt->data, &address, &host, &suffix); if(result) return result; @@ -1411,8 +1423,8 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, is_smtp_err = (smtpcode / 100 != 2); - /* If there is multiple RCPT TO to be issued, it is possible to ignore errors - and proceed with only the valid addresses. */ + /* If there are multiple RCPT TO commands to issue, 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); if(is_smtp_err) { @@ -1645,7 +1657,7 @@ static const struct SASLproto saslsmtp = { smtp_continue_auth, /* Send authentication continuation */ smtp_cancel_auth, /* Cancel authentication */ smtp_get_message, /* Get SASL response message */ - 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - CRLF */ 334, /* Code received when continuation is expected */ 235, /* Code to receive upon authentication success */ SASL_AUTH_DEFAULT, /* Default mechanisms */ @@ -1677,7 +1689,7 @@ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) /* Initialize the SASL storage */ Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); - /* Initialise the pingpong layer */ + /* Initialize the pingpong layer */ Curl_pp_init(&smtpc->pp, Curl_pgrs_now(data)); /* Parse the URL options */ @@ -1727,7 +1739,8 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, curlx_safefree(smtp->custom); if(status) { - connclose(conn, "SMTP done with bad status"); /* marked for closure */ + CURL_TRC_M(data, "SMTP done with bad status"); + connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } else if(!data->set.connect_only && data->set.mail_rcpt && @@ -1742,7 +1755,7 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, /* 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); + (int)status, premature, (int)result); return result; } @@ -1803,7 +1816,7 @@ static CURLcode smtp_perform(struct Curl_easy *data, out: CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", - result, *connected, *dophase_done); + (int)result, *connected, *dophase_done); return result; } @@ -1852,7 +1865,7 @@ static CURLcode smtp_regular_transfer(struct Curl_easy *data, result = smtp_dophase_done(data, smtp, connected); CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", - result, *dophase_done); + (int)result, *dophase_done); return result; } @@ -1884,7 +1897,7 @@ static CURLcode smtp_do(struct Curl_easy *data, bool *done) return result; result = smtp_regular_transfer(data, smtpc, smtp, done); - CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); + CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", (int)result, *done); return result; } @@ -1935,7 +1948,8 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) DEBUGF(infof(data, "DO phase is complete")); } - CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); + CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", (int)result, + *dophase_done); return result; } @@ -1977,7 +1991,7 @@ static CURLcode smtp_setup_connection(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; out: - CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); + CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", (int)result); return result; } diff --git a/lib/sockaddr.h b/lib/sockaddr.h index 2b0333508763..916360d08954 100644 --- a/lib/sockaddr.h +++ b/lib/sockaddr.h @@ -40,4 +40,22 @@ struct Curl_sockaddr_storage { } buffer; }; +/* + * The Curl_sockaddr_ex structure is libcurl's external API curl_sockaddr + * structure with enough space available to directly hold any + * protocol-specific address structures. The variable declared here will be + * used to pass / receive data to/from the fopensocket callback if this has + * been set, before that, it is initialized from parameters. + */ +struct Curl_sockaddr_ex { + int family; + int socktype; + int protocol; + unsigned int addrlen; + union { + struct sockaddr sa; + struct Curl_sockaddr_storage buf; + } addr; +}; + #endif /* HEADER_CURL_SOCKADDR_H */ diff --git a/lib/socketpair.c b/lib/socketpair.c index 76b959dd4afc..d4ac66e95ebe 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -96,14 +96,14 @@ static int wakeup_socketpair(curl_socket_t socks[2], bool nonblocking) #ifdef SOCK_CLOEXEC type |= SOCK_CLOEXEC; #endif -#ifdef SOCK_NONBLOCK +#ifdef CURL_USE_SOCK_NONBLOCK if(nonblocking) type |= SOCK_NONBLOCK; #endif if(CURL_SOCKETPAIR(AF_UNIX, type, 0, socks)) return -1; -#ifndef SOCK_NONBLOCK +#ifndef CURL_USE_SOCK_NONBLOCK if(nonblocking) { if(curlx_nonblock(socks[0], TRUE) < 0 || curlx_nonblock(socks[1], TRUE) < 0) { @@ -232,16 +232,9 @@ static int wakeup_inet(curl_socket_t socks[2], bool nonblocking) /* Do not block forever */ if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000)) goto error; - 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) || (SOCKEINPROGRESS == sockerr) + if(SOCK_EAGAIN(sockerr) +#ifndef USE_WINSOCK + || (sockerr == SOCKEINTR) || (sockerr == SOCKEINPROGRESS) #endif ) { continue; @@ -309,7 +302,7 @@ int Curl_wakeup_init(curl_socket_t socks[2], bool nonblocking) int Curl_wakeup_signal(curl_socket_t socks[2]) { - int err = 0; + int sockerr = 0; #ifdef USE_EVENTFD const uint64_t buf[1] = { 1 }; #else @@ -317,22 +310,19 @@ int Curl_wakeup_signal(curl_socket_t socks[2]) #endif while(1) { - err = 0; + sockerr = 0; if(wakeup_write(socks[1], buf, sizeof(buf)) < 0) { - err = SOCKERRNO; -#ifdef USE_WINSOCK - if(err == SOCKEWOULDBLOCK) - err = 0; /* wakeup is already ongoing */ -#else - if(SOCKEINTR == err) + sockerr = SOCKERRNO; +#ifndef USE_WINSOCK + if(sockerr == SOCKEINTR) continue; - if((err == SOCKEWOULDBLOCK) || (err == EAGAIN)) - err = 0; /* wakeup is already ongoing */ #endif + if(SOCK_EAGAIN(sockerr)) + sockerr = 0; /* wakeup is already ongoing */ } break; } - return err; + return sockerr; } CURLcode Curl_wakeup_consume(curl_socket_t socks[2], bool all) @@ -346,15 +336,13 @@ CURLcode Curl_wakeup_consume(curl_socket_t socks[2], bool all) if(!rc) break; else if(rc < 0) { -#ifdef USE_WINSOCK - if(SOCKERRNO == SOCKEWOULDBLOCK) - break; -#else - if(SOCKEINTR == SOCKERRNO) + int sockerr = SOCKERRNO; +#ifndef USE_WINSOCK + if(sockerr == SOCKEINTR) continue; - if((SOCKERRNO == SOCKEWOULDBLOCK) || (SOCKERRNO == EAGAIN)) - break; #endif + if(SOCK_EAGAIN(sockerr)) + break; result = CURLE_READ_ERROR; break; } diff --git a/lib/socketpair.h b/lib/socketpair.h index 0427e72fc53d..fd08c879dac6 100644 --- a/lib/socketpair.h +++ b/lib/socketpair.h @@ -27,7 +27,7 @@ #ifndef CURL_DISABLE_SOCKETPAIR -/* return < 0 for failure to initialise */ +/* return < 0 for failure to initialize */ int Curl_wakeup_init(curl_socket_t socks[2], bool nonblocking); void Curl_wakeup_destroy(curl_socket_t socks[2]); diff --git a/lib/socks.c b/lib/socks.c index b3da5be0a367..949f20c13539 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -38,9 +38,9 @@ #include "curl_trc.h" #include "select.h" #include "cfilters.h" -#include "cf-dns.h" #include "connect.h" #include "socks.h" +#include "vdns/cf-dns.h" #include "curlx/inet_pton.h" /* for the (SOCKS) connect state machine */ @@ -94,22 +94,18 @@ static const char * const cf_socks_statename[] = { #define SOCKS_CHUNK_SIZE 1024 #define SOCKS_CHUNKS 1 - struct socks_ctx { enum socks_state_t state; struct bufq iobuf; - uint16_t remote_port; - const char *user; - const char *passwd; + struct Curl_peer *dest; + struct Curl_creds *creds; CURLproxycode presult; uint32_t resolv_id; uint8_t ip_version; uint8_t proxy_type; unsigned char version; BIT(resolve_local); - BIT(start_resolving); BIT(socks4a); - char hostname[1]; }; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) @@ -273,8 +269,8 @@ static CURLproxycode socks4_req_add_hd(struct socks_ctx *sx, (void)data; buf[0] = 4; /* version (SOCKS4) */ buf[1] = 1; /* connect */ - buf[2] = (unsigned char)((sx->remote_port >> 8) & 0xffU); /* MSB */ - buf[3] = (unsigned char)(sx->remote_port & 0xffU); /* LSB */ + buf[2] = (unsigned char)((sx->dest->port >> 8) & 0xffU); /* MSB */ + buf[3] = (unsigned char)(sx->dest->port & 0xffU); /* LSB */ result = Curl_bufq_write(&sx->iobuf, buf, 4, &nwritten); if(result || (nwritten != 4)) @@ -288,8 +284,8 @@ static CURLproxycode socks4_req_add_user(struct socks_ctx *sx, CURLcode result; size_t nwritten; - if(sx->user) { - size_t plen = strlen(sx->user); + if(sx->creds) { + size_t plen = strlen(sx->creds->user); if(plen > 255) { /* there is no real size limit to this field in the protocol, but SOCKS5 limits the proxy user field to 255 bytes and it seems likely @@ -298,7 +294,7 @@ static CURLproxycode socks4_req_add_user(struct socks_ctx *sx, return CURLPX_LONG_USER; } /* add proxy name WITH trailing zero */ - result = Curl_bufq_cwrite(&sx->iobuf, sx->user, plen + 1, + result = Curl_bufq_cwrite(&sx->iobuf, sx->creds->user, plen + 1, &nwritten); if(result || (nwritten != (plen + 1))) return CURLPX_SEND_REQUEST; @@ -321,32 +317,19 @@ static CURLproxycode socks4_resolving(struct socks_ctx *sx, const struct Curl_addrinfo *ai = NULL; CURLcode result; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", - sx->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } - ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(ai) { struct sockaddr_in *saddr_in; char ipbuf[64]; @@ -364,8 +347,8 @@ static CURLproxycode socks4_resolving(struct socks_ctx *sx, return CURLPX_SEND_REQUEST; } else { - /* No ipv4 address resolved */ - failf(data, "SOCKS4 connection to %s not supported", sx->hostname); + /* No IPv4 address resolved */ + failf(data, "SOCKS4 connection to %s not supported", sx->dest->hostname); return CURLPX_RESOLVE_HOST; } @@ -479,15 +462,14 @@ static CURLproxycode socks4_connect(struct Curl_cfilter *cf, case SOCKS4_ST_START: Curl_bufq_reset(&sx->iobuf); - sx->start_resolving = FALSE; sx->socks4a = (sx->proxy_type == CURLPROXY_SOCKS4A); - sx->resolve_local = !sx->socks4a; sx->presult = CURLPX_OK; /* SOCKS4 can only do IPv4, insist! */ sx->ip_version = CURL_IPRESOLVE_V4; CURL_TRC_CF(data, cf, "SOCKS4%s connecting to %s:%u", - sx->socks4a ? "a" : "", sx->hostname, sx->remote_port); + sx->socks4a ? "a" : "", + sx->dest->hostname, sx->dest->port); /* * Compose socks4 request @@ -508,7 +490,7 @@ static CURLproxycode socks4_connect(struct Curl_cfilter *cf, /* socks4a, not resolving locally, sends the hostname. * add an invalid address + user + hostname */ unsigned char buf[4] = { 0, 0, 0, 1 }; - size_t hlen = strlen(sx->hostname) + 1; /* including NUL */ + size_t hlen = strlen(sx->dest->hostname) + 1; /* including NUL */ if(hlen > 255) { failf(data, "SOCKS4: too long hostname"); @@ -520,14 +502,14 @@ static CURLproxycode socks4_connect(struct Curl_cfilter *cf, presult = socks4_req_add_user(sx, data); if(presult) return socks_failed(sx, cf, data, presult); - result = Curl_bufq_cwrite(&sx->iobuf, sx->hostname, hlen, &nwritten); + result = Curl_bufq_cwrite(&sx->iobuf, sx->dest->hostname, hlen, + &nwritten); if(result || (nwritten != hlen)) return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST); /* request complete */ sxstate(sx, cf, data, SOCKS4_ST_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS4_ST_RESOLVING); FALLTHROUGH(); @@ -554,7 +536,7 @@ static CURLproxycode socks4_connect(struct Curl_cfilter *cf, FALLTHROUGH(); case SOCKS4_ST_RECV: - /* Receive 8 byte response */ + /* Receive 8-byte response */ presult = socks_recv(sx, cf, data, 8, &done); if(presult) return socks_failed(sx, cf, data, presult); @@ -591,7 +573,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, (void)cf; /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ - if(!sx->resolve_local && strlen(sx->hostname) > 255) { + if(!sx->resolve_local && strlen(sx->dest->hostname) > 255) { failf(data, "SOCKS5: the destination hostname is too long to be " "resolved remotely by the proxy."); return CURLPX_LONG_HOSTNAME; @@ -602,7 +584,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, "CURLOPT_SOCKS5_AUTH: %u", auth); if(!(auth & CURLAUTH_BASIC)) /* disable username/password auth */ - sx->user = NULL; + Curl_creds_unlink(&sx->creds); req[0] = 5; /* version */ nauths = 1; @@ -613,7 +595,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, req[1 + nauths] = 1; /* GSS-API */ } #endif - if(sx->user) { + if(sx->creds) { ++nauths; req[1 + nauths] = 2; /* username/password */ } @@ -657,8 +639,7 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT); return CURLPX_OK; } - failf(data, - "SOCKS5 GSSAPI per-message authentication is not enabled."); + failf(data, "SOCKS5 GSSAPI per-message authentication is not enabled."); return CURLPX_GSSAPI_PERMSG; case 2: /* regular name + password authentication */ @@ -686,9 +667,9 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, unsigned char buf[2]; CURLcode result; - if(sx->user && sx->passwd) { - ulen = strlen(sx->user); - plen = strlen(sx->passwd); + if(sx->creds) { + ulen = strlen(sx->creds->user); + plen = strlen(sx->creds->passwd); /* the lengths must fit in a single byte */ if(ulen > 255) { failf(data, "Excessive username length for proxy auth"); @@ -713,7 +694,7 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, if(result || (nwritten != 2)) return CURLPX_SEND_REQUEST; if(ulen) { - result = Curl_bufq_cwrite(&sx->iobuf, sx->user, ulen, &nwritten); + result = Curl_bufq_cwrite(&sx->iobuf, sx->creds->user, ulen, &nwritten); if(result || (nwritten != ulen)) return CURLPX_SEND_REQUEST; } @@ -722,7 +703,7 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, if(result || (nwritten != 1)) return CURLPX_SEND_REQUEST; if(plen) { - result = Curl_bufq_cwrite(&sx->iobuf, sx->passwd, plen, &nwritten); + result = Curl_bufq_cwrite(&sx->iobuf, sx->creds->passwd, plen, &nwritten); if(result || (nwritten != plen)) return CURLPX_SEND_REQUEST; } @@ -779,29 +760,29 @@ static CURLproxycode socks5_req1_init(struct socks_ctx *sx, /* remote resolving, send what type+addr/string to resolve */ #ifdef USE_IPV6 - if(strchr(sx->hostname, ':')) { + if(strchr(sx->dest->hostname, ':')) { desttype = 4; destination = ipbuf; destlen = 16; - if(curlx_inet_pton(AF_INET6, sx->hostname, ipbuf) != 1) + if(curlx_inet_pton(AF_INET6, sx->dest->hostname, ipbuf) != 1) return CURLPX_BAD_ADDRESS_TYPE; } else #endif - if(curlx_inet_pton(AF_INET, sx->hostname, ipbuf) == 1) { + if(curlx_inet_pton(AF_INET, sx->dest->hostname, ipbuf) == 1) { desttype = 1; destination = ipbuf; destlen = 4; } else { - const size_t hostname_len = strlen(sx->hostname); + const size_t hostname_len = strlen(sx->dest->hostname); /* socks5_req0_init() already rejects hostnames longer than 255 bytes, so this cast to unsigned char is safe. Assert to guard against future refactoring that might remove or reorder that earlier check. */ DEBUGASSERT(hostname_len <= 255); desttype = 3; - destination = (const unsigned char *)sx->hostname; - destlen = (unsigned char)hostname_len; /* one byte length */ + destination = (const unsigned char *)sx->dest->hostname; + destlen = (unsigned char)hostname_len; /* 1-byte length */ } req[3] = desttype; @@ -814,13 +795,13 @@ static CURLproxycode socks5_req1_init(struct socks_ctx *sx, if(result || (nwritten != destlen)) return CURLPX_SEND_REQUEST; /* PORT MSB+LSB */ - req[0] = (unsigned char)((sx->remote_port >> 8) & 0xff); - req[1] = (unsigned char)(sx->remote_port & 0xff); + req[0] = (unsigned char)((sx->dest->port >> 8) & 0xff); + req[1] = (unsigned char)(sx->dest->port & 0xff); result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten); if(result || (nwritten != 2)) return CURLPX_SEND_REQUEST; CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (remotely resolved)", - sx->hostname, sx->remote_port); + sx->dest->hostname, sx->dest->port); return CURLPX_OK; } @@ -837,39 +818,28 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, CURLcode result; CURLproxycode presult = CURLPX_OK; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } #ifdef USE_IPV6 if(data->set.ipver != CURL_IPRESOLVE_V4) - ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET6, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET6, 0); #endif if(!ai) - ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(!ai) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname); + failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", + sx->dest->hostname); presult = CURLPX_RESOLVE_HOST; goto out; } @@ -883,7 +853,7 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, saddr_in = (struct sockaddr_in *)(void *)ai->ai_addr; destination = (const unsigned char *)&saddr_in->sin_addr.s_addr; CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (locally resolved)", - dest, sx->remote_port); + dest, sx->dest->port); } #ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) { @@ -893,7 +863,7 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, saddr_in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr; destination = (const unsigned char *)&saddr_in6->sin6_addr.s6_addr; CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%u (locally resolved)", - dest, sx->remote_port); + dest, sx->dest->port); } #endif @@ -915,8 +885,8 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, goto out; } /* PORT MSB+LSB */ - req[0] = (unsigned char)((sx->remote_port >> 8) & 0xffU); - req[1] = (unsigned char)(sx->remote_port & 0xffU); + req[0] = (unsigned char)((sx->dest->port >> 8) & 0xffU); + req[1] = (unsigned char)(sx->dest->port & 0xffU); result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten); if(result || (nwritten != 2)) { presult = CURLPX_SEND_REQUEST; @@ -955,14 +925,14 @@ static CURLproxycode socks5_recv_resp1(struct socks_ctx *sx, +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ - | 1 | 1 | X'00' | 1 | Variable | 2 | + | 1 | 1 | 0x00 | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ ATYP: - o IP v4 address: X'01', BND.ADDR = 4 byte - o domain name: X'03', BND.ADDR = [ 1 byte length, string ] - o IP v6 address: X'04', BND.ADDR = 16 byte - */ + o IPv4 address: 0x01, BND.ADDR = 4-byte + o domain name: 0x03, BND.ADDR = [ 1-byte length, string ] + o IPv6 address: 0x04, BND.ADDR = 16-byte + */ if(resp[0] != 5) { /* version */ failf(data, "SOCKS5 reply has wrong version, version should be 5."); return CURLPX_BAD_VERSION; @@ -971,7 +941,7 @@ static CURLproxycode socks5_recv_resp1(struct socks_ctx *sx, CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; int code = resp[1]; failf(data, "cannot complete SOCKS5 connection to %s. (%d)", - sx->hostname, code); + sx->dest->hostname, code); if(code < 9) { /* RFC 1928 section 6 lists: */ static const CURLproxycode lookup[] = { @@ -1037,13 +1007,12 @@ static CURLproxycode socks5_connect(struct Curl_cfilter *cf, switch(sx->state) { case SOCKS_ST_INIT: sx->version = 5; - sx->resolve_local = (sx->proxy_type == CURLPROXY_SOCKS5); sxstate(sx, cf, data, SOCKS5_ST_START); FALLTHROUGH(); case SOCKS5_ST_START: CURL_TRC_CF(data, cf, "SOCKS5: connecting to %s:%u", - sx->hostname, sx->remote_port); + sx->dest->hostname, sx->dest->port); presult = socks5_req0_init(cf, sx, data); if(presult) return socks_failed(sx, cf, data, presult); @@ -1075,7 +1044,7 @@ static CURLproxycode socks5_connect(struct Curl_cfilter *cf, case SOCKS5_ST_GSSAPI_INIT: { #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* GSSAPI stuff done non-blocking */ - CURLcode result = Curl_SOCKS5_gssapi_negotiate(cf, data); + CURLcode result = Curl_SOCKS5_gssapi_negotiate(cf, data, sx->creds); if(result) { failf(data, "Unable to negotiate SOCKS5 GSS-API context."); return CURLPX_GSSAPI; @@ -1083,8 +1052,7 @@ static CURLproxycode socks5_connect(struct Curl_cfilter *cf, sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); goto process_state; #else - failf(data, - "SOCKS5 GSSAPI per-message authentication is not supported."); + failf(data, "SOCKS5 GSSAPI per-message authentication is not supported."); return socks_failed(sx, cf, data, CURLPX_GSSAPI_PERMSG); #endif } @@ -1127,7 +1095,6 @@ static CURLproxycode socks5_connect(struct Curl_cfilter *cf, sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS5_ST_RESOLVING); FALLTHROUGH(); @@ -1181,6 +1148,8 @@ static CURLproxycode socks5_connect(struct Curl_cfilter *cf, static void socks_proxy_ctx_free(struct socks_ctx *ctx) { if(ctx) { + Curl_peer_unlink(&ctx->dest); + Curl_creds_unlink(&ctx->creds); Curl_bufq_free(&ctx->iobuf); curlx_free(ctx); } @@ -1190,9 +1159,7 @@ static void socks_proxy_ctx_free(struct socks_ctx *ctx) 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() - -*/ + Note: this function's sub-functions call failf() */ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) @@ -1229,7 +1196,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, if(pxresult) { result = CURLE_PROXY; - data->info.pxcode = pxresult; + data->info.pxcode = (uint8_t)pxresult; goto out; } else if(ctx->state != SOCKS_ST_SUCCESS) @@ -1244,7 +1211,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, "(via %s port %u)", (cf->sockindex == SECONDARYSOCKET) ? "2nd " : "", ipquad.local_ip, ipquad.local_port, - ctx->hostname, ctx->remote_port, + ctx->dest->hostname, ctx->dest->port, ipquad.remote_ip, ipquad.remote_port); else infof(data, "Opened %sSOCKS connection", @@ -1255,10 +1222,8 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, out: *done = (bool)cf->connected; - if(*done || result) { - ctx->user = NULL; - ctx->passwd = NULL; - } + if(*done || result) + Curl_creds_unlink(&ctx->creds); return result; } @@ -1278,11 +1243,11 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf, case SOCKS5_ST_REQ0_SEND: case SOCKS5_ST_AUTH_SEND: case SOCKS5_ST_REQ1_SEND: - CURL_TRC_CF(data, cf, "adjust pollset out (%d)", sx->state); + CURL_TRC_CF(data, cf, "adjust pollset out (%d)", (int)sx->state); result = Curl_pollset_set_out_only(data, ps, sock); break; default: - CURL_TRC_CF(data, cf, "adjust pollset in (%d)", sx->state); + CURL_TRC_CF(data, cf, "adjust pollset in (%d)", (int)sx->state); result = Curl_pollset_set_in_only(data, ps, sock); break; } @@ -1290,14 +1255,6 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf, return result; } -static void socks_proxy_cf_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - cf->connected = FALSE; - if(cf->next) - cf->next->cft->do_close(cf->next, data); -} - static void socks_proxy_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1315,8 +1272,8 @@ static CURLcode socks_cf_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_HOST_PORT: if(sx) { - *pres1 = sx->remote_port; - *((const char **)pres2) = sx->hostname; + *pres1 = sx->dest->port; + *((const char **)pres2) = sx->dest->hostname; return CURLE_OK; } break; @@ -1340,7 +1297,6 @@ struct Curl_cftype Curl_cft_socks_proxy = { 0, socks_proxy_cf_destroy, socks_proxy_cf_connect, - socks_proxy_cf_close, Curl_cf_def_shutdown, socks_cf_adjust_pollset, Curl_cf_def_data_pending, @@ -1354,50 +1310,61 @@ struct Curl_cftype Curl_cft_socks_proxy = { CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const char *hostname, - uint16_t port, + struct Curl_peer *dest, uint8_t ip_version, uint8_t proxy_type, - const char *user, - const char *passwd) + struct Curl_creds *creds) { struct Curl_cfilter *cf; struct socks_ctx *ctx; - size_t hostlen = hostname ? strlen(hostname) : 0; + bool resolve_local = FALSE; + uint8_t dns_queries = Curl_resolv_dns_queries(data, ip_version); CURLcode result; - if(!hostlen) + if(!dest) return CURLE_FAILED_INIT; switch(proxy_type) { case CURLPROXY_SOCKS5: + resolve_local = TRUE; + break; case CURLPROXY_SOCKS5_HOSTNAME: + break; case CURLPROXY_SOCKS4: + resolve_local = TRUE; + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_AAAA); + break; case CURLPROXY_SOCKS4A: - break; /* all supported */ + break; default: failf(data, "unknown proxytype %d option given", proxy_type); return CURLE_COULDNT_CONNECT; } /* NUL byte already part of struct size */ - ctx = curlx_calloc(1, sizeof(*ctx) + hostlen); + ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { return CURLE_OUT_OF_MEMORY; } - memcpy(ctx->hostname, hostname, hostlen); - ctx->remote_port = port; + Curl_peer_link(&ctx->dest, dest); ctx->ip_version = ip_version; ctx->proxy_type = proxy_type; - ctx->user = user; - ctx->passwd = passwd; + ctx->resolve_local = resolve_local; + Curl_creds_link(&ctx->creds, creds); Curl_bufq_init2(&ctx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS, BUFQ_OPT_SOFT_LIMIT); result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, ctx); - if(!result) + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); + if(ctx->resolve_local) { + result = Curl_conn_dns_add_addr_resolve(data, cf_at->conn, + cf_at->sockindex, + ctx->dest, dns_queries, + TRNSPRT_TCP); + } + } else socks_proxy_ctx_free(ctx); return result; diff --git a/lib/socks.h b/lib/socks.h index ea368326d2bf..d8e77c7f13a1 100644 --- a/lib/socks.h +++ b/lib/socks.h @@ -26,6 +26,10 @@ #include "curl_setup.h" #ifndef CURL_DISABLE_PROXY + +struct Curl_peer; +struct Curl_creds; + /* * Helper read-from-socket functions. Does the same as Curl_read() but it * blocks until all bytes amount of buffersize will be read. No more, no less. @@ -43,22 +47,20 @@ CURLcode Curl_blockread_all(struct Curl_cfilter *cf, * This function handles the SOCKS5 GSS-API negotiation and initialization */ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_creds *creds); #endif -/* Insert a SOCKS filter after `cf_at` for connecting to `hostname` - * and `port` with optional credentials. - * Credentials are NOT duplicated and are +/* Insert a SOCKS filter after `cf_at` for connecting to `dest`. + * Credentials are optional and NOT duplicated and are * expected to exist during connect phase. */ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const char *hostname, - uint16_t port, + struct Curl_peer *dest, uint8_t ip_version, uint8_t proxy_type, - const char *user, - const char *passwd); + struct Curl_creds *creds); extern struct Curl_cftype Curl_cft_socks_proxy; diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c index 32db07044a92..c1cfba5b7189 100644 --- a/lib/socks_gssapi.c +++ b/lib/socks_gssapi.c @@ -35,7 +35,7 @@ #include "socks.h" #include "curlx/strdup.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -98,62 +98,33 @@ static int check_gss_err(struct Curl_easy *data, return 0; } -CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode socks5_gss_create_service_name(struct Curl_easy *data, + struct connectdata *conn, + const char *serviceptr, + gss_name_t *server) { - struct connectdata *conn = cf->conn; - curl_socket_t sock = conn->sock[cf->sockindex]; - CURLcode code; - size_t actualread; - size_t nwritten; - CURLcode result; OM_uint32 gss_major_status, gss_minor_status, gss_status; - OM_uint32 gss_ret_flags; - int gss_conf_state, gss_enc; - gss_buffer_desc service = GSS_C_EMPTY_BUFFER; - gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; - gss_buffer_desc gss_recv_token = GSS_C_EMPTY_BUFFER; - gss_buffer_desc gss_w_token = GSS_C_EMPTY_BUFFER; - gss_buffer_desc *gss_token = GSS_C_NO_BUFFER; - gss_name_t server = GSS_C_NO_NAME; - gss_name_t gss_client_name = GSS_C_NO_NAME; - unsigned short us_length; - unsigned char socksreq[4]; /* room for GSS-API exchange header only */ - const char *serviceptr = data->set.str[STRING_PROXY_SERVICE_NAME] ? - data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; - const size_t serviceptr_length = strlen(serviceptr); - gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; - - /* GSS-API request looks like - * +----+------+-----+----------------+ - * |VER | MTYP | LEN | TOKEN | - * +----+------+----------------------+ - * | 1 | 1 | 2 | up to 2^16 - 1 | - * +----+------+-----+----------------+ - */ + gss_buffer_desc service = GSS_C_EMPTY_BUFFER; /* prepare service name */ if(strchr(serviceptr, '/')) { - service.length = serviceptr_length; + service.length = strlen(serviceptr); service.value = curlx_memdup(serviceptr, service.length); if(!service.value) return CURLE_OUT_OF_MEMORY; gss_major_status = gss_import_name(&gss_minor_status, &service, - (gss_OID)GSS_C_NULL_OID, &server); + (gss_OID)GSS_C_NULL_OID, server); } else { - service.value = curlx_malloc(serviceptr_length + - strlen(conn->socks_proxy.host.name) + 2); + service.value = curl_maprintf("%s@%s", serviceptr, + conn->socks_proxy.peer->hostname); if(!service.value) return CURLE_OUT_OF_MEMORY; - service.length = serviceptr_length + - strlen(conn->socks_proxy.host.name) + 1; - curl_msnprintf(service.value, service.length + 1, "%s@%s", - serviceptr, conn->socks_proxy.host.name); + service.length = strlen(service.value); gss_major_status = gss_import_name(&gss_minor_status, &service, - GSS_C_NT_HOSTBASED_SERVICE, &server); + GSS_C_NT_HOSTBASED_SERVICE, server); } curlx_safefree(service.value); @@ -162,25 +133,51 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_import_name()")) { failf(data, "Failed to create service name."); - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server); return CURLE_COULDNT_CONNECT; } - (void)curlx_nonblock(sock, FALSE); + return CURLE_OK; +} + +static CURLcode socks5_gss_auth_loop(struct Curl_cfilter *cf, + struct Curl_easy *data, + gss_name_t *server_ptr, + gss_ctx_id_t *gss_context, + OM_uint32 *gss_ret_flags) +{ + OM_uint32 gss_major_status, gss_minor_status, gss_status; + gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_recv_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc *gss_token = GSS_C_NO_BUFFER; + unsigned short us_length; + unsigned char socksreq[4]; + size_t actualread; + size_t nwritten; + CURLcode result; + + /* GSS-API request looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ /* 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, &gss_minor_status, - &gss_context, - server, + gss_context, + *server_ptr, &Curl_krb5_mech_oid, NULL, gss_token, &gss_send_token, TRUE, - &gss_ret_flags); + gss_ret_flags, + GSS_C_NO_CREDENTIAL); if(gss_token != GSS_C_NO_BUFFER) { curlx_safefree(gss_recv_token.value); @@ -190,10 +187,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_minor_status, "gss_init_sec_context") || /* the size needs to fit in a 16-bit field */ (gss_send_token.length > 0xffff)) { - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server_ptr); gss_release_buffer(&gss_status, &gss_send_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); - failf(data, "Failed to initial GSS-API token."); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); + failf(data, "Failed to initialize GSS-API token."); return CURLE_COULDNT_CONNECT; } @@ -203,23 +200,24 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, us_length = htons((unsigned short)gss_send_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); - code = Curl_conn_cf_send(cf->next, data, socksreq, 4, FALSE, &nwritten); - if(code || (nwritten != 4)) { + result = Curl_conn_cf_send(cf->next, data, socksreq, 4, FALSE, + &nwritten); + if(result || (nwritten != 4)) { failf(data, "Failed to send GSS-API authentication request."); - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server_ptr); gss_release_buffer(&gss_status, &gss_send_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } - code = Curl_conn_cf_send(cf->next, data, - gss_send_token.value, - gss_send_token.length, FALSE, &nwritten); - if(code || (gss_send_token.length != nwritten)) { + result = Curl_conn_cf_send(cf->next, data, + gss_send_token.value, + gss_send_token.length, FALSE, &nwritten); + if(result || (gss_send_token.length != nwritten)) { failf(data, "Failed to send GSS-API authentication token."); - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server_ptr); gss_release_buffer(&gss_status, &gss_send_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } } @@ -241,8 +239,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive GSS-API authentication response."); - gss_release_name(&gss_status, &server); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, server_ptr); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -250,16 +248,16 @@ 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 (%d %d).", socksreq[0], socksreq[1]); - gss_release_name(&gss_status, &server); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, server_ptr); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } if(socksreq[1] != 1) { /* status / message type */ failf(data, "Invalid GSS-API authentication response type (%d %d).", socksreq[0], socksreq[1]); - gss_release_name(&gss_status, &server); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, server_ptr); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -268,8 +266,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!us_length) { failf(data, "Invalid zero-length GSS-API authentication token."); - gss_release_name(&gss_status, &server); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, server_ptr); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -279,8 +277,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Could not allocate memory for GSS-API authentication " "response token."); - gss_release_name(&gss_status, &server); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, server_ptr); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_OUT_OF_MEMORY; } @@ -289,25 +287,34 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != us_length)) { failf(data, "Failed to receive GSS-API authentication token."); - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server_ptr); curlx_safefree(gss_recv_token.value); gss_recv_token.length = 0; - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } gss_token = &gss_recv_token; } - gss_release_name(&gss_status, &server); + gss_release_name(&gss_status, server_ptr); + return CURLE_OK; +} + +static CURLcode socks5_gss_auth_verify(struct Curl_easy *data, + gss_ctx_id_t *gss_context) +{ + OM_uint32 gss_major_status, gss_minor_status, gss_status; + gss_name_t gss_client_name = GSS_C_NO_NAME; + gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; /* Everything is good so far, user was authenticated! */ - gss_major_status = gss_inquire_context(&gss_minor_status, gss_context, + gss_major_status = gss_inquire_context(&gss_minor_status, *gss_context, &gss_client_name, NULL, NULL, NULL, NULL, NULL, NULL); if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_inquire_context")) { - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); gss_release_name(&gss_status, &gss_client_name); failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; @@ -316,7 +323,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, &gss_send_token, NULL); if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_display_name")) { - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_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 username."); @@ -329,6 +336,26 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_release_name(&gss_status, &gss_client_name); gss_release_buffer(&gss_status, &gss_send_token); + return CURLE_OK; +} + +static CURLcode socks5_gss_negotiate_enc(struct Curl_cfilter *cf, + struct Curl_easy *data, + gss_ctx_id_t *gss_context, + OM_uint32 gss_ret_flags) +{ + struct connectdata *conn = cf->conn; + OM_uint32 gss_major_status, gss_minor_status, gss_status; + gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_recv_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_w_token = GSS_C_EMPTY_BUFFER; + unsigned short us_length; + unsigned char socksreq[4]; + size_t actualread; + size_t nwritten; + CURLcode result; + int gss_enc; + /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ socksreq[1] = 2; /* encryption message type */ @@ -383,19 +410,19 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_send_token.length = 1; gss_send_token.value = curlx_memdup(&gss_enc, gss_send_token.length); if(!gss_send_token.value) { - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_OUT_OF_MEMORY; } - gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, + gss_major_status = gss_wrap(&gss_minor_status, *gss_context, 0, GSS_C_QOP_DEFAULT, &gss_send_token, - &gss_conf_state, &gss_w_token); + NULL, &gss_w_token); if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_wrap")) { curlx_safefree(gss_send_token.value); gss_send_token.length = 0; gss_release_buffer(&gss_status, &gss_w_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); failf(data, "Failed to wrap GSS-API encryption value into token."); return CURLE_COULDNT_CONNECT; } @@ -406,30 +433,30 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, memcpy(socksreq + 2, &us_length, sizeof(short)); } - code = Curl_conn_cf_send(cf->next, data, socksreq, 4, FALSE, &nwritten); - if(code || (nwritten != 4)) { + result = Curl_conn_cf_send(cf->next, data, socksreq, 4, FALSE, &nwritten); + if(result || (nwritten != 4)) { failf(data, "Failed to send GSS-API encryption request."); gss_release_buffer(&gss_status, &gss_w_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - code = Curl_conn_cf_send(cf->next, data, socksreq, 1, FALSE, &nwritten); - if(code || (nwritten != 1)) { + result = Curl_conn_cf_send(cf->next, data, socksreq, 1, FALSE, &nwritten); + if(result || (nwritten != 1)) { failf(data, "Failed to send GSS-API encryption type."); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } } else { - code = Curl_conn_cf_send(cf->next, data, gss_w_token.value, - gss_w_token.length, FALSE, &nwritten); - if(code || (gss_w_token.length != nwritten)) { + result = Curl_conn_cf_send(cf->next, data, gss_w_token.value, + gss_w_token.length, FALSE, &nwritten); + if(result || (gss_w_token.length != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_w_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } gss_release_buffer(&gss_status, &gss_w_token); @@ -438,7 +465,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive GSS-API encryption response."); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -446,14 +473,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 (%d %d).", socksreq[0], socksreq[1]); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } if(socksreq[1] != 2) { /* status / message type */ failf(data, "Invalid GSS-API encryption response type (%d %d).", socksreq[0], socksreq[1]); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -462,14 +489,14 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!us_length) { failf(data, "Invalid zero-length GSS-API encryption token."); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } gss_recv_token.length = us_length; gss_recv_token.value = curlx_malloc(gss_recv_token.length); if(!gss_recv_token.value) { - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_OUT_OF_MEMORY; } result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value, @@ -479,20 +506,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Failed to receive GSS-API encryption type."); curlx_safefree(gss_recv_token.value); gss_recv_token.length = 0; - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } if(!data->set.socks5_gssapi_nec) { - gss_major_status = gss_unwrap(&gss_minor_status, gss_context, + gss_major_status = gss_unwrap(&gss_minor_status, *gss_context, &gss_recv_token, &gss_w_token, - 0, GSS_C_QOP_DEFAULT); + NULL, NULL); if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_unwrap")) { curlx_safefree(gss_recv_token.value); gss_recv_token.length = 0; gss_release_buffer(&gss_status, &gss_w_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); failf(data, "Failed to unwrap GSS-API encryption value into token."); return CURLE_COULDNT_CONNECT; } @@ -503,7 +530,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Invalid GSS-API encryption response length (%zu).", gss_w_token.length); gss_release_buffer(&gss_status, &gss_w_token); - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -516,7 +543,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_recv_token.length); curlx_safefree(gss_recv_token.value); gss_recv_token.length = 0; - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_COULDNT_CONNECT; } @@ -525,21 +552,49 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_recv_token.length = 0; } - (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")); + infof(data, "SOCKS5 access %s protection granted.", + (socksreq[0] == 0) ? "without GSS-API data" : + ((socksreq[0] == 1) ? "with GSS-API integrity" : + "with GSS-API confidentiality")); conn->socks5_gssapi_enctype = socksreq[0]; if(socksreq[0] == 0) - Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL); + Curl_gss_delete_sec_context(&gss_status, gss_context, NULL); return CURLE_OK; } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_creds *creds) +{ + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; + CURLcode result; + OM_uint32 gss_ret_flags = 0; + gss_name_t server = GSS_C_NO_NAME; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : "rcmd"; + gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + + result = socks5_gss_create_service_name(data, conn, service, &server); + if(!result) { + (void)curlx_nonblock(sock, FALSE); + result = socks5_gss_auth_loop(cf, data, &server, &gss_context, + &gss_ret_flags); + } + if(!result) + result = socks5_gss_auth_verify(data, &gss_context); + if(!result) + result = socks5_gss_negotiate_enc(cf, data, &gss_context, gss_ret_flags); + + /* unconditionally put it back to non-blocking */ + (void)curlx_nonblock(sock, TRUE); + + return result; +} + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c index 385312a3681c..576d6aef5bbc 100644 --- a/lib/socks_sspi.c +++ b/lib/socks_sspi.c @@ -37,7 +37,7 @@ #include "curlx/multibyte.h" /* - * Helper sspi error functions. + * Helper SSPI error functions. */ static int check_sspi_err(struct Curl_easy *data, SECURITY_STATUS status, @@ -58,12 +58,13 @@ static int check_sspi_err(struct Curl_easy *data, /* This is the SSPI-using version of this function */ static CURLcode socks5_sspi_setup(struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_creds *creds, CredHandle *cred_handle, char **service_namep) { struct connectdata *conn = cf->conn; - const char *service = data->set.str[STRING_PROXY_SERVICE_NAME] ? - data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : "rcmd"; SECURITY_STATUS status; /* prepare service name */ @@ -71,13 +72,12 @@ static CURLcode socks5_sspi_setup(struct Curl_cfilter *cf, *service_namep = curlx_strdup(service); else *service_namep = curl_maprintf("%s/%s", - service, conn->socks_proxy.host.name); + service, conn->socks_proxy.peer->hostname); if(!*service_namep) return CURLE_OUT_OF_MEMORY; status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT("Kerberos")), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(TEXT("Kerberos")), SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, cred_handle, NULL); @@ -163,7 +163,7 @@ static CURLcode socks5_sspi_loop(struct Curl_cfilter *cf, sspi_recv_token.cbBuffer = 0; if(check_sspi_err(data, status, "InitializeSecurityContext")) { - failf(data, "Failed to initialise security context."); + failf(data, "Failed to initialize security context."); return socks5_free_token(&sspi_send_token, CURLE_COULDNT_CONNECT); } @@ -420,8 +420,7 @@ static CURLcode socks5_sspi_encrypt(struct Curl_cfilter *cf, if(result || (actualread != us_length)) { failf(data, "Failed to receive SSPI encryption type."); - curlx_free(sspi_w_token[0].pvBuffer); - return result ? result : CURLE_COULDNT_CONNECT; + goto fail; } if(!data->set.socks5_gssapi_nec) { @@ -431,49 +430,48 @@ static CURLcode socks5_sspi_encrypt(struct Curl_cfilter *cf, sspi_w_token[1].cbBuffer = 0; sspi_w_token[1].pvBuffer = NULL; - status = Curl_pSecFn->DecryptMessage(sspi_context, &wrap_desc, - 0, &qop); + /* At least one of the descriptors must be of type SECBUFFER_DATA. The + message is decrypted in place so the SECBUFFER_DATA receives a pointer + to the message in SECBUFFER_STREAM. */ + status = Curl_pSecFn->DecryptMessage(sspi_context, &wrap_desc, 0, &qop); - if(check_sspi_err(data, status, "DecryptMessage")) { - if(sspi_w_token[1].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - curlx_free(sspi_w_token[0].pvBuffer); - return CURLE_COULDNT_CONNECT; - } + if(check_sspi_err(data, status, "DecryptMessage")) + goto fail; if(sspi_w_token[1].cbBuffer != 1) { failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[1].cbBuffer); - if(sspi_w_token[1].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - curlx_free(sspi_w_token[0].pvBuffer); - return CURLE_COULDNT_CONNECT; + goto fail; } memcpy(socksreq, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); - 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); - curlx_free(sspi_w_token[0].pvBuffer); - return CURLE_COULDNT_CONNECT; + goto fail; } memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); } curlx_free(sspi_w_token[0].pvBuffer); - infof(data, "SOCKS5 access with%s protection granted BUT NOT USED.", - (socksreq[0] == 0) ? "out GSS-API data" : - ((socksreq[0] == 1) ? " GSS-API integrity" : - " GSS-API confidentiality")); + infof(data, "SOCKS5 access %s protection granted BUT NOT USED.", + (socksreq[0] == 0) ? "without GSS-API data" : + ((socksreq[0] == 1) ? "with GSS-API integrity" : + "with GSS-API confidentiality")); + cf->conn->socks5_gssapi_enctype = socksreq[0]; return CURLE_OK; + +fail: + curlx_free(sspi_w_token[0].pvBuffer); + return CURLE_COULDNT_CONNECT; } CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_creds *creds) { struct connectdata *conn = cf->conn; curl_socket_t sock = conn->sock[cf->sockindex]; @@ -489,7 +487,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, memset(&sspi_context, 0, sizeof(sspi_context)); names.sUserName = NULL; - result = socks5_sspi_setup(cf, data, &cred_handle, &service_name); + result = socks5_sspi_setup(cf, data, creds, &cred_handle, &service_name); if(result) goto error; diff --git a/lib/splay.c b/lib/splay.c index ddab7a4d6e2c..788da215ee54 100644 --- a/lib/splay.c +++ b/lib/splay.c @@ -23,173 +23,267 @@ ***************************************************************************/ #include "curl_setup.h" +#include "urldata.h" #include "splay.h" -/* - * This macro compares two node keys i and j and returns: - * - * negative value: when i is smaller than j - * zero : when i is equal to j - * positive when : when i is larger than j - */ -#define splay_compare(i, j) curlx_ptimediff_us(i, j) + +void Curl_timeouts_init(struct Curl_timeouts *timeouts, + const struct curltime *ptime_base) +{ + timeouts->tree = NULL; + timeouts->time_base = ptime_base ? *ptime_base : curlx_now(); +} + +bool Curl_timeouts_has(struct Curl_easy *data) +{ + struct Curl_tree *node = data ? &data->state.timeouts.splaynode : NULL; + return node && node->registered; +} + +timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, + const struct curltime *pts) +{ + return curlx_ptimediff_us(pts, &timeouts->time_base); +} + +int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, + const struct curltime *pnow, + timediff_t *pexpire_offset_us, + uint32_t *pmid) +{ + if(timeouts->tree) { /* splay the lowest key to the root */ + timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree); + } + + if(timeouts->tree) { + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, pnow); + timediff_t delta_us = timeouts->tree->key - elapsed_us; + if(pmid) + *pmid = timeouts->tree->id; + if(pexpire_offset_us) + *pexpire_offset_us = timeouts->tree->key; + if(delta_us > 0) { /* expires in the future */ + timediff_t ms = curlx_us_to_ceil_ms(delta_us); + return (ms > INT_MAX) ? INT_MAX : (int)ms; + } + else /* has expired */ + return 0; + } + if(pmid) + *pmid = UINT32_MAX; + if(pexpire_offset_us) + *pexpire_offset_us = 0; + return -1; +} + +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid) +{ + if(timeouts->tree) { + struct Curl_tree *t = NULL; + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); + timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t); + if(t) { + *pmid = t->id; + return TRUE; + } + } + *pmid = UINT32_MAX; + return FALSE; +} + +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + DEBUGASSERT(!node->registered); + timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree, + node, data->mid); +} + +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + if(node->registered) { + int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree); +#ifdef DEBUGBUILD + if(rc) + curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); +#else + (void)rc; +#endif + return TRUE; + } + return FALSE; +} /* - * Splay using the key i (which may or may not be in the tree.) The starting - * root is t. + * Splay using the key i (which may or may not be in the tree). + * This rotates the tree, so: + * - root->smaller has all nodes smaller than `key` + * - root->larger has all nodes larger than `key` + * - root->key may equal `key` or not + * */ -struct Curl_tree *Curl_splay(const struct curltime *pkey, - struct Curl_tree *t) +struct Curl_tree *Curl_splay(timediff_t key, + struct Curl_tree *root) { struct Curl_tree N, *l, *r, *y; - if(!t) + if(!root) return NULL; N.smaller = N.larger = NULL; l = r = &N; for(;;) { - timediff_t comp = splay_compare(pkey, &t->key); - if(comp < 0) { - if(!t->smaller) + if(key < root->key) { + /* key is somewhere in root->smaller branch */ + if(!root->smaller) /* which is empty, done */ break; - if(splay_compare(pkey, &t->smaller->key) < 0) { - y = t->smaller; /* rotate smaller */ - t->smaller = y->larger; - y->larger = t; - t = y; - if(!t->smaller) + if(key < root->smaller->key) { + /* key is somewhere in root->smaller->smaller, make a "Zig step" */ + y = root->smaller; + root->smaller = y->larger; + y->larger = root; + root = y; + if(!root->smaller) break; } - r->smaller = t; /* link smaller */ - r = t; - t = t->smaller; + /* Making root->smaller the new root, the old root is no longer + * referenced. Remember it in the N tree's `r`ight/larger side. + * Everything in old root is smaller than what the right side + * of N already has, so it gets added to r->smaller. */ + r->smaller = root; + r = root; + root = root->smaller; } - else if(comp > 0) { - if(!t->larger) + else if(key > root->key) { + /* key is somewhere in root->larger branch */ + if(!root->larger) /* which is empty, done */ break; - if(splay_compare(pkey, &t->larger->key) > 0) { - y = t->larger; /* rotate larger */ - t->larger = y->smaller; - y->smaller = t; - t = y; - if(!t->larger) + if(key > root->larger->key) { + /* key is somewhere in root->larger->larger, make a "Zig step" */ + y = root->larger; + root->larger = y->smaller; + y->smaller = root; + root = y; + if(!root->larger) break; } - l->larger = t; /* link larger */ - l = t; - t = t->larger; + /* Making root->larger the new root, the old root is no longer + * referenced. Remember it in the N tree's `l`eft/smaller side. + * Everything in old root is larger than what the left side + * of N already has, so it gets added to l->larger. */ + l->larger = root; + l = root; + root = root->larger; } - else + else /* exact match, root is key, done */ break; } - l->larger = t->smaller; /* assemble */ - r->smaller = t->larger; - t->smaller = N.larger; - t->larger = N.smaller; + /* Put it all together again. + * root->smaller has everything larger than current `l`. + * root->larger has everything smaller than current `r`. */ + l->larger = root->smaller; + r->smaller = root->larger; + root->smaller = N.larger; + root->larger = N.smaller; - return t; + return root; } -static const struct curltime SPLAY_SUBNODE = { - ~0, -1 -}; - /* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * * @unittest: 1309 */ -struct Curl_tree *Curl_splayinsert(const struct curltime *pkey, - struct Curl_tree *t, - struct Curl_tree *node) +struct Curl_tree *Curl_splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id) { DEBUGASSERT(node); - if(t) { - t = Curl_splay(pkey, t); - DEBUGASSERT(t); - if(splay_compare(pkey, &t->key) == 0) { - /* 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 = SPLAY_SUBNODE; /* identify this node as a subnode */ - node->samen = t; - node->samep = t->samep; - t->samep->samen = node; - t->samep = node; - - return t; /* the root node always stays the same */ + node->key = key; + node->id = id; + node->same = NULL; + node->registered = TRUE; + if(root) { + root = Curl_splay(key, root); + DEBUGASSERT(root); + if(key == root->key) { + /* There already exists a node in the tree with the same key. + Append the new node to the `same` list. */ + struct Curl_tree **panchor = &root->same; + while(*panchor) + panchor = &(*panchor)->same; + *panchor = node; + return root; /* the root node always stays the same */ } } - if(!t) { + /* node becomes the new root. Insert old root as sub-branch. */ + if(!root) { node->smaller = node->larger = NULL; } - else if(splay_compare(pkey, &t->key) < 0) { - node->smaller = t->smaller; - node->larger = t; - t->smaller = NULL; + else if(key < root->key) { + node->smaller = root->smaller; + node->larger = root; + root->smaller = NULL; } else { - node->larger = t->larger; - node->smaller = t; - t->larger = NULL; + node->larger = root->larger; + node->smaller = root; + root->larger = NULL; } - node->key = *pkey; - /* no identical nodes (yet), we are the only one in the list of nodes */ - node->samen = node; - node->samep = node; return node; } /* Finds and deletes the best-fit node from the tree. Return a pointer to the resulting tree. best-fit means the smallest node if it is not larger than the key */ -struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, - struct Curl_tree *t, +struct Curl_tree *Curl_splaygetbest(timediff_t key, + struct Curl_tree *root, struct Curl_tree **removed) { - static const struct curltime tv_zero = { 0, 0 }; struct Curl_tree *x; - if(!t) { + if(!root) { *removed = NULL; /* none removed since there was no root */ return NULL; } /* find smallest */ - t = Curl_splay(&tv_zero, t); - DEBUGASSERT(t); - if(splay_compare(pkey, &t->key) < 0) { + root = Curl_splay(TIMEDIFF_T_MIN, root); + DEBUGASSERT(root); + if(key < root->key) { /* even the smallest is too big */ *removed = NULL; - return t; + return root; } /* FIRST! Check if there is a list with identical keys */ - x = t->samen; - if(x != t) { - /* there is, pick one from the list */ - - /* 'x' is the new root node */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - x->samep = t->samep; - t->samep->samen = x; - - *removed = t; + if(root->same) { + x = root->same; + DEBUGASSERT(x->key == root->key); + /* 'x' becomes the new root node */ + x->larger = root->larger; + x->smaller = root->smaller; + root->same = NULL; + root->registered = FALSE; + *removed = root; return x; /* new root */ } /* we splayed the tree to the smallest element, there is no smaller */ - x = t->larger; - *removed = t; + x = root->larger; + root->registered = FALSE; + *removed = root; return x; } @@ -205,87 +299,79 @@ struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, * * @unittest: 1309 */ -int Curl_splayremove(struct Curl_tree *t, +int Curl_splayremove(struct Curl_tree *root, struct Curl_tree *removenode, struct Curl_tree **newroot) { struct Curl_tree *x; - if(!t) + if(!root) return 1; DEBUGASSERT(removenode); + if(!removenode->registered) + return 2; - if(splay_compare(&SPLAY_SUBNODE, &removenode->key) == 0) { - /* It is a subnode within a 'same' linked list and thus we can unlink it - easily. */ - DEBUGASSERT(removenode->samen != removenode); - if(removenode->samen == removenode) - /* A non-subnode should never be set to SPLAY_SUBNODE */ - return 3; - - removenode->samep->samen = removenode->samen; - removenode->samen->samep = removenode->samep; - - /* Ensures that double-remove gets caught. */ - removenode->samen = removenode; - - *newroot = t; /* return the same root */ - return 0; - } - - t = Curl_splay(&removenode->key, t); - DEBUGASSERT(t); + root = Curl_splay(removenode->key, root); + DEBUGASSERT(root); - /* First make sure that we got the same root node as the one we want + /* First make sure that we got the same root key as the one we want to remove, as otherwise we might be trying to remove a node that - is not actually in the tree. - - We cannot compare the keys here as a double remove in quick - succession of a node with key != SPLAY_SUBNODE && same != NULL - could return the same key but a different node. */ - DEBUGASSERT(t == removenode); - if(t != removenode) + is not actually in the tree. */ + if(root->key != removenode->key) { + DEBUGASSERT(0); return 2; + } - /* 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) { + if(root != removenode) { + /* Should be in the root->same list then */ + struct Curl_tree **panchor; + for(panchor = &root->same; *panchor; panchor = &(*panchor)->same) { + if(*panchor == removenode) { + *panchor = removenode->same; + removenode->same = NULL; + removenode->registered = FALSE; + *newroot = root; + return 0; + } + } + /* not found in same list, error */ + DEBUGASSERT(0); + return 2; + } + /* removing the root node */ + if(root->same) { /* 'x' is the new root node, we make it use the root node's smaller/larger links */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - x->samep = t->samep; - t->samep->samen = x; + x = root->same; + x->larger = root->larger; + x->smaller = root->smaller; + root->same = NULL; } else { /* Remove the root node */ - if(!t->smaller) - x = t->larger; + if(!root->smaller) + x = root->larger; else { - x = Curl_splay(&removenode->key, t->smaller); + x = Curl_splay(removenode->key, root->smaller); DEBUGASSERT(x); - x->larger = t->larger; + x->larger = root->larger; } } - - *newroot = x; /* store new root pointer */ - + removenode->registered = FALSE; + *newroot = x; /* return new root */ return 0; } /* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, void *payload) +void Curl_splayset(struct Curl_tree *node, uint32_t id) { DEBUGASSERT(node); - node->ptr = payload; + node->id = id; } -void *Curl_splayget(struct Curl_tree *node) +uint32_t Curl_splayget(struct Curl_tree *node) { DEBUGASSERT(node); - return node->ptr; + return node->id; } diff --git a/lib/splay.h b/lib/splay.h index c6623f2ef0c4..3f9e66297cd2 100644 --- a/lib/splay.h +++ b/lib/splay.h @@ -27,33 +27,66 @@ #include "curlx/timeval.h" +struct Curl_easy; + /* 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 *ptr; /* data the splay code does not care about */ + struct Curl_tree *same; /* points to the next node with identical key */ + timediff_t key; /* this node's "sort" key */ + uint32_t id; /* provided id for this node */ + BIT(registered); /* node is registered in splay tree */ +}; + +struct Curl_timeouts { + struct Curl_tree *tree; + struct curltime time_base; }; -struct Curl_tree *Curl_splay(const struct curltime *pkey, - struct Curl_tree *t); +void Curl_timeouts_init(struct Curl_timeouts *timeouts, + const struct curltime *ptime_base); + +bool Curl_timeouts_has(struct Curl_easy *data); + +timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, + const struct curltime *pts); + +int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, + const struct curltime *pnow, + timediff_t *pexpire_offset_us, + uint32_t *pmid); + +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid); + +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us); + +/* Returns TRUE if data was registered in timeouts before */ +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data); + +struct Curl_tree *Curl_splay(timediff_t key, + struct Curl_tree *root); -struct Curl_tree *Curl_splayinsert(const struct curltime *pkey, - struct Curl_tree *t, - struct Curl_tree *node); +struct Curl_tree *Curl_splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id); -struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey, - struct Curl_tree *t, +struct Curl_tree *Curl_splaygetbest(timediff_t key, + struct Curl_tree *root, struct Curl_tree **removed); -int Curl_splayremove(struct Curl_tree *t, +int Curl_splayremove(struct Curl_tree *root, struct Curl_tree *removenode, struct Curl_tree **newroot); /* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, void *payload); -void *Curl_splayget(struct Curl_tree *node); +void Curl_splayset(struct Curl_tree *node, uint32_t id); +uint32_t Curl_splayget(struct Curl_tree *node); #endif /* HEADER_CURL_SPLAY_H */ diff --git a/lib/strcase.c b/lib/strcase.c index 9f70f41bd63c..6c3597cc3771 100644 --- a/lib/strcase.c +++ b/lib/strcase.c @@ -26,7 +26,7 @@ #include "strcase.h" /* Mapping table to go from lowercase to uppercase for plain ASCII.*/ -static const unsigned char touppermap[256] = { +const unsigned char Curl_touppermap[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, @@ -48,7 +48,7 @@ static const unsigned char touppermap[256] = { }; /* Mapping table to go from uppercase to lowercase for plain ASCII.*/ -static const unsigned char tolowermap[256] = { +const unsigned char Curl_tolowermap[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, @@ -69,20 +69,6 @@ static const unsigned char tolowermap[256] = { 255 }; -/* Portable, consistent toupper. Do not use toupper() because its behavior is - altered by the current locale. */ -char Curl_raw_toupper(char in) -{ - return (char)touppermap[(unsigned char)in]; -} - -/* Portable, consistent tolower. Do not use tolower() because its behavior is - altered by the current locale. */ -char Curl_raw_tolower(char in) -{ - return (char)tolowermap[(unsigned char)in]; -} - /* 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 diff --git a/lib/strcase.h b/lib/strcase.h index 54299812beae..559db875bdfe 100644 --- a/lib/strcase.h +++ b/lib/strcase.h @@ -25,8 +25,24 @@ ***************************************************************************/ #include "curl_setup.h" -char Curl_raw_toupper(char in); -char Curl_raw_tolower(char in); +/* Mapping tables for plain ASCII case conversion, defined in strcase.c. + Declared here so the conversions below inline at every call site without + relying on LTO or a unity build: casecompare() invokes one of them twice + per byte compared, where the call costs more than the lookup itself. */ +extern const unsigned char Curl_touppermap[256]; +extern const unsigned char Curl_tolowermap[256]; + +/* Portable, consistent toupper/tolower. Do not use toupper()/tolower() from + , whose behavior is altered by the current locale. */ +static CURL_INLINE char Curl_raw_toupper(char in) +{ + return (char)Curl_touppermap[(unsigned char)in]; +} + +static CURL_INLINE char Curl_raw_tolower(char in) +{ + return (char)Curl_tolowermap[(unsigned char)in]; +} /* checkprefix() is a shorter version of the above, used when the first argument is the string literal */ diff --git a/lib/strerror.c b/lib/strerror.c index 1e97c2829db5..6fc801dc6128 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -172,7 +172,7 @@ const char *curl_easy_strerror(CURLcode error) return "Can not set SSL crypto engine as default"; case CURLE_SSL_ENGINE_INITFAILED: - return "Failed to initialise SSL crypto engine"; + return "Failed to initialize SSL crypto engine"; case CURLE_SEND_ERROR: return "Failed sending data to the peer"; @@ -452,34 +452,34 @@ const char *curl_url_strerror(CURLUcode error) return "An unknown part ID was passed to a URL API function"; case CURLUE_NO_SCHEME: - return "No scheme part in the URL"; + return "No scheme present"; case CURLUE_NO_USER: - return "No user part in the URL"; + return "No user present"; case CURLUE_NO_PASSWORD: - return "No password part in the URL"; + return "No password present"; case CURLUE_NO_OPTIONS: - return "No options part in the URL"; + return "No options present"; case CURLUE_NO_HOST: - return "No host part in the URL"; + return "No host present"; case CURLUE_NO_PORT: - return "No port part in the URL"; + return "No port number present"; case CURLUE_NO_QUERY: - return "No query part in the URL"; + return "No query present"; case CURLUE_NO_FRAGMENT: - return "No fragment part in the URL"; + return "No fragment present"; case CURLUE_NO_ZONEID: - return "No zoneid part in the URL"; + return "No zoneid present"; case CURLUE_BAD_LOGIN: - return "Bad login part"; + return "Bad login"; case CURLUE_BAD_IPV6: return "Bad IPv6 address"; @@ -517,6 +517,9 @@ const char *curl_url_strerror(CURLUcode error) case CURLUE_TOO_LARGE: return "A value or data field is larger than allowed"; + case CURLUE_BACKSLASH: + return "Found a backslash where a forward slash was expected"; + case CURLUE_LAST: break; } @@ -647,14 +650,15 @@ const char *Curl_sspi_strerror(SECURITY_STATUS err, char *buf, size_t buflen) "SEC_E_ILLEGAL_MESSAGE (0x%08lx) - 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); + "the Windows System event log.", (unsigned long)err); } else { char msgbuf[256]; if(curlx_get_winapi_error((DWORD)err, msgbuf, sizeof(msgbuf))) - curl_msnprintf(buf, buflen, "%s (0x%08lx) - %s", txt, err, msgbuf); + curl_msnprintf(buf, buflen, "%s (0x%08lx) - %s", txt, (unsigned long)err, + msgbuf); else - curl_msnprintf(buf, buflen, "%s (0x%08lx)", txt, err); + curl_msnprintf(buf, buflen, "%s (0x%08lx)", txt, (unsigned long)err); } #else /* CURLVERBOSE */ if(err == SEC_E_OK) diff --git a/lib/system_win32.c b/lib/system_win32.c index 1b052357b5dd..4cbc2c25e9bf 100644 --- a/lib/system_win32.c +++ b/lib/system_win32.c @@ -38,33 +38,9 @@ CURLcode Curl_win32_init(long flags) should take place after this block. */ if(flags & CURL_GLOBAL_WIN32) { #ifdef USE_WINSOCK - WORD wVersionRequested; - WSADATA wsaData; - int res; - - wVersionRequested = MAKEWORD(2, 2); - res = WSAStartup(wVersionRequested, &wsaData); - - if(res) - /* Tell the user that we could not find a usable */ - /* winsock.dll. */ - return CURLE_FAILED_INIT; - - /* Confirm that the Windows Sockets DLL supports what we need.*/ - /* Note that if the DLL supports versions greater */ - /* than wVersionRequested, it will still return */ - /* wVersionRequested in wVersion. wHighVersion contains the */ - /* highest supported version. */ - - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { - /* Tell the user that we could not find a usable */ - - /* winsock.dll. */ - WSACleanup(); + WSADATA wsa; + if(WSAStartup(MAKEWORD(2, 2), &wsa)) return CURLE_FAILED_INIT; - } - /* The Windows Sockets DLL is acceptable. Proceed. */ #elif defined(USE_LWIPSOCK) lwip_init(); #endif diff --git a/lib/system_win32.h b/lib/system_win32.h index 8a51f096702a..d504b563db4b 100644 --- a/lib/system_win32.h +++ b/lib/system_win32.h @@ -26,8 +26,6 @@ #include "curl_setup.h" #ifdef _WIN32 -extern LARGE_INTEGER Curl_freq; - CURLcode Curl_win32_init(long flags); void Curl_win32_cleanup(long init_flags); #else diff --git a/lib/telnet.c b/lib/telnet.c index c5ce9c2c97e3..735fa66156fd 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -53,12 +53,13 @@ #include "curl_trc.h" #include "progress.h" #include "arpa_telnet.h" +#include "connect.h" #include "select.h" #include "curlx/strparse.h" #define SUBBUFSIZE 512 -#define CURL_SB_CLEAR(x) x->subpointer = (x)->subbuffer +#define CURL_SB_CLEAR(x) (x)->subpointer = (x)->subbuffer #define CURL_SB_TERM(x) \ do { \ (x)->subend = (x)->subpointer; \ @@ -74,8 +75,8 @@ #define CURL_SB_LEN(x) ((x)->subend - (x)->subpointer) /* For posterity: -#define CURL_SB_PEEK(x) ((*x->subpointer)&0xff) -#define CURL_SB_EOF(x) (x->subpointer >= x->subend) */ +#define CURL_SB_PEEK(x) (*(x)->subpointer & 0xff) +#define CURL_SB_EOF(x) ((x)->subpointer >= (x)->subend) */ /* For negotiation compliant to RFC 1143 */ #define CURL_NO 0 @@ -195,20 +196,15 @@ static CURLcode init_telnet(struct Curl_easy *data) tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES; tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; - /* To be compliant with previous releases of libcurl - we enable this option by default. This behavior - can be changed thanks to the "BINARY" option in - CURLOPT_TELNETOPTIONS - */ + /* To be compliant with previous releases of libcurl we enable this option + by default. This behavior can be changed with the "BINARY" option in + CURLOPT_TELNETOPTIONS */ tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES; tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES; - /* We must allow the server to echo what we sent - but it is not necessary to request the server - to do so (it might forces the server to close - the connection). Hence, we ignore ECHO in the - negotiate function - */ + /* We must allow the server to echo what we sent but it is not necessary + to request the server to do so (it might force the server to close + the connection). Hence, we ignore ECHO in the negotiate function */ tn->him_preferred[CURL_TELOPT_ECHO] = CURL_YES; /* Set the subnegotiation fields to send information after negotiation @@ -216,12 +212,11 @@ static CURLcode init_telnet(struct Curl_easy *data) Default values are (0,0) initialized by calloc. According to the RFC1013 it is valid: - A value equal to zero is acceptable for the width (or height), - and means that no character width (or height) is being sent. - In this case, the width (or height) that will be assumed by the - Telnet server is operating system specific (it will probably be - based upon the terminal type information that may have been sent - using the TERMINAL TYPE Telnet option). */ + A value equal to zero is acceptable for the width (or height), and means + that no character width (or height) is being sent. In this case, the width + (or height) that will be assumed by the Telnet server is operating system + specific (it will probably be 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 Curl_meta_set(data, CURL_META_TELNET_EASY, tn, telnet_easy_dtor); @@ -238,10 +233,8 @@ static void send_negotiation(struct Curl_easy *data, int cmd, int option) buf[2] = (unsigned char)option; bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); - if(bytes_written < 0) { - int err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } + if(bytes_written < 0) + failf(data, "Sending data failed (%d)", SOCKERRNO); printoption(data, "SENT", cmd, option); } @@ -645,13 +638,19 @@ static CURLcode send_telnet_data(struct Curl_easy *data, while(!result && total_written < outlen) { /* Make sure socket is writable to avoid EWOULDBLOCK condition */ struct pollfd pfd[1]; + timediff_t timeout_ms = Curl_timeleft_ms(data); pfd[0].fd = conn->sock[FIRSTSOCKET]; pfd[0].events = POLLOUT; - switch(Curl_poll(pfd, 1, -1)) { + if(timeout_ms < 0) + return CURLE_OPERATION_TIMEDOUT; + /* 0 means no timeout configured; pass -1 to poll for infinite wait */ + switch(Curl_poll(pfd, 1, timeout_ms ? timeout_ms : -1)) { case -1: /* error, abort writing */ - case 0: /* timeout (will never happen) */ result = CURLE_SEND_ERROR; break; + case 0: /* timeout */ + result = CURLE_OPERATION_TIMEDOUT; + break; default: /* write! */ bytes_written = 0; result = Curl_xfer_send(data, outbuf + total_written, @@ -673,7 +672,6 @@ static void sendsuboption(struct Curl_easy *data, struct TELNET *tn, int option) { ssize_t bytes_written; - int err; unsigned short x, y; const unsigned char *uc1, *uc2; struct connectdata *conn = data->conn; @@ -706,19 +704,15 @@ static void sendsuboption(struct Curl_easy *data, /* we send the header of the suboption... */ bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } + if(bytes_written < 0) + failf(data, "Sending data failed (%d)", SOCKERRNO); /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ send_telnet_data(data, tn, (const char *)tn->subbuffer + 3, 4); /* ... and the footer */ bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } + if(bytes_written < 0) + failf(data, "Sending data failed (%d)", SOCKERRNO); break; } } @@ -773,7 +767,7 @@ static void rec_do(struct Curl_easy *data, struct TELNET *tn, int option) break; case CURL_OPPOSITE: tn->us[option] = CURL_WANTNO; - tn->himq[option] = CURL_EMPTY; + tn->usq[option] = CURL_EMPTY; send_negotiation(data, CURL_WONT, option); break; } @@ -840,13 +834,14 @@ static CURLcode check_telnet_options(struct Curl_easy *data, /* Add the username as an environment variable if it was given on the command line */ - if(data->state.aptr.user) { + if(data->state.creds) { char buffer[256]; - if(str_is_nonascii(data->conn->user)) { + if(str_is_nonascii(Curl_creds_user(data->conn->creds))) { DEBUGF(infof(data, "set a non ASCII username in telnet")); return CURLE_BAD_FUNCTION_ARGUMENT; } - curl_msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user); + curl_msnprintf(buffer, sizeof(buffer), "USER,%s", + Curl_creds_user(data->conn->creds)); beg = curl_slist_append(tn->telnet_vars, buffer); if(!beg) { curl_slist_free_all(tn->telnet_vars); @@ -979,7 +974,6 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) unsigned char temp[2048]; ssize_t bytes_written; size_t len; - int err; struct connectdata *conn = data->conn; if(!CURL_SB_LEN(tn)) /* ignore empty suboption */ @@ -992,7 +986,7 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) if(bad_option(tn->subopt_ttype)) return CURLE_BAD_FUNCTION_ARGUMENT; if(strlen(tn->subopt_ttype) > 1000) { - failf(data, "Tool long telnet TTYPE"); + failf(data, "Too long telnet TTYPE"); return CURLE_SEND_ERROR; } len = curl_msnprintf((char *)temp, sizeof(temp), "%c%c%c%c%s%c%c", @@ -1002,8 +996,7 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); + failf(data, "Sending data failed (%d)", SOCKERRNO); return CURLE_SEND_ERROR; } printsub(data, '>', &temp[2], len-2); @@ -1012,7 +1005,7 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) if(bad_option(tn->subopt_xdisploc)) return CURLE_BAD_FUNCTION_ARGUMENT; if(strlen(tn->subopt_xdisploc) > 1000) { - failf(data, "Tool long telnet XDISPLOC"); + failf(data, "Too long telnet XDISPLOC"); return CURLE_SEND_ERROR; } len = curl_msnprintf((char *)temp, sizeof(temp), "%c%c%c%c%s%c%c", @@ -1021,8 +1014,7 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) CURL_SE); bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); + failf(data, "Sending data failed (%d)", SOCKERRNO); return CURLE_SEND_ERROR; } printsub(data, '>', &temp[2], len - 2); @@ -1036,7 +1028,7 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) if(bad_option(v->data)) return CURLE_BAD_FUNCTION_ARGUMENT; /* Add the variable if it fits */ - if(len + tmplen < (int)sizeof(temp) - 6) { + if(len + tmplen < sizeof(temp) - 6) { const char *s = strchr(v->data, ','); if(!s) len += curl_msnprintf((char *)&temp[len], sizeof(temp) - len, @@ -1053,10 +1045,8 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) "%c%c", CURL_IAC, CURL_SE); len += 2; bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } + if(bytes_written < 0) + failf(data, "Sending data failed (%d)", SOCKERRNO); printsub(data, '>', &temp[2], len - 2); break; } @@ -1239,7 +1229,6 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) DWORD obj_count; DWORD wait_timeout; DWORD readfile_read; - int err; #else timediff_t interval_ms; struct pollfd pfd[2]; @@ -1278,12 +1267,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } /* Tell Winsock what events we want to listen to */ - if(WSAEventSelect(sockfd, event_handle, FD_READ | FD_CLOSE) != 0) { + if(WSAEventSelect(sockfd, event_handle, FD_READ | FD_CLOSE)) { WSACloseEvent(event_handle); return CURLE_RECV_ERROR; } - /* The get the Windows file handle for stdin */ + /* Then get the Windows file handle for stdin */ stdin_handle = GetStdHandle(STD_INPUT_HANDLE); /* Create the list of objects to wait for */ @@ -1377,9 +1366,9 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) case WAIT_OBJECT_0: { events.lNetworkEvents = 0; if(WSAEnumNetworkEvents(sockfd, event_handle, &events) != 0) { - err = SOCKERRNO; - if(err != SOCKEINPROGRESS) { - infof(data, "WSAEnumNetworkEvents failed (%d)", err); + int sockerr = SOCKERRNO; + if(sockerr != SOCKEINPROGRESS) { + infof(data, "WSAEnumNetworkEvents failed (%d)", sockerr); keepon = FALSE; result = CURLE_READ_ERROR; } diff --git a/lib/tftp.c b/lib/tftp.c index 6cc672d447bf..d95da69dc373 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -167,7 +167,8 @@ static CURLcode tftp_set_timeouts(struct tftp_conn *state) } /* Set per-block timeout to total */ - if(timeout_ms > 0) + if((timeout_ms > 0) && (timeout_ms < 3600000)) + /* do the calculation only if the timeout is "reasonable" */ timeout = (time_t)(timeout_ms + 500) / 1000; else timeout = 15; @@ -266,16 +267,19 @@ static CURLcode tftp_parse_option_ack(struct tftp_conn *state, while(tmp < ptr + len) { const char *option, *value; + size_t olen; tmp = tftp_option_get(tmp, ptr + len - tmp, &option, &value); if(!tmp) { failf(data, "Malformed ACK packet, rejecting"); return CURLE_TFTP_ILLEGAL; } + olen = strlen(option); infof(data, "got option=(%s) value=(%s)", option, value); - if(checkprefix(TFTP_OPTION_BLKSIZE, option)) { + if((CURL_CSTRLEN(TFTP_OPTION_BLKSIZE) == olen) && + checkprefix(TFTP_OPTION_BLKSIZE, option)) { curl_off_t blksize; if(curlx_str_number(&value, &blksize, TFTP_BLKSIZE_MAX)) { failf(data, "%s (%d)", "blksize is larger than max supported", @@ -304,7 +308,8 @@ static CURLcode tftp_parse_option_ack(struct tftp_conn *state, infof(data, "blksize parsed from OACK (%u) requested (%u)", state->blksize, state->requested_blksize); } - else if(checkprefix(TFTP_OPTION_TSIZE, option)) { + else if((CURL_CSTRLEN(TFTP_OPTION_TSIZE) == olen) && + checkprefix(TFTP_OPTION_TSIZE, option)) { curl_off_t tsize = 0; /* tsize should be ignored on upload: Who cares about the size of the remote file? */ @@ -486,7 +491,7 @@ static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) break; default: - failf(data, "tftp_tx: internal error, event: %i", (int)event); + failf(data, "tftp_tx: internal error, event: %d", (int)event); break; } @@ -671,7 +676,7 @@ static CURLcode tftp_send_first(struct tftp_conn *state, } if(data->state.upload) { - /* If we are uploading, send an WRQ */ + /* If we are uploading, send a WRQ */ setpacketevent(&state->spacket, TFTP_EVENT_WRQ); if(data->state.infilesize != -1) Curl_pgrsSetUploadSize(data, data->state.infilesize); @@ -736,7 +741,7 @@ static CURLcode tftp_send_first(struct tftp_conn *state, } } - /* the typecase for the 3rd argument is mostly for systems that do + /* the typecast 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) @@ -869,7 +874,7 @@ static CURLcode tftp_state_machine(struct tftp_conn *state, infof(data, "%s", "TFTP finished"); break; default: - DEBUGF(infof(data, "STATE: %d", state->state)); + DEBUGF(infof(data, "STATE: %d", (int)state->state)); failf(data, "%s", "Internal state machine error"); result = CURLE_TFTP_ILLEGAL; break; @@ -937,7 +942,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) /* we do not keep TFTP connections up because there is none or little gain * for UDP */ - connclose(conn, "TFTP"); + connclose(conn); state->data = data; state->sockfd = conn->sock[FIRSTSOCKET]; @@ -952,7 +957,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; ((struct sockaddr *)&state->local_addr)->sa_family = - (CURL_SA_FAMILY_T)(remote_addr->family); + (CURL_SA_FAMILY_T)remote_addr->family; result = tftp_set_timeouts(state); if(result) @@ -983,8 +988,6 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) conn->bits.bound = TRUE; } - Curl_pgrsStartNow(data); - *done = TRUE; return CURLE_OK; @@ -1192,9 +1195,9 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) if(rc == -1) { /* bail out */ - int error = SOCKERRNO; + int sockerr = SOCKERRNO; char buffer[STRERROR_LEN]; - failf(data, "%s", curlx_strerror(error, buffer, sizeof(buffer))); + failf(data, "%s", curlx_strerror(sockerr, buffer, sizeof(buffer))); state->event = TFTP_EVENT_ERROR; } else if(rc) { diff --git a/lib/thrdpool.c b/lib/thrdpool.c index 22faa396ed66..900fd1cfc09d 100644 --- a/lib/thrdpool.c +++ b/lib/thrdpool.c @@ -28,6 +28,7 @@ #include "llist.h" #include "curl_threads.h" #include "curlx/timeval.h" +#include "curlx/strparse.h" #include "thrdpool.h" #ifdef CURLVERBOSE #include "curl_trc.h" @@ -49,7 +50,7 @@ struct thrdslot { }; struct curl_thrdpool { - char *name; + const char *name; uint64_t refcount; curl_mutex_t lock; curl_cond_t await; @@ -64,6 +65,9 @@ struct curl_thrdpool { uint32_t max_threads; uint32_t idle_time_ms; uint32_t next_id; +#ifdef DEBUGBUILD + int dbg_fail_starts; /* fail this many thread starts */ +#endif BIT(aborted); BIT(detached); }; @@ -130,9 +134,9 @@ static CURL_THREAD_RETURN_T CURL_STDCALL thrdslot_run(void *arg) * on activating threads that have no means to shut down. */ if((tpool->idle_time_ms > 0) && (Curl_llist_count(&tpool->slots) > tpool->min_threads)) { - CURLcode r = Curl_cond_timedwait(&tslot->await, &tpool->lock, - tpool->idle_time_ms); - if((r == CURLE_OPERATION_TIMEDOUT) && + CURLcode result = Curl_cond_timedwait(&tslot->await, &tpool->lock, + tpool->idle_time_ms); + if((result == CURLE_OPERATION_TIMEDOUT) && (Curl_llist_count(&tpool->slots) > tpool->min_threads)) { goto out; } @@ -166,7 +170,14 @@ static CURLcode thrdslot_start(struct curl_thrdpool *tpool) tpool->refcount++; tslot->running = TRUE; - tslot->thread = Curl_thread_create(thrdslot_run, tslot); +#ifdef DEBUGBUILD + if(tpool->dbg_fail_starts > 0) { + --tpool->dbg_fail_starts; + tslot->thread = curl_thread_t_null; + } + else +#endif + tslot->thread = Curl_thread_create(thrdslot_run, tslot); if(tslot->thread == curl_thread_t_null) { /* never started */ tslot->running = FALSE; thrdpool_unlink(tpool, TRUE); @@ -225,7 +236,6 @@ static bool thrdpool_unlink(struct curl_thrdpool *tpool, bool locked) thrdpool_join_zombies(tpool); if(locked) Curl_mutex_release(&tpool->lock); - curlx_free(tpool->name); Curl_cond_destroy(&tpool->await); Curl_mutex_destroy(&tpool->lock); curlx_free(tpool); @@ -301,6 +311,7 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, { struct curl_thrdpool *tpool; CURLcode result = CURLE_OUT_OF_MEMORY; + DEBUGASSERT(name); tpool = curlx_calloc(1, sizeof(*tpool)); if(!tpool) @@ -316,9 +327,19 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, tpool->fn_return = fn_return; tpool->fn_user_data = user_data; - tpool->name = curlx_strdup(name); - if(!tpool->name) - goto out; + /* a const string that remains */ + tpool->name = name; + +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_THRDPOOL_FAIL_STARTS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + tpool->dbg_fail_starts = (int)l; + } + } +#endif result = Curl_thrdpool_set_props(tpool, min_threads, max_threads, idle_time_ms); diff --git a/lib/thrdqueue.c b/lib/thrdqueue.c index 1521ccfbee10..b0094059052e 100644 --- a/lib/thrdqueue.c +++ b/lib/thrdqueue.c @@ -37,7 +37,7 @@ struct curl_thrdq { - char *name; + const char *name; curl_mutex_t lock; curl_cond_t await; struct Curl_llist sendq; @@ -47,7 +47,6 @@ struct curl_thrdq { Curl_thrdq_item_process_cb *fn_process; Curl_thrdq_ev_cb *fn_event; void *fn_user_data; - uint32_t send_max_len; BIT(aborted); }; @@ -188,7 +187,6 @@ static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join) Curl_llist_destroy(&tqueue->sendq, NULL); Curl_llist_destroy(&tqueue->recvq, NULL); - curlx_free(tqueue->name); Curl_cond_destroy(&tqueue->await); if(locked) Curl_mutex_release(&tqueue->lock); @@ -198,7 +196,6 @@ static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join) CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, const char *name, - uint32_t max_len, uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms, @@ -209,6 +206,7 @@ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, { struct curl_thrdq *tqueue; CURLcode result = CURLE_OUT_OF_MEMORY; + DEBUGASSERT(name); tqueue = curlx_calloc(1, sizeof(*tqueue)); if(!tqueue) @@ -222,11 +220,9 @@ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, tqueue->fn_process = fn_process; tqueue->fn_event = fn_event; tqueue->fn_user_data = user_data; - tqueue->send_max_len = max_len; - tqueue->name = curlx_strdup(name); - if(!tqueue->name) - goto out; + /* a const string that remains */ + tqueue->name = name; result = Curl_thrdpool_create(&tqueue->tpool, name, min_threads, max_threads, idle_time_ms, @@ -253,11 +249,18 @@ void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join) thrdq_unlink(tqueue, TRUE, join); } +static uint32_t thrdq_get_signals(struct curl_thrdq *tqueue) +{ + size_t qlen = Curl_llist_count(&tqueue->sendq); + return (qlen <= UINT32_MAX) ? (uint32_t)qlen : UINT32_MAX; +} + CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, const char *description, timediff_t timeout_ms) { - CURLcode result = CURLE_AGAIN; - size_t signals = 0; + struct thrdq_item *qitem; + CURLcode result = CURLE_OK; + uint32_t signals = 0; Curl_mutex_acquire(&tqueue->lock); if(tqueue->aborted) { @@ -270,19 +273,13 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, goto out; } - if(!tqueue->send_max_len || - (Curl_llist_count(&tqueue->sendq) < tqueue->send_max_len)) { - struct thrdq_item *qitem = thrdq_item_create(tqueue, item, description, - timeout_ms); - if(!qitem) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - item = NULL; - Curl_llist_append(&tqueue->sendq, qitem, &qitem->node); - signals = Curl_llist_count(&tqueue->sendq); - result = CURLE_OK; + qitem = thrdq_item_create(tqueue, item, description, timeout_ms); + if(!qitem) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + Curl_llist_append(&tqueue->sendq, qitem, &qitem->node); + signals = thrdq_get_signals(tqueue); out: Curl_mutex_release(&tqueue->lock); @@ -295,10 +292,22 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, return result; } +bool Curl_thrdq_check_started(struct curl_thrdq *tqueue) +{ + size_t unprocessed; + + Curl_mutex_acquire(&tqueue->lock); + unprocessed = tqueue->aborted ? 0 : Curl_llist_count(&tqueue->sendq); + Curl_mutex_release(&tqueue->lock); + return !unprocessed || + !Curl_thrdpool_signal(tqueue->tpool, (uint32_t)unprocessed); +} + CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem) { CURLcode result = CURLE_AGAIN; struct Curl_llist_node *e; + uint32_t signals = 0; *pitem = NULL; Curl_mutex_acquire(&tqueue->lock); @@ -316,8 +325,18 @@ CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem) thrdq_item_destroy(qitem); result = CURLE_OK; } + else + signals = thrdq_get_signals(tqueue); + out: Curl_mutex_release(&tqueue->lock); + /* Signal thread pool unlocked to avoid deadlocks. If items await + * processing while nothing was ready, make sure the pool has a + * thread to work on them. An earlier thread start may have failed, + * which `Curl_thrdq_send()` cannot report to its caller. Without + * this, such items would sit unprocessed until the next send. */ + if(signals) + (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals); return result; } @@ -351,24 +370,27 @@ void Curl_thrdq_clear(struct curl_thrdq *tqueue, Curl_mutex_release(&tqueue->lock); } -CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue, - uint32_t timeout_ms) +#ifdef UNITTESTS +/* @unittest 3301 */ +UNITTEST CURLcode thrdq_await_done(struct curl_thrdq *tqueue, + uint32_t timeout_ms); +UNITTEST CURLcode thrdq_await_done(struct curl_thrdq *tqueue, + uint32_t timeout_ms) { return Curl_thrdpool_await_idle(tqueue->tpool, timeout_ms); } +#endif CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, - uint32_t max_len, uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms) { CURLcode result; - size_t signals; + uint32_t signals; Curl_mutex_acquire(&tqueue->lock); - tqueue->send_max_len = max_len; - signals = Curl_llist_count(&tqueue->sendq); + signals = thrdq_get_signals(tqueue); Curl_mutex_release(&tqueue->lock); result = Curl_thrdpool_set_props(tqueue->tpool, min_threads, diff --git a/lib/thrdqueue.h b/lib/thrdqueue.h index d267f5d09105..0d84f6f6f144 100644 --- a/lib/thrdqueue.h +++ b/lib/thrdqueue.h @@ -26,23 +26,24 @@ #include "curl_setup.h" #include "curlx/timediff.h" +struct curl_thrdq; + #ifdef USE_THREADS struct Curl_easy; -struct curl_thrdq; typedef enum { CURL_THRDQ_EV_ITEM_DONE /* an item has been processed and is ready */ } Curl_thrdq_event; -/* Notification callback when "events" happen in the queue. May be - * call from any thread, queue is not locked. */ +/* Notification callback when "events" happen in the queue. May be called from + * any thread, queue is not locked. */ typedef void Curl_thrdq_ev_cb(const struct curl_thrdq *tqueue, Curl_thrdq_event ev, void *user_data); -/* Process a queued item. Maybe call from any thread. Queue is - * not locked. */ +/* Process a queued item. May be called from any thread. Queue is not + * locked. */ typedef void Curl_thrdq_item_process_cb(void *item); /* Free an item. May be called from any thread at any time for an @@ -53,7 +54,6 @@ typedef void Curl_thrdq_item_free_cb(void *item); */ CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue, const char *name, - uint32_t max_len, /* 0 for unlimited */ uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms, @@ -73,7 +73,6 @@ void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join); * to "item" on success, e.g. the queue takes ownership. * `description` is an optional string describing the item for tracing * purposes. It needs to have the same lifetime as `item`. - * Returns CURLE_AGAIN when the queue has already been full. * * With`timeout_ms` != 0, items that get stuck that long in the send * queue are removed and added to the receive queue right away. @@ -85,10 +84,21 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, * The caller takes ownership of the item received, e.g. the queue * relinquishes all references to item. * Returns CURLE_AGAIN when there is no processed item, setting `pitem` - * to NULL. + * to NULL. When nothing has been processed while items await sending, + * the pool is signalled to make sure a worker thread exists: an + * earlier thread start may have failed. */ CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem); +/* Check that items awaiting processing have a worker thread to run + * them, signalling the pool to start one when needed -- an earlier + * thread start may have failed. Returns TRUE when everything is ok: + * no items are waiting or the pool took the signal. FALSE when a + * thread start just failed again; callers may want to check again + * soon rather than wait indefinitely. + */ +bool Curl_thrdq_check_started(struct curl_thrdq *tqueue); + /* Return TRUE if the passed "item" matches. */ typedef bool Curl_thrdq_item_match_cb(void *item, void *match_data); @@ -103,7 +113,6 @@ CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue, uint32_t timeout_ms); CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, - uint32_t max_len, /* 0 for unlimited */ uint32_t min_threads, uint32_t max_threads, uint32_t idle_time_ms); diff --git a/lib/transfer.c b/lib/transfer.c index fd1a903dabc8..3b6c5c03dd36 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -51,15 +51,13 @@ #endif #ifndef HAVE_SOCKET -#error "We cannot compile without socket() support!" +#error "We cannot compile without socket() support" #endif #include "urldata.h" -#include "hostip.h" #include "cfilters.h" #include "cw-out.h" -#include "dnscache.h" #include "transfer.h" #include "sendf.h" #include "curl_trc.h" @@ -73,6 +71,7 @@ #include "setopt.h" #include "headers.h" #include "bufref.h" +#include "rtsp.h" #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_IMAP) @@ -155,7 +154,7 @@ static bool xfer_recv_shutdown_started(struct Curl_easy *data) { if(!data || !data->conn) return FALSE; - return Curl_shutdown_started(data, data->conn->recv_idx); + return Curl_shutdown_started(data->conn, data->conn->recv_idx); } CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) @@ -250,8 +249,7 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, bytestoread = xfer_blen; if(bytestoread && Curl_rlimit_active(&data->progress.dl.rlimit)) { - curl_off_t dl_avail = Curl_rlimit_avail(&data->progress.dl.rlimit, - Curl_pgrs_now(data)); + curl_off_t dl_avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); #if 0 DEBUGF(infof(data, "dl_rlimit, available=%" FMT_OFF_T, dl_avail)); #endif @@ -332,7 +330,7 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, out: Curl_multi_xfer_buf_release(data, xfer_buf); if(result) - DEBUGF(infof(data, "sendrecv_dl() -> %d", result)); + DEBUGF(infof(data, "sendrecv_dl() -> %d", (int)result)); return result; } @@ -356,6 +354,7 @@ static CURLcode sendrecv_ul(struct Curl_easy *data) CURLcode Curl_sendrecv(struct Curl_easy *data) { struct SingleRequest *k = &data->req; + const struct curltime *pnow = NULL; CURLcode result = CURLE_OK; if(Curl_xfer_is_blocked(data)) { @@ -378,25 +377,20 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) goto out; } - result = Curl_pgrsCheck(data); - if(result) - goto out; - + pnow = Curl_pgrs_now(data); if(CURL_REQ_WANT_IO(data)) { - if(Curl_timeleft_ms(data) < 0) { + if(Curl_timeleft_now_ms(data, pnow) < 0) { if(k->size != -1) { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " out of %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->progress.t_startsingle), + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -408,7 +402,7 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) * The transfer has been performed. Make some general checks before * returning. */ - if(!(data->req.no_body) && (k->size != -1) && + if(!data->req.no_body && (k->size != -1) && (k->bytecount != k->size) && !k->newurl) { failf(data, "transfer closed with %" FMT_OFF_T " bytes remaining to read", k->size - k->bytecount); @@ -421,11 +415,11 @@ CURLcode Curl_sendrecv(struct Curl_easy *data) if(!CURL_REQ_WANT_IO(data)) data->req.done = TRUE; - result = Curl_pgrsUpdate(data); + result = Curl_pgrsCheckX(data, pnow); out: if(result) - DEBUGF(infof(data, "Curl_sendrecv() -> %d", result)); + DEBUGF(infof(data, "Curl_sendrecv() -> %d", (int)result)); return result; } @@ -438,40 +432,6 @@ void Curl_init_CONNECT(struct Curl_easy *data) data->state.upload = (data->state.httpreq == HTTPREQ_PUT); } -/* - * Restore the user credentials to those set in options. - */ -CURLcode Curl_reset_userpwd(struct Curl_easy *data) -{ - CURLcode result; - if(data->set.str[STRING_USERNAME] || data->set.str[STRING_PASSWORD]) - data->state.creds_from = CREDS_OPTION; - result = Curl_setstropt(&data->state.aptr.user, - data->set.str[STRING_USERNAME]); - if(!result) - result = Curl_setstropt(&data->state.aptr.passwd, - data->set.str[STRING_PASSWORD]); - return result; -} - -/* - * Restore the proxy credentials to those set in options. - */ -CURLcode Curl_reset_proxypwd(struct Curl_easy *data) -{ -#ifndef CURL_DISABLE_PROXY - CURLcode result = Curl_setstropt(&data->state.aptr.proxyuser, - data->set.str[STRING_PROXYUSERNAME]); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxypasswd, - data->set.str[STRING_PROXYPASSWORD]); - return result; -#else - (void)data; - return CURLE_OK; -#endif -} - /* * Curl_pretransfer() is called immediately before a transfer starts, and only * once for one transfer no matter if it has redirects or do multi-pass @@ -489,7 +449,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) * By resetting it here, we ensure each new request starts fresh. */ data->state.retrycount = 0; - if(!data->set.str[STRING_SET_URL] && !data->set.uh) { + if(!CURL_EASY_STR(data, STRING_SET_URL) && !data->set.uh) { /* we cannot do anything without URL */ failf(data, "No URL set"); return CURLE_URL_MALFORMAT; @@ -498,19 +458,22 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) /* 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) { + char *url = NULL; CURLUcode uc; - curlx_free(data->set.str[STRING_SET_URL]); - uc = curl_url_get(data->set.uh, - CURLUPART_URL, &data->set.str[STRING_SET_URL], 0); + uc = curl_url_get(data->set.uh, CURLUPART_URL, &url, 0); if(uc) { /* clear the pointer to not point to freed memory anymore */ Curl_bufref_set(&data->state.url, NULL, 0, NULL); failf(data, "No URL set"); return CURLE_URL_MALFORMAT; } + result = CURL_EASY_STR_SETN(data, STRING_SET_URL, url); + if(result) + return result; } - Curl_bufref_set(&data->state.url, data->set.str[STRING_SET_URL], 0, NULL); + Curl_bufref_set(&data->state.url, CURL_EASY_STR(data, STRING_SET_URL), + 0, NULL); if(data->set.postfields && data->set.set_resume_from) { /* we cannot */ @@ -524,6 +487,10 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) #endif data->state.httpreq = data->set.method; + /* initial transfer request coming up, forget the initial origin + * from a previous perform() on this handle. */ + Curl_peer_unlink(&data->state.initial_origin); + Curl_peer_unlink(&data->state.origin); data->state.requests = 0; data->state.followlocation = 0; /* reset the location-follow counter */ data->state.this_is_a_follow = FALSE; /* reset this */ @@ -539,9 +506,11 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) Curl_data_priority_clear_state(data); if(data->set.http_auto_referer) Curl_bufref_free(&data->state.referer); - if(data->set.str[STRING_SET_REFERER]) - Curl_bufref_set(&data->state.referer, data->set.str[STRING_SET_REFERER], - 0, NULL); + if(CURL_EASY_STR(data, STRING_SET_REFERER)) + Curl_bufref_set(&data->state.referer, + CURL_EASY_STR(data, STRING_SET_REFERER), 0, NULL); + else + Curl_bufref_free(&data->state.referer); if(data->state.httpreq == HTTPREQ_PUT) data->state.infilesize = data->set.filesize; @@ -555,7 +524,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.infilesize = 0; /* If there is a list of cookie files to read, do it now! */ - result = Curl_cookie_loadfiles(data); + result = Curl_cookie_loadfiles(data, + data->set.cookiesession ? + COOKIE_NOSESSION : 0); if(!result) Curl_cookie_run(data); /* activate */ @@ -583,7 +554,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) Curl_initinfo(data); /* reset session-specific information "variables" */ Curl_pgrsResetTransferSizes(data); - Curl_pgrsStartNow(data); + Curl_pgrsStart(data, NULL); /* 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 @@ -613,23 +584,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) result = Curl_hsts_loadcb(data, data->hsts); } - /* - * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * anything through an HTTP proxy we cannot limit this based on protocol. - */ - if(!result && data->set.str[STRING_USERAGENT]) { - curlx_free(data->state.aptr.uagent); - data->state.aptr.uagent = - curl_maprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); - if(!data->state.aptr.uagent) - return CURLE_OUT_OF_MEMORY; - } - - if(!result) - result = Curl_reset_userpwd(data); - if(!result) - result = Curl_reset_proxypwd(data); - data->req.headerbytecount = 0; Curl_headers_cleanup(data); return result; @@ -692,7 +646,7 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) if(!*url) return CURLE_OUT_OF_MEMORY; - connclose(conn, "retry"); /* close this connection */ + connclose(conn); /* close this connection */ 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 because nothing @@ -704,15 +658,15 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) static void xfer_setup( struct Curl_easy *data, /* transfer */ - int send_idx, /* sockindex to send on or -1 */ - int recv_idx, /* sockindex to receive on or -1 */ + int8_t send_idx, /* sockindex to send on or -1 */ + int8_t recv_idx, /* sockindex to receive on or -1 */ curl_off_t recv_size /* how much to receive, -1 if unknown */ ) { struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - DEBUGASSERT(conn != NULL); + DEBUGASSERT(conn); /* indexes are in range */ DEBUGASSERT((send_idx <= 1) && (send_idx >= -1)); DEBUGASSERT((recv_idx <= 1) && (recv_idx >= -1)); @@ -753,20 +707,20 @@ void Curl_xfer_setup_nop(struct Curl_easy *data) } void Curl_xfer_setup_sendrecv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size) { xfer_setup(data, sockindex, sockindex, recv_size); } void Curl_xfer_setup_send(struct Curl_easy *data, - int sockindex) + int8_t sockindex) { xfer_setup(data, sockindex, -1, -1); } void Curl_xfer_setup_recv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size) { xfer_setup(data, -1, sockindex, recv_size); @@ -811,7 +765,7 @@ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, data->req.download_done = TRUE; } CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", - blen, is_eos, result); + blen, is_eos, (int)result); return result; } @@ -868,7 +822,7 @@ CURLcode Curl_xfer_send(struct Curl_easy *data, data->info.request_size += *pnwritten; DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu", - blen, eos, result, *pnwritten)); + blen, eos, (int)result, *pnwritten)); return result; } @@ -936,22 +890,14 @@ CURLcode Curl_xfer_pause_recv(struct Curl_easy *data, bool enable) bool Curl_xfer_is_secure(struct Curl_easy *data) { - const struct Curl_scheme *scheme = NULL; - - if(data->conn) { - scheme = data->conn->scheme; - /* if we are connected, but not use SSL, the transfer is not secure. - * This covers an insecure http:// proxy that is not tunneling. - * We enforce tunneling for such cases, but better be sure here. */ - if(Curl_conn_is_connected(data->conn, FIRSTSOCKET) && - !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) - return FALSE; - } - else if(data->info.conn_scheme) { /* was connected once */ - scheme = Curl_get_scheme(data->info.conn_scheme); - } - else { /* never connected (yet?) */ - DEBUGASSERT(0); /* not implemented, would need to parse URL */ +#ifndef CURL_DISABLE_PROXY + if(data->conn && data->conn->bits.origin_is_proxy) { + /* talking to a forward proxy, not secure. we do not use + * a forward proxy for https: and other 's' URLs. Let's just check that + * this did not fail somewhere. */ + DEBUGASSERT(!(data->state.origin->scheme->flags & PROTOPT_SSL)); + return FALSE; } - return scheme ? (scheme->flags & PROTOPT_SSL) : FALSE; +#endif + return (data->state.origin->scheme->flags & PROTOPT_SSL); } diff --git a/lib/transfer.h b/lib/transfer.h index b29e70b9ec12..0ad0d549edd8 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -31,8 +31,6 @@ char *Curl_checkheaders(const struct Curl_easy *data, void Curl_init_CONNECT(struct Curl_easy *data); -CURLcode Curl_reset_userpwd(struct Curl_easy *data); -CURLcode Curl_reset_proxypwd(struct Curl_easy *data); CURLcode Curl_pretransfer(struct Curl_easy *data); CURLcode Curl_sendrecv(struct Curl_easy *data); @@ -71,12 +69,12 @@ void Curl_xfer_setup_nop(struct Curl_easy *data); /* The transfer sends data on the given socket index */ void Curl_xfer_setup_send(struct Curl_easy *data, - int sockindex); + int8_t sockindex); /* The transfer receives data on the given socket index, the * amount to receive (or -1 if unknown). */ void Curl_xfer_setup_recv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size); /* *After* Curl_xfer_setup_xxx(), tell the transfer to shutdown the @@ -91,7 +89,7 @@ void Curl_xfer_set_shutdown(struct Curl_easy *data, * the amount to receive or -1 if unknown. */ void Curl_xfer_setup_sendrecv(struct Curl_easy *data, - int sockindex, + int8_t sockindex, curl_off_t recv_size); /** @@ -145,8 +143,12 @@ bool Curl_xfer_recv_is_paused(struct Curl_easy *data); CURLcode Curl_xfer_pause_send(struct Curl_easy *data, bool enable); CURLcode Curl_xfer_pause_recv(struct Curl_easy *data, bool enable); -/* TRUE if the transfer is secure (e.g. TLS) from libcurl to the - * URL's host. */ +/* TRUE if the transfer is secure, e.g. uses TLS and does not + * use a forward proxy. */ bool Curl_xfer_is_secure(struct Curl_easy *data); +/* Internal variant of the API function */ +CURLcode Curl_easy_recv(struct Curl_easy *data, + void *buffer, size_t buflen, size_t *n); + #endif /* HEADER_CURL_TRANSFER_H */ diff --git a/lib/uint-bset.c b/lib/uint-bset.c index 5469174944d9..55aedb234b05 100644 --- a/lib/uint-bset.c +++ b/lib/uint-bset.c @@ -157,7 +157,7 @@ bool Curl_uint32_bset_next(struct uint32_bset *bset, uint32_t last, /* 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 */ + /* more bits set, next is `last` + trailing 0s of the shifted slot */ *pnext = last + CURL_CTZ64(x); return TRUE; } @@ -179,10 +179,10 @@ uint32_t Curl_popcount64(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 uint64_t m1 = 0x5555555555555555LL; /* 0101+ */ - const uint64_t m2 = 0x3333333333333333LL; /* 00110011+ */ - const uint64_t m4 = 0x0f0f0f0f0f0f0f0fLL; /* 00001111+ */ - /* 1 + 256^1 + 256^2 + 256^3 + ... + 256^7 */ + const uint64_t m1 = 0x5555555555555555LL; /* 0101+ */ + const uint64_t m2 = 0x3333333333333333LL; /* 00110011+ */ + const uint64_t m4 = 0x0f0f0f0f0f0f0f0fLL; /* 00001111+ */ + /* 1 + 256^1 + 256^2 + 256^3 + ... + 256^7 */ const uint64_t h01 = 0x0101010101010101LL; x -= (x >> 1) & m1; /* replace every 2 bits with bits present */ x = (x & m2) + ((x >> 2) & m2); /* replace every nibble with bits present */ diff --git a/lib/uint-hashset.c b/lib/uint-hashset.c new file mode 100644 index 000000000000..84e9e5ec7bef --- /dev/null +++ b/lib/uint-hashset.c @@ -0,0 +1,311 @@ +/*************************************************************************** + * _ _ ____ _ + * 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-hashset.h" +#include "curlx/strdup.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_U8_STRSET_MAGIC 0x7117e783 +#endif + +#define CURL_U8_STRSET_DEBUG 0 + +#define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) + +static const uint8_t u8_smask[] = { + 0x00U, + 0x01U, + 0x03U, + 0x07U, + 0x0FU, + 0x1FU, + 0x3FU, + 0x7FU, + 0xFFU, +}; + +#define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) +#define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) +#define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) + +/* A hashset for tuples (id, string) using Robin Hood Hashing. + * + * The basic idea here to handle collisions by robbing "rich" entries and + * giving to the "poor": + * - We have an array: (id, string) are ideally placed at index "id % size". + * - If slot at index is already occupied, we have a collision. + * - A simple collision strategy would look at the next index, and the + * next until finding an empty slot. + * - The drawback is that this may lead to many checks on lookups, as it + * will need to also look at subsequent slots until it finds the match. + * The amount of lookups is the "probe sequence length" (psl) and this + * may vary greatly between entries. + * - Robin Hood Hashing balances the 'psl's of all entries more evenly: + * - psl == 0 means an entry is in exactly the right slot + * - psl == 1 means it is in the slot right after. psl == 2 is the slot + * after that, etc. + * - when inserting a new entry, track its psl. Finding a slot where + * the existing entry has a lower psl makes a swap. Put the new entry + * and its psl there, take the previous entry and its psl and find + * the next best slot for the previous entry. */ +void Curl_u8_strset_init(struct u8_strset *set) +{ +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + memset(set, 0, sizeof(*set)); +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + set->data = set->sdata; + set->ids = set->sids; + set->psl = set->spsl; + set->slotbits = CURL_U8_STRSET_START_BITS; + set->count = 0; +#ifdef DEBUGBUILD + set->init = CURL_U8_STRSET_MAGIC; +#endif +} + +void Curl_u8_strset_clear(struct u8_strset *set) +{ + uint16_t i; + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) + curlx_safefree(set->data[i]); + + if(set->data != set->sdata) + curlx_safefree(set->data); + Curl_u8_strset_init(set); +} + +static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) +{ + uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t psl = 0; + while(set->data[i]) { + if(psl > set->psl[i]) { /* SWAP */ + char *tmpdata; + tmpdata = set->data[i]; + set->data[i] = val; + val = tmpdata; + CURL_SWAP(set->psl[i], psl); + CURL_SWAP(set->ids[i], id); + } + i = CURL_U8_SET_SLOT_IDX(set, i + 1); + ++psl; + } + set->ids[i] = id; + set->data[i] = val; + set->psl[i] = psl; + ++set->count; +} + +static bool u8_strset_grow(struct u8_strset *set) +{ + uint8_t i, *prev_ids, nslotbits; + uint16_t prev_slots; + char **prev_data; + size_t nslots; + void *d; + + if(set->slotbits >= 8) + return FALSE; + nslotbits = (uint8_t)(set->slotbits + 1); +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n", + set->slotbits, nslotbits); +#endif + nslots = CURL_U8_SLOT_CNT(nslotbits); + d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots)); + if(!d) + return FALSE; + + prev_data = set->data; + prev_ids = set->ids; + prev_slots = set->slotbits; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" +#endif + set->data = (char **)d; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + set->ids = (uint8_t *)d + (nslots * sizeof(char *)); + set->psl = set->ids + nslots; + set->slotbits = nslotbits; + set->count = 0; + /* re-add previous entries */ + for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { + if(prev_data[i]) + u8_strset_addn(set, prev_ids[i], prev_data[i]); + } + if(prev_data != set->sdata) + curlx_free(prev_data); + return TRUE; +} + +static bool u8_strset_get_index(struct u8_strset *set, + uint8_t id, uint8_t *pindex) +{ + uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t psl = 0; + while(set->data[i] && (psl <= set->psl[i])) { + if(set->ids[i] == id) { + *pindex = i; +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]); +#endif + return TRUE; + } + i = CURL_U8_SET_SLOT_IDX(set, i + 1); + ++psl; + } +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_index %d not found\n", id); +#endif + *pindex = 0; + return FALSE; +} + +uint16_t Curl_u8_strset_count(struct u8_strset *set) +{ + return set->count; +} + +const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) +{ + uint8_t i; + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(u8_strset_get_index(set, id, &i)) + return set->data[i]; + return NULL; +} + +CURLcode Curl_u8_strset_setn(struct u8_strset *set, + uint8_t id, char *str) +{ + uint8_t i; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); +#if CURL_U8_STRSET_DEBUG + curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str); +#endif + if(!str) { + Curl_u8_strset_unset(set, id); + return CURLE_OK; + } + + if(u8_strset_get_index(set, id, &i)) { + /* `id` is in set, replace value */ + curlx_free(set->data[i]); + set->data[i] = str; + return CURLE_OK; + } + /* `id` not in set yet, grow if full */ + if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { + curlx_free(str); + return CURLE_OUT_OF_MEMORY; + } + + u8_strset_addn(set, id, str); + return CURLE_OK; +} + +CURLcode Curl_u8_strset_setx(struct u8_strset *set, + uint8_t id, const char *str, size_t slen) +{ + char *val; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(!str) { + Curl_u8_strset_unset(set, id); + return CURLE_OK; + } + + val = curlx_memdup0(str, slen); + if(!val) + return CURLE_OUT_OF_MEMORY; + return Curl_u8_strset_setn(set, id, val); +} + +CURLcode Curl_u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str) +{ + return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); +} + +static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) +{ + uint8_t i, j; + + DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); + if(u8_strset_get_index(set, id, &i)) { + /* `id` is in set */ + if(zero) + curlx_strzero(set->data[i]); + curlx_safefree(set->data[i]); + set->ids[i] = set->psl[i] = 0; + --set->count; + j = CURL_U8_SET_SLOT_IDX(set, i + 1); + /* shift all entries with positive psl "down" */ + while(set->data[j] && set->psl[j]) { + set->data[i] = set->data[j]; + set->ids[i] = set->ids[j]; + set->psl[i] = (uint8_t)(set->psl[j] - 1); + set->data[j] = NULL; + set->ids[j] = set->psl[j] = 0; + i = j; + j = CURL_U8_SET_SLOT_IDX(set, i + 1); + } + } +} + +void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id) +{ + u8_strset_unset(set, id, FALSE); +} + +void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id) +{ + u8_strset_unset(set, id, TRUE); +} + +CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) +{ + CURLcode result = CURLE_OK; + uint16_t i; + + DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); + Curl_u8_strset_clear(dest); + for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { + if(src->data[i]) + result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); + } + return result; +} diff --git a/lib/uint-hashset.h b/lib/uint-hashset.h new file mode 100644 index 000000000000..e5a1e02f00a8 --- /dev/null +++ b/lib/uint-hashset.h @@ -0,0 +1,82 @@ +#ifndef HEADER_CURL_UINT_HASHSET_H +#define HEADER_CURL_UINT_HASHSET_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" + +/* How large should the initial set be? + * Measuring our test suite with set growth force fail, gives + * BITS RESULT + * 1 1261 tests out of 1951 reported OK: 64% + * 2 1792 tests out of 1951 reported OK: 91% + * 3 1944 tests out of 1951 reported OK: 99% + * 4 1949 tests out of 1951 reported OK: 99% + * 5 single fail of 3211, unit test for u8_strset + * meaning 91% of our tests to not set more than 4 strings and + * 99% do not set more than 8. + */ +#define CURL_U8_STRSET_START_BITS 3 +#define CURL_U8_STRSET_START_DIM (1U << CURL_U8_STRSET_START_BITS) + +/* A set that can hold up to 256 strings identified by an `id'. + * Setting a string for an existing id replaces the previous one. + * Getting the string for an id not in the set returns NULL. + * Setting an id to NULL unsets the id. + */ +struct u8_strset { + char **data; /* #slots array of null-terminated strings */ + uint8_t *ids; /* #slots array of `id` values */ + uint8_t *psl; /* #slots array of "probe sequence length" values */ + char *sdata[CURL_U8_STRSET_START_DIM]; + uint8_t sids[CURL_U8_STRSET_START_DIM]; + uint8_t spsl[CURL_U8_STRSET_START_DIM]; + uint16_t count; + uint8_t slotbits; +#ifdef DEBUGBUILD + int32_t init; +#endif +}; + +void Curl_u8_strset_init(struct u8_strset *set); +void Curl_u8_strset_clear(struct u8_strset *set); + +uint16_t Curl_u8_strset_count(struct u8_strset *set); +const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id); + +/* Set string for id, makes a copy. */ +CURLcode Curl_u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str); +CURLcode Curl_u8_strset_setx(struct u8_strset *set, + uint8_t id, const char *str, size_t slen); +/* Set string for id, takes ownership of `str` even on failure. */ +CURLcode Curl_u8_strset_setn(struct u8_strset *set, + uint8_t id, char *str); +void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id); + +/* Remove the string if in the set and zero its memory */ +void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id); + +CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src); + +#endif /* HEADER_CURL_UINT_HASHSET_H */ diff --git a/lib/uint-spbset.c b/lib/uint-spbset.c index 3daa2eea758e..66b0fcb6708c 100644 --- a/lib/uint-spbset.c +++ b/lib/uint-spbset.c @@ -74,90 +74,142 @@ uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset) return n; } -static struct uint32_spbset_chunk *uint32_spbset_get_chunk( - struct uint32_spbset *bset, uint32_t i, bool grow) +static bool uint32_spbset_empty_chunk(struct uint32_spbset_chunk *chunk) { - struct uint32_spbset_chunk *chunk, **panchor = NULL; - uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK); - - if(!bset) - return NULL; + uint32_t i; + for(i = 0; i < CURL_UINT32_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + return FALSE; + } + return TRUE; +} +static struct uint32_spbset_chunk *uint32_spbset_unlink_empty( + struct uint32_spbset *bset, uint32_t for_offset) +{ + struct uint32_spbset_chunk *chunk, **panchor = 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; + if(uint32_spbset_empty_chunk(chunk)) break; + } + if(chunk) { + if(chunk == &bset->head) { /* head chunk is empty */ + if(!bset->head.next || (for_offset < bset->head.next->offset)) { + return &bset->head; + } + /* swap head and next, unlink */ + chunk = bset->head.next; + memcpy(&bset->head, chunk, sizeof(bset->head)); + memset(chunk, 0, sizeof(*chunk)); + } + else { + *panchor = chunk->next; /* unlink */ + memset(chunk, 0, sizeof(*chunk)); + } + } + return chunk; +} + +static struct uint32_spbset_chunk *uint32_spbset_insert_chunk( + struct uint32_spbset *bset, struct uint32_spbset_chunk *nchunk) +{ + struct uint32_spbset_chunk *chunk, **panchor; + + /* insert nchunk into set's ordered chunk list */ + if(nchunk->offset < bset->head.offset) { + /* swap chunk and head */ + uint32_t offset = nchunk->offset; + memcpy(nchunk, &bset->head, sizeof(*nchunk)); + memset(&bset->head, 0, sizeof(bset->head)); + bset->head.next = nchunk; + bset->head.offset = offset; + return &bset->head; + } + DEBUGASSERT(nchunk->offset > bset->head.offset); + panchor = &bset->head.next; + for(chunk = *panchor; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(chunk->offset > nchunk->offset) { /* insert before this chunk */ + nchunk->next = chunk; + *panchor = nchunk; + return nchunk; } } + /* no chunk with larger offset, append */ + *panchor = nchunk; + return nchunk; +} - if(!grow) +static struct uint32_spbset_chunk *uint32_spbset_get_chunk( + struct uint32_spbset *bset, uint32_t i, bool grow) +{ + struct uint32_spbset_chunk *chunk; + uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK); + + if(!bset) return NULL; - /* need a new one */ - chunk = curlx_calloc(1, sizeof(*chunk)); - if(!chunk) + for(chunk = &bset->head; chunk; chunk = chunk->next) { + if(chunk->offset == i_offset) + return chunk; + else if(chunk->offset > i_offset) + break; /* need new chunk here */ + } + if(!grow) /* just a check if the chunk exists */ return NULL; - if(panchor) { /* insert between panchor and *panchor */ - chunk->next = *panchor; - *panchor = chunk; + /* Is there an empty chunk to reuse? */ + chunk = uint32_spbset_unlink_empty(bset, i_offset); + if(chunk) { + chunk->offset = i_offset; + if(chunk == &bset->head) /* head chunk is empty, stayed linked */ + return &bset->head; + /* was really unlinked, need to insert below */ } - else { /* prepend to head, switching places */ - memcpy(chunk, &bset->head, sizeof(*chunk)); - memset(&bset->head, 0, sizeof(bset->head)); - bset->head.next = chunk; + else { + /* need a new one */ + chunk = curlx_calloc(1, sizeof(*chunk)); + if(!chunk) + return NULL; + chunk->offset = i_offset; } - chunk->offset = i_offset; - return chunk; + + return uint32_spbset_insert_chunk(bset, chunk); } bool Curl_uint32_spbset_add(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, TRUE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, TRUE); if(!chunk) return FALSE; DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] |= ((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] |= ((uint64_t)1 << (i % 64)); return TRUE; } void Curl_uint32_spbset_remove(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] &= ~((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] &= ~((uint64_t)1 << (i % 64)); } } bool Curl_uint32_spbset_contains(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - return (chunk->slots[i_chunk / 64] & - ((uint64_t)1 << (i_chunk % 64))) != 0; + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + return (chunk->slots[i / 64] & ((uint64_t)1 << (i % 64))) != 0; } return FALSE; } @@ -203,7 +255,7 @@ static bool uint32_spbset_chunk_next(struct uint32_spbset_chunk *chunk, if(i < CURL_UINT32_SPBSET_CH_SLOTS) { x = (chunk->slots[i] >> (last % 64)); if(x) { - /* more bits set, next is `last` + trailing0s of the shifted slot */ + /* more bits set, next is `last` + trailing 0s of the shifted slot */ *pnext = last + CURL_CTZ64(x); return TRUE; } diff --git a/lib/url.c b/lib/url.c index 5fe68033d9d6..6eeae43d0d42 100644 --- a/lib/url.c +++ b/lib/url.c @@ -55,7 +55,7 @@ #endif #ifndef HAVE_SOCKET -#error "We cannot compile without socket() support!" +#error "We cannot compile without socket() support" #endif #if defined(HAVE_IF_NAMETOINDEX) && defined(USE_WINSOCK) @@ -70,7 +70,6 @@ #include "bufref.h" #include "vtls/vtls.h" #include "vssh/vssh.h" -#include "hostip.h" #include "transfer.h" #include "curl_addrinfo.h" #include "curl_trc.h" @@ -84,9 +83,8 @@ #include "getinfo.h" #include "pop3.h" #include "urlapi-int.h" -#include "system_win32.h" #include "hsts.h" -#include "noproxy.h" +#include "proxy.h" #include "cfilters.h" #include "idn.h" #include "http_proxy.h" @@ -99,6 +97,7 @@ #include "headers.h" #include "curlx/strerr.h" #include "curlx/strparse.h" +#include "peer.h" /* Now for the protocols */ #include "ftp.h" @@ -106,6 +105,7 @@ #include "telnet.h" #include "tftp.h" #include "http.h" +#include "vauth/vauth.h" #include "file.h" #include "curl_ldap.h" #include "vssh/ssh.h" @@ -118,12 +118,6 @@ #include "smtp.h" #include "ws.h" -#ifdef USE_NGHTTP2 -static void data_priority_cleanup(struct Curl_easy *data); -#else -#define data_priority_cleanup(x) -#endif - /* Some parts of the code (e.g. chunked encoding) assume this buffer has more * than a few bytes to play with. Do not let it become too small or bad things * will happen. @@ -132,9 +126,6 @@ static void data_priority_cleanup(struct Curl_easy *data); # error READBUFFER_SIZE is too small #endif -/* Reject URLs exceeding this length */ -#define MAX_URL_LEN 0xffff - /* * get_protocol_family() * @@ -156,12 +147,17 @@ static curl_prot_t get_protocol_family(const struct Curl_scheme *s) void Curl_freeset(struct Curl_easy *data) { /* Free all dynamic strings stored in the data->set substructure. */ - enum dupstring i; enum dupblob j; - for(i = (enum dupstring)0; i < STRING_LAST; i++) { - curlx_safefree(data->set.str[i]); - } + CURL_EASY_STR_CLEAR0(data, STRING_PASSWORD); + CURL_EASY_STR_CLEAR0(data, STRING_KEY_PASSWD); + CURL_EASY_STR_CLEAR0(data, STRING_BEARER); +#ifndef CURL_DISABLE_PROXY + CURL_EASY_STR_CLEAR0(data, STRING_PROXYPASSWORD); + CURL_EASY_STR_CLEAR0(data, STRING_KEY_PASSWD_PROXY); +#endif + Curl_u8_strset_clear(&data->set.strings); + curlx_safefree(data->set.str_copypostfields); for(j = (enum dupblob)0; j < BLOB_LAST; j++) { curlx_safefree(data->set.blobs[j]); @@ -185,11 +181,6 @@ void Curl_freeset(struct Curl_easy *data) static void up_free(struct Curl_easy *data) { struct urlpieces *up = &data->state.up; - curlx_safefree(up->scheme); - curlx_safefree(up->hostname); - curlx_safefree(up->port); - curlx_safefree(up->user); - curlx_safefree(up->password); curlx_safefree(up->options); curlx_safefree(up->path); curlx_safefree(up->query); @@ -199,11 +190,10 @@ static void up_free(struct Curl_easy *data) /* * This is the internal function curl_easy_cleanup() calls. This should - * cleanup and free all resources associated with this sessionhandle. + * cleanup and free all resources associated with this Curl_easy. * * We ignore SIGPIPE when this is called from curl_easy_cleanup. */ - CURLcode Curl_close(struct Curl_easy **datap) { struct Curl_easy *data; @@ -218,7 +208,7 @@ CURLcode Curl_close(struct Curl_easy **datap) /* This handle is still part of a multi handle, take care of this first and detach this handle from there. This detaches the connection. */ - curl_multi_remove_handle(data->multi, data); + Curl_multi_remove_handle(data->multi, data); } else { /* Detach connection if any is left. This should not be normal, but can be @@ -233,7 +223,7 @@ CURLcode Curl_close(struct Curl_easy **datap) } DEBUGASSERT(!data->conn || data->state.internal); - Curl_expire_clear(data); /* shut off any timers left */ + Curl_expire_clear_all(data); /* shut off any timers left */ if(data->state.rangestringalloc) curlx_free(data->state.range); @@ -252,7 +242,8 @@ CURLcode Curl_close(struct Curl_easy **datap) /* Close down all open SSL info and sessions */ Curl_ssl_close_all(data); - curlx_safefree(data->state.first_host); + Curl_peer_unlink(&data->state.origin); + Curl_peer_unlink(&data->state.initial_origin); Curl_ssl_free_certinfo(data); Curl_bufref_free(&data->state.referer); @@ -261,11 +252,11 @@ CURLcode Curl_close(struct Curl_easy **datap) 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_save(data, data->asi, CURL_EASY_STR(data, STRING_ALTSVC)); Curl_altsvc_cleanup(&data->asi); #endif #ifndef CURL_DISABLE_HSTS - Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); + Curl_hsts_save(data, data->hsts, CURL_EASY_STR(data, STRING_HSTS)); if(!data->share || !data->share->hsts) Curl_hsts_cleanup(&data->hsts); curl_slist_free_all(data->state.hstslist); /* clean up list */ @@ -277,30 +268,19 @@ CURLcode Curl_close(struct Curl_easy **datap) curlx_safefree(data->info.contenttype); curlx_safefree(data->info.wouldredirect); - data_priority_cleanup(data); - /* No longer a dirty share, if it exists */ if(Curl_share_easy_unlink(data)) DEBUGASSERT(0); Curl_hash_destroy(&data->meta_hash); - curlx_safefree(data->state.aptr.uagent); - curlx_safefree(data->state.aptr.accept_encoding); - curlx_safefree(data->state.aptr.rangeline); - curlx_safefree(data->state.aptr.ref); - curlx_safefree(data->state.aptr.host); + Curl_creds_unlink(&data->state.creds); +#ifndef CURL_DISABLE_HTTP + curlx_safefree(data->state.rangeline); + curlx_safefree(data->state.http_host); +#endif #ifndef CURL_DISABLE_COOKIES curlx_safefree(data->req.cookiehost); #endif -#ifndef CURL_DISABLE_RTSP - curlx_safefree(data->state.aptr.rtsp_transport); -#endif - curlx_safefree(data->state.aptr.user); - curlx_safefree(data->state.aptr.passwd); -#ifndef CURL_DISABLE_PROXY - curlx_safefree(data->state.aptr.proxyuser); - curlx_safefree(data->state.aptr.proxypasswd); -#endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) Curl_mime_cleanpart(data->state.formp); @@ -312,6 +292,14 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_freeset(data); Curl_headers_cleanup(data); Curl_netrc_cleanup(&data->state.netrc); +#ifndef CURL_DISABLE_DIGEST_AUTH + curlx_free(data->state.envproxy); +#endif + Curl_ssl_config_cleanup(&data->set.ssl.primary); +#ifndef CURL_DISABLE_PROXY + Curl_ssl_config_cleanup(&data->set.proxy_ssl.primary); +#endif + curlx_memzero(data, sizeof(*data)); curlx_free(data); return CURLE_OK; } @@ -328,6 +316,8 @@ void Curl_init_userdefined(struct Curl_easy *data) set->in_set = stdin; /* default input from stdin */ set->err = stderr; /* default stderr to stderr */ + Curl_u8_strset_init(&data->set.strings); + #if defined(__clang__) && __clang_major__ >= 16 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcast-function-type-strict" @@ -366,7 +356,9 @@ void Curl_init_userdefined(struct Curl_easy *data) set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ + Curl_ssl_config_init(&data->set.ssl.primary); #ifndef CURL_DISABLE_PROXY + Curl_ssl_config_init(&data->set.proxy_ssl.primary); set->proxyport = 0; set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ @@ -374,7 +366,6 @@ void Curl_init_userdefined(struct Curl_easy *data) set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI; #endif - Curl_ssl_easy_config_init(data); #ifndef CURL_DISABLE_DOH set->doh_verifyhost = TRUE; set->doh_verifypeer = TRUE; @@ -431,7 +422,7 @@ void Curl_init_userdefined(struct Curl_easy *data) set->http09_allowed = FALSE; set->httpwant = CURL_HTTP_VERSION_NONE; #if defined(USE_HTTP2) || defined(USE_HTTP3) - memset(&set->priority, 0, sizeof(set->priority)); + set->weight = 0; #endif set->quick_exit = 0L; #ifndef CURL_DISABLE_WEBSOCKETS @@ -448,17 +439,16 @@ static void easy_meta_freeentry(void *p) /* Always FALSE. Cannot use a 0 assert here since compilers * are not in agreement if they then want a NORETURN attribute or * not. *sigh* */ - DEBUGASSERT(p == NULL); + DEBUGASSERT(!p); } /** * Curl_open() * - * @param curl is a pointer to a sessionhandle pointer that gets set by this + * @param curl is a pointer to a Curl_easy pointer that gets set by this * function. * @return CURLcode */ - CURLcode Curl_open(struct Curl_easy **curl) { struct Curl_easy *data; @@ -474,16 +464,16 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; /* most recent connection is not yet defined */ data->state.lastconnect_id = -1; - data->state.recent_conn_id = -1; /* and not assigned an id yet */ data->id = -1; data->mid = UINT32_MAX; data->master_mid = UINT32_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); + DEBUGASSERT(STRING_LAST <= UINT8_MAX); + Curl_u8_strset_init(&data->set.strings); curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); Curl_bufref_init(&data->state.url); Curl_bufref_init(&data->state.referer); @@ -501,7 +491,7 @@ CURLcode Curl_open(struct Curl_easy **curl) void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) { - size_t i; + int8_t i; DEBUGASSERT(conn); @@ -509,38 +499,28 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) !conn->bits.shutdown_handler) conn->scheme->run->disconnect(data, conn, TRUE); - for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { - Curl_conn_cf_discard_all(data, conn, (int)i); + for(i = 0; i < (int8_t)CURL_ARRAYSIZE(conn->cfilter); ++i) { + Curl_conn_cf_discard_all(data, conn, i); } - Curl_free_idnconverted_hostname(&conn->host); - Curl_free_idnconverted_hostname(&conn->conn_to_host); #ifndef CURL_DISABLE_PROXY - Curl_free_idnconverted_hostname(&conn->http_proxy.host); - Curl_free_idnconverted_hostname(&conn->socks_proxy.host); - curlx_safefree(conn->http_proxy.user); - curlx_safefree(conn->socks_proxy.user); - curlx_safefree(conn->http_proxy.passwd); - curlx_safefree(conn->socks_proxy.passwd); - curlx_safefree(conn->http_proxy.host.rawalloc); /* http proxy name */ - curlx_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name */ -#endif - curlx_safefree(conn->user); - curlx_safefree(conn->passwd); - curlx_safefree(conn->sasl_authzid); + Curl_peer_unlink(&conn->http_proxy.peer); + Curl_peer_unlink(&conn->socks_proxy.peer); + Curl_creds_unlink(&conn->http_proxy.creds); + Curl_creds_unlink(&conn->socks_proxy.creds); +#endif + Curl_creds_unlink(&conn->creds); + Curl_peer_unlink(&conn->creds_origin); curlx_safefree(conn->options); - curlx_safefree(conn->oauth_bearer); - curlx_safefree(conn->host.rawalloc); /* hostname buffer */ - curlx_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */ - curlx_safefree(conn->secondaryhostname); curlx_safefree(conn->localdev); Curl_ssl_conn_config_cleanup(conn); -#ifdef USE_UNIX_SOCKETS - curlx_safefree(conn->unix_domain_socket); -#endif curlx_safefree(conn->destination); Curl_hash_destroy(&conn->meta_hash); + Curl_peer_unlink(&conn->origin); + Curl_peer_unlink(&conn->via_peer); + Curl_peer_unlink(&conn->origin2); + Curl_peer_unlink(&conn->via_peer2); curlx_free(conn); /* free all the connection oriented data */ } @@ -576,122 +556,14 @@ static bool proxy_info_matches(const struct proxy_info *data, const struct proxy_info *needle) { if((data->proxytype == needle->proxytype) && - (data->port == needle->port) && - curl_strequal(data->host.name, needle->host.name)) { - - if(Curl_timestrcmp(data->user, needle->user) || - Curl_timestrcmp(data->passwd, needle->passwd)) - return FALSE; + Curl_peer_same_destination(data->peer, needle->peer) && + Curl_creds_same(data->creds, needle->creds)) { return TRUE; } return FALSE; } #endif -/* A connection has to have been idle for less than 'conn_max_idle_ms' - (the success rate is too low after this), or created less than - 'conn_max_age_ms' ago, to be subject for reuse. */ -static bool conn_maxage(struct Curl_easy *data, - struct connectdata *conn, - struct curltime now) -{ - timediff_t age_ms; - - if(data->set.conn_max_idle_ms) { - age_ms = curlx_ptimediff_ms(&now, &conn->lastused); - if(age_ms > data->set.conn_max_idle_ms) { - infof(data, "Too old connection (%" FMT_TIMEDIFF_T - " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", - age_ms, data->set.conn_max_idle_ms); - return TRUE; - } - } - - if(data->set.conn_max_age_ms) { - age_ms = curlx_ptimediff_ms(&now, &conn->created); - if(age_ms > data->set.conn_max_age_ms) { - infof(data, - "Too old connection (created %" FMT_TIMEDIFF_T - " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it", - age_ms, data->set.conn_max_age_ms); - return TRUE; - } - } - - return FALSE; -} - -/* - * Return TRUE iff the given connection is considered dead. - */ -bool Curl_conn_seems_dead(struct connectdata *conn, - struct Curl_easy *data) -{ - DEBUGASSERT(!data->conn); - if(!CONN_INUSE(conn)) { - /* The check for a dead socket makes sense only if the connection is not in - use */ - bool dead; - - if(conn_maxage(data, conn, *Curl_pgrs_now(data))) { - /* avoid check if already too old */ - dead = TRUE; - } - else if(conn->scheme->run->connection_is_dead) { - /* The protocol has a special method for checking the state of the - connection. Use it to check if the connection is dead. */ - /* briefly attach the connection for the check */ - Curl_attach_connection(data, conn); - dead = conn->scheme->run->connection_is_dead(data, conn); - Curl_detach_connection(data); - } - else { - 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 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) { - /* remove connection from cpool */ - infof(data, "Connection %" FMT_OFF_T " seems to be dead", - conn->connection_id); - return TRUE; - } - } - return FALSE; -} - -CURLcode Curl_conn_upkeep(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->keepalive) <= - data->set.upkeep_interval_ms) - return result; - - /* briefly attach for action */ - Curl_attach_connection(data, conn); - result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET); - Curl_detach_connection(data); - - conn->keepalive = *Curl_pgrs_now(data); - return result; -} - #ifdef USE_SSH static bool ssh_config_matches(struct connectdata *one, struct connectdata *two) @@ -700,8 +572,8 @@ static bool ssh_config_matches(struct connectdata *one, 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); + return sshc1 && sshc2 && Curl_safecmp(sshc1->priv_key, sshc2->priv_key) && + Curl_safecmp(sshc1->pub_key, sshc2->pub_key); } #endif @@ -709,12 +581,17 @@ struct url_conn_match { struct connectdata *found; struct Curl_easy *data; struct connectdata *needle; + struct curltime now; BIT(may_multiplex); BIT(want_ntlm_http); BIT(want_proxy_ntlm_http); BIT(want_nego_http); BIT(want_proxy_nego_http); - BIT(req_tls); /* require TLS use from a clear-text start */ + BIT(may_tls); /* May upgrade clear-text connection to TLS, can only reuse + * connections that have matching TLS configuration. + * Always TRUE if `req_tls` is TRUE. */ + BIT(require_tls); /* Requires TLS use from a clear-text start, can only + * reuse connections that have TLS. */ BIT(wait_pipe); BIT(force_reuse); BIT(seen_pending_conn); @@ -744,8 +621,7 @@ static bool url_match_connect_config(struct connectdata *conn, 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. - */ + few edge cases should not hurt anyone much. */ if((conn->localport != m->needle->localport) || (conn->localportrange != m->needle->localportrange) || (m->needle->localdev && @@ -753,30 +629,11 @@ static bool url_match_connect_config(struct connectdata *conn, return FALSE; } - if(m->needle->bits.conn_to_host != conn->bits.conn_to_host) + if(!m->needle->via_peer != !conn->via_peer) /* do not mix connections that use the "connect to host" feature and * connections that do not use this feature */ return FALSE; - 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; - - /* 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 - return TRUE; } @@ -865,8 +722,12 @@ static bool url_match_ssl_use(struct connectdata *conn, if(!(m->needle->scheme->flags & PROTOPT_SSL_REUSE) || (get_protocol_family(conn->scheme) != m->needle->scheme->protocol)) return FALSE; + /* We may reuse this as an auto-TLS upgrade, but only if the SSL + * config parameters match. */ + if(!Curl_ssl_conn_config_match(m->data, conn, FALSE)) + return FALSE; } - else if(m->req_tls) + else if(m->require_tls) /* a clear-text STARTTLS protocol with required TLS */ return FALSE; return TRUE; @@ -876,36 +737,27 @@ static bool url_match_ssl_use(struct connectdata *conn, 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) + if(m->needle->bits.origin_is_proxy != conn->bits.origin_is_proxy) return FALSE; - if(m->needle->bits.socksproxy && - !proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) + if(!proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) return FALSE; - 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(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) + if(CURL_PROXY_IS_HTTPS(m->needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " has different SSL proxy parameters, cannot reuse", + conn->connection_id)); 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 */ } + /* the SSL config to the server, which may apply here is checked + * further below */ } return TRUE; } @@ -984,7 +836,7 @@ static bool url_match_proto_config(struct connectdata *conn, #endif #ifndef CURL_DISABLE_FTP else if(get_protocol_family(m->needle->scheme) & PROTO_FAMILY_FTP) { - if(!ftp_conns_match(m->needle, conn)) + if(!Curl_ftp_conns_match(m->needle, conn)) return FALSE; } #endif @@ -994,20 +846,15 @@ static bool url_match_proto_config(struct connectdata *conn, static bool url_match_auth(struct connectdata *conn, struct url_conn_match *m) { - if(!(m->needle->scheme->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 */ + if(!Curl_creds_same(m->needle->creds, conn->creds)) { + if(m->needle->creds) + return FALSE; + if(!Curl_creds_same(m->data->state.creds, conn->creds)) 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 defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* 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 @@ -1018,49 +865,29 @@ static bool url_match_auth(struct connectdata *conn, 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->scheme->flags & PROTOPT_SSL) -#ifndef CURL_DISABLE_PROXY - || !m->needle->bits.httpproxy || m->needle->bits.tunnel_proxy -#endif - ) { - if(!curl_strequal(m->needle->scheme->name, conn->scheme->name)) { - /* `needle` and `conn` do not have the same scheme... */ - if(get_protocol_family(conn->scheme) != m->needle->scheme->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; - } - } - - /* If needle has "conn_to_*" set, conn must match this */ - if((m->needle->bits.conn_to_host && !curl_strequal( - 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; + /* Different connect-to peers never match */ + if(!Curl_peer_same_destination(m->needle->via_peer, conn->via_peer)) + return FALSE; - /* hostname and port must match */ - if(!curl_strequal(m->needle->host.name, conn->host.name) || - m->needle->remote_port != conn->remote_port) + if(m->needle->origin->scheme != conn->origin->scheme) { + /* `needle` and `conn` not having the same scheme. + * This is allowed for the same family *if* conn is using TLS. + * - IMAP+STARTTLS works for IMAPS. + * - IMAPS works for IMAP. */ + if(get_protocol_family(conn->origin->scheme) != + m->needle->scheme->protocol) { return FALSE; + } } - return TRUE; + /* Scheme mismatch is acceptable, compare hostname/port */ + return Curl_peer_same_destination(m->needle->origin, conn->origin); } 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->scheme->flags & PROTOPT_SSL) && + /* If talking/upgrading to TLS, conn needs to use the same SSL options. */ + if(((m->needle->scheme->flags & PROTOPT_SSL) || m->may_tls) && !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL parameters, cannot reuse", @@ -1074,50 +901,34 @@ static bool url_match_ssl_config(struct connectdata *conn, 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 it does - not have any auth ongoing. */ -#ifdef USE_SPNEGO - if((conn->http_ntlm_state == NTLMSTATE_NONE) - && (conn->http_negotiate_state == GSS_AUTHNONE)) { -#else - if(conn->http_ntlm_state == NTLMSTATE_NONE) { -#endif - m->found = conn; - } + if(conn->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM. We cannot reuse if transfer + * has different Auth input parameters. */ + if(!m->want_ntlm_http || + !Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin)) return FALSE; - } } - else if(conn->http_ntlm_state != NTLMSTATE_NONE) { - /* Connection is using NTLM auth but we do not want NTLM */ - return FALSE; + else if(m->want_ntlm_http) { + /* Transfer wants NTLM, connection is not using it. + * Do not reuse when connection has credentials and they differ. */ + if(conn->creds && + (!Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) + 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)) + if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { + if(!m->want_proxy_ntlm_http || + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; } - else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { - /* Proxy connection is using NTLM auth but we do not want NTLM */ - return FALSE; + else if(m->want_proxy_ntlm_http) { + if(conn->http_proxy.creds && + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + return FALSE; } #endif if(m->want_ntlm_http || m->want_proxy_ntlm_http) { @@ -1148,36 +959,34 @@ static bool url_match_auth_ntlm(struct connectdata *conn, static bool url_match_auth_nego(struct connectdata *conn, struct url_conn_match *m) { - /* If we are looking for an HTTP+Negotiate connection, check if this is - already authenticating with the right credentials. If not, keep looking - so that we can reuse Negotiate connections if possible. */ - if(m->want_nego_http) { - if(Curl_timestrcmp(m->needle->user, conn->user) || - Curl_timestrcmp(m->needle->passwd, conn->passwd)) + if(conn->http_negotiate_state != GSS_AUTHNONE) { + /* Connection is using Negotiate. We cannot reuse if transfer + * has different Auth input parameters. */ + if(!m->want_nego_http || + !Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin)) return FALSE; } - else if(conn->http_negotiate_state != GSS_AUTHNONE) { - /* Connection is using Negotiate auth but we do not want Negotiate */ - return FALSE; + else if(m->want_nego_http) { + /* Transfer wants Negotiate, connection is not using it. + * Do not reuse when connection has credentials and they differ. */ + if(conn->creds && + (!Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) + return FALSE; } #ifndef CURL_DISABLE_PROXY /* Same for Proxy Negotiate authentication */ - if(m->want_proxy_nego_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)) + if(conn->proxy_negotiate_state != GSS_AUTHNONE) { + if(!m->want_proxy_nego_http || + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; } - else if(conn->proxy_negotiate_state != GSS_AUTHNONE) { - /* Proxy connection is using Negotiate auth but we do not want Negotiate */ - return FALSE; + else if(m->want_proxy_nego_http) { + if(conn->http_proxy.creds && + !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) + return FALSE; } #endif if(m->want_nego_http || m->want_proxy_nego_http) { @@ -1222,6 +1031,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) 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)) @@ -1230,7 +1040,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_http_multiplex(conn, m)) return FALSE; else if(m->wait_pipe) - /* we decided to wait on PIPELINING */ + /* wait on multiplexing */ return TRUE; if(!url_match_auth(conn, m)) @@ -1252,9 +1062,23 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) if(!url_match_multiplex_limits(conn, m)) return FALSE; - if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data)) { - /* remove and disconnect. */ - Curl_conn_terminate(m->data, conn, FALSE); + if(m->data->set.conn_max_age_ms > 0) { + timediff_t age_ms = curlx_ptimediff_ms(&m->now, &conn->created); + if(age_ms > m->data->set.conn_max_age_ms) { + /* Transfer is looking for a younger connection. */ + if(!CONN_INUSE(conn)) + Curl_conn_close(m->data, conn, FALSE); + return FALSE; + } + } + + /* If we are going to pick an idle connection, do an extra + * health check before we reuse it. */ + if(!CONN_INUSE(conn) && + !Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) { + infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating", + conn->connection_id); + Curl_conn_close(m->data, conn, FALSE); return FALSE; } @@ -1269,7 +1093,7 @@ static bool url_match_result(void *userdata) 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); + Curl_attach_connection(match->data, match->found, TRUE); return TRUE; } else if(match->seen_single_use_conn && !match->seen_multiplex_conn) { @@ -1300,22 +1124,27 @@ static bool url_attach_existing(struct Curl_easy *data, struct connectdata *needle, bool *waitpipe) { + struct cpool *cpool = Curl_cpool_get_instance(data); struct url_conn_match match; bool success; DEBUGASSERT(!data->conn); + memset(&match, 0, sizeof(match)); match.data = data; match.needle = needle; + match.now = *Curl_pgrs_now(data); match.may_multiplex = xfer_may_multiplex(data, needle); + Curl_cpool_prune_dead(cpool, data); + #ifdef USE_NTLM match.want_ntlm_http = (data->state.authhost.want & CURLAUTH_NTLM) && (needle->scheme->protocol & PROTO_FAMILY_HTTP); #ifndef CURL_DISABLE_PROXY match.want_proxy_ntlm_http = - needle->bits.proxy_user_passwd && + needle->http_proxy.creds && (data->state.authproxy.want & CURLAUTH_NTLM) && (needle->scheme->protocol & PROTO_FAMILY_HTTP); #endif @@ -1327,12 +1156,13 @@ static bool url_attach_existing(struct Curl_easy *data, (needle->scheme->protocol & PROTO_FAMILY_HTTP); #ifndef CURL_DISABLE_PROXY match.want_proxy_nego_http = - needle->bits.proxy_user_passwd && + needle->http_proxy.creds && (data->state.authproxy.want & CURLAUTH_NEGOTIATE) && (needle->scheme->protocol & PROTO_FAMILY_HTTP); #endif #endif - match.req_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; + match.require_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; + match.may_tls = data->set.use_ssl > CURLUSESSL_NONE; /* Find a connection in the pool that matches what "data + needle" * requires. If a suitable candidate is found, it is attached to "data". */ @@ -1362,36 +1192,9 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->send_idx = 0; /* default for sending transfer data */ conn->connection_id = -1; /* no ID */ conn->attached_xfers = 0; - conn->remote_port = 0; /* unknown at this point */ - /* Store creation time to help future close decision making */ - conn->created = *Curl_pgrs_now(data); - - /* Store current time to give a baseline to keepalive connection times. */ - conn->keepalive = conn->created; - -#ifndef CURL_DISABLE_PROXY - conn->http_proxy.proxytype = data->set.proxytype; - conn->socks_proxy.proxytype = CURLPROXY_SOCKS4; - - /* note that these two proxy bits are set 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]); - 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))); - 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; - conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; -#endif /* CURL_DISABLE_PROXY */ + /* Remember time this connection started */ + conn->lastused = conn->lastupkeep = conn->created = *Curl_pgrs_now(data); #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; @@ -1402,8 +1205,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ /* Store the local bind parameters that will be used for this connection */ - if(data->set.str[STRING_DEVICE]) { - conn->localdev = curlx_strdup(data->set.str[STRING_DEVICE]); + if(CURL_EASY_STR(data, STRING_DEVICE)) { + conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); if(!conn->localdev) goto error; } @@ -1416,11 +1219,9 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) it may live on without (this specific) Curl_easy */ conn->fclosesocket = data->set.fclosesocket; conn->closesocket_client = data->set.closesocket_client; - conn->lastused = conn->created; -#ifdef HAVE_GSSAPI +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) conn->gssapi_delegation = data->set.gssapi_delegation; #endif - DEBUGF(infof(data, "alloc connection, bits.close=%d", conn->bits.close)); return conn; error: @@ -1429,36 +1230,25 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return NULL; } -static CURLcode findprotocol(struct Curl_easy *data, - struct connectdata *conn, - const char *protostr) +static CURLcode url_set_conn_scheme(struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_scheme *scheme) { - const struct Curl_scheme *p = Curl_get_scheme(protostr); - - if(p && p->run && /* Protocol found supported. Check if allowed */ - (data->set.allowed_protocols & p->protocol)) { - - /* it is allowed for "normal" request, now do an extra check if this is - the result of a redirect */ - if(data->state.this_is_a_follow && - !(data->set.redir_protocols & p->protocol)) - /* nope, get out */ - ; - else { - /* Perform setup complement if some. */ - conn->scheme = conn->given = p; - /* 'port' and 'remote_port' are set in setup_connection_internals() */ - return CURLE_OK; - } + /* URL scheme is usable for connection when it is + * - allowed + * - not from a redirect or an allowed redirect protocol */ + if(scheme->run && + (data->set.allowed_protocols & scheme->protocol) && + (!data->state.this_is_a_follow || + (data->set.redir_protocols & scheme->protocol))) { + conn->scheme = conn->given = scheme; + return CURLE_OK; } - - /* 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\" %s%s", protostr, - p ? "disabled" : "not supported", - data->state.this_is_a_follow ? " (in redirect)" : ""); - + if(scheme->flags & PROTOPT_NO_TRANSFER) + failf(data, "Protocol \"%s\" is not for transfers", scheme->name); + else + failf(data, "Protocol \"%s\" is disabled%s", scheme->name, + data->state.this_is_a_follow ? " (in redirect)" : ""); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1476,269 +1266,281 @@ CURLcode Curl_uc_to_curlcode(CURLUcode uc) } } -#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! - */ - -static void zonefrom_url(CURLU *uh, struct Curl_easy *data, - struct connectdata *conn) +#ifndef CURL_DISABLE_HSTS +static CURLcode hsts_upgrade(struct Curl_easy *data, + CURLU *uh, + uint16_t port_override, + uint32_t scope_id) { - char *zoneid; - CURLUcode uc = curl_url_get(uh, CURLUPART_ZONEID, &zoneid, 0); -#if !defined(HAVE_IF_NAMETOINDEX) || !defined(CURLVERBOSE) - (void)data; -#endif + /* HSTS upgrade */ + if(data->hsts && (data->state.origin->scheme == &Curl_scheme_http) && + Curl_hsts_applies(data->hsts, data->state.origin)) { + char *url; + CURLUcode uc; + CURLcode result; - if(!uc && zoneid) { - 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; -#ifdef HAVE_IF_NAMETOINDEX - else { - /* Zone identifier is not numeric */ - unsigned int scopeidx = 0; - scopeidx = if_nametoindex(zoneid); - if(!scopeidx) { -#ifdef CURLVERBOSE - char buffer[STRERROR_LEN]; - infof(data, "Invalid zoneid: %s; %s", zoneid, - curlx_strerror(errno, buffer, sizeof(buffer))); -#endif - } - else - conn->scope_id = scopeidx; - } -#endif /* HAVE_IF_NAMETOINDEX */ + uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0); + if(uc) + return Curl_uc_to_curlcode(uc); + Curl_bufref_free(&data->state.url); + /* after update, get the updated version */ + uc = curl_url_get(uh, CURLUPART_URL, &url, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + Curl_bufref_set(&data->state.url, url, 0, curl_free); - curlx_free(zoneid); + result = Curl_peer_from_url(uh, data, port_override, scope_id, + &data->state.origin); + if(result) + return result; + infof(data, "Switched from HTTP to HTTPS due to HSTS => %s", url); } + return CURLE_OK; } #else -#define zonefrom_url(a, b, c) Curl_nop_stmt +#define hsts_upgrade(x, y, z, a) CURLE_OK #endif +static bool str_has_ctrl(const char *input) +{ + if(input) { + const unsigned char *str = (const unsigned char *)input; + while(*str) { + if(*str < 0x20) + return TRUE; + str++; + } + } + return FALSE; +} + +#ifndef CURL_DISABLE_NETRC /* - * Parse URL and fill in the relevant members of the connection struct. + * Override the login details from the URL with that in the CURLOPT_USERPWD + * option or a .netrc file, if applicable. */ -static CURLcode parseurlandfillconn(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, + struct Curl_creds **pcreds) { - CURLcode result; - CURLU *uh; - CURLUcode uc; - char *hostname; - size_t hlen; - bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); + struct Curl_creds *ncreds_out = NULL; + CURLcode result = CURLE_OK; - up_free(data); /* cleanup previous leftovers first */ + if(data->set.use_netrc) { /* not CURL_NETRC_IGNORED */ + struct Curl_creds *ncreds_in = NULL; + bool scan_netrc = TRUE; + NETRCcode ret; + CURLUcode uc; - /* parse the URL */ - if(use_set_uh) { - uh = data->state.uh = curl_url_dup(data->set.uh); - } - else { - uh = data->state.uh = curl_url(); - } + if(*pcreds) { + switch((*pcreds)->source) { + case CREDS_OPTION: + /* we never override credentials set via CURLOPT_*, leave. */ + scan_netrc = FALSE; + break; + case CREDS_URL: /* only apply when netrc is not required */ + if(data->set.use_netrc == CURL_NETRC_REQUIRED) { + /* We ignore password from URL */ + ncreds_in = *pcreds; + } + else if(!Curl_creds_has_user(*pcreds) || + !Curl_creds_has_passwd(*pcreds)) { + /* We use netrc to complete what is missing */ + ncreds_in = *pcreds; + } + else + scan_netrc = FALSE; + break; + default: /* ignore credentials from other sources */ + break; + } + } - if(!uh) - return CURLE_OUT_OF_MEMORY; + if(!scan_netrc) + goto out; - if(data->set.str[STRING_DEFAULT_PROTOCOL] && - !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { - char *url = curl_maprintf("%s://%s", - data->set.str[STRING_DEFAULT_PROTOCOL], - Curl_bufref_ptr(&data->state.url)); - if(!url) - return CURLE_OUT_OF_MEMORY; - Curl_bufref_set(&data->state.url, url, 0, curl_free); + ret = Curl_netrc_scan(data, &data->state.netrc, + data->state.origin->hostname, + Curl_creds_user(ncreds_in), + CURL_EASY_STR(data, STRING_NETRC_FILE), + &ncreds_out); + DEBUGASSERT(!ret || !ncreds_out); + if(ret == NETRC_OUT_OF_MEMORY) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + else if(ret && ((ret == NETRC_NO_MATCH) || + (data->set.use_netrc == CURL_NETRC_OPTIONAL))) { + infof(data, "Could not find host %s in the %s file; using defaults", + data->state.origin->hostname, + (CURL_EASY_STR(data, STRING_NETRC_FILE) ? + CURL_EASY_STR(data, STRING_NETRC_FILE) : ".netrc")); + } + else if(ret) { + const char *m = Curl_netrc_strerror(ret); + failf(data, ".netrc error: %s", m); + result = CURLE_READ_ERROR; + goto out; + } + else if(ncreds_out) { + if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL)) { + /* if the protocol cannot handle control codes in credentials, make + sure there are none */ + if(str_has_ctrl(ncreds_out->user) || + str_has_ctrl(ncreds_out->passwd)) { + failf(data, "control code detected in .netrc credentials"); + result = CURLE_READ_ERROR; + goto out; + } + } + CURL_TRC_M(data, "netrc: using credentials for %s as %s", + data->state.origin->hostname, ncreds_out->user); + result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd, + *pcreds, CREDS_NETRC, pcreds); + if(result) + goto out; + /* for updated strings, we update them in the URL */ + uc = curl_url_set(data->state.uh, CURLUPART_USER, + Curl_creds_user(*pcreds), CURLU_URLENCODE); + if(!uc) + uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, + Curl_creds_passwd(*pcreds), + CURLU_URLENCODE); + if(uc) + result = Curl_uc_to_curlcode(uc); + } + else + DEBUGASSERT(0); } - if(!use_set_uh) { - char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), - (unsigned int)(CURLU_GUESS_SCHEME | - CURLU_NON_SUPPORT_SCHEME | - (data->set.disallow_username_in_url ? - CURLU_DISALLOW_USER : 0) | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); - if(uc) { - failf(data, "URL rejected: %s", curl_url_strerror(uc)); - return Curl_uc_to_curlcode(uc); - } +#ifdef CURLVERBOSE + Curl_creds_trace(data, data->state.creds, "transfer credentials"); +#endif - /* after it was parsed, get the generated normalized version */ - uc = curl_url_get(uh, CURLUPART_URL, &newurl, 0); - if(uc) - return Curl_uc_to_curlcode(uc); - Curl_bufref_set(&data->state.url, newurl, 0, curl_free); - } +out: + Curl_creds_unlink(&ncreds_out); + return result; +} +#endif /* CURL_DISABLE_NETRC */ - uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0); - if(uc) - return Curl_uc_to_curlcode(uc); +static CURLcode url_set_data_creds(struct Curl_easy *data, CURLU *uh) +{ + struct Curl_creds *newcreds = NULL; + CURLcode result = CURLE_OK; - uc = curl_url_get(uh, CURLUPART_HOST, &data->state.up.hostname, 0); - if(uc) { - if(!curl_strequal("file", data->state.up.scheme)) - return CURLE_OUT_OF_MEMORY; - } - else if(strlen(data->state.up.hostname) > MAX_URL_LEN) { - failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); - return CURLE_URL_MALFORMAT; + if((CURL_EASY_STR(data, STRING_USERNAME) || + CURL_EASY_STR(data, STRING_PASSWORD) || + CURL_EASY_STR(data, STRING_BEARER) || + CURL_EASY_STR(data, STRING_SASL_AUTHZID) || + CURL_EASY_STR(data, STRING_SERVICE_NAME)) && + Curl_auth_allowed_to_origin(data, data->state.origin)) { + result = Curl_creds_create(CURL_EASY_STR(data, STRING_USERNAME), + CURL_EASY_STR(data, STRING_PASSWORD), + CURL_EASY_STR(data, STRING_BEARER), + CURL_EASY_STR(data, STRING_SASL_AUTHZID), + CURL_EASY_STR(data, STRING_SERVICE_NAME), + CREDS_OPTION, &newcreds); + if(result) + goto out; + if(newcreds && + !(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) && + (str_has_ctrl(Curl_creds_user(newcreds)) || + str_has_ctrl(Curl_creds_passwd(newcreds)))) { + /* if the protocol cannot handle control codes in credentials, make + sure there are none */ + failf(data, "control code detected in credentials"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } } - hostname = data->state.up.hostname; - hlen = hostname ? strlen(hostname) : 0; + /* Extract credentials from the URL only if there are none OR + * if no CURLOPT_USER was set. */ + if(!newcreds || !Curl_creds_has_user(newcreds)) { + char *user = NULL; + char *passwd = NULL; + char *udecoded = NULL; + char *pdecoded = NULL; + CURLUcode uc; - if(hostname && hostname[0] == '[') { - /* This looks like an IPv6 address literal. See if there is an address - scope. */ - /* cut off the brackets after copying this! */ - hostname++; - hlen -= 2; + uc = curl_url_get(uh, CURLUPART_USER, &user, 0); + if(uc && (uc != CURLUE_NO_USER)) + result = Curl_uc_to_curlcode(uc); + if(!result) { + uc = curl_url_get(uh, CURLUPART_PASSWORD, &passwd, 0); + if(uc && (uc != CURLUE_NO_PASSWORD)) + result = Curl_uc_to_curlcode(uc); + } + if(!result && user) { + result = Curl_urldecode(user, 0, &udecoded, NULL, + (data->state.origin->scheme->flags & + PROTOPT_USERPWDCTRL) ? + REJECT_ZERO : REJECT_CTRL); + } + if(!result && passwd) { + result = Curl_urldecode(passwd, 0, &pdecoded, NULL, + (data->state.origin->scheme->flags & + PROTOPT_USERPWDCTRL) ? + REJECT_ZERO : REJECT_CTRL); + } + if(!result) + result = Curl_creds_merge(udecoded, pdecoded, newcreds, + CREDS_URL, &newcreds); - zonefrom_url(uh, data, conn); + curlx_free(udecoded); + curlx_free(pdecoded); + curlx_free(passwd); + curlx_free(user); + if(result) { + failf(data, "error extracting credentials from URL"); + goto out; + } } - /* make sure the connect struct gets its own copy of the hostname */ - conn->host.rawalloc = curlx_strdup(hostname ? hostname : ""); - if(!conn->host.rawalloc) - return CURLE_OUT_OF_MEMORY; - conn->host.rawalloc[hlen] = 0; /* cut off for ipv6 case */ - conn->host.name = conn->host.rawalloc; +#ifndef CURL_DISABLE_NETRC + /* Check for overridden login details and set them accordingly so that + they are known when protocol->setup_connection is called! */ + result = url_set_data_creds_netrc(data, &newcreds); +#endif /* CURL_DISABLE_NETRC */ - /************************************************************* - * IDN-convert the hostnames - *************************************************************/ - result = Curl_idnconvert_hostname(&conn->host); - if(result) - return result; - -#ifndef CURL_DISABLE_HSTS - /* HSTS upgrade */ - if(data->hsts && curl_strequal("http", data->state.up.scheme)) { - /* This MUST use the IDN decoded name */ - if(Curl_hsts(data->hsts, conn->host.name, strlen(conn->host.name), TRUE)) { - char *url; - curlx_safefree(data->state.up.scheme); - uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0); - if(uc) - return Curl_uc_to_curlcode(uc); - Curl_bufref_free(&data->state.url); - /* after update, get the updated version */ - uc = curl_url_get(uh, CURLUPART_URL, &url, 0); - if(uc) - return Curl_uc_to_curlcode(uc); - uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0); - if(uc) { - curlx_free(url); - return Curl_uc_to_curlcode(uc); - } - Curl_bufref_set(&data->state.url, url, 0, curl_free); - infof(data, "Switched from HTTP to HTTPS due to HSTS => %s", url); - } +out: + if(!result && !Curl_creds_equal(data->state.creds, newcreds)) { + /* Do we have more things to trigger on credentials change? */ + Curl_creds_link(&data->state.creds, newcreds); } -#endif + Curl_creds_unlink(&newcreds); + return result; +} - result = findprotocol(data, conn, data->state.up.scheme); - if(result) - return result; +static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; - /* - * 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 || (data->state.creds_from != CREDS_OPTION)) { - uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); - if(!uc) { - char *decoded; - result = Curl_urldecode(data->state.up.password, 0, &decoded, NULL, - conn->scheme->flags&PROTOPT_USERPWDCTRL ? - REJECT_ZERO : REJECT_CTRL); - if(result) - return result; - conn->passwd = decoded; - 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); - } + Curl_peer_link(&conn->origin, data->state.origin); - 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); - if(!uc) { - char *decoded; - result = Curl_urldecode(data->state.up.user, 0, &decoded, NULL, - conn->scheme->flags&PROTOPT_USERPWDCTRL ? - REJECT_ZERO : REJECT_CTRL); - if(result) - return result; - conn->user = decoded; - result = Curl_setstropt(&data->state.aptr.user, decoded); - data->state.creds_from = CREDS_URL; + /* set the connection scheme */ + result = url_set_conn_scheme(data, conn, conn->origin->scheme); + if(result) + goto out; + + /* set the connection options */ + if(CURL_EASY_STR(data, STRING_OPTIONS)) { + conn->options = curlx_strdup(CURL_EASY_STR(data, STRING_OPTIONS)); + if(!conn->options) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - else if(uc != CURLUE_NO_USER) - return Curl_uc_to_curlcode(uc); - if(result) - return result; } - - uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options, - CURLU_URLDECODE); - if(!uc) { + else if(data->state.up.options) { conn->options = curlx_strdup(data->state.up.options); - if(!conn->options) - return CURLE_OUT_OF_MEMORY; - } - else if(uc != CURLUE_NO_OPTIONS) - return Curl_uc_to_curlcode(uc); - - uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path, CURLU_URLENCODE); - if(uc) - return Curl_uc_to_curlcode(uc); - - uc = curl_url_get(uh, CURLUPART_PORT, &data->state.up.port, - CURLU_DEFAULT_PORT); - if(uc) { - if((uc == CURLUE_OUT_OF_MEMORY) || - !curl_strequal("file", data->state.up.scheme)) - return CURLE_OUT_OF_MEMORY; - } - else { - 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(!conn->options) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - if(valid) - conn->remote_port = (unsigned short)port; } - uc = curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); - if(uc && (uc != CURLUE_NO_QUERY)) - return CURLE_OUT_OF_MEMORY; - -#ifdef USE_IPV6 - if(data->set.scope_id) - /* Override any scope that was set above. */ - conn->scope_id = data->set.scope_id; -#endif - - return CURLE_OK; +out: + return result; } /* @@ -1749,14 +1551,14 @@ static CURLcode setup_range(struct Curl_easy *data) { struct UrlState *s = &data->state; s->resume_from = data->set.set_resume_from; - if(s->resume_from || data->set.str[STRING_SET_RANGE]) { + if(s->resume_from || CURL_EASY_STR(data, STRING_SET_RANGE)) { if(s->rangestringalloc) curlx_free(s->range); if(s->resume_from) s->range = curl_maprintf("%" FMT_OFF_T "-", s->resume_from); else - s->range = curlx_strdup(data->set.str[STRING_SET_RANGE]); + s->range = curlx_strdup(CURL_EASY_STR(data, STRING_SET_RANGE)); if(!s->range) return CURLE_OUT_OF_MEMORY; @@ -1784,544 +1586,50 @@ static CURLcode setup_range(struct Curl_easy *data) static CURLcode setup_connection_internals(struct Curl_easy *data, struct connectdata *conn) { - const char *hostname; - uint16_t port; + struct Curl_peer *peer = NULL; CURLcode result; - DEBUGF(infof(data, "setup connection, bits.close=%d", conn->bits.close)); if(conn->scheme->run->setup_connection) { result = conn->scheme->run->setup_connection(data, conn); if(result) return result; } - DEBUGF(infof(data, "setup connection, bits.close=%d", conn->bits.close)); /* 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->http_proxy.port; - } - else -#endif - { - port = conn->bits.conn_to_port ? - conn->conn_to_port : conn->remote_port; - hostname = conn->bits.conn_to_host ? - conn->conn_to_host.name : conn->host.name; - } + peer = Curl_conn_get_destination(conn, FIRSTSOCKET); + if(!peer) + return CURLE_FAILED_INIT; -#ifdef USE_IPV6 /* IPv6 addresses with a scope_id (0 is default == global) have a * printable representation with a '%' suffix. */ - if(conn->scope_id) - conn->destination = curl_maprintf("[%s:%u]%%%u", hostname, port, - conn->scope_id); + if(peer->ipv6) + if(peer->scopeid) + conn->destination = curl_maprintf("[%s%%%u]:%u", + peer->hostname, peer->scopeid, peer->port); + else + conn->destination = curl_maprintf("[%s]:%u", + peer->hostname, peer->port); else -#endif - conn->destination = curl_maprintf("%s:%u", hostname, port); + conn->destination = curl_maprintf("%s:%u", peer->hostname, peer->port); if(!conn->destination) return CURLE_OUT_OF_MEMORY; Curl_strntolower(conn->destination, conn->destination, strlen(conn->destination)); - return CURLE_OK; -} - -#ifndef CURL_DISABLE_PROXY - -#ifndef CURL_DISABLE_HTTP -/**************************************************************** - * Detect what (if any) proxy to use. Remember that this selects a host - * name and is not limited to HTTP proxies only. - * The returned pointer must be freed by the caller (unless NULL) - ****************************************************************/ -static char *detect_proxy(struct Curl_easy *data, - struct connectdata *conn) -{ - char *proxy = NULL; - - /* If proxy was not specified, we check for default proxy environment - * variables, to enable i.e Lynx compliance: - * - * http_proxy=http://some.server.dom:port/ - * https_proxy=http://some.server.dom:port/ - * ftp_proxy=http://some.server.dom:port/ - * no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - * all_proxy=http://some.server.dom:port/ - * (seems to exist for the CERN www lib. Probably - * the first to check for.) - * - * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions do not exist. - */ - char proxy_env[20]; - const char *envp; - VERBOSE(envp = proxy_env); - - curl_msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", - conn->scheme->name); - - /* read the protocol proxy: */ - proxy = curl_getenv(proxy_env); - - /* - * We do not try the uppercase version of HTTP_PROXY because of - * security reasons: - * - * When curl is used in a webserver application - * environment (cgi or php), this environment variable can - * be controlled by the web server user by setting the - * http header 'Proxy:' to some value. - * - * This can cause 'internal' http/ftp requests to be - * arbitrarily redirected by any external attacker. - */ - if(!proxy && !curl_strequal("http_proxy", proxy_env)) { - /* There was no lowercase variable, try the uppercase version: */ - Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - proxy = curl_getenv(proxy_env); - } - - if(!proxy) { -#ifndef CURL_DISABLE_WEBSOCKETS - /* websocket proxy fallbacks */ - if(curl_strequal("ws_proxy", proxy_env)) { - proxy = curl_getenv("http_proxy"); - } - else if(curl_strequal("wss_proxy", proxy_env)) { - proxy = curl_getenv("https_proxy"); - if(!proxy) - proxy = curl_getenv("HTTPS_PROXY"); - } - if(!proxy) { -#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); - - return proxy; -} -#endif /* CURL_DISABLE_HTTP */ - -/* - * If this is supposed to use a proxy, we need to figure out the proxy - * 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, - struct connectdata *conn, const char *proxy, - long proxytype) -{ - char *portptr = NULL; - char *proxyuser = NULL; - char *proxypasswd = NULL; - char *host = NULL; - bool sockstype; - CURLUcode uc; - struct proxy_info *proxyinfo; - CURLU *uhp = curl_url(); - CURLcode result = CURLE_OK; - char *scheme = NULL; -#ifdef USE_UNIX_SOCKETS - char *path = NULL; - bool is_unix_proxy = FALSE; -#endif - - if(!uhp) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - /* When parsing the proxy, allowing non-supported schemes since we have - these made up ones for proxies. Guess scheme for URLs without it. */ - uc = curl_url_set(uhp, CURLUPART_URL, proxy, - CURLU_NON_SUPPORT_SCHEME | CURLU_GUESS_SCHEME); - if(!uc) { - /* parsed okay as a URL */ - uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, 0); - if(uc) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - if(curl_strequal("https", scheme)) { - if(proxytype != CURLPROXY_HTTPS2) - proxytype = CURLPROXY_HTTPS; - else - proxytype = CURLPROXY_HTTPS2; - } - else if(curl_strequal("socks5h", scheme)) - proxytype = CURLPROXY_SOCKS5_HOSTNAME; - else if(curl_strequal("socks5", scheme)) - proxytype = CURLPROXY_SOCKS5; - else if(curl_strequal("socks4a", scheme)) - proxytype = CURLPROXY_SOCKS4A; - else if(curl_strequal("socks4", scheme) || - curl_strequal("socks", scheme)) - proxytype = CURLPROXY_SOCKS4; - else if(curl_strequal("http", scheme)) - ; /* leave it as HTTP or HTTP/1.0 */ - else { - /* Any other xxx:// reject! */ - failf(data, "Unsupported proxy scheme for \'%s\'", proxy); - result = CURLE_COULDNT_CONNECT; - goto error; - } - } - else { - failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy, - curl_url_strerror(uc)); - result = CURLE_COULDNT_RESOLVE_PROXY; - goto error; - } - - if(IS_HTTPS_PROXY(proxytype) && - !Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY)) { - failf(data, "Unsupported proxy \'%s\', libcurl is built without the " - "HTTPS-proxy support.", proxy); - result = CURLE_NOT_BUILT_IN; - goto error; - } - - sockstype = - proxytype == CURLPROXY_SOCKS5_HOSTNAME || - proxytype == CURLPROXY_SOCKS5 || - proxytype == CURLPROXY_SOCKS4A || - proxytype == CURLPROXY_SOCKS4; - - proxyinfo = sockstype ? &conn->socks_proxy : &conn->http_proxy; - proxyinfo->proxytype = (unsigned char)proxytype; - - /* Is there a username and password given in this proxy URL? */ - uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE); - if(uc && (uc != CURLUE_NO_USER)) { - result = Curl_uc_to_curlcode(uc); - goto error; - } - uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE); - if(uc && (uc != CURLUE_NO_PASSWORD)) { - result = Curl_uc_to_curlcode(uc); - goto error; - } - - if(proxyuser || proxypasswd) { - curlx_free(proxyinfo->user); - proxyinfo->user = proxyuser; - result = Curl_setstropt(&data->state.aptr.proxyuser, proxyuser); - proxyuser = NULL; - if(result) - goto error; - curlx_safefree(proxyinfo->passwd); - if(!proxypasswd) { - proxypasswd = curlx_strdup(""); - if(!proxypasswd) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - proxyinfo->passwd = proxypasswd; - result = Curl_setstropt(&data->state.aptr.proxypasswd, proxypasswd); - proxypasswd = NULL; - if(result) - goto error; - conn->bits.proxy_user_passwd = TRUE; /* enable it */ - } - - uc = curl_url_get(uhp, CURLUPART_PORT, &portptr, 0); - if(uc == CURLUE_OUT_OF_MEMORY) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - if(portptr) { - curl_off_t num; - const char *p = portptr; - if(!curlx_str_number(&p, &num, UINT16_MAX)) - proxyinfo->port = (uint16_t)num; - /* Should we not error out when the port number is invalid? */ - curlx_free(portptr); - } +#ifdef USE_IPV6 + if(data->set.scope_id) + conn->scope_id = data->set.scope_id; else { - if(data->set.proxyport) - /* None given in the proxy string, then get the default one if it is - given */ - proxyinfo->port = data->set.proxyport; - else { - if(IS_HTTPS_PROXY(proxytype)) - proxyinfo->port = CURL_DEFAULT_HTTPS_PROXY_PORT; - else - proxyinfo->port = CURL_DEFAULT_PROXY_PORT; - } - } - - /* now, clone the proxy hostname */ - uc = curl_url_get(uhp, CURLUPART_HOST, &host, CURLU_URLDECODE); - if(uc) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } -#ifdef USE_UNIX_SOCKETS - if(sockstype && curl_strequal(UNIX_SOCKET_PREFIX, host)) { - uc = curl_url_get(uhp, CURLUPART_PATH, &path, CURLU_URLDECODE); - if(uc) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - /* path will be "/", if no path was found */ - if(strcmp("/", path)) { - is_unix_proxy = TRUE; - curlx_free(host); - host = curl_maprintf(UNIX_SOCKET_PREFIX "%s", path); - if(!host) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - curlx_free(proxyinfo->host.rawalloc); - proxyinfo->host.rawalloc = host; - proxyinfo->host.name = host; - host = NULL; - } - } - - if(!is_unix_proxy) { -#endif - curlx_free(proxyinfo->host.rawalloc); - proxyinfo->host.rawalloc = host; - if(host[0] == '[') { - /* this is a numerical IPv6, strip off the brackets */ - size_t len = strlen(host); - host[len - 1] = 0; /* clear the trailing bracket */ - host++; - zonefrom_url(uhp, data, conn); - } - proxyinfo->host.name = host; - host = NULL; -#ifdef USE_UNIX_SOCKETS - } -#endif - -error: - curlx_free(proxyuser); - curlx_free(proxypasswd); - curlx_free(host); - curlx_free(scheme); -#ifdef USE_UNIX_SOCKETS - curlx_free(path); -#endif - curl_url_cleanup(uhp); - return result; -} - -/* - * Extract the user and password from the authentication string - */ -static CURLcode parse_proxy_auth(struct Curl_easy *data, - struct connectdata *conn) -{ - const char *proxyuser = data->state.aptr.proxyuser ? - data->state.aptr.proxyuser : ""; - const char *proxypasswd = data->state.aptr.proxypasswd ? - data->state.aptr.proxypasswd : ""; - CURLcode result = CURLE_OUT_OF_MEMORY; - - conn->http_proxy.user = curlx_strdup(proxyuser); - if(conn->http_proxy.user) { - conn->http_proxy.passwd = curlx_strdup(proxypasswd); - if(conn->http_proxy.passwd) - result = CURLE_OK; - else - curlx_safefree(conn->http_proxy.user); - } - return result; -} - -/* 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) -{ - char *proxy = NULL; - char *socksproxy = NULL; - char *no_proxy = NULL; - CURLcode result = CURLE_OK; - - /************************************************************* - * Extract the user and password from the authentication string - *************************************************************/ - if(conn->bits.proxy_user_passwd) { - result = parse_proxy_auth(data, conn); - if(result) - goto out; - } - - /************************************************************* - * Detect what (if any) proxy to use - *************************************************************/ - if(data->set.str[STRING_PROXY]) { - proxy = curlx_strdup(data->set.str[STRING_PROXY]); - /* if global proxy is set, this is it */ - if(!proxy) { - failf(data, "memory shortage"); - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - - if(data->set.str[STRING_PRE_PROXY]) { - socksproxy = curlx_strdup(data->set.str[STRING_PRE_PROXY]); - /* if global socks proxy is set, this is it */ - if(!socksproxy) { - failf(data, "memory shortage"); - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - - if(!data->set.str[STRING_NOPROXY]) { - const char *p = "no_proxy"; - no_proxy = curl_getenv(p); - if(!no_proxy) { - p = "NO_PROXY"; - no_proxy = curl_getenv(p); - } - if(no_proxy) { - infof(data, "Uses proxy env variable %s == '%s'", p, no_proxy); - } - } - - if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy)) { - curlx_safefree(proxy); - curlx_safefree(socksproxy); + struct Curl_peer *first = Curl_conn_get_first_peer(conn, FIRSTSOCKET); + if(!first) + return CURLE_FAILED_INIT; + conn->scope_id = first->scopeid; } -#ifndef CURL_DISABLE_HTTP - else if(!proxy && !socksproxy) - /* if the host is not in the noproxy list, detect proxy. */ - proxy = detect_proxy(data, conn); -#endif /* CURL_DISABLE_HTTP */ - curlx_safefree(no_proxy); - -#ifdef USE_UNIX_SOCKETS - /* For the time being do not mix proxy and Unix domain sockets. See #1274 */ - if(proxy && conn->unix_domain_socket) { - curlx_free(proxy); - proxy = NULL; - } -#endif - - if(proxy && (!*proxy || (conn->scheme->flags & PROTOPT_NONETWORK))) { - curlx_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->scheme->flags & PROTOPT_NONETWORK))) { - curlx_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 reuse an existing - * connection that may exist registered to the same proxy host. - ***********************************************************************/ - if(proxy || socksproxy) { - if(proxy) { - result = parse_proxy(data, conn, proxy, conn->http_proxy.proxytype); - curlx_safefree(proxy); /* parse_proxy copies the proxy string */ - if(result) - goto out; - } - - if(socksproxy) { - result = parse_proxy(data, conn, socksproxy, - conn->socks_proxy.proxytype); - /* parse_proxy copies the socks proxy string */ - curlx_safefree(socksproxy); - if(result) - goto out; - } - - if(conn->http_proxy.host.rawalloc) { -#ifdef CURL_DISABLE_HTTP - /* asking for an HTTP proxy is a bit funny when HTTP is disabled... */ - result = CURLE_UNSUPPORTED_PROTOCOL; - goto out; -#else - /* force this connection's protocol to become HTTP if compatible */ - if(!(conn->scheme->protocol & PROTO_FAMILY_HTTP)) { - if((conn->scheme->flags & PROTOPT_PROXY_AS_HTTP) && - !conn->bits.tunnel_proxy) - conn->scheme = &Curl_scheme_http; - else - /* if not converting to HTTP over the proxy, enforce tunneling */ - conn->bits.tunnel_proxy = TRUE; - } - conn->bits.httpproxy = TRUE; #endif - } - else { - conn->bits.httpproxy = FALSE; /* not an HTTP proxy */ - conn->bits.tunnel_proxy = FALSE; /* no tunneling if not HTTP */ - } - - if(conn->socks_proxy.host.rawalloc) { - if(!conn->http_proxy.host.rawalloc) { - /* once a socks proxy */ - if(!conn->socks_proxy.user) { - conn->socks_proxy.user = conn->http_proxy.user; - conn->http_proxy.user = NULL; - curlx_free(conn->socks_proxy.passwd); - conn->socks_proxy.passwd = conn->http_proxy.passwd; - conn->http_proxy.passwd = NULL; - } - } - conn->bits.socksproxy = TRUE; - } - else - conn->bits.socksproxy = FALSE; /* not a socks proxy */ - } - else { - conn->bits.socksproxy = FALSE; - conn->bits.httpproxy = FALSE; - } - conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy; - - if(!conn->bits.proxy) { - /* we are not using the proxy after all... */ - conn->bits.proxy = FALSE; - conn->bits.httpproxy = FALSE; - conn->bits.socksproxy = FALSE; - conn->bits.proxy_user_passwd = FALSE; - conn->bits.tunnel_proxy = FALSE; - /* CURLPROXY_HTTPS does not have its own flag in conn->bits, yet we need - to signal that CURLPROXY_HTTPS is not used for this connection */ - conn->http_proxy.proxytype = CURLPROXY_HTTP; - } -out: - - curlx_free(socksproxy); - curlx_free(proxy); - return result; + return CURLE_OK; } -#endif /* CURL_DISABLE_PROXY */ /* * Curl_parse_login_details() @@ -2401,319 +1709,45 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, /* Allocate the options portion buffer */ if(optionsp) { char *obuf = NULL; - if(olen) { - obuf = curlx_memdup0(&osep[1], olen); - if(!obuf) - goto error; - } - *optionsp = obuf; - } - *userp = ubuf; - *passwdp = pbuf; - return CURLE_OK; -error: - curlx_free(ubuf); - curlx_free(pbuf); - return CURLE_OUT_OF_MEMORY; -} - -/************************************************************* - * Figure out the remote port number and fix it in the URL - * - * No matter if we use a proxy or not, we have to figure out the remote - * port number of various reasons. - * - * The port number embedded in the URL is replaced, if necessary. - *************************************************************/ -static CURLcode parse_remote_port(struct Curl_easy *data, - struct connectdata *conn) -{ - if(data->set.use_port && data->state.allow_port) { - /* if set, we use this instead of the port possibly given in the URL */ - char portbuf[16]; - CURLUcode uc; - conn->remote_port = data->set.use_port; - curl_msnprintf(portbuf, sizeof(portbuf), "%d", conn->remote_port); - uc = curl_url_set(data->state.uh, CURLUPART_PORT, portbuf, 0); - if(uc) - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; -} - -#ifndef CURL_DISABLE_NETRC -static bool str_has_ctrl(const char *input) -{ - if(input) { - const unsigned char *str = (const unsigned char *)input; - while(*str) { - if(*str < 0x20) - return TRUE; - str++; - } - } - return FALSE; -} -#endif - -/* - * Override the login details from the URL with that in the CURLOPT_USERPWD - * option or a .netrc file, if applicable. - */ -static CURLcode override_login(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLUcode uc; - char **userp = &conn->user; - char **passwdp = &conn->passwd; - char **optionsp = &conn->options; - - if(data->set.str[STRING_OPTIONS]) { - curlx_free(*optionsp); - *optionsp = curlx_strdup(data->set.str[STRING_OPTIONS]); - if(!*optionsp) - return CURLE_OUT_OF_MEMORY; - } - -#ifndef CURL_DISABLE_NETRC - if(data->set.use_netrc == CURL_NETRC_REQUIRED) { - curlx_safefree(*userp); - curlx_safefree(*passwdp); - } - conn->bits.netrc = FALSE; - if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) { - bool url_provided = FALSE; - - 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; - } - - if(!*passwdp) { - NETRCcode ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, - userp, passwdp, - data->set.str[STRING_NETRC_FILE]); - if(ret == NETRC_OUT_OF_MEMORY) - return CURLE_OUT_OF_MEMORY; - else if(ret && ((ret == NETRC_NO_MATCH) || - (data->set.use_netrc == CURL_NETRC_OPTIONAL))) { - infof(data, "Could not 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->scheme->flags & PROTOPT_USERPWDCTRL)) { - /* if the protocol cannot 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) { - curlx_free(conn->user); - conn->user = curlx_strdup(*userp); - if(!conn->user) - return CURLE_OUT_OF_MEMORY; - } - /* no user was set but a password, set a blank user */ - if(!*userp && *passwdp) { - *userp = curlx_strdup(""); - if(!*userp) - return CURLE_OUT_OF_MEMORY; - } - } -#endif - - /* for updated strings, we update them in the URL */ - if(*userp) { - CURLcode result; - if(data->state.aptr.user != *userp) { - /* nothing to do then */ - result = Curl_setstropt(&data->state.aptr.user, *userp); - if(result) - return result; - data->state.creds_from = CREDS_NETRC; - } - } - if(data->state.aptr.user) { - uc = curl_url_set(data->state.uh, CURLUPART_USER, data->state.aptr.user, - CURLU_URLENCODE); - if(uc) - return Curl_uc_to_curlcode(uc); - if(!*userp) { - *userp = curlx_strdup(data->state.aptr.user); - if(!*userp) - return CURLE_OUT_OF_MEMORY; - } - } - if(*passwdp) { - 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, - data->state.aptr.passwd, CURLU_URLENCODE); - if(uc) - return Curl_uc_to_curlcode(uc); - if(!*passwdp) { - *passwdp = curlx_strdup(data->state.aptr.passwd); - if(!*passwdp) - return CURLE_OUT_OF_MEMORY; - } - } - - return CURLE_OK; -} - -/* - * Set the login details so they are available in the connection - */ -static CURLcode set_login(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - const char *setuser = CURL_DEFAULT_USER; - const char *setpasswd = CURL_DEFAULT_PASSWORD; - - /* If our protocol needs a password and we have none, use the defaults */ - if((conn->scheme->flags & PROTOPT_NEEDSPWD) && !data->state.aptr.user) - ; - else { - setuser = ""; - setpasswd = ""; - } - /* Store the default user */ - if(!conn->user) { - conn->user = curlx_strdup(setuser); - if(!conn->user) - return CURLE_OUT_OF_MEMORY; - } - - /* Store the default password */ - if(!conn->passwd) { - conn->passwd = curlx_strdup(setpasswd); - if(!conn->passwd) - result = CURLE_OUT_OF_MEMORY; - } - - return result; -} - -/* - * Parses a "host:port" string to connect to. - * The hostname and the port may be empty; in this case, NULL is returned for - * the hostname and -1 for the port. - */ -static CURLcode parse_connect_to_host_port(struct Curl_easy *data, - const char *host, - char **hostname_result, - int *port_result) -{ - char *host_dup; - char *hostptr; - char *host_portno; - char *portptr; - int port = -1; - CURLcode result = CURLE_OK; - - *hostname_result = NULL; - *port_result = -1; - - if(!host || !*host) - return CURLE_OK; - - host_dup = curlx_strdup(host); - if(!host_dup) - return CURLE_OUT_OF_MEMORY; - - hostptr = host_dup; - - /* start scanning for port number at this point */ - portptr = hostptr; - - /* detect and extract RFC6874-style IPv6-addresses */ - if(*hostptr == '[') { -#ifdef USE_IPV6 - char *ptr = ++hostptr; /* advance beyond the initial bracket */ - while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.'))) - ptr++; - if(*ptr == '%') { - /* There might be a zone identifier */ - if(strncmp("%25", ptr, 3)) - infof(data, "Please URL encode %% as %%25, see RFC 6874."); - ptr++; - /* Allow unreserved characters as defined in RFC 3986 */ - while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') || - (*ptr == '.') || (*ptr == '_') || (*ptr == '~'))) - ptr++; - } - if(*ptr == ']') - /* yeps, it ended nicely with a bracket as well */ - *ptr++ = '\0'; - else - infof(data, "Invalid IPv6 address format"); - portptr = ptr; - /* 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 - failf(data, "Use of IPv6 in *_CONNECT_TO without IPv6 support built-in"); - result = CURLE_NOT_BUILT_IN; - goto error; -#endif - } - - /* Get port number off server.com:1080 */ - host_portno = strchr(portptr, ':'); - if(host_portno) { - *host_portno = '\0'; /* cut off number from hostname */ - host_portno++; - if(*host_portno) { - 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; + if(olen) { + obuf = curlx_memdup0(&osep[1], olen); + if(!obuf) goto error; - } - port = (int)portparse; /* we know it will fit */ } + *optionsp = obuf; } + *userp = ubuf; + *passwdp = pbuf; + return CURLE_OK; +error: + curlx_free(ubuf); + curlx_free(pbuf); + return CURLE_OUT_OF_MEMORY; +} - /* now, clone the cleaned hostname */ - DEBUGASSERT(hostptr); - *hostname_result = curlx_strdup(hostptr); - if(!*hostname_result) { - result = CURLE_OUT_OF_MEMORY; - goto error; +/* + * Set the login details so they are available in the connection + */ +static CURLcode url_set_conn_login(struct Curl_easy *data, + struct connectdata *conn) +{ + /* If our protocol needs a password and we have none, use the defaults */ + if((conn->scheme->flags & PROTOPT_NEEDSPWD) && !conn->creds) { + Curl_peer_link(&conn->creds_origin, data->state.origin); + if(data->state.creds) + Curl_creds_link(&conn->creds, data->state.creds); + else + return Curl_creds_create(CURL_DEFAULT_USER, CURL_DEFAULT_PASSWORD, + NULL, NULL, NULL, CREDS_NONE, &conn->creds); + } + else if(!(conn->scheme->flags & PROTOPT_CREDSPERREQUEST)) { + /* for protocols that do not handle credentials per request, + * the connection credentials are set by the initial transfer. */ + Curl_peer_link(&conn->creds_origin, data->state.origin); + Curl_creds_link(&conn->creds, data->state.creds); } - *port_result = port; - -error: - curlx_free(host_dup); - return result; + return CURLE_OK; } /* @@ -2721,18 +1755,16 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, * "HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT". */ static CURLcode parse_connect_to_string(struct Curl_easy *data, - struct connectdata *conn, - const char *conn_to_host, - char **host_result, - int *port_result) + const struct Curl_peer *dest, + const char *conn_to_line, + struct Curl_peer **pvia_dest) { CURLcode result = CURLE_OK; - const char *ptr = conn_to_host; + const char *ptr = conn_to_line; bool host_match = FALSE; bool port_match = FALSE; - *host_result = NULL; - *port_result = -1; + *pvia_dest = NULL; if(*ptr == ':') { /* an empty hostname always matches */ @@ -2743,15 +1775,16 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, /* check whether the URL's hostname matches. Use the URL hostname * when it was an IPv6 address. Otherwise use the connection's hostname * that has IDN conversion. */ - char *hostname_to_match = - (data->state.up.hostname && data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->host.name; - size_t hlen = strlen(hostname_to_match); - host_match = curl_strnequal(ptr, hostname_to_match, hlen); - ptr += hlen; - - host_match = host_match && *ptr == ':'; - ptr++; + size_t hlen = strlen(dest->hostname); + host_match = curl_strnequal(ptr, dest->hostname, hlen); + if(!host_match && (dest->user_hostname != dest->hostname)) { + /* hostname was normalized, could be IPv6 or IDN */ + hlen = strlen(dest->user_hostname); + host_match = curl_strnequal(ptr, dest->user_hostname, hlen); + } + host_match = host_match && ptr[hlen] == ':'; + if(host_match) + ptr += hlen + 1; } if(host_match) { @@ -2766,68 +1799,44 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, if(ptr_next) { curl_off_t port_to_match; if(!curlx_str_number(&ptr, &port_to_match, 0xffff) && - (port_to_match == (curl_off_t)conn->remote_port)) + ((uint16_t)port_to_match == dest->port)) { port_match = TRUE; + } ptr = ptr_next + 1; } } } - if(host_match && port_match) { - /* parse the hostname and port to connect to */ - result = parse_connect_to_host_port(data, ptr, host_result, port_result); - } + if(host_match && port_match && ptr && *ptr) + result = Curl_peer_from_connect_to(data, dest, ptr, pvia_dest); return result; } -/* - * Processes all strings in the "connect to" slist, and uses the "connect - * to host" and "connect to port" of the first string that matches. - */ -static CURLcode parse_connect_to_slist(struct Curl_easy *data, - struct connectdata *conn, - struct curl_slist *conn_to_host) +/* With `conn->origin` known, determine if we should talk to that + * directly or via another peer. This is the result of inspecting + * the "connect to" slist and "alt-svc" settings. */ +static CURLcode url_set_conn_peer(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - char *host = NULL; - int port = -1; + struct Curl_peer *origin = conn->origin; + struct Curl_peer *via_peer = NULL; + struct curl_slist *conn_to_entry = data->set.connect_to; + + DEBUGASSERT(!conn->via_peer); + Curl_peer_unlink(&conn->via_peer); - while(conn_to_host && !host && port == -1) { - result = parse_connect_to_string(data, conn, conn_to_host->data, - &host, &port); + while(conn_to_entry && !via_peer) { + result = parse_connect_to_string(data, origin, conn_to_entry->data, + &via_peer); if(result) return result; - - if(host && *host) { - conn->conn_to_host.rawalloc = host; - conn->conn_to_host.name = host; - conn->bits.conn_to_host = TRUE; - - infof(data, "Connecting to hostname: %s", host); - } - else { - /* no "connect to host" */ - conn->bits.conn_to_host = FALSE; - curlx_safefree(host); - } - - if(port >= 0) { - conn->conn_to_port = (uint16_t)port; - conn->bits.conn_to_port = TRUE; - infof(data, "Connecting to port: %u", conn->conn_to_port); - } - else { - /* no "connect to port" */ - conn->bits.conn_to_port = FALSE; - port = -1; - } - - conn_to_host = conn_to_host->next; + conn_to_entry = conn_to_entry->next; } #ifndef CURL_DISABLE_ALTSVC - if(data->asi && !host && (port == -1) && + if(data->asi && !via_peer && ((conn->scheme->protocol == CURLPROTO_HTTPS) || #ifdef DEBUGBUILD /* allow debug builds to circumvent the HTTPS restriction */ @@ -2858,13 +1867,12 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, allowed_alpns |= ALPN_h1; allowed_alpns &= (int)data->asi->flags; - host = conn->host.rawalloc; - DEBUGF(infof(data, "check Alt-Svc for host %s", host)); + DEBUGF(infof(data, "check Alt-Svc for host '%s'", origin->hostname)); #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 */ + origin, ALPN_h3, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -2874,7 +1882,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, !neg->h2_prior_knowledge) { srcalpnid = ALPN_h2; hit = Curl_altsvc_lookup(data->asi, - ALPN_h2, host, conn->remote_port, /* from */ + origin, ALPN_h2, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -2883,7 +1891,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, !neg->only_10) { srcalpnid = ALPN_h1; hit = Curl_altsvc_lookup(data->asi, - ALPN_h1, host, conn->remote_port, /* from */ + origin, ALPN_h1, /* from */ &as /* to */, allowed_alpns, &same_dest); } @@ -2908,18 +1916,16 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, } } else if(hit) { - char *hostd = curlx_strdup(as->dst.host); - if(!hostd) - return CURLE_OUT_OF_MEMORY; - conn->conn_to_host.rawalloc = hostd; - conn->conn_to_host.name = hostd; - conn->bits.conn_to_host = TRUE; - conn->conn_to_port = as->dst.port; - conn->bits.conn_to_port = TRUE; - conn->bits.altused = TRUE; + result = Curl_peer_create(data, conn->origin->scheme, + as->dst.host, as->dst.port, + &via_peer); + if(result) + return result; infof(data, "Alt-svc connecting from [%s]%s:%u to [%s]%s:%u", - Curl_alpnid2str(srcalpnid), host, conn->remote_port, - Curl_alpnid2str(as->dst.alpnid), hostd, as->dst.port); + Curl_alpnid2str(srcalpnid), origin->hostname, origin->port, + Curl_alpnid2str(as->dst.alpnid), + via_peer->hostname, via_peer->port); + conn->bits.altused = TRUE; if(srcalpnid != as->dst.alpnid) { /* protocol version switch */ switch(as->dst.alpnid) { @@ -2942,15 +1948,10 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, } #endif - return result; -} + if(via_peer) + conn->via_peer = via_peer; -static void url_move_hostname(struct hostname *dest, struct hostname *src) -{ - curlx_safefree(dest->rawalloc); - Curl_free_idnconverted_hostname(dest); - *dest = *src; - memset(src, 0, sizeof(*src)); + return result; } /* @@ -2963,51 +1964,32 @@ static void url_conn_reuse_adjust(struct Curl_easy *data, /* get the user+password information from the needle since it may * be new for this request even when we reuse conn */ - if(needle->user) { + if(needle->creds) { /* use the new username and password though */ - curlx_free(conn->user); - curlx_free(conn->passwd); - conn->user = needle->user; - conn->passwd = needle->passwd; - needle->user = NULL; - needle->passwd = NULL; + Curl_creds_link(&conn->creds, needle->creds); } #ifndef CURL_DISABLE_PROXY - conn->bits.proxy_user_passwd = needle->bits.proxy_user_passwd; - if(conn->bits.proxy_user_passwd) { - /* use the new proxy username and proxy password though */ - curlx_free(conn->http_proxy.user); - curlx_free(conn->socks_proxy.user); - curlx_free(conn->http_proxy.passwd); - curlx_free(conn->socks_proxy.passwd); - conn->http_proxy.user = needle->http_proxy.user; - conn->socks_proxy.user = needle->socks_proxy.user; - conn->http_proxy.passwd = needle->http_proxy.passwd; - conn->socks_proxy.passwd = needle->socks_proxy.passwd; - needle->http_proxy.user = NULL; - needle->socks_proxy.user = NULL; - needle->http_proxy.passwd = NULL; - needle->socks_proxy.passwd = NULL; - } + /* use the new proxy username and proxy password though */ + Curl_creds_link(&conn->http_proxy.creds, needle->http_proxy.creds); + Curl_creds_link(&conn->socks_proxy.creds, needle->socks_proxy.creds); #endif /* 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: + * the authority of the URL, e.g. conn->origin. For example: * - we use a proxy (not tunneling). we want to send all requests * that use the same proxy on this connection. * - we have a "connect-to" setting that may redirect the hostname of * a new request to the same remote endpoint of an existing conn. * We want to reuse an existing conn to the remote endpoint. - * Since connection reuse does not match on conn->host necessarily, we + * Since connection reuse does not match on conn->origin necessarily, we * switch conn to needle's host settings. */ - url_move_hostname(&conn->host, &needle->host); - url_move_hostname(&conn->conn_to_host, &needle->conn_to_host); - - conn->conn_to_port = needle->conn_to_port; - conn->remote_port = needle->remote_port; + Curl_peer_link(&conn->origin, needle->origin); + Curl_peer_link(&conn->via_peer, needle->via_peer); + Curl_peer_link(&conn->origin2, needle->origin2); + Curl_peer_link(&conn->via_peer2, needle->via_peer2); } static void conn_meta_freeentry(void *p) @@ -3016,7 +1998,7 @@ static void conn_meta_freeentry(void *p) /* Always FALSE. Cannot use a 0 assert here since compilers * are not in agreement if they then want a NORETURN attribute or * not. *sigh* */ - DEBUGASSERT(p == NULL); + DEBUGASSERT(!p); } static CURLcode url_create_needle(struct Curl_easy *data, @@ -3024,19 +2006,10 @@ static CURLcode url_create_needle(struct Curl_easy *data, { struct connectdata *needle = NULL; CURLcode result = CURLE_OK; + bool network_scheme = TRUE; /* almost all are */ - /************************************************************* - * Check input data - *************************************************************/ - if(!Curl_bufref_ptr(&data->state.url)) { - result = CURLE_URL_MALFORMAT; - goto out; - } - - /* First, split up the current URL in parts so that we can use the - parts for checking against the already present connections. In order - to not have to modify everything at once, we allocate a temporary - connection data struct and fill in for comparison purposes. */ + /* Allocate a temporary connection data struct (needle) and fill in for + comparison purposes. */ needle = allocate_conn(data); if(!needle) { result = CURLE_OUT_OF_MEMORY; @@ -3047,126 +2020,63 @@ static CURLcode url_create_needle(struct Curl_easy *data, Curl_hash_init(&needle->meta_hash, 23, Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry); - result = parseurlandfillconn(data, needle); - if(result) - goto out; - - if(data->set.str[STRING_SASL_AUTHZID]) { - needle->sasl_authzid = curlx_strdup(data->set.str[STRING_SASL_AUTHZID]); - if(!needle->sasl_authzid) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - - if(data->set.str[STRING_BEARER]) { - needle->oauth_bearer = curlx_strdup(data->set.str[STRING_BEARER]); - if(!needle->oauth_bearer) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - -#ifdef USE_UNIX_SOCKETS - if(data->set.str[STRING_UNIX_SOCKET_PATH]) { - needle->unix_domain_socket = - curlx_strdup(data->set.str[STRING_UNIX_SOCKET_PATH]); - if(!needle->unix_domain_socket) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - needle->bits.abstract_unix_socket = data->set.abstract_unix_socket; - } -#endif - - /* 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, needle); - if(result) - goto out; - - /************************************************************* - * If the protocol is using SSL and HTTP proxy is used, we set - * the tunnel_proxy bit. - *************************************************************/ - if((needle->given->flags & PROTOPT_SSL) && needle->bits.httpproxy) - needle->bits.tunnel_proxy = TRUE; -#endif - /************************************************************* - * Figure out the remote port number and fix it in the URL + * Determine `conn->origin` and populate `data->state.up` and + * other URL related properties. *************************************************************/ - result = parse_remote_port(data, needle); - if(result) - goto out; - - /* Check for overridden login details and set them accordingly so that - they are known when protocol->setup_connection is called! */ - result = override_login(data, needle); - if(result) - goto out; - - result = set_login(data, needle); /* default credentials */ + result = url_set_conn_origin_etc(data, needle); if(result) goto out; - /************************************************************* - * Process the "connect to" linked list of hostname/port mappings. - * Do this after the remote port number has been fixed in the URL. - *************************************************************/ - result = parse_connect_to_slist(data, needle, data->set.connect_to); - if(result) - goto out; + DEBUGASSERT(needle->origin); + network_scheme = !(needle->origin->scheme->flags & PROTOPT_NONETWORK); +#ifdef USE_UNIX_SOCKETS /************************************************************* - * IDN-convert the proxy hostnames + * Set UDS first. It overrides "via_peer" and proxy settings. *************************************************************/ -#ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy) { - result = Curl_idnconvert_hostname(&needle->http_proxy.host); + if(network_scheme && CURL_EASY_STR(data, STRING_UNIX_SOCKET_PATH)) { + result = Curl_peer_uds_create( + needle->origin->scheme, CURL_EASY_STR(data, STRING_UNIX_SOCKET_PATH), + (bool)data->set.abstract_unix_socket, &needle->via_peer); if(result) goto out; } - if(needle->bits.socksproxy) { - result = Curl_idnconvert_hostname(&needle->socks_proxy.host); - if(result) - goto out; - } -#endif - if(needle->bits.conn_to_host) { - result = Curl_idnconvert_hostname(&needle->conn_to_host); +#endif /* USE_UNIX_SOCKETS */ + + if(network_scheme && !needle->via_peer) { + /************************************************************* + * If the `via_peer` is not already set (via UDS above), + * determine if we talk to `conn->origin` directly or use + * `conn->via_peer` using "connect to" and "alt-svc" properties. + *************************************************************/ + result = url_set_conn_peer(data, needle); if(result) goto out; } /************************************************************* * Check whether the host and the "connect to host" are equal. - * Do this after the hostnames have been IDN-converted. + * Do this after the hostnames have been IDN-converted and + * before initializing the proxy. *************************************************************/ - if(needle->bits.conn_to_host && - curl_strequal(needle->conn_to_host.name, needle->host.name)) { - needle->bits.conn_to_host = FALSE; + if(Curl_peer_equal(needle->origin, needle->via_peer)) { + Curl_peer_unlink(&needle->via_peer); } - /************************************************************* - * Check whether the port and the "connect to port" are equal. - * Do this after the remote port number has been fixed in the URL. - *************************************************************/ - if(needle->bits.conn_to_port && - needle->conn_to_port == needle->remote_port) { - needle->bits.conn_to_port = FALSE; +#ifndef CURL_DISABLE_PROXY + /* Going via a unix socket ignores any proxy settings */ + if(network_scheme && + (!needle->via_peer || !needle->via_peer->unix_socket)) { + result = Curl_proxy_init_conn(data, needle); + if(result) + goto out; } +#endif /* CURL_DISABLE_PROXY */ -#ifndef CURL_DISABLE_PROXY - /************************************************************* - * If the "connect to" feature is used with an HTTP proxy, - * we set the tunnel_proxy bit. - *************************************************************/ - if((needle->bits.conn_to_host || needle->bits.conn_to_port) && - needle->bits.httpproxy) - needle->bits.tunnel_proxy = TRUE; -#endif + result = url_set_conn_login(data, needle); /* default credentials */ + if(result) + goto out; /************************************************************* * Setup internals depending on protocol. Needs to be done after @@ -3183,7 +2093,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, needle->bits.tls_enable_alpn = TRUE; } - if(!(needle->scheme->flags & PROTOPT_NONETWORK)) { + if(network_scheme) { /* Setup callbacks for network connections */ needle->recv[FIRSTSOCKET] = Curl_cf_recv; needle->send[FIRSTSOCKET] = Curl_cf_send; @@ -3191,7 +2101,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, needle->send[SECONDARYSOCKET] = Curl_cf_send; needle->bits.tcp_fastopen = data->set.tcp_fastopen; #ifdef USE_UNIX_SOCKETS - if(Curl_conn_get_unix_path(needle)) + if(Curl_conn_get_first_peer(needle, FIRSTSOCKET)->unix_socket) needle->transport_wanted = TRNSPRT_UNIX; #endif } @@ -3199,6 +2109,7 @@ static CURLcode url_create_needle(struct Curl_easy *data, out: if(!result) { DEBUGASSERT(needle); + DEBUGASSERT(needle->origin); *pneedle = needle; } else { @@ -3209,6 +2120,118 @@ static CURLcode url_create_needle(struct Curl_easy *data, return result; } +static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + CURLU *uh; + CURLUcode uc; + bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); + uint16_t port_override = data->state.allow_port ? data->set.use_port : 0; + uint32_t scope_id = 0; + + /************************************************************* + * Check input data + *************************************************************/ + if(!Curl_bufref_ptr(&data->state.url)) { + result = CURLE_URL_MALFORMAT; + goto out; + } + + up_free(data); /* cleanup previous leftovers first */ + + /* parse the URL */ + if(use_set_uh) + uh = data->state.uh = curl_url_dup(data->set.uh); + else + uh = data->state.uh = curl_url(); + if(!uh) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Calculate the *real* URL this transfer uses, applying defaults + * where information is missing. */ + if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL) && + !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { + char *url = curl_maprintf("%s://%s", + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), + Curl_bufref_ptr(&data->state.url)); + if(!url) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_bufref_set(&data->state.url, url, 0, curl_free); + } + + if(!use_set_uh) { + char *newurl; + uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), + (unsigned int)(CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + + /* after it was parsed, get the generated normalized version */ + uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + Curl_bufref_set(&data->state.url, newurl, 0, curl_free); + } + +#ifdef USE_IPV6 + scope_id = data->set.scope_id; +#endif + + /* `uh` is now as the connection should use it, probably. */ + result = Curl_peer_from_url(uh, data, port_override, scope_id, + &data->state.origin); + if(result) + goto out; + /* The origin might get changed when HSTS applies */ + result = hsts_upgrade(data, uh, port_override, scope_id); + if(result) + goto out; + + /* When the transfers initial_origin is not set, this is the initial + * request. Remember this starting point. */ + if(!data->state.initial_origin) + Curl_peer_link(&data->state.initial_origin, data->state.origin); + + uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path, CURLU_URLENCODE); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + uc = curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, + CURLU_GET_EMPTY); + if(uc && (uc != CURLUE_NO_QUERY)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options, + CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_OPTIONS)) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + + result = url_set_data_creds(data, uh); + if(result) + goto out; + +out: + return result; +} + /** * Find an existing connection for the transfer or create a new one. * Returns @@ -3242,21 +2265,21 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) DEBUGASSERT(needle->scheme->run->connect_it); data->info.conn_scheme = needle->scheme->name; /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (needle->scheme->protocol) & CURLPROTO_MASK; + data->info.conn_protocol = needle->scheme->protocol & CURLPROTO_MASK; result = needle->scheme->run->connect_it(data, &done); if(result) goto out; /* Setup a "faked" transfer that will do nothing */ - Curl_attach_connection(data, needle); + result = Curl_cpool_add(data, needle); + Curl_attach_connection(data, needle, TRUE); needle = NULL; - result = Curl_cpool_add(data, data->conn); if(!result) { /* Setup whatever necessary for a resumed transfer */ result = setup_range(data); if(!result) { Curl_xfer_setup_nop(data); - result = Curl_init_do(data, data->conn); + result = Curl_init_transfer(data, data->conn); } } @@ -3270,13 +2293,10 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) #endif /* Complete the easy's SSL configuration for connection cache matching */ - result = Curl_ssl_easy_config_complete(data); + result = Curl_ssl_easy_config_complete(data, needle->origin); if(result) goto out; - /* Get rid of any dead connections so limit are easier kept. */ - Curl_cpool_prune_dead(data); - /************************************************************* * Reuse of existing connection is not allowed when * - connect_only is set or @@ -3303,22 +2323,21 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) infof(data, "Reusing existing %s: connection%s with %s %s", conn->given->name, 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); + (conn->socks_proxy.peer || conn->http_proxy.peer) ? "proxy" : "host", + conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : + conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : + conn->origin->hostname); #else infof(data, "Reusing existing %s: connection%s with host %s", conn->given->name, tls_upgraded ? " (upgraded to SSL)" : "", - conn->host.dispname); + conn->origin->hostname); #endif } else { /* We have decided that we want a new connection. We may not be able to do that if we have reached the limit of how many connections we are allowed to open. */ - DEBUGF(infof(data, "new connection, bits.close=%d", needle->bits.close)); if(waitpipe) { /* There is a connection that *might* become usable for multiplexing @@ -3328,7 +2347,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) goto out; } else { - switch(Curl_cpool_check_limits(data, needle)) { + switch(Curl_cpool_check_limits(data, needle, &needle->created)) { case CPOOL_LIMIT_DEST: infof(data, "No more connections allowed to host"); result = CURLE_NO_CONNECTION_AVAILABLE; @@ -3353,14 +2372,15 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) * remaining parts like the cloned SSL configuration. */ result = Curl_ssl_conn_config_init(data, needle); if(result) { - DEBUGF(curl_mfprintf(stderr, "Error: init connection ssl config\n")); + DEBUGF(curl_mfprintf(stderr, "Error: init connection SSL config\n")); goto out; } - /* attach it and no longer own it */ - Curl_attach_connection(data, needle); - needle = NULL; - result = Curl_cpool_add(data, data->conn); + /* Add needle to conn pool, which assigns the connection id. + * Attach regardless of result, for correct handling. */ + result = Curl_cpool_add(data, needle); + Curl_attach_connection(data, needle, TRUE); + needle = NULL; if(result) goto out; @@ -3385,7 +2405,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) } /* Setup and init stuff before DO starts, in preparing for the transfer. */ - result = Curl_init_do(data, data->conn); + result = Curl_init_transfer(data, data->conn); if(result) goto out; @@ -3397,12 +2417,12 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) /* persist the scheme and handler the transfer is using */ data->info.conn_scheme = data->conn->scheme->name; /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (data->conn->scheme->protocol) & CURLPROTO_MASK; + data->info.conn_protocol = data->conn->scheme->protocol & CURLPROTO_MASK; data->info.used_proxy = #ifdef CURL_DISABLE_PROXY 0 #else - data->conn->bits.proxy + (data->conn->socks_proxy.peer || data->conn->http_proxy.peer) #endif ; @@ -3419,21 +2439,33 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) { CURLcode result; - struct connectdata *conn; + struct connectdata *conn = NULL; *pconnected = FALSE; /* Set the request to virgin state based on transfer settings */ Curl_req_hard_reset(&data->req, data); + /* Determine the origin of the transfer and what credentials to use */ + result = url_set_data_origin_and_creds(data); + if(result) + goto out; + if(!data->state.origin) { /* just make really sure */ + DEBUGASSERT(0); + result = CURLE_FAILED_INIT; + goto out; + } /* Get or create a connection for the transfer. */ result = url_find_or_create_conn(data); conn = data->conn; - if(result) goto out; + if(!data->conn) { /* just make really sure */ + DEBUGASSERT(0); + result = CURLE_FAILED_INIT; + goto out; + } - DEBUGASSERT(conn); Curl_pgrsTime(data, TIMER_POSTQUEUE); if(conn->bits.reuse) { if(conn->attached_xfers > 1) @@ -3445,11 +2477,10 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) *pconnected = TRUE; } else { - result = Curl_conn_setup(data, conn, FIRSTSOCKET, NULL, - CURL_CF_SSL_DEFAULT); + result = Curl_conn_setup(data, conn, FIRSTSOCKET, CURL_CF_SSL_DEFAULT); if(!result) result = Curl_headers_init(data); - CURL_TRC_M(data, "Curl_conn_setup() -> %d", result); + CURL_TRC_M(data, "Curl_conn_setup() -> %d", (int)result); } out: @@ -3460,23 +2491,22 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) /* We are not allowed to return failure with memory left allocated in the connectdata struct, free those here */ Curl_detach_connection(data); - Curl_conn_terminate(data, conn, TRUE); + Curl_conn_close(data, conn, TRUE); } return result; } /* - * Curl_init_do() inits the readwrite session. This is inited each time (in - * the DO function before the protocol-specific DO functions are invoked) for - * a transfer, sometimes multiple times on the same Curl_easy. Make sure - * nothing in here depends on stuff that are setup dynamically for the - * transfer. + * Curl_init_transfer() is called each time before the transfer starts - to + * prepare for a transfer, sometimes multiple times on the same Curl_easy. + * Make sure nothing in here depends on stuff that is setup dynamically for + * the transfer. * * Allow this function to get called with 'conn' set to NULL. */ -CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) +CURLcode Curl_init_transfer(struct Curl_easy *data, struct connectdata *conn) { CURLcode result; @@ -3505,99 +2535,9 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) #if defined(USE_HTTP2) || defined(USE_HTTP3) -#ifdef USE_NGHTTP2 - -static void priority_remove_child(struct Curl_easy *parent, - struct Curl_easy *child) -{ - struct Curl_data_prio_node **pnext = &parent->set.priority.children; - struct Curl_data_prio_node *pnode = parent->set.priority.children; - - DEBUGASSERT(child->set.priority.parent == parent); - while(pnode && pnode->data != child) { - pnext = &pnode->next; - pnode = pnode->next; - } - - DEBUGASSERT(pnode); - if(pnode) { - *pnext = pnode->next; - curlx_free(pnode); - } - - child->set.priority.parent = 0; - child->set.priority.exclusive = FALSE; -} - -CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, - struct Curl_easy *child, - bool exclusive) -{ - if(child->set.priority.parent) { - priority_remove_child(child->set.priority.parent, child); - } - - if(parent) { - struct Curl_data_prio_node **tail; - struct Curl_data_prio_node *pnode; - - pnode = curlx_calloc(1, sizeof(*pnode)); - if(!pnode) - return CURLE_OUT_OF_MEMORY; - pnode->data = child; - - if(parent->set.priority.children && exclusive) { - /* exclusive: move all existing children underneath the new child */ - struct Curl_data_prio_node *node = parent->set.priority.children; - while(node) { - node->data->set.priority.parent = child; - node = node->next; - } - - tail = &child->set.priority.children; - while(*tail) - tail = &(*tail)->next; - - DEBUGASSERT(!*tail); - *tail = parent->set.priority.children; - parent->set.priority.children = 0; - } - - tail = &parent->set.priority.children; - while(*tail) { - (*tail)->data->set.priority.exclusive = FALSE; - tail = &(*tail)->next; - } - - DEBUGASSERT(!*tail); - *tail = pnode; - } - - child->set.priority.parent = parent; - child->set.priority.exclusive = exclusive; - return CURLE_OK; -} - -#endif /* USE_NGHTTP2 */ - -#ifdef USE_NGHTTP2 -static void data_priority_cleanup(struct Curl_easy *data) -{ - while(data->set.priority.children) { - struct Curl_easy *tmp = data->set.priority.children->data; - priority_remove_child(data, tmp); - if(data->set.priority.parent) - Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE); - } - - if(data->set.priority.parent) - priority_remove_child(data->set.priority.parent, data); -} -#endif - void Curl_data_priority_clear_state(struct Curl_easy *data) { - memset(&data->state.priority, 0, sizeof(data->state.priority)); + data->state.weight = 0; } #endif /* USE_HTTP2 || USE_HTTP3 */ @@ -3623,6 +2563,31 @@ void *Curl_conn_meta_get(struct connectdata *conn, const char *key) return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); } +struct Curl_easy *Curl_get_admin(struct Curl_easy *data) +{ + struct Curl_easy *admin; + + if(!data->mid) /* already an admin handle */ + admin = data; + else if(data->multi) + admin = data->multi->admin; + else if(data->multi_easy) + admin = data->multi_easy->admin; + else { + DEBUGASSERT(0); /* we do not want this. does it happen? */ + admin = data; + } + if(admin != data) { + admin->set.conn_max_idle_ms = data->set.conn_max_idle_ms; + admin->set.conn_max_age_ms = data->set.conn_max_age_ms; + admin->set.upkeep_interval_ms = data->set.upkeep_interval_ms; + admin->set.timeout = data->set.timeout; + admin->set.server_response_timeout = data->set.server_response_timeout; + admin->set.no_signal = data->set.no_signal; + } + return admin; +} + CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2) { if(r1 && (r1 != CURLE_AGAIN)) diff --git a/lib/url.h b/lib/url.h index 66baf7017428..43ee63cfc597 100644 --- a/lib/url.h +++ b/lib/url.h @@ -25,11 +25,14 @@ ***************************************************************************/ #include "curl_setup.h" +/* Reject URLs exceeding this length */ +#define MAX_URL_LEN 0xffff + /* * Prototypes for library-wide functions */ -CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn); +CURLcode Curl_init_transfer(struct Curl_easy *data, struct connectdata *conn); CURLcode Curl_open(struct Curl_easy **curl); void Curl_init_userdefined(struct Curl_easy *data); @@ -64,26 +67,18 @@ CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void Curl_conn_meta_remove(struct connectdata *conn, const char *key); void *Curl_conn_meta_get(struct connectdata *conn, const char *key); +/* Get an admin handle for internal operations from the given + * easy handle, if possible. The admin handle inherits certain + * properties from `data`. If no admin handle is available (not multi + * or share attached), the easy handle itself is returned. */ +struct Curl_easy *Curl_get_admin(struct Curl_easy *data); + #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless specified */ -/** - * Return TRUE iff the given connection is considered dead. - */ -bool Curl_conn_seems_dead(struct connectdata *conn, - struct Curl_easy *data); - -/** - * Perform upkeep operations on the connection. - */ -CURLcode Curl_conn_upkeep(struct Curl_easy *data, - struct connectdata *conn); - -/** - * Always eval all arguments, return the first - * result != (CURLE_OK | CURLE_AGAIN) or `r1`. - */ +/* Always eval all arguments, return the first + * result != (CURLE_OK | CURLE_AGAIN) or `r1`. */ CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2); #if defined(USE_HTTP2) || defined(USE_HTTP3) diff --git a/lib/urlapi-int.h b/lib/urlapi-int.h index 4d8f2c1cb8df..cf1fe6e7cc6c 100644 --- a/lib/urlapi-int.h +++ b/lib/urlapi-int.h @@ -34,11 +34,11 @@ struct Curl_URL { char *options; /* IMAP only? */ char *host; char *zoneid; /* for numerical IPv6 addresses */ - char *port; char *path; char *query; char *fragment; - unsigned short portnum; /* the numerical version (if 'port' is set) */ + uint16_t portnum; /* the numerical port if present */ + BIT(port_present); /* to support missing port */ BIT(query_present); /* to support blank */ BIT(fragment_present); /* to support blank */ BIT(guessed_scheme); /* when a URL without scheme is parsed */ @@ -65,4 +65,6 @@ CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace); bool Curl_url_same_origin(CURLU *base, CURLU *href); +CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport); + #endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/lib/urlapi.c b/lib/urlapi.c index 103e0a6b57e1..734efbc35e69 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -53,6 +53,10 @@ /* scheme is not URL encoded, the longest libcurl supported ones are... */ #define MAX_SCHEME_LEN 40 +#define MAX_ZONEID_LEN 16 + +/* characters not allowed in hostnames */ +#define HOSTNAME_INVALID_CHARS " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" /* * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make @@ -69,11 +73,11 @@ static void free_urlhandle(struct Curl_URL *u) { curlx_free(u->scheme); curlx_free(u->user); + curlx_strzero(u->password); curlx_free(u->password); curlx_free(u->options); curlx_free(u->host); curlx_free(u->zoneid); - curlx_free(u->port); curlx_free(u->path); curlx_free(u->query); curlx_free(u->fragment); @@ -206,7 +210,7 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) { /* RFC 3986 3.1 explains: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - */ + */ } else { break; @@ -253,12 +257,18 @@ CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) * Parse the login details (username, password and options) from the URL and * strip them out of the hostname * + * @unittest 1675 */ -static CURLUcode parse_hostname_login(struct Curl_URL *u, - const char *login, - size_t len, - unsigned int flags, - size_t *offset) /* to the hostname */ +UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, + const char *login, + size_t len, + unsigned int flags, + size_t *hostname_offset); +UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, + const char *login, + size_t len, + unsigned int flags, + size_t *hostname_offset) { CURLUcode ures = CURLUE_OK; CURLcode result; @@ -278,7 +288,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, DEBUGASSERT(login); - *offset = 0; + *hostname_offset = 0; ptr = memchr(login, '@', len); if(!ptr) goto out; @@ -316,6 +326,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, } if(passwdp) { + curlx_strzero(u->password); curlx_free(u->password); u->password = passwdp; } @@ -326,17 +337,19 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, } /* the hostname starts at this offset */ - *offset = ptr - login; + *hostname_offset = ptr - login; return CURLUE_OK; out: curlx_free(userp); + curlx_strzero(passwdp); curlx_free(passwdp); curlx_free(optionsp); - u->user = NULL; - u->password = NULL; - u->options = NULL; + curlx_safefree(u->user); + curlx_strzero(u->password); + curlx_safefree(u->password); + curlx_safefree(u->options); return ures; } @@ -352,6 +365,8 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, /* * Find the end of an IPv6 address on the ']' ending bracket. */ + u->portnum = 0; + u->port_present = FALSE; if(hostname[0] == '[') { portptr = strchr(hostname, ']'); if(!portptr) @@ -371,28 +386,30 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, if(portptr) { curl_off_t port; size_t keep = portptr - hostname; + int rc; /* Browser behavior adaptation. If there is a colon with no digits after, cut off the name there which makes us ignore the colon and use the default port. Firefox, Chrome and Safari all do that. Do not do it if the URL has no scheme, to make something that looks like - a scheme not work! - */ + a scheme not work! */ curlx_dyn_setlen(host, keep); portptr++; if(!*portptr) return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; - - if(curlx_str_number(&portptr, &port, 0xffff) || *portptr) + if(*portptr == '\\') + return CURLUE_BACKSLASH; + rc = curlx_str_number(&portptr, &port, 0xffff); + if(rc) + return CURLUE_BAD_PORT_NUMBER; + else if(*portptr == '\\') + return CURLUE_BACKSLASH; + else if(*portptr) return CURLUE_BAD_PORT_NUMBER; - u->portnum = (unsigned short)port; - /* generate a new port number string to get rid of leading zeroes etc */ - curlx_free(u->port); - u->port = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port); - if(!u->port) - return CURLUE_OUT_OF_MEMORY; + u->portnum = (uint16_t)port; + u->port_present = TRUE; } return CURLUE_OK; @@ -422,13 +439,13 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, hlen = len; if(hostname[len] == '%') { /* this could now be '%[zone id]' */ - char zoneid[16]; + char zoneid[MAX_ZONEID_LEN]; int i = 0; char *h = &hostname[len + 1]; /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; - while(*h && (*h != ']') && (i < 15)) + while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1))) zoneid[i++] = *h++; if(!i || (']' != *h)) return CURLUE_BAD_IPV6; @@ -450,7 +467,7 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, hostname[hlen] = 0; /* end the address there */ if(curlx_inet_pton(AF_INET6, hostname, dest) != 1) return CURLUE_BAD_IPV6; - if(curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) { + if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) { hlen = strlen(hostname); /* might be shorter now */ hostname[hlen + 1] = 0; } @@ -471,10 +488,17 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, return ipv6_parse(u, hostname, hlen); else { /* letters from the second string are not ok */ - len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%"); + len = strcspn(hostname, HOSTNAME_INVALID_CHARS); if(hlen != len) /* hostname with bad content */ return CURLUE_BAD_HOSTNAME; + else if((hlen >= 2) && + (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.')) + /* more than one trailing dot is not allowed */ + return CURLUE_BAD_HOSTNAME; + else if((hlen == 1) && (hostname[0] == '.')) + /* a single dot alone is not allowed */ + return CURLUE_BAD_HOSTNAME; } return CURLUE_OK; } @@ -489,11 +513,13 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, * Output the "normalized" version of that input string in plain quad decimal * integers. * + * A single dot following the numerical address is accepted and "swallowed" as + * if it was never there. + * * Returns the host type. * * @unittest 1675 */ - UNITTEST int ipv4_normalize(struct dynbuf *host); UNITTEST int ipv4_normalize(struct dynbuf *host) { @@ -510,9 +536,11 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) int rc; curl_off_t l; if(*c == '0') { - if(c[1] == 'x') { + if(Curl_raw_tolower(c[1]) == 'x') { c += 2; /* skip the prefix */ rc = curlx_str_hex(&c, &l, UINT_MAX); + if(rc) + return HOST_NAME; } else rc = curlx_str_octal(&c, &l, UINT_MAX); @@ -520,17 +548,26 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) else rc = curlx_str_number(&c, &l, UINT_MAX); - if(rc) - return HOST_NAME; - - parts[n] = (unsigned int)l; + if(rc) { + if(!n || (rc != STRE_NO_NUM) || *c) + return HOST_NAME; + n--; + } + else + parts[n] = (unsigned int)l; switch(*c) { case '.': - if(n == 3) - return HOST_NAME; - n++; - c++; + if(n == 3) { + if(c[1]) + /* something follows this dot */ + return HOST_NAME; + done = TRUE; + } + else { + n++; + c++; + } break; case '\0': @@ -557,7 +594,7 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), + parts[0], ((parts[1] >> 16) & 0xff), ((parts[1] >> 8) & 0xff), (parts[1] & 0xff)); @@ -567,8 +604,8 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), - (parts[1]), + parts[0], + parts[1], ((parts[2] >> 8) & 0xff), (parts[2] & 0xff)); break; @@ -578,10 +615,10 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) return HOST_NAME; curlx_dyn_reset(host); result = curlx_dyn_addf(host, "%u.%u.%u.%u", - (parts[0]), - (parts[1]), - (parts[2]), - (parts[3])); + parts[0], + parts[1], + parts[2], + parts[3]); break; } if(result) @@ -631,20 +668,23 @@ static CURLUcode parse_authority(struct Curl_URL *u, */ uc = parse_hostname_login(u, auth, authlen, flags, &offset); if(uc) - goto out; + return uc; result = curlx_dyn_addn(host, auth + offset, authlen - offset); if(result) { uc = cc2cu(result); - goto out; + return uc; } uc = parse_port(u, host, has_scheme); - if(uc) - goto out; if(!curlx_dyn_len(host)) - return CURLUE_NO_HOST; + /* this makes no-host errors override port number problems */ + uc = CURLUE_NO_HOST; + if(!uc) + uc = urldecode_host(host); + if(uc) + return uc; switch(ipv4_normalize(host)) { case HOST_IPV4: @@ -653,9 +693,7 @@ static CURLUcode parse_authority(struct Curl_URL *u, uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_NAME: - uc = urldecode_host(host); - if(!uc) - uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); + uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_ERROR: uc = CURLUE_OUT_OF_MEMORY; @@ -665,7 +703,6 @@ static CURLUcode parse_authority(struct Curl_URL *u, break; } -out: return uc; } @@ -713,6 +750,29 @@ static bool is_dot(const char **str, size_t *clen) #define ISSLASH(x) ((x) == '/') +/* prescan the string to see if it needs work */ +static bool needs_dedotdot(const char *p, size_t pn) +{ + /* a single byte path cannot be cleaned up */ + if(pn < 2) + return FALSE; + while(pn) { + if(is_dot(&p, &pn)) { + /* "./" or dot before end of string */ + if(!pn || ISSLASH(*p)) + return TRUE; + /* "../" or ".." before end of string */ + else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p))) + return TRUE; + } + else { + p++; + pn--; + } + } + return FALSE; +} + /* * dedotdotify() * @@ -724,7 +784,8 @@ static bool is_dot(const char **str, size_t *clen) * * RETURNS * - * Zero for success and 'out' set to an allocated dedotdotified string. + * Zero for success and 'out' set to an allocated string (or NULL if there's + * nothing to do). * * @unittest 1395 */ @@ -739,8 +800,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) size_t dlen = clen; *outp = NULL; - /* a single byte path cannot be cleaned up */ - if(clen < 2) + if(!needs_dedotdot(input, clen)) return 0; curlx_dyn_init(&out, clen + 1); @@ -855,13 +915,19 @@ UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u, path = &url[5]; pathlen = urllen - 5; + /* RFC 8089: file-hier-part = ( "//" auth-path ) / local-path, where + local-path also starts with a "/". So reject anything that does not + start with at least one "/" */ + if(path[0] != '/') + return CURLUE_BAD_FILE_URL; + /* Extra handling URLs with an authority component (i.e. that start with * "file://") * * We allow omitted hostname (e.g. file:/) -- valid according to * RFC 8089, but not the (current) WHAT-WG URL spec. */ - if(path[0] == '/' && path[1] == '/') { + if(path[1] == '/') { /* swallow the two slashes */ const char *ptr = &path[2]; @@ -931,22 +997,30 @@ static CURLUcode parse_scheme(const char *url, CURLU *u, char *schemebuf, const char *schemep = NULL; if(schemelen) { - int i = 0; + int num_slashes = 0; const char *p = &url[schemelen + 1]; - while((*p == '/') && (i < 4)) { - p++; - i++; - } - - schemep = schemebuf; - if(!Curl_get_scheme(schemep) && - !(flags & CURLU_NON_SUPPORT_SCHEME)) + if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME)) return CURLUE_UNSUPPORTED_SCHEME; - if((i < 1) || (i > 3)) - /* less than one or more than three slashes */ + if(!ISSLASH(*p)) + /* less than one */ return CURLUE_BAD_SLASHES; + if((flags & CURLU_NO_AUTHORITY)) { + while(ISSLASH(*p) && (num_slashes < 2)) { + p++; + num_slashes++; + } + } + else { + while(ISSLASH(*p) && (num_slashes < 4)) { + p++; + num_slashes++; + } + if(num_slashes > 3) + return CURLUE_BAD_SLASHES; + } + schemep = schemebuf; *hostpp = p; /* hostname starts here */ } else { @@ -1006,7 +1080,7 @@ static CURLUcode handle_fragment(CURLU *u, const char *fragment, CURLUcode ures; u->fragment_present = TRUE; if(fraglen > 1) { - /* skip the leading '#' in the copy but include the terminating null */ + /* skip the leading '#' in the copy but include the null-terminator */ if(flags & CURLU_URLENCODE) { struct dynbuf enc; curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); @@ -1138,8 +1212,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) /* this pathlen also contains the query and the fragment */ pathlen = urllen - (path - url); if(hostlen) { - ures = parse_authority(u, hostp, hostlen, flags, &host, - u->scheme != NULL); + ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme); if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme) ures = guess_scheme(u, &host); } @@ -1212,9 +1285,12 @@ static CURLUcode redirect_url(const char *base, const char *relurl, const char *cutoff = NULL; size_t prelen; CURLUcode uc; + /* this can get here with a NULL u->scheme only if asked to use the default + scheme, so allow fallback to that */ + const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME; /* protsep points to the start of the hostname, after [scheme]:// */ - const char *protsep = base + strlen(u->scheme) + 3; + const char *protsep = base + strlen(scheme) + 3; DEBUGASSERT(base && relurl && u); /* all set here */ if(!base) return CURLUE_MALFORMED_INPUT; /* should never happen */ @@ -1235,16 +1311,16 @@ static CURLUcode redirect_url(const char *base, const char *relurl, case '#': /* fragment-only change */ - if(u->fragment) + if(u->fragment_present) cutoff = strchr(protsep, '#'); break; default: /* path or query-only change */ - if(u->query && u->query[0]) + if(u->query_present) /* remove existing query */ cutoff = strchr(protsep, '?'); - else if(u->fragment && u->fragment[0]) + else if(u->fragment_present) /* Remove existing fragment */ cutoff = strchr(protsep, '#'); @@ -1309,12 +1385,12 @@ CURLU *curl_url_dup(const CURLU *in) DUP(u, in, password); DUP(u, in, options); DUP(u, in, host); - DUP(u, in, port); DUP(u, in, path); DUP(u, in, query); DUP(u, in, fragment); DUP(u, in, zoneid); u->portnum = in->portnum; + u->port_present = in->port_present; u->fragment_present = in->fragment_present; u->query_present = in->query_present; } @@ -1417,6 +1493,20 @@ static CURLUcode urlget_format(const CURLU *u, CURLUPart what, return CURLUE_OK; } +static CURLUcode file_url(const CURLU *u, char **part, + const char *fragmentsep, + const char *querysep) +{ + char *url = curl_maprintf("file://%s%s%s%s%s", + u->path, querysep, u->query ? u->query : "", + fragmentsep, u->fragment ? u->fragment : ""); + if(!url) + return CURLUE_OUT_OF_MEMORY; + + *part = url; + return CURLUE_OK; +} + static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags) { char *url; @@ -1428,17 +1518,14 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags) (u->query_present && flags & CURLU_GET_EMPTY)) ? "?" : ""; char portbuf[7]; - if(u->scheme && curl_strequal("file", u->scheme)) { - url = curl_maprintf("file://%s%s%s%s%s", - u->path, querysep, u->query ? u->query : "", - fragmentsep, u->fragment ? u->fragment : ""); - } + if(curl_strequal("file", u->scheme)) + return file_url(u, part, fragmentsep, querysep); else if(!u->host) return CURLUE_NO_HOST; else { const char *scheme; char *options = u->options; - char *port = u->port; + char *port = NULL; const struct Curl_scheme *h = NULL; char schemebuf[MAX_SCHEME_LEN + 5]; if(u->scheme) @@ -1448,25 +1535,29 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags) else return CURLUE_NO_SCHEME; + if(u->port_present) { + curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum); + port = portbuf; + } + h = Curl_get_scheme(scheme); - if(!port && (flags & CURLU_DEFAULT_PORT)) { - /* there is no stored port number, but asked to deliver - a default one for the scheme */ - if(h) { + if(h) { + if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) { + /* there is no stored port number, but asked to deliver a default one + for the scheme */ curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); port = portbuf; } - } - else if(port) { - /* there is a stored port number, but asked to inhibit if it matches - the default one for the scheme */ - if(h && (h->defport == u->portnum) && - (flags & CURLU_NO_DEFAULT_PORT)) + else if(u->port_present && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) { + /* there is a stored port number, but asked to inhibit if it matches + the default port for the scheme */ port = NULL; - } + } - if(h && !(h->flags & PROTOPT_URLOPTIONS)) - options = NULL; + if(!(h->flags & PROTOPT_URLOPTIONS)) + options = NULL; + } if(u->host[0] == '[') { if(u->zoneid) { @@ -1571,10 +1662,24 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_ZONEID; break; case CURLUPART_PORT: - ptr = u->port; + ptr = NULL; ifmissing = CURLUE_NO_PORT; flags &= ~U_CURLU_URLDECODE; /* never for port */ - if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { + if(u->port_present) { + const struct Curl_scheme *h = u->scheme ? + Curl_get_scheme(u->scheme) : NULL; + /* there is a stored port number, but ask to inhibit if + it matches the default one for the scheme */ + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) { + ptr = NULL; + } + else { + curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum); + ptr = portbuf; + } + } + else if((flags & CURLU_DEFAULT_PORT) && u->scheme) { /* there is no stored port number, but asked to deliver a default one for the scheme */ const struct Curl_scheme *h = Curl_get_scheme(u->scheme); @@ -1583,14 +1688,6 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = portbuf; } } - 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_scheme *h = Curl_get_scheme(u->scheme); - if(h && (h->defport == u->portnum) && - (flags & CURLU_NO_DEFAULT_PORT)) - ptr = NULL; - } break; case CURLUPART_PATH: ptr = u->path; @@ -1602,7 +1699,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_QUERY; plusdecode = flags & CURLU_URLDECODE; if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) - /* there was a blank query and the user do not ask for it */ + /* there was a blank query and the user does not ask for it */ ptr = NULL; break; case CURLUPART_FRAGMENT: @@ -1657,7 +1754,6 @@ static CURLUcode set_url_scheme(CURLU *u, const char *scheme, 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 */ @@ -1665,12 +1761,8 @@ static CURLUcode set_url_port(CURLU *u, const char *provided_port) if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port) /* weirdly provided number, not good! */ return CURLUE_BAD_PORT_NUMBER; - tmp = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port); - if(!tmp) - return CURLUE_OUT_OF_MEMORY; - curlx_free(u->port); - u->port = tmp; - u->portnum = (unsigned short)port; + u->portnum = (uint16_t)port; + u->port_present = TRUE; return CURLUE_OK; } @@ -1691,8 +1783,11 @@ static CURLUcode set_url(CURLU *u, const char *url, size_t part_size, and this is a redirect */ uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags); if(!uc) { - /* success, meaning the "" is a fine relative URL, but nothing - changes */ + /* success, meaning the "" is a fine relative URL, and the new URL + inherits scheme/authority/path/query, but not fragment, from the + existing URL (RFC 3986 section 5.2.2) */ + curlx_safefree(u->fragment); + u->fragment_present = FALSE; curlx_free(oldurl); return CURLUE_OK; } @@ -1707,8 +1802,12 @@ static CURLUcode set_url(CURLU *u, const char *url, size_t part_size, return parseurl_and_replace(url, u, flags); /* if the old URL is incomplete (we cannot get an absolute URL in - 'oldurl'), replace the existing with the new */ - uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags); + 'oldurl'), replace the existing with the new. + Always include "scheme://" to make the URL "complete" */ + /* Preserve empty query/fragment separators: they affect where relative + references splice into the base URL. */ + uc = curl_url_get(u, CURLUPART_URL, &oldurl, + (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY); if(uc == CURLUE_OUT_OF_MEMORY) return uc; else if(uc) @@ -1736,6 +1835,7 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what) curlx_safefree(u->user); break; case CURLUPART_PASSWORD: + curlx_strzero(u->password); curlx_safefree(u->password); break; case CURLUPART_OPTIONS: @@ -1749,7 +1849,7 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what) break; case CURLUPART_PORT: u->portnum = 0; - curlx_safefree(u->port); + u->port_present = FALSE; break; case CURLUPART_PATH: curlx_safefree(u->path); @@ -1794,6 +1894,126 @@ static bool allowed_in_path(unsigned char x) return FALSE; } +static CURLUcode url_encode_part(struct dynbuf *encp, + const char *part, + bool plusencode, + bool pathmode, + bool equalsencode) +{ + const unsigned char *i; + + for(i = (const unsigned char *)part; *i; i++) { + CURLcode result; + if((*i == ' ') && plusencode) + result = curlx_dyn_addn(encp, "+", 1); + else if(ISUNRESERVED(*i) || + (pathmode && allowed_in_path(*i)) || + ((*i == '=') && equalsencode)) { + if((*i == '=') && equalsencode) + /* only skip the first equals sign */ + equalsencode = FALSE; + result = curlx_dyn_addn(encp, i, 1); + } + else { + unsigned char out[3] = { '%' }; + Curl_hexbyte(&out[1], *i); + result = curlx_dyn_addn(encp, out, 3); + } + if(result) + return cc2cu(result); + } + return CURLUE_OK; +} + +static CURLUcode url_uppercasehex_part(struct dynbuf *encp, + const char *part) +{ + char *p; + CURLcode result = curlx_dyn_add(encp, part); + if(result) + return cc2cu(result); + p = curlx_dyn_ptr(encp); + while(*p) { + /* make sure percent encoded are upper case */ + if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && + (ISLOWER(p[1]) || ISLOWER(p[2]))) { + p[1] = Curl_raw_toupper(p[1]); + p[2] = Curl_raw_toupper(p[2]); + p += 3; + } + else + p++; + } + return CURLUE_OK; +} + +static CURLUcode url_append_query(CURLU *u, struct dynbuf *encp) +{ + /* Append the 'encp' string onto the old query. Add a '&' separator if none + is already present at the end of the existing query */ + + size_t querylen = u->query ? strlen(u->query) : 0; + bool addamperand = querylen && (u->query[querylen - 1] != '&'); + if(querylen) { + struct dynbuf qbuf; + CURLcode result; + const char *newp = curlx_dyn_ptr(encp); + curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); + + /* add original query */ + result = curlx_dyn_addn(&qbuf, u->query, querylen); + if(!result && addamperand) + /* add ampersand */ + result = curlx_dyn_addn(&qbuf, "&", 1); + if(!result) + /* add new query part */ + result = curlx_dyn_add(&qbuf, newp); + if(result) + goto nomem; + curlx_dyn_free(encp); + curlx_free(u->query); + u->query = curlx_dyn_ptr(&qbuf); + return CURLUE_OK; +nomem: + curlx_dyn_free(encp); + return cc2cu(result); + } + else { + curlx_free(u->query); + u->query = curlx_dyn_ptr(encp); + } + return CURLUE_OK; +} + +static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, + bool urlencode, + unsigned int flags) +{ + size_t n = curlx_dyn_len(encp); + bool bad = FALSE; + char *newp = curlx_dyn_ptr(encp); + if(!n) + /* an empty hostname is okay if told so */ + bad = (flags & CURLU_NO_AUTHORITY) ? FALSE : TRUE; + else if(!urlencode) { + /* if the hostname part was not URL encoded here, it was set already 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; + curlx_free(decoded); + } + else if(hostname_check(u, newp, n)) + bad = TRUE; + if(bad) { + curlx_dyn_free(encp); + return CURLUE_BAD_HOSTNAME; + } + return CURLUE_OK; +} + CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { @@ -1867,8 +2087,9 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } DEBUGASSERT(storep); { - const char *newp; + const char *newp = NULL; struct dynbuf enc; + CURLUcode status; curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash); if(leadingslash && (part[0] != '/')) { @@ -1876,113 +2097,23 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(result) return cc2cu(result); } - if(urlencode) { - const unsigned char *i; - - for(i = (const unsigned char *)part; *i; i++) { - CURLcode result; - if((*i == ' ') && plusencode) { - result = curlx_dyn_addn(&enc, "+", 1); - if(result) - return CURLUE_OUT_OF_MEMORY; - } - else if(ISUNRESERVED(*i) || - (pathmode && allowed_in_path(*i)) || - ((*i == '=') && equalsencode)) { - if((*i == '=') && equalsencode) - /* only skip the first equals sign */ - equalsencode = FALSE; - result = curlx_dyn_addn(&enc, i, 1); - if(result) - return cc2cu(result); - } - else { - unsigned char out[3] = { '%' }; - Curl_hexbyte(&out[1], *i); - result = curlx_dyn_addn(&enc, out, 3); - if(result) - return cc2cu(result); - } - } - } - else { - char *p; - 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]) && - (ISUPPER(p[1]) || ISUPPER(p[2]))) { - p[1] = Curl_raw_tolower(p[1]); - p[2] = Curl_raw_tolower(p[2]); - p += 3; - } - else - p++; - } - } - newp = curlx_dyn_ptr(&enc); - - 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 qbuf; - curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); - - if(curlx_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ - goto nomem; - - if(addamperand) { - if(curlx_dyn_addn(&qbuf, "&", 1)) - goto nomem; - } - if(curlx_dyn_add(&qbuf, newp)) - goto nomem; - curlx_dyn_free(&enc); - curlx_free(*storep); - *storep = curlx_dyn_ptr(&qbuf); - return CURLUE_OK; -nomem: - curlx_dyn_free(&enc); - return CURLUE_OUT_OF_MEMORY; - } - } - - else if(what == CURLUPART_HOST) { - size_t n = curlx_dyn_len(&enc); - if(!n && (flags & CURLU_NO_AUTHORITY)) { - /* Skip hostname check, it is allowed to be empty. */ - } - else { - bool bad = FALSE; - if(!n) - bad = TRUE; /* empty hostname is not okay */ - else if(!urlencode) { - /* if the hostname 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; - curlx_free(decoded); - } - else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) - bad = TRUE; - if(bad) { - curlx_dyn_free(&enc); - return CURLUE_BAD_HOSTNAME; - } - } + if(urlencode) + status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode); + else + status = url_uppercasehex_part(&enc, part); + if(!status) { + newp = curlx_dyn_ptr(&enc); + + if(appendquery && newp) + return url_append_query(u, &enc); + else if(what == CURLUPART_HOST) + status = url_sethost(u, &enc, urlencode, flags); } + if(status) + return status; + if(what == CURLUPART_PASSWORD) + curlx_strzero(*storep); curlx_free(*storep); *storep = (char *)CURL_UNCONST(newp); } @@ -2001,23 +2132,42 @@ bool Curl_url_same_origin(CURLU *base, CURLU *href) if(href->host) { if(!curl_strequal(base->host, href->host)) return FALSE; - if(!curl_strequal(base->port, href->port)) { - /* This may still match if only one has an explicit port - * and it is the default for the scheme. */ - if(base->port && href->port) - return FALSE; + if(base->port_present != href->port_present) { + /* one is present, one is not */ s = Curl_get_scheme(base->scheme); if(!s) /* Cannot match default port for unknown scheme */ return FALSE; - - /* The port which is set must be the default one */ - if((base->port && (base->portnum != s->defport)) || - (href->port && (href->portnum != s->defport))) + /* to match, the present one must be the default port */ + if((base->port_present && (base->portnum != s->defport)) || + (href->port_present && (href->portnum != s->defport))) return FALSE; } + else if(base->portnum != href->portnum) /* both present or missing */ + return FALSE; + + if(!curl_strequal(base->zoneid ? base->zoneid : "", + href->zoneid ? href->zoneid : "")) + return FALSE; } - else if(href->port) /* no host in href, then there must be no port */ + else if(href->port_present) /* no host in href, then there must be no port */ return FALSE; return TRUE; } + +CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport) +{ + if(u->port_present) { + *pport = u->portnum; + return CURLUE_OK; + } + else if(u->scheme) { + const struct Curl_scheme *s = Curl_get_scheme(u->scheme); + if(s && s->defport) { + *pport = s->defport; + return CURLUE_OK; + } + } + *pport = 0; + return CURLUE_NO_PORT; +} diff --git a/lib/urldata.h b/lib/urldata.h index 83d21d954984..1c0101847b94 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -37,7 +37,7 @@ #define DEFAULT_CONNCACHE_SIZE 5 -/* length of longest IPv6 address string including the trailing null */ +/* length of longest IPv6 address string including the null-terminator */ #define MAX_IPADR_LEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") /* Max string input length is a precaution against abuse and to detect junk @@ -53,13 +53,15 @@ #include "curlx/timeval.h" -#include "asyn.h" +#include "api.h" #include "cookie.h" +#include "creds.h" #include "psl.h" #include "formdata.h" #include "http_chunks.h" /* for the structs and enum stuff */ -#include "hostip.h" #include "hash.h" +#include "peer.h" +#include "proxy.h" #include "splay.h" #include "curlx/dynbuf.h" #include "bufref.h" @@ -67,10 +69,14 @@ #include "request.h" #include "ratelimit.h" #include "netrc.h" +#include "uint-hashset.h" +#include "vdns/asyn.h" +#include "vdns/hostip.h" +#include "vtls/vtls_config.h" /* On error return, the value of `pnwritten` has no meaning */ typedef CURLcode (Curl_send)(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ + int8_t sockindex, /* socketindex */ const uint8_t *buf, /* data to write */ size_t len, /* amount to send */ bool eos, /* last chunk */ @@ -78,7 +84,7 @@ typedef CURLcode (Curl_send)(struct Curl_easy *data, /* transfer */ /* On error return, the value of `pnread` has no meaning */ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ + int8_t sockindex, /* socketindex */ char *buf, /* store data here */ size_t len, /* max amount to read */ size_t *pnread); /* how much received */ @@ -94,7 +100,10 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #include "cf-socket.h" #ifdef HAVE_GSSAPI -# ifdef HAVE_GSSGNU +# ifdef HAVE_GSSAPPLE +# include +# include +# elif defined(HAVE_GSSGNU) # include # elif defined(HAVE_GSSAPI_H) # include @@ -125,83 +134,6 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #define UPLOADBUFFER_MAX (2 * 1024 * 1024) #define UPLOADBUFFER_MIN CURL_MAX_WRITE_SIZE -#define CURLEASY_MAGIC_NUMBER 0xc0dedbadU -#ifdef DEBUGBUILD -/* On a debug build, we want to fail hard on easy handles that - * are not NULL, but no longer have the MAGIC touch. This gives - * us early warning on things only discovered by valgrind otherwise. */ -#define GOOD_EASY_HANDLE(x) \ - (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) ? TRUE : \ - (DEBUGASSERT(!(x)), FALSE)) -#else -#define GOOD_EASY_HANDLE(x) \ - ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) -#endif - -struct ssl_primary_config { - char *CApath; /* certificate directory (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; - struct curl_blob *ca_info_blob; - struct curl_blob *issuercert_blob; -#ifdef USE_TLS_SRP - char *username; /* TLS username (for, e.g., SRP) */ - char *password; /* TLS password (for, e.g., SRP) */ -#endif - char *curves; /* list of curves to use */ -#if UNITY_CERTVERIFY - /* Unity: peer verification callback and its userdata. These live in the - primary config rather than in ssl_config_data so that they take part in - the connection reuse check, i.e. a connection verified by one callback is - never reused by a transfer carrying a different one. */ - curl_unity_certverify_callback unity_certverify; - void *unity_certverify_userp; -#endif /* UNITY_CERTVERIFY */ - uint32_t version_max; /* max supported version the client wants to use */ - uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ - uint8_t version; /* what version the client wants to use */ - BIT(verifypeer); /* set TRUE if this is desired */ - BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ - BIT(verifystatus); /* set TRUE if certificate status must be checked */ - BIT(cache_session); /* cache session or not */ -}; - -struct ssl_config_data { - struct ssl_primary_config primary; - long certverifyresult; /* result from the certificate verification */ - curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ - void *fsslctxp; /* parameter for call back */ - char *cert_type; /* format for certificate (default: PEM) */ - 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(earlydata); /* use TLS 1.3 early data */ - BIT(enable_beast); /* allow this flaw for interoperability's sake */ - BIT(no_revoke); /* disable SSL certificate revocation checks */ - BIT(no_partialchain); /* do not accept partial certificate chains */ - BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation - list errors */ - BIT(native_ca_store); /* use the native CA store of operating system */ - BIT(auto_client_cert); /* automatically locate and use a client - certificate for authentication (Schannel) */ - BIT(custom_cafile); /* application has set custom CA file */ - BIT(custom_capath); /* application has set custom CA path */ - BIT(custom_cablob); /* application has set custom CA blob */ -}; - -struct ssl_general_config { - int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ -}; - #ifdef USE_WINDOWS_SSPI #include "curl_sspi.h" #endif @@ -209,14 +141,12 @@ struct ssl_general_config { #ifndef CURL_DISABLE_DIGEST_AUTH /* Struct used for Digest challenge-response authentication */ struct digestdata { + struct Curl_creds *creds; + struct Curl_peer *origin; #ifdef USE_WINDOWS_SSPI BYTE *input_token; size_t input_token_len; CtxtHandle *http_context; - /* copy of user/passwd used to make the identity for http_context. - either may be NULL. */ - char *user; - char *passwd; #else char *nonce; char *cnonce; @@ -254,23 +184,12 @@ typedef enum { struct ConnectBits { BIT(connect_only); #ifndef CURL_DISABLE_PROXY - BIT(httpproxy); /* if set, this transfer is done through an HTTP proxy */ - BIT(socksproxy); /* if set, this transfer is done through a socks proxy */ - BIT(proxy_user_passwd); /* user+password for the proxy? */ - BIT(tunnel_proxy); /* if CONNECT is used to "tunnel" through the proxy. - This is implicit when SSL-protocols are used through - proxies, but can also be enabled explicitly by - apps */ - BIT(proxy); /* if set, this transfer is done through a proxy - any type */ + BIT(origin_is_proxy); /* if set, the connection's origin is a proxy */ #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 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 */ - BIT(conn_to_port); /* if set, this connection has a "connect to port" - that overrides the port in the URL (remote port) */ BIT(ipv6); /* we communicate with a site using an IPv6 address */ BIT(do_more); /* this is set TRUE if the ->curl_do_more() function is supposed to be called, after ->curl_do() */ @@ -286,10 +205,6 @@ struct ConnectBits { 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 */ -#endif -#ifndef CURL_DISABLE_NETRC - BIT(netrc); /* name+password provided by netrc */ #endif BIT(bound); /* set true if bind() has already been done on this socket/ connection */ @@ -297,9 +212,6 @@ struct ConnectBits { BIT(multiplex); /* connection is multiplexed */ BIT(tcp_fastopen); /* use TCP Fast Open */ BIT(tls_enable_alpn); /* TLS ALPN extension? */ -#ifdef USE_UNIX_SOCKETS - BIT(abstract_unix_socket); -#endif BIT(sock_accepted); /* TRUE if the SECONDARYSOCKET was created with accept() */ BIT(parallel_connect); /* set TRUE when a parallel connect attempt has @@ -328,6 +240,8 @@ struct hostname { #define TRNSPRT_QUIC 5 #define TRNSPRT_UNIX 6 +#define TRNSPRT_IS_DGRAM(x) (((x) == TRNSPRT_UDP) || ((x) == TRNSPRT_QUIC)) + struct ip_quadruple { char remote_ip[MAX_IPADR_LEN]; char local_ip[MAX_IPADR_LEN]; @@ -341,35 +255,31 @@ struct ip_quadruple { ((x)->transport == TRNSPRT_UDP) || \ ((x)->transport == TRNSPRT_QUIC)) -struct proxy_info { - struct hostname host; - uint16_t port; - uint8_t proxytype; /* what kind of proxy that is in use */ - char *user; /* proxy username string, allocated */ - char *passwd; /* proxy password string, allocated */ -}; - /* * The connectdata struct contains all fields and variables that should be * unique for an entire connection. */ struct connectdata { - 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 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)->attached_xfers) - - /**** Fields set when inited and not modified again */ 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 */ + + /* 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, check for equality. */ + struct Curl_multi *attached_multi; + + /* Who the connection is talking to, ultimately */ + struct Curl_peer *origin; /* connection ultimately talks to this */ + struct Curl_peer *via_peer; /* if set, connection really talks to this */ + struct Curl_peer *origin2; /* origin of SECONDARYSOCKET */ + struct Curl_peer *via_peer2; /* peer of SECONDARYSOCKET */ + struct Curl_creds *creds; /* When connection itself is tied to credentials */ + struct Curl_peer *creds_origin; /* origin tied credentials are for */ + const struct Curl_scheme *scheme; /* Connection's real protocol handler */ + const struct Curl_scheme *given; /* The protocol first given */ /* `meta_hash` is a general key-value store for implementations * with the lifetime of the connection. @@ -377,30 +287,26 @@ struct connectdata { * the connection is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash meta_hash; - struct hostname host; - 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 */ + struct Curl_llist_node cpool_node; /* conncache lists */ + struct Curl_llist_node cshutdn_node; /* cshutdn list */ + char *destination; /* hostname+port, used in conncache */ + + struct curltime created; /* creation time */ + struct curltime lastused; /* when returned to the connection pool as idle */ + struct curltime lastchecked; /* when last checked alive status */ + struct curltime lastupkeep; /* when last done conn_upkeep */ + #ifndef CURL_DISABLE_PROXY struct proxy_info socks_proxy; struct proxy_info http_proxy; #endif - 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 created; /* creation time */ - struct curltime lastused; /* when returned to the connection pool as idle */ - /* A connection can have one or two sockets and connection filters. - * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ - curl_socket_t sock[2]; struct Curl_cfilter *cfilter[2]; /* connection filters */ Curl_recv *recv[2]; Curl_send *send[2]; - int recv_idx; /* on which socket index to receive, default 0 */ - int send_idx; /* on which socket index to send, default 0 */ + /* A connection can have one or two sockets and connection filters. + * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ + curl_socket_t sock[2]; #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) @@ -409,31 +315,18 @@ struct connectdata { timediff_t timeout_ms; /* 0 means no timeout */ } shutdown; + curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ + void *closesocket_client; + struct ssl_primary_config ssl_config; #ifndef CURL_DISABLE_PROXY struct ssl_primary_config proxy_ssl_config; #endif - struct ConnectBits bits; /* various state-flags for this connection */ - - const struct Curl_scheme *scheme; /* Connection's protocol handler */ - const struct Curl_scheme *given; /* The protocol first given */ - - /* Protocols can use a custom keepalive mechanism to keep connections alive. - This allows those protocols to track the last time the keepalive mechanism - was used on this connection. */ - struct curltime keepalive; - - /* 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, check for equality. */ - struct Curl_multi *attached_multi; + char *options; /* options string, allocated */ /*************** Request - specific items ************/ -#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) - CtxtHandle *sslContext; /* mingw-w64 v9+. MS SDK 7.0A+. */ +#ifdef USE_WINDOWS_SSPI + CtxtHandle *sslContext; #endif #ifdef USE_NTLM @@ -446,32 +339,31 @@ struct connectdata { curlnegotiate proxy_negotiate_state; #endif -#ifdef USE_UNIX_SOCKETS - char *unix_domain_socket; -#endif - /* When this connection is created, store the conditions for the local end bind. This is stored before the actual bind and before any connection is made and will serve the purpose of being used for comparison reasons so that subsequent bound-requested connections are not accidentally reusing wrong connections. */ char *localdev; + struct ConnectBits bits; /* various state-flags for this connection */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) int socks5_gssapi_enctype; #endif + + /* 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)->attached_xfers) uint32_t attached_xfers; /* # of attached easy handles */ #ifdef USE_IPV6 uint32_t scope_id; /* Scope id for IPv6 */ #endif - /* The field below gets set in connect.c:connecthost() */ - uint16_t remote_port; /* the remote port, not the proxy port! */ - uint16_t conn_to_port; /* the remote port to connect to. valid only if - bits.conn_to_port is set */ uint16_t localportrange; uint16_t localport; - uint16_t secondary_port; /* secondary socket remote port to connect to - (ftp) */ + int8_t recv_idx; /* on which socket index to receive, default 0 */ + int8_t send_idx; /* on which socket index to send, default 0 */ uint8_t transport_wanted; /* one of the TRNSPRT_* defines. Not necessarily the transport the connection ends using due to Alt-Svc and happy eyeballing. Use Curl_conn_get_transport() for actual value once the @@ -481,18 +373,18 @@ struct connectdata { * 0 at start, then one of 09, 10, 11, etc. */ uint8_t httpversion_seen; uint8_t gssapi_delegation; /* inherited from set.gssapi_delegation */ + }; #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) + ((c)->socks_proxy.peer ? (c)->socks_proxy.peer->user_hostname : \ + (c)->http_proxy.peer ? (c)->http_proxy.peer->user_hostname : \ + (c)->via_peer ? (c)->via_peer->user_hostname : \ + (c)->origin->user_hostname) #else #define CURL_CONN_HOST_DISPNAME(c) \ - (c)->bits.conn_to_host ? (c)->conn_to_host.dispname : \ - (c)->host.dispname + ((c)->via_peer ? (c)->via_peer->user_hostname : (c)->origin->user_hostname) #endif /* The end of connectdata. */ @@ -502,22 +394,6 @@ struct connectdata { * All variables in this struct must be initialized/reset in Curl_initinfo(). */ struct PureInfo { - int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ - int httpproxycode; /* response code from proxy when received separate */ - 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 request_size; /* the amount of bytes sent in the request(s) */ - curl_off_t numconnects; /* how many new connections libcurl created */ - uint32_t proxyauthavail; /* what proxy auth types were announced */ - uint32_t httpauthavail; /* what host auth types were announced */ - uint32_t proxyauthpicked; /* selected proxy auth type */ - uint32_t httpauthpicked; /* selected host auth type */ - char *contenttype; /* the content type of the object */ - char *wouldredirect; /* URL this would have been redirected to if asked to */ - curl_off_t retry_after; /* info from Retry-After: header */ - uint32_t 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, @@ -525,14 +401,26 @@ struct PureInfo { 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 */ - const char *conn_scheme; - uint32_t conn_protocol; struct curl_certinfo certs; /* info about the certs. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ - CURLproxycode pxcode; + time_t filetime; /* If requested, this is might get set. Set to -1 if the + time was unretrievable. */ + curl_off_t request_size; /* the amount of bytes sent in the request(s) */ + curl_off_t numconnects; /* how many new connections libcurl created */ + char *contenttype; /* the content type of the object */ + char *wouldredirect; /* URL this would have been redirected to if asked to */ + curl_off_t retry_after; /* info from Retry-After: header */ + const char *conn_scheme; + int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ + int httpproxycode; /* response code from proxy when received separate */ + int httpversion; /* the http version number X.Y = X*10+Y */ + uint32_t conn_protocol; + uint32_t proxyauthavail; /* what proxy auth types were announced */ + uint32_t httpauthavail; /* what host auth types were announced */ + uint32_t proxyauthpicked; /* selected proxy auth type */ + uint32_t httpauthpicked; /* selected host auth type */ + uint32_t header_size; /* size of read header(s) in bytes */ + uint8_t pxcode; /* holds a CURLproxycode */ 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 */ @@ -547,61 +435,45 @@ struct pgrs_dir { struct Progress { struct curltime now; /* current time of processing */ - time_t lastshow; /* time() of the last displayed progress meter or NULL to - force redraw at next call */ + struct curltime start; /* when transfer was initialized, set once */ + struct pgrs_dir ul; struct pgrs_dir dl; curl_off_t deliver; /* amount of data delivered to application */ - curl_off_t current_speed; /* uses the currently fastest transfer */ curl_off_t earlydata_sent; - timediff_t timespent; - - 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; + struct { + timediff_t startop_us; /* since start when operations started */ + timediff_t startsingle_us; /* since start when last request started */ + timediff_t startqueue_us; /* since start when last entered queueing */ + timediff_t startredirect_us; /* since start when last redirected */ + timediff_t lastshow_us; /* since start when last progress shown */ + } delta; + struct { + timediff_t spent_us; /* all time spent since start */ + timediff_t queued_us; /* time spent since startsingle's for queueing */ + timediff_t nslookup_us; /* same for name resolves */ + timediff_t connect_us; /* same for connects */ + timediff_t appconnect_us; /* same for application connects, e.g. TLS */ + timediff_t pretransfer_us; /* same until requests were sent */ + timediff_t starttransfer_us; /* same until responses started */ + timediff_t posttransfer_us; /* same until responses ended */ + } total; #define CURL_SPEED_RECORDS (5 + 1) /* 6 entries for 5 seconds */ curl_off_t speed_amount[CURL_SPEED_RECORDS]; - struct curltime speed_time[CURL_SPEED_RECORDS]; + timediff_t speed_time[CURL_SPEED_RECORDS]; uint32_t 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); + BIT(startransfer_added); }; -typedef enum { - RTSPREQ_NONE, /* first in list */ - RTSPREQ_OPTIONS, - RTSPREQ_DESCRIBE, - RTSPREQ_ANNOUNCE, - RTSPREQ_SETUP, - RTSPREQ_PLAY, - RTSPREQ_PAUSE, - RTSPREQ_TEARDOWN, - RTSPREQ_GET_PARAMETER, - RTSPREQ_SET_PARAMETER, - RTSPREQ_RECORD, - RTSPREQ_RECEIVE, - RTSPREQ_LAST /* last in list */ -} Curl_RtspReq; - struct auth { uint32_t want; /* Bitmask set to the authentication methods wanted by app (with CURLOPT_HTTPAUTH or CURLOPT_PROXYAUTH). */ @@ -612,8 +484,6 @@ struct auth { actual request */ BIT(multipass); /* TRUE if this is not yet authenticated but within the auth multipass negotiation */ - BIT(iestyle); /* TRUE if digest should be done IE-style or FALSE if it - should be RFC compliant */ }; #ifdef USE_NGHTTP2 @@ -623,30 +493,11 @@ struct Curl_data_prio_node { }; #endif -/** - * Priority information for an easy handle in relation to others - * on the same connection. - */ -struct Curl_data_priority { -#ifdef USE_NGHTTP2 - /* tree like dependencies only implemented in nghttp2 */ - struct Curl_easy *parent; - struct Curl_data_prio_node *children; -#endif - int weight; -#ifdef USE_NGHTTP2 - BIT(exclusive); -#endif -}; - /* Timers */ typedef enum { EXPIRE_100_TIMEOUT, EXPIRE_ASYNC_NAME, EXPIRE_CONNECTTIMEOUT, - EXPIRE_DNS_PER_NAME, /* family1 */ - EXPIRE_DNS_PER_NAME2, /* family2 */ - EXPIRE_HAPPY_EYEBALLS_DNS, /* See asyn-ares.c */ EXPIRE_HAPPY_EYEBALLS, EXPIRE_MULTI_PENDING, EXPIRE_SPEEDCHECK, @@ -659,54 +510,38 @@ typedef enum { EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; -/* - * One instance for each timeout an easy handle can set. - */ -struct time_node { - struct Curl_llist_node list; - struct curltime time; - expire_id eid; +struct expire_timers { + struct Curl_tree splaynode; /* for the splay stuff */ + /* microsecond offset from Curl_timeouts base timestamp */ + timediff_t offset_us[EXPIRE_LAST]; + uint8_t next[EXPIRE_LAST]; + uint8_t first; }; /* individual pieces of the URL */ struct urlpieces { - char *scheme; - char *hostname; - char *port; - char *user; - char *password; char *options; char *path; 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 { - /* buffers to store authentication data in, as parsed from input options */ - struct curltime keeps_speed; /* for the progress meter really */ + curl_off_t lastconnect_id; /* The last assigned connection or -1 */ + /* Origin of the initial (e.g. not followed) request of a transfer. + Credentials from CURLOPT_* are only valid for this origin. + Always set once a transfer starts searching for connections. */ + struct Curl_peer *initial_origin; + /* Current origin of the transfer, changes to origin of follow + * requests. */ + struct Curl_peer *origin; - 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 curltime keeps_speed; /* for the progress meter really */ struct dynbuf headerb; /* buffer to store headers in */ #ifndef CURL_DISABLE_HSTS struct curl_slist *hstslist; /* list of HSTS files set by curl_easy_setopt(HSTS) calls */ #endif - curl_off_t current_speed; /* the ProgressShow() function sets this, - bytes / second */ - /* 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; - int first_remote_port; - curl_prot_t first_remote_protocol; int os_errno; /* filled in with errno whenever an error occurs */ int requests; /* request counter: redirects + authentication retakes */ #ifdef HAVE_SIGNAL @@ -714,6 +549,7 @@ struct UrlState { void (*prev_signal)(int sig); #endif #ifndef CURL_DISABLE_DIGEST_AUTH + char *envproxy; /* last proxy string used for proxy-related state */ struct digestdata digest; /* state data for host Digest auth */ struct digestdata proxydigest; /* state data for proxy Digest auth */ #endif @@ -732,35 +568,16 @@ struct UrlState { 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 */ - struct Curl_llist timeoutlist; /* list of pending timeouts */ - struct time_node expires[EXPIRE_LAST]; /* nodes for each expire type */ + struct expire_timers timeouts; /* expire timeouts */ /* a place to store the most recently set (S)FTP entrypath */ char *most_recent_ftp_entrypath; char *range; /* range, if used. See README for detailed specification on this syntax. */ curl_off_t resume_from; /* continue [ftp] transfer from here */ - -#ifndef CURL_DISABLE_RTSP - /* This RTSP state information survives requests and connections */ - uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ - uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ - uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ - uint8_t rtp_channel_mask[32]; /* for the correctness checking of the - interleaved data */ -#endif - curl_off_t infilesize; /* size of file to upload, -1 means unknown. Copied from set.filesize at start of operation */ -#if defined(USE_HTTP2) || defined(USE_HTTP3) - struct Curl_data_priority priority; /* shallow copy of data->set */ -#endif - curl_read_callback fread_func; /* read callback/function */ void *in; /* CURLOPT_READDATA */ CURLU *uh; /* URL handle for the current parsed URL */ @@ -775,12 +592,8 @@ struct UrlState { 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 */ struct Curl_llist httphdrs; /* received headers */ struct curl_header headerout[2]; /* for external purposes */ - struct Curl_header_store *prevhead; /* the latest added header */ #endif #ifndef CURL_DISABLE_COOKIES struct curl_slist *cookielist; /* list of cookie files set by @@ -795,42 +608,34 @@ struct UrlState { struct store_netrc netrc; #endif - /* Dynamically allocated strings, MUST be freed before this struct is - killed. */ - struct dynamically_allocated_data { - char *uagent; - char *accept_encoding; - char *rangeline; - char *ref; - char *host; -#ifndef CURL_DISABLE_RTSP - char *rtsp_transport; -#endif + struct Curl_creds *creds; /* Credentials for the origin only */ - /* transfer credentials */ - char *user; - char *passwd; -#ifndef CURL_DISABLE_PROXY - char *proxyuser; - char *proxypasswd; -#endif - } aptr; #ifndef CURL_DISABLE_HTTP + char *rangeline; /* allocated */ + char *http_host; /* allocated */ struct http_negotiation http_neg; +#endif +#ifndef CURL_DISABLE_RTSP + /* This RTSP state information survives requests and connections */ + uint8_t rtp_channel_mask[32]; /* for the correctness checking of the + interleaved data */ + uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ + uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ + uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ +#endif +#if defined(USE_HTTP2) || defined(USE_HTTP3) + int weight; /* shallow copy of data->set */ #endif uint16_t followlocation; /* redirect counter */ uint8_t retrycount; /* number of retries on a new connection, up to CONN_MAX_RETRIES */ uint8_t httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ - 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! */ - BIT(multi_owned_by_easy); +#ifdef USE_OPENSSL + BIT(provider_loaded); +#endif /* USE_OPENSSL */ + BIT(really_alive); /* transfer is really alive in multi, passed INIT */ BIT(this_is_a_follow); /* this is a followed Location: request */ BIT(refused_stream); /* this was refused, try again */ BIT(errorbuf); /* Set to TRUE if the error buffer is already filled in. @@ -845,9 +650,9 @@ struct UrlState { 417 response */ BIT(use_range); BIT(rangestringalloc); /* the range string is malloc()'ed */ - 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(done); /* set to FALSE when Curl_init_transfer() is called and set to + TRUE when multi_done() is called, to prevent multi_done() from + being invoked twice. */ #ifndef CURL_DISABLE_COOKIES BIT(cookie_engine); #endif @@ -937,7 +742,7 @@ enum dupstring { STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSL_ENGINE, /* name of ssl engine */ + STRING_SSL_ENGINE, /* name of SSL engine */ STRING_USERNAME, /* , if used */ STRING_PASSWORD, /* , if used */ STRING_OPTIONS, /* , if used */ @@ -962,14 +767,6 @@ enum dupstring { #ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, -#endif -#ifdef USE_TLS_SRP - STRING_TLSAUTH_USERNAME, /* 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 @@ -996,6 +793,11 @@ enum dupstring { #ifndef CURL_DISABLE_AWS STRING_AWS_SIGV4, /* Parameters for V4 signature */ #endif +#ifndef CURL_DISABLE_HTTPSIG + STRING_HTTPSIG_KEY, /* hex-encoded key data */ + STRING_HTTPSIG_KEYID, /* key identifier */ + STRING_HTTPSIG_HEADERS, /* space-separated components to sign */ +#endif #ifndef CURL_DISABLE_PROXY STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ #endif @@ -1003,14 +805,6 @@ enum dupstring { STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ STRING_SSL_SIGNATURE_ALGORITHMS, /* CURLOPT_SSL_SIGNATURE_ALGORITHMS */ - /* -- end of null-terminated strings -- */ - - STRING_LASTZEROTERMINATED, - - /* -- below this are pointers to binary data that cannot be strdup'ed. --- */ - - STRING_COPYPOSTFIELDS, /* if POST, set the fields' values here */ - STRING_LAST /* not used, an end-of-list marker */ }; @@ -1038,6 +832,7 @@ struct UserDefined { uint32_t httpauth; /* kind of HTTP authentication to use (bitmask) */ uint32_t proxyauth; /* kind of proxy authentication to use (bitmask) */ void *postfields; /* if POST, set the fields' values here */ + char *str_copypostfields; /* CURLOPT_COPYPOSTFIELDS value */ 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 @@ -1116,9 +911,6 @@ struct UserDefined { curl_off_t max_filesize; /* Maximum file size to download */ #ifndef CURL_DISABLE_FTP timediff_t accepttimeout; /* in milliseconds, 0 means no timeout */ - uint8_t ftp_filemethod; /* how to get to a file: curl_ftpfile */ - uint8_t ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ - uint8_t ftp_ccc; /* FTP CCC options: curl_ftpccc */ #endif #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) struct curl_slist *quote; /* after connection is established */ @@ -1135,9 +927,9 @@ struct UserDefined { uint32_t ssh_auth_types; /* allowed SSH auth types */ uint32_t new_directory_perms; /* when creating remote dirs */ #endif - uint32_t new_file_perms; /* when creating remote files */ - char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ + struct u8_strset strings; struct curl_blob *blobs[BLOB_LAST]; + uint32_t new_file_perms; /* when creating remote files */ #ifdef USE_IPV6 uint32_t scope_id; /* Scope id for IPv6 */ #endif @@ -1145,8 +937,6 @@ struct UserDefined { curl_prot_t redir_protocols; #ifndef CURL_DISABLE_RTSP void *rtp_out; /* write RTP to this if non-NULL */ - /* Common RTSP header options */ - Curl_RtspReq rtspreq; /* RTSP request type */ #endif #ifndef CURL_DISABLE_FTP curl_chunk_bgn_callback chunk_bgn; /* called before part of transfer @@ -1164,13 +954,6 @@ struct UserDefined { timediff_t happy_eyeballs_timeout; /* ms, 0 is a valid value */ timediff_t server_response_timeout; /* ms, 0 means no timeout */ timediff_t shutdowntimeout; /* ms, 0 means default timeout */ - int tcp_keepidle; /* seconds in idle before sending keepalive probe */ - int tcp_keepintvl; /* seconds between TCP keepalive probes */ - int tcp_keepcnt; /* maximum number of keepalive probes */ - -#if defined(USE_HTTP2) || defined(USE_HTTP3) - struct Curl_data_priority priority; -#endif curl_resolver_start_callback resolver_start; /* optional callback called before resolver start */ void *resolver_start_client; /* pointer to pass to resolver start callback */ @@ -1184,9 +967,15 @@ struct UserDefined { #ifndef CURL_DISABLE_SMTP struct curl_slist *mail_rcpt; /* linked list of mail recipients */ #endif + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ + int tcp_keepintvl; /* seconds between TCP keepalive probes */ + int tcp_keepcnt; /* maximum number of keepalive probes */ + uint32_t maxconnects; /* Max idle connections in the connection cache */ -#ifdef USE_ECH - int tls_ech; /* TLS ECH configuration */ +#if defined(USE_HTTP2) || defined(USE_HTTP3) + /* Priority information for an easy handle in relation to others on the same + connection. */ + int weight; #endif short maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 for infinity */ @@ -1202,6 +991,12 @@ struct UserDefined { #ifndef CURL_DISABLE_TFTP uint16_t tftp_blksize; /* in bytes, 0 means use default */ #endif +#ifndef CURL_DISABLE_RTSP + uint8_t rtspreq; /* RTSP request type */ +#endif +#ifdef USE_ECH + uint8_t tls_ech; /* TLS ECH configuration */ +#endif #ifndef CURL_DISABLE_NETRC uint8_t use_netrc; /* enum CURL_NETRC_OPTION values */ #endif @@ -1209,9 +1004,15 @@ struct UserDefined { /* 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 - */ + */ uint8_t ftp_create_missing_dirs; #endif +#ifndef CURL_DISABLE_FTP + uint8_t ftp_filemethod; /* how to get to a file: curl_ftpfile */ + uint8_t ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ + uint8_t ftp_ccc; /* FTP CCC options: curl_ftpccc */ +#endif + uint8_t httpsig_algorithm; /* CURLHTTPSIG_* algorithm for RFC 9421 */ uint8_t use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or IMAP or POP3 or others! (type: curl_usessl)*/ uint8_t timecondition; /* kind of time comparison: curl_TimeCond */ @@ -1221,9 +1022,8 @@ struct UserDefined { uint8_t ipver; /* the CURL_IPRESOLVE_* defines in the public header file 0 - whatever, 1 - v2, 2 - v6 */ uint8_t upload_flags; /* flags set by CURLOPT_UPLOAD_FLAGS */ -#ifdef HAVE_GSSAPI - /* GSS-API credential delegation, see the documentation of - CURLOPT_GSSAPI_DELEGATION */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* GSS-API/SSPI credential delegation, see CURLOPT_GSSAPI_DELEGATION */ uint8_t gssapi_delegation; #endif uint8_t http_follow_mode; /* follow HTTP redirects */ @@ -1340,10 +1140,10 @@ struct UserDefined { #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, +/* callback that gets called when the transfer `data` is done and + * `data->master_mid` is set to an existing easy handle. */ +typedef void multi_sub_xfer_done_cb(struct Curl_easy *data, + struct Curl_easy *master, CURLcode result); /* @@ -1357,37 +1157,38 @@ typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, */ 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 */ + /* First a simple identifier to more easily detect if a user mixes up this + easy handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */ uint32_t magic; - /* 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. */ uint32_t mid; - uint32_t master_mid; /* if set, this transfer belongs to a master */ - multi_sub_xfer_done_cb *sub_xfer_done; - - struct connectdata *conn; - CURLMstate mstate; /* the handle's state */ CURLcode result; /* previous result */ - struct Curl_message msg; /* A single posted message. */ - + struct connectdata *conn; struct Curl_multi *multi; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the multi interface */ + struct Curl_eapi_stack callstack; /* easy api calls ongoing */ + + struct Curl_share *share; /* Share, handles global variable mutexing */ + struct Curl_multi *multi_easy; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the easy interface */ - struct Curl_share *share; /* Share, handles global variable mutexing */ + struct Curl_message msg; /* A single posted message. */ + + /* once an easy handle is tied to a connection pool a non-negative number to + distinguish this transfer from other using the same pool. For easier + tracking in log output. This may wrap around after CURL_OFF_T_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; + uint32_t master_mid; /* if set, this transfer belongs to a master */ + multi_sub_xfer_done_cb *sub_xfer_done; /* `meta_hash` is a general key-value store for implementations * with the lifetime of the easy handle. @@ -1423,6 +1224,17 @@ struct Curl_easy { valid after a client has asked for it */ }; +#define CURL_EASY_STR(d, id) \ + Curl_u8_strset_get(&(d)->set.strings, (uint8_t)(id)) +#define CURL_EASY_STR_SET(d, id, s, slen) \ + Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), (s), (slen)) +#define CURL_EASY_STR_SETN(d, id, s) \ + Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), (s)) +#define CURL_EASY_STR_CLEAR(d, id) \ + Curl_u8_strset_unset(&(d)->set.strings, (uint8_t)(id)) +#define CURL_EASY_STR_CLEAR0(d, id) \ + Curl_u8_strset_unset0(&(d)->set.strings, (uint8_t)(id)) + #define LIBCURL_NAME "libcurl" #endif /* HEADER_CURL_URLDATA_H */ diff --git a/lib/vauth/cleartext.c b/lib/vauth/cleartext.c index 7976adec9c8f..ca0c9967ff9c 100644 --- a/lib/vauth/cleartext.c +++ b/lib/vauth/cleartext.c @@ -40,24 +40,21 @@ * * Parameters: * - * authzid [in] - The authorization identity. - * authcid [in] - The authentication identity. + * creds [in] - The credentials. * passwd [in] - The password. * out [out] - The result storage. * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_plain_message(const char *authzid, - const char *authcid, - const char *passwd, +CURLcode Curl_auth_create_plain_message(struct Curl_creds *creds, struct bufref *out) { size_t len; char *auth; - size_t zlen = (authzid == NULL ? 0 : strlen(authzid)); - size_t clen = strlen(authcid); - size_t plen = strlen(passwd); + size_t zlen = strlen(Curl_creds_sasl_authzid(creds)); + size_t clen = strlen(Curl_creds_user(creds)); + size_t plen = strlen(Curl_creds_passwd(creds)); if((zlen > CURL_MAX_INPUT_LENGTH) || (clen > CURL_MAX_INPUT_LENGTH) || (plen > CURL_MAX_INPUT_LENGTH)) @@ -65,8 +62,10 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, len = zlen + clen + plen + 2; - auth = curl_maprintf("%s%c%s%c%s", authzid ? authzid : "", '\0', - authcid, '\0', passwd); + auth = curl_maprintf("%s%c%s%c%s", + Curl_creds_sasl_authzid(creds), '\0', + Curl_creds_user(creds), '\0', + Curl_creds_passwd(creds)); if(!auth) return CURLE_OUT_OF_MEMORY; Curl_bufref_set(out, auth, len, curl_free); diff --git a/lib/vauth/cram.c b/lib/vauth/cram.c index d3f2d137b779..023c31b58b67 100644 --- a/lib/vauth/cram.c +++ b/lib/vauth/cram.c @@ -47,18 +47,19 @@ * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct bufref *out) { struct HMAC_context *ctxt; unsigned char digest[MD5_DIGEST_LEN]; char *response; + const char *user = Curl_creds_user(creds); + const char *passwd = Curl_creds_passwd(creds); /* Compute the digest using the password as the key */ ctxt = Curl_HMAC_init(&Curl_HMAC_MD5, - (const unsigned char *)passwdp, - curlx_uztoui(strlen(passwdp))); + (const unsigned char *)passwd, + curlx_uztoui(strlen(passwd))); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -73,7 +74,7 @@ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, /* Generate the response */ response = curl_maprintf( "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - userp, digest[0], digest[1], digest[2], digest[3], digest[4], + user, digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]); if(!response) diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 5ecfd60aad6f..748c0d834726 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -36,6 +36,7 @@ #include "curl_sha512_256.h" #include "curlx/strparse.h" #include "rand.h" +#include "escape.h" #ifndef USE_WINDOWS_SSPI #define SESSION_ALGO 1 /* for algos with this bit set */ @@ -163,6 +164,11 @@ static char *auth_digest_string_quoted(const char *s) if(!result) result = curlx_dyn_addn(&out, s, 1); } + else if((*s < ' ') || (*s > 0x7e)) { + unsigned char buf[3] = { '%' }; + Curl_hexbyte(&buf[1], (unsigned char)*s); + result = curlx_dyn_addn(&out, buf, 3); + } else result = curlx_dyn_addn(&out, s, 1); if(result) @@ -173,7 +179,7 @@ static char *auth_digest_string_quoted(const char *s) } /* Retrieves the value for a corresponding key from the challenge string - * returns TRUE if the key could be found, FALSE if it does not exists + * returns TRUE if the key could be found, FALSE if it does not exist */ static bool auth_digest_get_key_value(const char *chlg, const char *key, char *buf, size_t buflen) @@ -231,7 +237,7 @@ static bool auth_digest_get_key_value(const char *chlg, const char *key, static void auth_digest_get_qop_values(const char *options, int *value) { struct Curl_str out; - /* Initialise the output */ + /* Initialize the output */ *value = 0; while(!curlx_str_until(&options, &out, 32, ',')) { @@ -332,13 +338,16 @@ bool Curl_auth_is_digest_supported(void) */ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, const struct bufref *chlg, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, struct bufref *out) { + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; size_t i; struct MD5_context *ctxt; + const char *userp = Curl_creds_user(creds); + const char *passwdp = Curl_creds_passwd(creds); char *response = NULL; unsigned char digest[MD5_DIGEST_LEN]; char HA1_hex[(2 * MD5_DIGEST_LEN) + 1]; @@ -370,7 +379,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return result; /* We only support md5 sessions */ - if(strcmp(algorithm, "md5-sess") != 0) + if(strcmp(algorithm, "md5-sess")) return CURLE_BAD_CONTENT_ENCODING; /* Get the qop-values from the qop-options */ @@ -418,7 +427,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, curl_msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); /* Generate our SPN */ - spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); + spn = Curl_auth_build_spn(service, data->state.origin->hostname, NULL); if(!spn) return CURLE_OUT_OF_MEMORY; @@ -517,7 +526,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(digest->nonce) before = TRUE; - /* Clean up any former leftovers and initialise to defaults */ + /* Clean up any former leftovers and initialize to defaults */ Curl_auth_digest_cleanup(digest); for(;;) { @@ -666,8 +675,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Parameters: * * data [in] - The session handle. - * userp [in] - The username. - * passwdp [in] - The user's password. + * creds [in] - The credentials * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. * digest [in/out] - The digest data struct being used and modified. @@ -679,8 +687,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, */ static CURLcode auth_create_digest_http_message( struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, const unsigned char *request, const unsigned char *uripath, struct digestdata *digest, @@ -689,6 +696,8 @@ static CURLcode auth_create_digest_http_message( CURLcode (*hash)(unsigned char *, const unsigned char *, const size_t)) { CURLcode result; + const char *userp = Curl_creds_user(creds); + const char *passwdp = Curl_creds_passwd(creds); unsigned char hashbuf[32]; /* 32 bytes/256 bits */ unsigned char request_digest[65]; unsigned char ha1[65]; /* 64 digits and 1 zero byte */ @@ -742,16 +751,15 @@ static CURLcode auth_create_digest_http_message( convert_to_ascii(hashbuf, (unsigned char *)userh); } - /* - If the algorithm is "MD5" or unspecified (which then defaults to MD5): + /* If the algorithm is "MD5" or unspecified (which then defaults to MD5): - A1 = unq(username-value) ":" unq(realm-value) ":" passwd + A1 = unq(username-value) ":" unq(realm-value) ":" passwd - If the algorithm is "MD5-sess" then: + If the algorithm is "MD5-sess" then: - A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" - unq(nonce-value) ":" unq(cnonce-value) - */ + A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" + unq(nonce-value) ":" unq(cnonce-value) + */ hashthis = curl_maprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", passwdp); @@ -781,18 +789,17 @@ static CURLcode auth_create_digest_http_message( convert_to_ascii(hashbuf, ha1); } - /* - If the "qop" directive's value is "auth" or is unspecified, then A2 is: + /* If the "qop" directive's value is "auth" or is unspecified, then A2 is: - A2 = Method ":" digest-uri-value + A2 = Method ":" digest-uri-value - If the "qop" value is "auth-int", then A2 is: + If the "qop" value is "auth-int", then A2 is: - A2 = Method ":" digest-uri-value ":" H(entity-body) + A2 = Method ":" digest-uri-value ":" H(entity-body) - (The "Method" value is the HTTP request method as specified in section - 5.1.1 of RFC 2616) - */ + (The "Method" value is the HTTP request method as specified in section + 5.1.1 of RFC 2616) + */ uri_quoted = auth_digest_string_quoted((const char *)uripath); if(!uri_quoted) { @@ -835,7 +842,8 @@ static CURLcode auth_create_digest_http_message( if(digest->qop) hashthis = curl_maprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, - digest->nc, digest->cnonce, digest->qop, ha2); + (unsigned int)digest->nc, digest->cnonce, + digest->qop, ha2); else hashthis = curl_maprintf("%s:%s:%s", ha1, digest->nonce, ha2); @@ -862,7 +870,7 @@ static CURLcode auth_create_digest_http_message( 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); if(!userp_quoted) { result = CURLE_OUT_OF_MEMORY; @@ -900,7 +908,7 @@ static CURLcode auth_create_digest_http_message( nonce_quoted, uri_quoted, digest->cnonce, - digest->nc, + (unsigned int)digest->nc, digest->qop, request_digest); @@ -986,29 +994,28 @@ static CURLcode auth_create_digest_http_message( * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, const unsigned char *request, const unsigned char *uripath, struct digestdata *digest, char **outptr, size_t *outlen) { if(digest->algo <= ALGO_MD5SESS) - return auth_create_digest_http_message(data, userp, passwdp, + return auth_create_digest_http_message(data, creds, request, uripath, digest, outptr, outlen, auth_digest_md5_to_ascii, Curl_md5it); if(digest->algo <= ALGO_SHA256SESS) - return auth_create_digest_http_message(data, userp, passwdp, + return auth_create_digest_http_message(data, creds, 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, + return auth_create_digest_http_message(data, creds, request, uripath, digest, outptr, outlen, auth_digest_sha256_to_ascii, @@ -1031,6 +1038,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, */ void Curl_auth_digest_cleanup(struct digestdata *digest) { + Curl_peer_unlink(&digest->origin); + Curl_creds_unlink(&digest->creds); curlx_safefree(digest->nonce); curlx_safefree(digest->cnonce); curlx_safefree(digest->realm); diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c index f0b6780fca6f..e3648e37b857 100644 --- a/lib/vauth/digest_sspi.c +++ b/lib/vauth/digest_sspi.c @@ -28,6 +28,7 @@ #if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_DIGEST_AUTH) +#include "creds.h" #include "vauth/vauth.h" #include "vauth/digest.h" #include "curlx/multibyte.h" @@ -53,7 +54,7 @@ bool Curl_auth_is_digest_supported(void) /* Query the security package for Digest */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -83,9 +84,8 @@ bool Curl_auth_is_digest_supported(void) */ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, const struct bufref *chlg, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, struct bufref *out) { CURLcode result = CURLE_OK; @@ -95,14 +95,16 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, CredHandle credentials; CtxtHandle context; PSecPkgInfo SecurityPackage; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; /* Ensure we have a valid challenge message */ if(!Curl_bufref_len(chlg)) { @@ -113,7 +115,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -131,15 +133,16 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Generate our SPN */ - spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); + spn = Curl_auth_build_spn(service, data->state.origin->hostname, NULL); if(!spn) { curlx_free(output_token); return CURLE_OUT_OF_MEMORY; } - if(userp && *userp) { + if(Curl_creds_has_user(creds)) { /* Populate our identity structure */ - result = Curl_create_sspi_identity(userp, passwdp, &identity); + result = Curl_create_sspi_identity(creds->user, creds->passwd, + &identity); if(result) { curlx_free(spn); curlx_free(output_token); @@ -155,7 +158,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, NULL); @@ -240,7 +243,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, * Returns CURLE_OK on success. */ CURLcode Curl_override_sspi_http_realm(const char *chlg, - SEC_WINNT_AUTH_IDENTITY *identity) + SEC_WINNT_AUTH_IDENTITY_EX *identity) { xcharp_u domain, dup_domain; @@ -381,8 +384,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, const unsigned char *request, const unsigned char *uripath, struct digestdata *digest, @@ -400,7 +402,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -421,16 +423,12 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* If the user/passwd that was used to make the identity for http_context has changed then delete that context. */ - if((userp && !digest->user) || (!userp && digest->user) || - (passwdp && !digest->passwd) || (!passwdp && digest->passwd) || - (userp && digest->user && Curl_timestrcmp(userp, digest->user)) || - (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) { + if(!Curl_creds_same(creds, digest->creds)) { if(digest->http_context) { Curl_pSecFn->DeleteSecurityContext(digest->http_context); curlx_safefree(digest->http_context); } - curlx_safefree(digest->user); - curlx_safefree(digest->passwd); + Curl_creds_unlink(&digest->creds); } if(digest->http_context) { @@ -458,7 +456,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 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", status); + infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", + (unsigned long)status); Curl_pSecFn->DeleteSecurityContext(digest->http_context); curlx_safefree(digest->http_context); } @@ -466,20 +465,19 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(!digest->http_context) { CredHandle credentials; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; SecBuffer resp_buf; SecBufferDesc resp_desc; unsigned long attrs; TCHAR *spn; - /* free the copy of user/passwd used to make the previous identity */ - curlx_safefree(digest->user); - curlx_safefree(digest->passwd); + /* free the credentials used to make the previous identity */ + Curl_creds_unlink(&digest->creds); - if(userp && *userp) { + if(Curl_creds_has_user(creds)) { /* Populate our identity structure */ - if(Curl_create_sspi_identity(userp, passwdp, &identity)) { + if(Curl_create_sspi_identity(creds->user, creds->passwd, &identity)) { curlx_free(output_token); return CURLE_OUT_OF_MEMORY; } @@ -499,30 +497,12 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Use the current Windows user */ p_identity = NULL; - if(userp) { - digest->user = curlx_strdup(userp); - - if(!digest->user) { - curlx_free(output_token); - Curl_sspi_free_identity(p_identity); - return CURLE_OUT_OF_MEMORY; - } - } - - if(passwdp) { - digest->passwd = curlx_strdup(passwdp); - - if(!digest->passwd) { - curlx_free(output_token); - Curl_sspi_free_identity(p_identity); - curlx_safefree(digest->user); - return CURLE_OUT_OF_MEMORY; - } - } + if(creds) + Curl_creds_link(&digest->creds, creds); /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + CURL_UNCONST(TEXT(SP_NAME_DIGEST)), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, NULL); @@ -577,11 +557,12 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Generate our response message */ status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, - spn, - ISC_REQ_USE_HTTP_STYLE, 0, 0, - &chlg_desc, 0, - digest->http_context, - &resp_desc, &attrs, NULL); + spn, + ISC_REQ_USE_HTTP_STYLE, + 0, 0, + &chlg_desc, 0, + digest->http_context, + &resp_desc, &attrs, NULL); curlx_free(spn); if(status == SEC_I_COMPLETE_NEEDED || @@ -649,8 +630,8 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) } /* Free the copy of user/passwd used to make the identity for http_context */ - curlx_safefree(digest->user); - curlx_safefree(digest->passwd); + Curl_creds_unlink(&digest->creds); + Curl_peer_unlink(&digest->origin); } #endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */ diff --git a/lib/vauth/gsasl.c b/lib/vauth/gsasl.c index 958f4ffab746..37adba593dd5 100644 --- a/lib/vauth/gsasl.c +++ b/lib/vauth/gsasl.c @@ -32,6 +32,10 @@ #include +#if GSASL_VERSION_NUMBER < 0x010600 +#error "libgsasl 1.6.0 or greater required" +#endif + bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, const char *mech, struct gsasldata *gsasl) @@ -47,6 +51,7 @@ bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, res = gsasl_client_start(gsasl->ctx, mech, &gsasl->client); if(res != GSASL_OK) { gsasl_done(gsasl->ctx); + gsasl->ctx = NULL; return FALSE; } @@ -54,15 +59,14 @@ bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, } CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct gsasldata *gsasl) { #if GSASL_VERSION_NUMBER >= 0x010b00 int res; res = #endif - gsasl_property_set(gsasl->client, GSASL_AUTHID, userp); + gsasl_property_set(gsasl->client, GSASL_AUTHID, creds->user); #if GSASL_VERSION_NUMBER >= 0x010b00 if(res != GSASL_OK) { failf(data, "setting AUTHID failed: %s", gsasl_strerror(res)); @@ -73,7 +77,7 @@ CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, #if GSASL_VERSION_NUMBER >= 0x010b00 res = #endif - gsasl_property_set(gsasl->client, GSASL_PASSWORD, passwdp); + gsasl_property_set(gsasl->client, GSASL_PASSWORD, creds->passwd); #if GSASL_VERSION_NUMBER >= 0x010b00 if(res != GSASL_OK) { failf(data, "setting PASSWORD failed: %s", gsasl_strerror(res)); diff --git a/lib/vauth/krb5_gssapi.c b/lib/vauth/krb5_gssapi.c index 64c735be582a..6d9a12517743 100644 --- a/lib/vauth/krb5_gssapi.c +++ b/lib/vauth/krb5_gssapi.c @@ -33,7 +33,7 @@ #include "curl_gssapi.h" #include "curl_trc.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -74,9 +74,8 @@ bool Curl_auth_is_gssapi_supported(void) * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const bool mutual_auth, const struct bufref *chlg, @@ -89,9 +88,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, OM_uint32 unused_status; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; - - (void)userp; - (void)passwdp; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; if(!krb5->spn) { gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; @@ -138,7 +136,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, &input_token, &output_token, mutual_auth, - NULL); + NULL, + GSS_C_NO_CREDENTIAL); if(GSS_ERROR(major_status)) { if(output_token.value) @@ -320,7 +319,7 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) } } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/lib/vauth/krb5_sspi.c b/lib/vauth/krb5_sspi.c index e7491be022f8..24d2421be652 100644 --- a/lib/vauth/krb5_sspi.c +++ b/lib/vauth/krb5_sspi.c @@ -46,7 +46,7 @@ bool Curl_auth_is_gssapi_supported(void) /* Query the security package for Kerberos */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -79,9 +79,8 @@ bool Curl_auth_is_gssapi_supported(void) * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const bool mutual_auth, const struct bufref *chlg, @@ -97,6 +96,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; if(!krb5->spn) { /* Generate our SPN */ @@ -108,7 +109,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -128,9 +129,10 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->credentials) { /* Do we have credentials to use or are we using single sign-on? */ - if(userp && *userp) { + if(Curl_creds_has_user(creds)) { /* Populate our identity structure */ - result = Curl_create_sspi_identity(userp, passwdp, &krb5->identity); + result = Curl_create_sspi_identity( + creds->user, creds->passwd, &krb5->identity); if(result) return result; @@ -148,12 +150,14 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), SECPKG_CRED_OUTBOUND, NULL, krb5->p_identity, NULL, NULL, krb5->credentials, NULL); - if(status != SEC_E_OK) + if(status != SEC_E_OK) { + curlx_safefree(krb5->credentials); return CURLE_LOGIN_DENIED; + } /* Allocate our new context handle */ krb5->context = curlx_calloc(1, sizeof(CtxtHandle)); @@ -287,10 +291,13 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, input_buf[1].pvBuffer = NULL; input_buf[1].cbBuffer = 0; - /* Decrypt the inbound challenge and obtain the qop */ + /* Decrypt the inbound challenge and obtain the qop. The encrypted message + is decrypted in place, overwriting the original contents of its buffer. + The SECBUFFER_DATA receives a pointer to the message in + SECBUFFER_STREAM. */ status = Curl_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); if(status != SEC_E_OK) { - infof(data, "GSSAPI handshake failure (empty security message)"); + infof(data, "GSSAPI handshake failure (decryption failed)"); return CURLE_BAD_CONTENT_ENCODING; } @@ -306,9 +313,6 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, max_size = ((unsigned long)indata[1] << 16) | ((unsigned long)indata[2] << 8) | indata[3]; - /* Free the challenge as it is not required anymore */ - Curl_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); - /* Process the security layer */ if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { infof(data, "GSSAPI handshake failure (invalid security layer)"); @@ -428,15 +432,13 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) /* Free our security context */ if(krb5->context) { Curl_pSecFn->DeleteSecurityContext(krb5->context); - curlx_free(krb5->context); - krb5->context = NULL; + curlx_safefree(krb5->context); } /* Free our credentials handle */ if(krb5->credentials) { Curl_pSecFn->FreeCredentialsHandle(krb5->credentials); - curlx_free(krb5->credentials); - krb5->credentials = NULL; + curlx_safefree(krb5->credentials); } /* Free our identity */ diff --git a/lib/vauth/ntlm.c b/lib/vauth/ntlm.c index 1e485ed34d6f..3f1ffb50ce7a 100644 --- a/lib/vauth/ntlm.c +++ b/lib/vauth/ntlm.c @@ -301,7 +301,7 @@ static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, 2. A 'short' containing the allocated space for the buffer in bytes. 3. A 'long' containing the offset to the start of the buffer in bytes, from the beginning of the NTLM message. -*/ + */ /* * Curl_auth_is_ntlm_supported() @@ -341,7 +341,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, /* NTLM type-2 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x02000000) 12 Target Name security buffer @@ -361,8 +361,8 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, ntlm->flags = 0; if((type2len < 32) || - (memcmp(type2, NTLMSSP_SIGNATURE, 8) != 0) || - (memcmp(type2 + 8, type2_marker, sizeof(type2_marker)) != 0)) { + memcmp(type2, NTLMSSP_SIGNATURE, 8) || + memcmp(type2 + 8, type2_marker, sizeof(type2_marker))) { /* This was not a good enough type-2 message */ infof(data, "NTLM handshake failure (bad type-2 message)"); return CURLE_BAD_CONTENT_ENCODING; @@ -421,9 +421,8 @@ static void unicodecpy(unsigned char *dest, const char *src, size_t length) * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, struct ntlmdata *ntlm, struct bufref *out) @@ -431,7 +430,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* NTLM type-1 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x01000000) 12 Flags long @@ -442,6 +441,8 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, (*) -> Optional */ + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; size_t size; char *ntlmbuf; @@ -453,12 +454,11 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, size_t domoff = hostoff + hostlen; /* This is 0: remember that host and domain are empty */ (void)data; - (void)userp; - (void)passwdp; + (void)creds; (void)service; (void)host; - /* Clean up any former leftovers and initialise to defaults */ + /* Clean up any former leftovers and initialize to defaults */ Curl_auth_cleanup_ntlm(ntlm); ntlmbuf = curl_maprintf(NTLMSSP_SIGNATURE "%c" @@ -542,21 +542,20 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct ntlmdata *ntlm, struct bufref *out) { /* NTLM type-3 message structure: Index Description Content - 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + 0 NTLMSSP Signature null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x03000000) 12 LM/LMv2 Response security buffer 20 NTLM/NTLMv2 Response security buffer 28 Target Name security buffer - 36 username security buffer + 36 Username security buffer 44 Workstation Name security buffer (52) Session Key security buffer (*) (60) Flags long (*) @@ -579,6 +578,8 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* 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 *userp = Curl_creds_user(creds); + const char *passwdp = Curl_creds_passwd(creds); const char *user; const char *domain = ""; size_t hostoff = 0; diff --git a/lib/vauth/ntlm_sspi.c b/lib/vauth/ntlm_sspi.c index 4c41eb21f4e6..85c7256025ef 100644 --- a/lib/vauth/ntlm_sspi.c +++ b/lib/vauth/ntlm_sspi.c @@ -46,7 +46,7 @@ bool Curl_auth_is_ntlm_supported(void) /* Query the security package for NTLM */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -76,9 +76,8 @@ bool Curl_auth_is_ntlm_supported(void) * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, struct ntlmdata *ntlm, struct bufref *out) @@ -88,13 +87,15 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, SecBufferDesc type_1_desc; SECURITY_STATUS status; unsigned long attrs; + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; - /* Clean up any former leftovers and initialise to defaults */ + /* Clean up any former leftovers and initialize to defaults */ Curl_auth_cleanup_ntlm(ntlm); /* Query the security package for NTLM */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -111,11 +112,12 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, if(!ntlm->output_token) return CURLE_OUT_OF_MEMORY; - if(userp && *userp) { + if(Curl_creds_has_user(creds)) { CURLcode result; /* Populate our identity structure */ - result = Curl_create_sspi_identity(userp, passwdp, &ntlm->identity); + result = Curl_create_sspi_identity( + creds->user, creds->passwd, &ntlm->identity); if(result) return result; @@ -133,12 +135,14 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + CURL_UNCONST(TEXT(SP_NAME_NTLM)), SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, NULL, NULL, ntlm->credentials, NULL); - if(status != SEC_E_OK) + if(status != SEC_E_OK) { + curlx_safefree(ntlm->credentials); return CURLE_LOGIN_DENIED; + } /* Allocate our new context handle */ ntlm->context = curlx_calloc(1, sizeof(CtxtHandle)); @@ -227,8 +231,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct ntlmdata *ntlm, struct bufref *out) { @@ -239,9 +242,9 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, SecBufferDesc type_3_desc; SECURITY_STATUS status; unsigned long attrs; + SecPkgContext_Bindings pkgBindings = { 0, NULL }; - (void)passwdp; - (void)userp; + (void)creds; /* Setup the type-2 "input" security buffer */ type_2_desc.ulVersion = SECBUFFER_VERSION; @@ -251,8 +254,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_2_bufs[0].pvBuffer = ntlm->input_token; type_2_bufs[0].cbBuffer = curlx_uztoul(ntlm->input_token_len); -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS - /* ssl context comes from schannel. + /* SSL context comes from schannel. * When extended protection is used in IIS server, * we have to pass a second SecBuffer to the SecBufferDesc * otherwise IIS does not pass the authentication (401 response). @@ -260,9 +262,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 */ if(ntlm->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; status = Curl_pSecFn->QueryContextAttributes( ntlm->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, @@ -275,7 +274,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_2_bufs[1].pvBuffer = pkgBindings.Bindings; } } -#endif /* Setup the type-3 "output" security buffer */ type_3_desc.ulVersion = SECBUFFER_VERSION; @@ -294,9 +292,13 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 0, ntlm->context, &type_3_desc, &attrs, NULL); + + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); + if(status != SEC_E_OK) { infof(data, "NTLM handshake failure (type-3 message): Status=0x%08lx", - status); + (unsigned long)status); if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; @@ -325,15 +327,13 @@ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) /* Free our security context */ if(ntlm->context) { Curl_pSecFn->DeleteSecurityContext(ntlm->context); - curlx_free(ntlm->context); - ntlm->context = NULL; + curlx_safefree(ntlm->context); } /* Free our credentials handle */ if(ntlm->credentials) { Curl_pSecFn->FreeCredentialsHandle(ntlm->credentials); - curlx_free(ntlm->credentials); - ntlm->credentials = NULL; + curlx_safefree(ntlm->credentials); } /* Free our identity */ diff --git a/lib/vauth/oauth2.c b/lib/vauth/oauth2.c index 4541d1d551bb..f597114638a5 100644 --- a/lib/vauth/oauth2.c +++ b/lib/vauth/oauth2.c @@ -47,21 +47,22 @@ * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_oauth_bearer_message(const char *user, +CURLcode Curl_auth_create_oauth_bearer_message(struct Curl_creds *creds, const char *host, const long port, - const char *bearer, struct bufref *out) { char *oauth; /* Generate the message */ if(port == 0 || port == 80) - oauth = curl_maprintf("n,a=%s,\1host=%s\1auth=Bearer %s\1\1", user, host, - bearer); + oauth = curl_maprintf("n,a=%s,\1host=%s\1auth=Bearer %s\1\1", + Curl_creds_user(creds), host, + Curl_creds_oauth_bearer(creds)); else oauth = curl_maprintf("n,a=%s,\1host=%s\1port=%ld\1auth=Bearer %s\1\1", - user, host, port, bearer); + Curl_creds_user(creds), host, port, + Curl_creds_oauth_bearer(creds)); if(!oauth) return CURLE_OUT_OF_MEMORY; @@ -83,12 +84,13 @@ CURLcode Curl_auth_create_oauth_bearer_message(const char *user, * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_xoauth_bearer_message(const char *user, - const char *bearer, +CURLcode Curl_auth_create_xoauth_bearer_message(struct Curl_creds *creds, struct bufref *out) { /* Generate the message */ - char *xoauth = curl_maprintf("user=%s\1auth=Bearer %s\1\1", user, bearer); + char *xoauth = curl_maprintf("user=%s\1auth=Bearer %s\1\1", + Curl_creds_user(creds), + Curl_creds_oauth_bearer(creds)); if(!xoauth) return CURLE_OUT_OF_MEMORY; diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c index 38bb4c1422c4..b2bc28fb3fa7 100644 --- a/lib/vauth/spnego_gssapi.c +++ b/lib/vauth/spnego_gssapi.c @@ -32,7 +32,7 @@ #include "curl_gssapi.h" #include "curl_trc.h" -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @@ -70,9 +70,8 @@ bool Curl_auth_is_spnego_supported(void) * Returns CURLE_OK on success. */ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, - const char *user, - const char *password, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const char *chlg64, struct negotiatedata *nego) @@ -90,8 +89,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, struct gss_channel_bindings_struct chan; #endif - (void)user; - (void)password; + (void)creds; if(nego->context && nego->status == GSS_S_COMPLETE) { /* We finished successfully our part of authentication, but server @@ -105,6 +103,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; /* Generate our SPN */ + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; char *spn = Curl_auth_build_spn(service, NULL, host); if(!spn) return CURLE_OUT_OF_MEMORY; @@ -158,6 +158,57 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } #endif +#ifdef HAVE_GSS_SET_NEG_MECHS + /* Acquire explicit credentials and restrict SPNEGO sub-mechanisms to + * exclude NTLM. We enumerate all available mechanisms and filter out + * the NTLMSSP OID, matching SSPI's "!ntlm". */ + if(nego->cred == GSS_C_NO_CREDENTIAL) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + gss_OID_set available_mechs = GSS_C_NO_OID_SET; + gss_OID_set filtered_mechs = GSS_C_NO_OID_SET; + + /* Acquire default credentials for SPNEGO */ + major_status = Curl_gss_acquire_cred(&minor_status, GSS_C_NO_NAME, + GSS_C_INDEFINITE, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &nego->cred, NULL, NULL); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_acquire_cred() failed: ", + major_status, minor_status); + curlx_safefree(input_token.value); + return CURLE_AUTH_ERROR; + } + + /* Get all available mechanisms */ + major_status = Curl_gss_indicate_mechs(&minor_status, &available_mechs); + if(!GSS_ERROR(major_status)) { + /* Build a set excluding NTLMSSP */ + major_status = gss_create_empty_oid_set(&minor_status, &filtered_mechs); + if(!GSS_ERROR(major_status)) { + size_t i; + for(i = 0; i < available_mechs->count; i++) { + gss_OID oid = &available_mechs->elements[i]; + if(oid->length != ntlmssp_oid.length || + memcmp(oid->elements, ntlmssp_oid.elements, oid->length)) { + gss_add_oid_set_member(&minor_status, oid, &filtered_mechs); + } + } + /* Restrict SPNEGO to only use non-NTLM mechanisms */ + major_status = Curl_gss_set_neg_mechs(&minor_status, nego->cred, + filtered_mechs); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_set_neg_mechs() failed: ", + major_status, minor_status); + } + gss_release_oid_set(&minor_status, &filtered_mechs); + } + gss_release_oid_set(&minor_status, &available_mechs); + } + } +#endif /* HAVE_GSS_SET_NEG_MECHS */ + /* Generate our challenge-response message */ major_status = Curl_gss_init_sec_context(data, &minor_status, @@ -168,7 +219,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, &input_token, &output_token, TRUE, - NULL); + NULL, + nego->cred); /* Free the decoded challenge as it is not required anymore */ curlx_safefree(input_token.value); @@ -191,6 +243,29 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_AUTH_ERROR; } + /* Check if NTLM was selected and is disallowed */ + if(nego->context != GSS_C_NO_CONTEXT) { + /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ + static const gss_OID_desc ntlmssp_oid = { + 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") + }; + OM_uint32 inquire_major, inquire_minor; + gss_OID mech_type = GSS_C_NO_OID; + + inquire_major = Curl_gss_inquire_context(&inquire_minor, + nego->context, + &mech_type); + if(!GSS_ERROR(inquire_major) && mech_type && + mech_type->length == ntlmssp_oid.length && + !memcmp(mech_type->elements, ntlmssp_oid.elements, + ntlmssp_oid.length)) { + infof(data, "SPNEGO chose NTLM, but NTLM is not allowed"); + gss_release_buffer(&unused_status, &output_token); + Curl_auth_cleanup_spnego(nego); + return CURLE_AUTH_ERROR; + } + } + /* Free previous token */ if(nego->output_token.length && nego->output_token.value) gss_release_buffer(&unused_status, &nego->output_token); @@ -280,6 +355,12 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->spn = GSS_C_NO_NAME; } + /* Free our credentials */ + if(nego->cred != GSS_C_NO_CREDENTIAL) { + Curl_gss_release_cred(&minor_status, &nego->cred); + nego->cred = GSS_C_NO_CREDENTIAL; + } + /* Reset any variables */ nego->status = 0; nego->noauthpersist = FALSE; @@ -288,7 +369,7 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->havemultiplerequests = FALSE; } -#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) && !defined(HAVE_GSSAPPLE) #pragma GCC diagnostic pop #endif diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c index 1baf59320a37..4babd4510d5d 100644 --- a/lib/vauth/spnego_sspi.c +++ b/lib/vauth/spnego_sspi.c @@ -48,7 +48,7 @@ bool Curl_auth_is_spnego_supported(void) /* Query the security package for Negotiate */ status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), &SecurityPackage); /* Release the package buffer as it is not required anymore */ @@ -78,9 +78,8 @@ bool Curl_auth_is_spnego_supported(void) * Returns CURLE_OK on success. */ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, - const char *user, - const char *password, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const char *chlg64, struct negotiatedata *nego) @@ -94,6 +93,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, SecBufferDesc chlg_desc; SecBufferDesc resp_desc; unsigned long attrs; + SecPkgContext_Bindings pkgBindings = { 0, NULL }; if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server @@ -105,6 +105,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->spn) { /* Generate our SPN */ + const char *service = Curl_creds_has_sasl_service(creds) ? + Curl_creds_sasl_service(creds) : default_service; nego->spn = Curl_auth_build_spn(service, host, NULL); if(!nego->spn) return CURLE_OUT_OF_MEMORY; @@ -113,7 +115,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->output_token) { /* Query the security package for Negotiate */ nego->status = Curl_pSecFn->QuerySecurityPackageInfo( - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), &SecurityPackage); if(nego->status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); @@ -133,9 +135,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->credentials) { /* Do we have credentials to use or are we using single sign-on? */ - if(user && *user) { + if(Curl_creds_has_user(creds)) { /* Populate our identity structure */ - result = Curl_create_sspi_identity(user, password, &nego->identity); + result = Curl_create_sspi_identity(creds->user, creds->passwd, + &nego->identity); if(result) return result; @@ -146,6 +149,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Use the current Windows user */ nego->p_identity = NULL; + /* Exclude NTLM from SPNEGO negotiation via the PackageList field */ + if(!nego->p_identity) { + memset(&nego->identity, 0, sizeof(nego->identity)); + nego->identity.Version = SEC_WINNT_AUTH_IDENTITY_VERSION; + nego->identity.Length = sizeof(nego->identity); + nego->identity.Flags = CURL_SEC_WINNT_AUTH_IDENTITY; + nego->p_identity = &nego->identity; + } + + /* Use the special name "!ntlm" to prevent NTLM from being used: + * https://learn.microsoft.com/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_exa + */ + nego->identity.PackageList = CURL_UNCONST(TEXT("!ntlm")); + nego->identity.PackageListLength = 5; + /* Allocate our credentials handle */ nego->credentials = curlx_calloc(1, sizeof(CredHandle)); if(!nego->credentials) @@ -153,12 +171,14 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Acquire our credentials handle */ nego->status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), SECPKG_CRED_OUTBOUND, NULL, nego->p_identity, NULL, NULL, nego->credentials, NULL); - if(nego->status != SEC_E_OK) + if(nego->status != SEC_E_OK) { + curlx_safefree(nego->credentials); return CURLE_AUTH_ERROR; + } /* Allocate our new context handle */ nego->context = curlx_calloc(1, sizeof(CtxtHandle)); @@ -166,6 +186,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 0; + chlg_desc.pBuffers = chlg_buf; + if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { @@ -181,37 +205,30 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } /* Setup the challenge "input" security buffer */ - chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; - chlg_desc.pBuffers = &chlg_buf[0]; chlg_buf[0].BufferType = SECBUFFER_TOKEN; chlg_buf[0].pvBuffer = chlg; chlg_buf[0].cbBuffer = curlx_uztoul(chlglen); + } -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS - /* ssl context comes from Schannel. - * When extended protection is used in IIS server, - * we have to pass a second SecBuffer to the SecBufferDesc - * otherwise IIS does not pass the authentication (401 response). - * Minimum supported version is Windows 7. - * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 - */ - if(nego->sslContext) { - SEC_CHANNEL_BINDINGS channelBindings; - SecPkgContext_Bindings pkgBindings; - pkgBindings.Bindings = &channelBindings; - nego->status = Curl_pSecFn->QueryContextAttributes( - nego->sslContext, - SECPKG_ATTR_ENDPOINT_BINDINGS, - &pkgBindings); - if(nego->status == SEC_E_OK) { - chlg_desc.cBuffers++; - chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; - chlg_buf[1].cbBuffer = pkgBindings.BindingsLength; - chlg_buf[1].pvBuffer = pkgBindings.Bindings; - } + /* SSL context comes from Schannel. + * When extended protection is used in IIS server, pass its channel + * bindings on the initial call too. HTTP Negotiate can create and send a + * Kerberos token before receiving a challenge from the server. + * Minimum supported version is Windows 7. + * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811 + */ + if(nego->sslContext) { + nego->status = Curl_pSecFn->QueryContextAttributes( + nego->sslContext, + SECPKG_ATTR_ENDPOINT_BINDINGS, + &pkgBindings); + if(nego->status == SEC_E_OK) { + SecBuffer *binding_buf = &chlg_buf[chlg_desc.cBuffers++]; + binding_buf->BufferType = SECBUFFER_CHANNEL_BINDINGS; + binding_buf->cbBuffer = pkgBindings.BindingsLength; + binding_buf->pvBuffer = pkgBindings.Bindings; } -#endif } /* Setup the response "output" security buffer */ @@ -223,15 +240,24 @@ 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 = - 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, NULL); + { + DWORD sspi_flags = ISC_REQ_CONFIDENTIALITY; + if(data->set.gssapi_delegation & CURLGSSAPI_DELEGATION_FLAG) + sspi_flags |= ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH; + nego->status = + Curl_pSecFn->InitializeSecurityContext(nego->credentials, + chlg ? nego->context : NULL, + nego->spn, + sspi_flags, + 0, SECURITY_NATIVE_DREP, + chlg_desc.cBuffers ? + &chlg_desc : NULL, + 0, nego->context, + &resp_desc, &attrs, NULL); + } + + if(pkgBindings.Bindings) + Curl_pSecFn->FreeContextBuffer(pkgBindings.Bindings); /* Free the decoded challenge as it is not required anymore */ curlx_free(chlg); @@ -313,15 +339,13 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) /* Free our security context */ if(nego->context) { Curl_pSecFn->DeleteSecurityContext(nego->context); - curlx_free(nego->context); - nego->context = NULL; + curlx_safefree(nego->context); } /* Free our credentials handle */ if(nego->credentials) { Curl_pSecFn->FreeCredentialsHandle(nego->credentials); - curlx_free(nego->credentials); - nego->credentials = NULL; + curlx_safefree(nego->credentials); } /* Free our identity */ diff --git a/lib/vauth/vauth.c b/lib/vauth/vauth.c index a00078fce193..3259556e5f78 100644 --- a/lib/vauth/vauth.c +++ b/lib/vauth/vauth.c @@ -24,6 +24,7 @@ #include "curl_setup.h" #include "vauth/vauth.h" +#include "creds.h" #include "curlx/multibyte.h" #include "url.h" @@ -111,15 +112,16 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, * * Returns TRUE on success; otherwise FALSE. */ -bool Curl_auth_user_contains_domain(const char *user) +bool Curl_auth_user_contains_domain(struct Curl_creds *creds) { bool valid = FALSE; - if(user && *user) { + if(Curl_creds_has_user(creds)) { /* Check we have a domain name or UPN present */ - const char *p = strpbrk(user, "\\/@"); + const char *p = strpbrk(creds->user, "\\/@"); - valid = (p != NULL && p > user && p < user + strlen(user) - 1); + valid = p && (p > creds->user) && + (p < (creds->user + strlen(creds->user) - 1)); } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else @@ -133,17 +135,18 @@ bool Curl_auth_user_contains_domain(const char *user) /* * Curl_auth_allowed_to_host() tells if authentication, cookies or other - * "sensitive data" can (still) be sent to this host. + * "sensitive data" can be sent to the connection's origin. */ 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 && - curl_strequal(data->state.first_host, conn->host.name) && - (data->state.first_remote_port == conn->remote_port) && - (data->state.first_remote_protocol == conn->scheme->protocol)); + return Curl_auth_allowed_to_origin(data, data->state.origin); +} + +bool Curl_auth_allowed_to_origin(struct Curl_easy *data, + struct Curl_peer *origin) +{ + return data->set.allow_auth_to_other_hosts || + Curl_peer_equal(data->state.initial_origin, origin); } #ifdef USE_NTLM diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h index 279da60be2cb..bddd55f8d56a 100644 --- a/lib/vauth/vauth.h +++ b/lib/vauth/vauth.h @@ -30,7 +30,9 @@ #include "urldata.h" struct Curl_easy; +struct Curl_creds; struct connectdata; +struct Curl_peer; #ifndef CURL_DISABLE_DIGEST_AUTH struct digestdata; @@ -58,6 +60,8 @@ struct gsasldata; * "sensitive data" can (still) be sent to this host. */ bool Curl_auth_allowed_to_host(struct Curl_easy *data); +bool Curl_auth_allowed_to_origin(struct Curl_easy *data, + struct Curl_peer *origin); /* This is used to build an SPN string */ #ifndef USE_WINDOWS_SSPI @@ -69,12 +73,10 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, #endif /* This is used to test if the user contains a Windows domain name */ -bool Curl_auth_user_contains_domain(const char *user); +bool Curl_auth_user_contains_domain(struct Curl_creds *creds); /* This is used to generate a PLAIN cleartext message */ -CURLcode Curl_auth_create_plain_message(const char *authzid, - const char *authcid, - const char *passwd, +CURLcode Curl_auth_create_plain_message(struct Curl_creds *creds, struct bufref *out); /* This is used to generate a LOGIN cleartext message */ @@ -86,8 +88,7 @@ void Curl_auth_create_external_message(const char *user, struct bufref *out); #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, - const char *passwdp, + struct Curl_creds *creds, struct bufref *out); /* This is used to evaluate if DIGEST is supported */ @@ -96,9 +97,8 @@ bool Curl_auth_is_digest_supported(void); /* This is used to generate a base64 encoded DIGEST-MD5 response message */ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, const struct bufref *chlg, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, struct bufref *out); /* This is used to decode an HTTP DIGEST challenge message */ @@ -107,8 +107,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, /* This is used to generate an HTTP DIGEST response message */ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, const unsigned char *request, const unsigned char *uripath, struct digestdata *digest, @@ -140,8 +139,7 @@ bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, struct gsasldata *gsasl); /* This is used to start a gsasl method */ CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct gsasldata *gsasl); /* This is used to process and generate a new SASL token */ @@ -166,13 +164,11 @@ struct ntlmdata { /* The sslContext is used for the Schannel bindings. The * api is available on the Windows 7 SDK and later. */ -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS CtxtHandle *sslContext; -#endif CredHandle *credentials; CtxtHandle *context; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; size_t token_max; BYTE *output_token; BYTE *input_token; @@ -197,9 +193,8 @@ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm); /* This is used to generate a base64 encoded NTLM type-1 message */ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, struct ntlmdata *ntlm, struct bufref *out); @@ -211,8 +206,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, /* This is used to generate a base64 encoded NTLM type-3 message */ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, + struct Curl_creds *creds, struct ntlmdata *ntlm, struct bufref *out); @@ -221,15 +215,13 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, #endif /* USE_NTLM */ /* This is used to generate a base64 encoded OAuth 2.0 message */ -CURLcode Curl_auth_create_oauth_bearer_message(const char *user, +CURLcode Curl_auth_create_oauth_bearer_message(struct Curl_creds *creds, const char *host, const long port, - const char *bearer, struct bufref *out); /* This is used to generate a base64 encoded XOAuth 2.0 message */ -CURLcode Curl_auth_create_xoauth_bearer_message(const char *user, - const char *bearer, +CURLcode Curl_auth_create_xoauth_bearer_message(struct Curl_creds *creds, struct bufref *out); #ifdef USE_KERBEROS5 @@ -242,8 +234,8 @@ struct kerberos5data { CredHandle *credentials; CtxtHandle *context; TCHAR *spn; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; size_t token_max; BYTE *output_token; #else @@ -260,9 +252,8 @@ bool Curl_auth_is_gssapi_supported(void); /* This is used to generate a base64 encoded GSSAPI (Kerberos V5) user token message */ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const bool mutual_auth, const struct bufref *chlg, @@ -298,20 +289,19 @@ struct negotiatedata { OM_uint32 status; gss_ctx_id_t context; gss_name_t spn; + gss_cred_id_t cred; gss_buffer_desc output_token; #ifdef GSS_C_CHANNEL_BOUND_FLAG struct dynbuf channel_binding_data; #endif #else #ifdef USE_WINDOWS_SSPI -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS CtxtHandle *sslContext; -#endif SECURITY_STATUS status; CredHandle *credentials; CtxtHandle *context; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; + SEC_WINNT_AUTH_IDENTITY_EX identity; + SEC_WINNT_AUTH_IDENTITY_EX *p_identity; TCHAR *spn; size_t token_max; BYTE *output_token; @@ -324,15 +314,13 @@ struct negotiatedata { BIT(havemultiplerequests); }; -struct negotiatedata * -Curl_auth_nego_get(struct connectdata *conn, bool proxy); +struct negotiatedata *Curl_auth_nego_get(struct connectdata *conn, bool proxy); /* This is used to decode a base64 encoded SPNEGO (Negotiate) challenge message */ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, - const char *user, - const char *password, - const char *service, + struct Curl_creds *creds, + const char *default_service, const char *host, const char *chlg64, struct negotiatedata *nego); diff --git a/lib/asyn-ares.c b/lib/vdns/asyn-ares.c similarity index 81% rename from lib/asyn-ares.c rename to lib/vdns/asyn-ares.c index a4a74642859a..dfb087599527 100644 --- a/lib/asyn-ares.c +++ b/lib/vdns/asyn-ares.c @@ -49,7 +49,6 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "url.h" #include "multiif.h" #include "curlx/inet_pton.h" @@ -57,16 +56,17 @@ #include "select.h" #include "progress.h" #include "curlx/timediff.h" -#include "httpsrr.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include #if ARES_VERSION < 0x011000 -#error "requires c-ares 1.16.0 or newer" +#error "c-ares 1.16.0 or greater required" #endif #ifdef USE_HTTPSRR #if ARES_VERSION < 0x011c00 -#error "requires c-ares 1.28.0 or newer for HTTPSRR" +#error "c-ares 1.28.0 or greater required for HTTPSRR" #endif #define HTTPSRR_WORKS #endif @@ -148,7 +148,7 @@ static CURLcode async_ares_init(struct Curl_easy *data, 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; @@ -188,25 +188,6 @@ static CURLcode async_ares_init(struct Curl_easy *data, return result; } -/* - * async_ares_cleanup() cleans up async resolver data. - */ -static void async_ares_cleanup(struct Curl_resolv_async *async) -{ - struct async_ares_ctx *ares = &async->ares; - if(ares->res_A) { - Curl_freeaddrinfo(ares->res_A); - ares->res_A = NULL; - } - if(ares->res_AAAA) { - Curl_freeaddrinfo(ares->res_AAAA); - ares->res_AAAA = NULL; - } -#ifdef USE_HTTPSRR - Curl_httpsrr_cleanup(&ares->hinfo); -#endif -} - void Curl_async_ares_shutdown(struct Curl_easy *data, struct Curl_resolv_async *async) { @@ -227,7 +208,6 @@ void Curl_async_ares_destroy(struct Curl_easy *data, ares_destroy(ares->channel); ares->channel = NULL; } - async_ares_cleanup(async); } CURLcode Curl_async_pollset(struct Curl_easy *data, @@ -259,6 +239,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns) { struct async_ares_ctx *ares = &async->ares; + struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; DEBUGASSERT(pdns); @@ -279,47 +260,52 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, /* all c-ares operations done, what is the result to report? */ result = ares->result; if(ares->ares_status == ARES_SUCCESS && !result) { - struct Curl_dns_entry *dns = - Curl_dnscache_mk_entry2(data, async->dns_queries, - &ares->res_AAAA, &ares->res_A, - async->hostname, async->port); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto out; + + if(CURL_DNSQ_IS_ADDR(async->dns_queries)) { + dns = Curl_dnsc_mk_addr2(data, async->dns_queries, + &async->ai_AAAA, &async->ai_A, + async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } } + #ifdef HTTPSRR_WORKS - if(async->dns_queries & CURL_DNSQ_HTTPS) { - if(ares->hinfo.complete) { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - Curl_dns_entry_set_https_rr(dns, lhrr); + if(!dns && (async->dns_queries & CURL_DNSQ_HTTPS)) { + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - else - Curl_dns_entry_set_https_rr(dns, NULL); } #endif - if(!result) { - *pdns = dns; - } } /* if we have not found anything, report the proper * CURLE_COULDNT_RESOLVE_* code */ - if(!result && !*pdns) { + if(!result && !dns) { const char *msg = NULL; + /* only an authoritative "does not exist" answer from every query + may be cached as a negative entry, not transient failures like + timeouts or server troubles */ + async->negative_answer = !ares->transient_err && + ((ares->ares_status == ARES_ENOTFOUND) || + (ares->ares_status == ARES_ENODATA)); if(ares->ares_status != ARES_SUCCESS) msg = ares_strerror(ares->ares_status); result = Curl_async_failed(data, async, msg); } - CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound", - result, *pdns ? "" : "not "); - async_ares_cleanup(async); + CURL_TRC_DNS(data, "[%s] ares_take_result, result=%d, ares_result=%d, " + "ares_status=%d, dns=%sfound", + Curl_resolv_query_str(async->dns_queries), + (int)result, (int)ares->result, ares->ares_status, + dns ? "" : "not "); out: if(result != CURLE_AGAIN) ares->result = result; + *pdns = result ? NULL : dns; return result; } @@ -349,67 +335,6 @@ static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares, return 1000; } -static const struct Curl_addrinfo *async_ares_get_ai( - const struct Curl_addrinfo *ai, - int ai_family, - unsigned int index) -{ - unsigned int i = 0; - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index) -{ - struct async_ares_ctx *ares = &async->ares; - - (void)data; - switch(ai_family) { - case AF_INET: - if(ares->res_A) - return async_ares_get_ai(ares->res_A, ai_family, index); - break; - case AF_INET6: - if(ares->res_AAAA) - return async_ares_get_ai(ares->res_AAAA, ai_family, index); - break; - default: - break; - } - return NULL; -} - -#ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - if(Curl_async_knows_https(data, async)) - return &async->ares.hinfo; - return NULL; -} - -bool Curl_async_knows_https(struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - (void)data; - if(async->dns_queries & CURL_DNSQ_HTTPS) - return ((async->dns_responses & CURL_DNSQ_HTTPS) || - !async->queries_ongoing); - return TRUE; /* we know it will never come */ -} - -#endif /* USE_HTTPSRR */ - /* * Curl_async_await() * @@ -489,7 +414,7 @@ static struct Curl_addrinfo *async_ares_node2addr( struct Curl_addrinfo *calast = NULL; int error = 0; - for(ai = node; ai != NULL; ai = ai->ai_next) { + for(ai = node; ai; ai = ai->ai_next) { size_t ss_size; struct Curl_addrinfo *ca; /* ignore elements with unsupported address family, @@ -566,11 +491,15 @@ static void async_ares_A_cb(void *user_data, int status, int timeouts, async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; - ares->res_A = async_ares_node2addr(ares_ai->nodes); + async->ai_A = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ - ares->ares_status = status; + else { + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; + if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; + } } #ifdef CURLRES_IPV6 @@ -589,11 +518,15 @@ static void async_ares_AAAA_cb(void *user_data, int status, int timeouts, async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; - ares->res_AAAA = async_ares_node2addr(ares_ai->nodes); + async->ai_AAAA = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ - ares->ares_status = status; + else { + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; + if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; + } } #endif /* CURLRES_IPV6 */ @@ -612,9 +545,12 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; async->done = !async->queries_ongoing; + ares->ares_status = status; + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; if((ARES_SUCCESS != status) || !dnsrec) return; - ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); + ares->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } #endif /* USE_HTTPSRR */ @@ -653,7 +589,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, } #endif - curl_msnprintf(service, sizeof(service), "%d", async->port); + curl_msnprintf(service, sizeof(service), "%d", async->peer->port); socktype = (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; @@ -663,12 +599,13 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname); + CURL_TRC_DNS(data, "[AAAA] ares: query records for %s", + async->peer->hostname); hints.ai_family = PF_INET6; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; async->queries_ongoing++; - ares_getaddrinfo(ares->channel, async->hostname, + ares_getaddrinfo(ares->channel, async->peer->hostname, service, &hints, async_ares_AAAA_cb, async); } #endif /* CURLRES_IPV6 */ @@ -677,32 +614,33 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname); + CURL_TRC_DNS(data, "[A] ares: query records for %s", + async->peer->hostname); hints.ai_family = PF_INET; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; async->queries_ongoing++; - ares_getaddrinfo(ares->channel, async->hostname, + ares_getaddrinfo(ares->channel, async->peer->hostname, service, &hints, async_ares_A_cb, async); } #ifdef USE_HTTPSRR - memset(&ares->hinfo, 0, sizeof(ares->hinfo)); if(async->dns_queries & CURL_DNSQ_HTTPS) { - char *rrname = NULL; - if(async->port != 443) { - rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); - if(!rrname) + char *https_name = NULL; + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) return CURLE_OUT_OF_MEMORY; } - CURL_TRC_DNS(data, "ares: query HTTPS records for %s", - rrname ? rrname : async->hostname); - ares->hinfo.rrname = rrname; + CURL_TRC_DNS(data, "[HTTPS] ares: query records for %s", + https_name ? https_name : async->peer->hostname); async->queries_ongoing++; ares_query_dnsrec(ares->channel, - rrname ? rrname : async->hostname, + https_name ? https_name : async->peer->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_ares_rr_done, async, NULL); + curlx_free(https_name); } #endif /* USE_HTTPSRR */ @@ -722,7 +660,7 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, { struct async_ares_ctx *ares = async ? &async->ares : NULL; CURLcode result = CURLE_NOT_BUILT_IN; - const char *servers = data->set.str[STRING_DNS_SERVERS]; + const char *servers = CURL_EASY_STR(data, STRING_DNS_SERVERS); int ares_result = ARES_SUCCESS; #ifdef DEBUGBUILD @@ -758,7 +696,7 @@ static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = async ? &async->ares : NULL; - const char *interf = data->set.str[STRING_DNS_INTERFACE]; + const char *interf = CURL_EASY_STR(data, STRING_DNS_INTERFACE); if(!interf) interf = ""; @@ -775,7 +713,7 @@ static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, { struct async_ares_ctx *ares = async ? &async->ares : NULL; struct in_addr a4; - const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; + const char *local_ip4 = CURL_EASY_STR(data, STRING_DNS_LOCAL_IP4); if(!local_ip4 || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ @@ -800,7 +738,7 @@ static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, #ifdef USE_IPV6 struct async_ares_ctx *ares = async ? &async->ares : NULL; unsigned char a6[INET6_ADDRSTRLEN]; - const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; + const char *local_ip6 = CURL_EASY_STR(data, STRING_DNS_LOCAL_IP6); if(!local_ip6 || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ diff --git a/lib/asyn-base.c b/lib/vdns/asyn-base.c similarity index 92% rename from lib/asyn-base.c rename to lib/vdns/asyn-base.c index 62cb0effe491..f3db691c8c4d 100644 --- a/lib/asyn-base.c +++ b/lib/vdns/asyn-base.c @@ -43,12 +43,14 @@ #include "urldata.h" #include "connect.h" +#include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "multiif.h" #include "progress.h" #include "select.h" #include "url.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" /*********************************************************************** * Only for builds using asynchronous name resolves @@ -69,7 +71,7 @@ timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, #ifdef USE_ARES #if ARES_VERSION < 0x011000 -#error "requires c-ares 1.16.0 or newer" +#error "c-ares 1.16.0 or greater required" #endif /* @@ -238,6 +240,14 @@ void Curl_async_destroy(struct Curl_easy *data, #ifndef CURL_DISABLE_DOH Curl_doh_cleanup(data, async); #endif + if(async->ai_A) + Curl_freeaddrinfo(async->ai_A); + if(async->ai_AAAA) + Curl_freeaddrinfo(async->ai_AAAA); +#ifdef USE_HTTPSRR + Curl_httpsrr_destroy(async->httpsrr); +#endif + Curl_peer_unlink(&async->peer); curlx_safefree(async); } } @@ -256,9 +266,10 @@ CURLcode Curl_async_failed(struct Curl_easy *data, } #endif - failf(data, "Could not resolve %s: %s%s%s%s", - host_or_proxy, async->hostname, - detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); + if(async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve %s: %s%s%s%s", + host_or_proxy, async->peer->hostname, + detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); return result; } diff --git a/lib/asyn-thrdd.c b/lib/vdns/asyn-thrdd.c similarity index 67% rename from lib/asyn-thrdd.c rename to lib/vdns/asyn-thrdd.c index 90f055c2a159..1ca217a79e60 100644 --- a/lib/asyn-thrdd.c +++ b/lib/vdns/asyn-thrdd.c @@ -52,8 +52,6 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" -#include "httpsrr.h" #include "url.h" #include "multiif.h" #include "curl_threads.h" @@ -61,6 +59,8 @@ #include "rand.h" #include "select.h" #include "thrdqueue.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/strparse.h" #include "curlx/wait.h" @@ -111,15 +111,17 @@ struct async_thrdd_item { #ifdef CURLVERBOSE char description[CURL_ASYN_ITEM_DESC_LEN]; #endif - int sock_error; + int sockerr; uint32_t mid; uint32_t resolv_id; uint16_t port; uint8_t transport; uint8_t dns_queries; + BIT(negative); /* resolver answered that the name does not exist */ #ifdef DEBUGBUILD uint32_t delay_ms; uint32_t delay_fail_ms; + BIT(dbg_negative); #endif char hostname[1]; }; @@ -182,6 +184,8 @@ static struct async_thrdd_item *async_thrdd_item_create( item->delay_fail_ms = (uint32_t)l + c; } } + if(getenv("CURL_DBG_RESOLV_FAIL_NEGATIVE")) + item->dbg_negative = TRUE; } #endif @@ -205,49 +209,54 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, async->queries_ongoing--; async->done = !async->queries_ongoing; if((ARES_SUCCESS == status) && dnsrec) - async->result = Curl_httpsrr_from_ares(dnsrec, &thrdd->rr.hinfo); + async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } static CURLcode async_rr_start(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_thrdd_ctx *thrdd = &async->thrdd; + char *https_name = NULL; int status; - char *rrname = NULL; + CURLcode result = CURLE_OK; DEBUGASSERT(!thrdd->rr.channel); - if(async->port != 443) { - rrname = curl_maprintf("_%d_.https.%s", async->port, async->hostname); - if(!rrname) - return CURLE_OUT_OF_MEMORY; + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } } status = ares_init_options(&thrdd->rr.channel, NULL, 0); if(status != ARES_SUCCESS) { thrdd->rr.channel = NULL; - curlx_free(rrname); - return CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; + goto out; } #ifdef DEBUGBUILD if(getenv("CURL_DNS_SERVER")) { const char *servers = getenv("CURL_DNS_SERVER"); status = ares_set_servers_ports_csv(thrdd->rr.channel, servers); if(status) { - curlx_free(rrname); - return CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; + goto out; } } #endif - memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); - thrdd->rr.hinfo.rrname = rrname; async->queries_ongoing++; ares_query_dnsrec(thrdd->rr.channel, - rrname ? rrname : async->hostname, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, + https_name ? https_name : async->peer->hostname, + ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_thrdd_rr_done, async, NULL); - CURL_TRC_DNS(data, "[HTTPS-RR] initiated request for %s", - rrname ? rrname : async->hostname); - return CURLE_OK; + CURL_TRC_DNS(data, "[HTTPS] query records for %s", + https_name ? https_name : async->peer->hostname); + +out: + curlx_free(https_name); + return result; } #endif @@ -287,7 +296,6 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data, ares_destroy(async->thrdd.rr.channel); async->thrdd.rr.channel = NULL; } - Curl_httpsrr_cleanup(&async->thrdd.rr.hinfo); #endif async_thrdd_item_destroy(async->thrdd.res_A); async->thrdd.res_A = NULL; @@ -305,32 +313,56 @@ CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; timediff_t milli, ms; + CURLcode result = CURLE_AGAIN; if(!thrdd) return CURLE_FAILED_INIT; - while(async->queries_ongoing && !async->done) { - Curl_async_thrdd_multi_process(data->multi); - if(async->done) - break; - - ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); - if(ms < 3) - milli = 0; - else if(ms <= 50) - milli = ms / 3; - else if(ms <= 250) - milli = 50; - else - milli = 200; - CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); - curlx_wait_ms(milli); + while(result == CURLE_AGAIN) { + while(async->queries_ongoing && !async->done) { + Curl_async_thrdd_multi_process(data->multi); + if(async->done) + break; + + ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms / 3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); + curlx_wait_ms(milli); + } + result = Curl_async_take_result(data, async, pdns); } - return Curl_async_take_result(data, async, pdns); + return result; } #ifdef HAVE_GETADDRINFO +/* Was the getaddrinfo() failure an authoritative negative answer, + i.e. the resolver responded that the name (or its data) does not + exist? Transient failures like EAI_AGAIN and local troubles must + not count as negative answers. */ +static bool gai_negative(int rc) +{ + switch(rc) { +#ifdef EAI_NONAME + case EAI_NONAME: +#endif +#if defined(EAI_NODATA) && \ + (!defined(EAI_NONAME) || (EAI_NODATA != EAI_NONAME)) + case EAI_NODATA: +#endif + return TRUE; + default: + return FALSE; + } +} + /* Process the item, using Curl_getaddrinfo_ex() */ static void async_thrdd_item_process(void *arg) { @@ -346,6 +378,7 @@ static void async_thrdd_item_process(void *arg) } if(item->delay_fail_ms) { curlx_wait_ms(item->delay_fail_ms); + item->negative = item->dbg_negative; return; } #endif @@ -372,9 +405,10 @@ static void async_thrdd_item_process(void *arg) rc = Curl_getaddrinfo_ex(item->hostname, service, &hints, &item->res); if(rc) { - item->sock_error = SOCKERRNO ? SOCKERRNO : rc; - if(item->sock_error == 0) - item->sock_error = RESOLVER_ENOMEM; + item->sockerr = SOCKERRNO ? SOCKERRNO : rc; + if(item->sockerr == 0) + item->sockerr = RESOLVER_ENOMEM; + item->negative = gai_negative(rc); } else { Curl_addrinfo_set_port(item->res, item->port); @@ -394,20 +428,24 @@ static void async_thrdd_item_process(void *arg) } if(item->delay_fail_ms) { curlx_wait_ms(item->delay_fail_ms); + item->negative = item->dbg_negative; return; } #endif item->res = Curl_ipv4_resolve_r(item->hostname, item->port); if(!item->res) { - item->sock_error = SOCKERRNO; - if(item->sock_error == 0) - item->sock_error = RESOLVER_ENOMEM; + item->sockerr = SOCKERRNO; + if(item->sockerr == 0) + item->sockerr = RESOLVER_ENOMEM; + /* this resolver cannot tell a transient failure from an + authoritative negative answer, treat it as before */ + item->negative = TRUE; } } #endif /* HAVE_GETADDRINFO */ -#ifdef ENABLE_WAKEUP +#ifdef ENABLE_INTERNAL_WAKEUP static void async_thrdd_event(const struct curl_thrdq *tqueue, Curl_thrdq_event ev, void *user_data) @@ -416,7 +454,7 @@ static void async_thrdd_event(const struct curl_thrdq *tqueue, (void)tqueue; switch(ev) { case CURL_THRDQ_EV_ITEM_DONE: - (void)curl_multi_wakeup(multi); + Curl_multi_wakeup_internal(multi); break; default: break; @@ -439,7 +477,7 @@ CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, { CURLcode result; DEBUGASSERT(!multi->resolv_thrdq); - result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", 0, + result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", min_threads, max_threads, idle_time_ms, async_thrdd_item_free, async_thrdd_item_process, @@ -481,8 +519,13 @@ static void async_thrdd_report_item(struct Curl_easy *data, struct dynbuf tmp; const char *sep = ""; const struct Curl_addrinfo *ai = item->res; - int ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET; CURLcode result; + int ai_family; +#ifdef USE_IPV6 + ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET; +#else + ai_family = AF_INET; +#endif if(!CURL_TRC_DNS_is_verbose(data)) return; @@ -528,28 +571,23 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) if(async) { struct async_thrdd_item **pdest = &async->thrdd.res_A; - async->dns_responses |= item->dns_queries; - --async->queries_ongoing; - async->done = !async->queries_ongoing; - #ifdef CURLRES_IPV6 if(item->dns_queries & CURL_DNSQ_AAAA) pdest = &async->thrdd.res_AAAA; #endif if(!*pdest) { - VERBOSE(async_thrdd_report_item(data, item)); *pdest = item; item = NULL; } else DEBUGASSERT(0); /* should not receive duplicates here */ + + --async->queries_ongoing; Curl_multi_mark_dirty(data); } async_thrdd_item_free(item); } -#ifdef CURLVERBOSE - Curl_thrdq_trace(multi->resolv_thrdq, multi->admin); -#endif + VERBOSE(Curl_thrdq_trace(multi->resolv_thrdq, multi->admin)); } CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, @@ -557,7 +595,7 @@ CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, uint32_t max_threads, uint32_t idle_time_ms) { - return Curl_thrdq_set_props(multi->resolv_thrdq, 0, + return Curl_thrdq_set_props(multi->resolv_thrdq, min_threads, max_threads, idle_time_ms); } @@ -569,7 +607,7 @@ static CURLcode async_thrdd_query(struct Curl_easy *data, CURLcode result; item = async_thrdd_item_create(data, async->id, dns_queries, - async->hostname, async->port, + async->peer->hostname, async->peer->port, async->transport); if(!item) { result = CURLE_OUT_OF_MEMORY; @@ -613,7 +651,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, return result; #ifdef CURLRES_IPV6 - /* Do not start an AAAA query for an ipv4 address when + /* Do not start an AAAA query for an IPv4 address when * we will start an A query for it. */ if((async->dns_queries & CURL_DNSQ_AAAA) && !(async->is_ipv4addr && (async->dns_queries & CURL_DNSQ_A))) { @@ -633,9 +671,12 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, #endif out: + if(!async->queries_ongoing) + async->done = TRUE; + if(result) CURL_TRC_DNS(data, "error queueing query %s:%d -> %d", - async->hostname, async->port, result); + async->peer->hostname, async->peer->port, (int)result); return result; } @@ -658,9 +699,10 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, #endif if(!async->done) { -#ifndef ENABLE_WAKEUP + const struct curltime *pnow = Curl_pgrs_now(data); +#ifndef ENABLE_INTERNAL_WAKEUP timediff_t stutter_ms, elapsed_ms; - elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + elapsed_ms = curlx_ptimediff_ms(pnow, &async->start); if(elapsed_ms < 3) stutter_ms = 1; else if(elapsed_ms <= 50) @@ -670,12 +712,128 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, else stutter_ms = 200; timeout_ms = CURLMIN(stutter_ms, timeout_ms); +#else + if(async->queries_ongoing && + !Curl_thrdq_check_started(data->multi->resolv_thrdq)) { + /* The queue has items but starting a worker thread to process + them just failed again; expire soon to check once more, + instead of sleeping on the full resolve timeout. */ + CURL_TRC_DNS(data, "resolver thread start failed again, retrying"); + timeout_ms = CURLMIN(100, timeout_ms); + } #endif - Curl_expire(data, timeout_ms, EXPIRE_ASYNC_NAME); + Curl_expire_set(data, EXPIRE_ASYNC_NAME, timeout_ms, pnow); } return CURLE_OK; } +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +static bool async_thrdd_item_missing_scope(struct Curl_easy *data, + struct async_thrdd_item *item) +{ + const struct Curl_addrinfo *ai; + + /* scope id already globally set */ + if(data->conn && data->conn->scope_id) + return FALSE; + + for(ai = item->res; ai; ai = ai->ai_next) { + if(ai->ai_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (void *)ai->ai_addr; + if(IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) && !sa6->sin6_scope_id) + return TRUE; + } + } + return FALSE; +} + +static void async_thrdd_item_strip_results(struct async_thrdd_item *item, + int ai_family) +{ + struct Curl_addrinfo *ai = item->res, **panchor = &item->res; + while(ai) { + if(ai->ai_family == ai_family) { + *panchor = ai->ai_next; + ai->ai_next = NULL; + Curl_freeaddrinfo(ai); + ai = *panchor; + } + else { + panchor = &ai->ai_next; + ai = ai->ai_next; + } + } +} + +#endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ + +static CURLcode async_thrdd_check_done(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + struct async_thrdd_ctx *thrdd = &async->thrdd; + + (void)data; + if(thrdd->res_A && !thrdd->processed_A) { + VERBOSE(async_thrdd_report_item(data, thrdd->res_A)); + /* move addrinfos to the async result member */ + async->dns_responses |= thrdd->res_A->dns_queries; + async->ai_A = thrdd->res_A->res; + thrdd->res_A->res = NULL; + thrdd->processed_A = TRUE; + } + + if(thrdd->res_AAAA && !thrdd->processed_AAAA) { +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + /* do we accept the incoming AAAA response? */ + if(!(thrdd->res_AAAA->dns_queries & CURL_DNSQ_A) && + async_thrdd_item_missing_scope(data, thrdd->res_AAAA)) { + /* We queried "only" AF_INET6. This may be problematic when the + * result has ipv6 link-local addresses and did not give + * any scope id for it. glibc has a long outstanding bug + * + * that gives scope ids only on AF_UNSPEC queries. */ + struct async_thrdd_item *item = thrdd->res_AAAA; + CURLcode result; + + /* Reuse the item and queue it again, this time with added + * CURL_DNSQ_A which resolves using AF_UNSPEC. */ + thrdd->res_AAAA = NULL; + item->dns_queries |= CURL_DNSQ_A; + if(item->res) { + Curl_freeaddrinfo(item->res); + item->res = NULL; + } + + CURL_TRC_DNS(data, "re-queueing query %s for AF_UNSPEC resolve", + item->description); + result = Curl_thrdq_send(data->multi->resolv_thrdq, item, + async_item_description(item), + async->timeout_ms); + if(result) { + async_thrdd_item_free(item); + return result; + } + async->queries_ongoing++; + return CURLE_AGAIN; + } + /* accepting the AAAA result, strip it of any AF_INET entries, + * as we might have resolved it with AF_UNSPEC. */ + async_thrdd_item_strip_results(thrdd->res_AAAA, AF_INET); +#endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ + VERBOSE(async_thrdd_report_item(data, thrdd->res_AAAA)); + /* move addrinfos to the async result member */ + async->dns_responses |= thrdd->res_AAAA->dns_queries; + async->ai_AAAA = thrdd->res_AAAA->res; + thrdd->res_AAAA->res = NULL; + thrdd->processed_AAAA = TRUE; + } + + if(async->queries_ongoing) + return CURLE_AGAIN; + async->done = TRUE; + return CURLE_OK; +} + /* * Curl_async_take_result() is called repeatedly to check if a previous * name resolve request has completed. It should also make sure to time-out if @@ -691,53 +849,63 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, DEBUGASSERT(pdns); *pdns = NULL; - if(!async->queries_ongoing && !async->done) { - DEBUGASSERT(0); - return CURLE_FAILED_INIT; - } #ifdef USE_HTTPSRR_ARES /* best effort, ignore errors */ if(thrdd->rr.channel) (void)Curl_ares_perform(thrdd->rr.channel, 0); #endif +#ifndef ENABLE_INTERNAL_WAKEUP + Curl_async_thrdd_multi_process(data->multi); +#endif - if(!async->done) - return CURLE_AGAIN; + result = async_thrdd_check_done(data, async); + if(result) + return result; + + Curl_expire_clear(data, EXPIRE_ASYNC_NAME); + + /* A failure is an authoritative negative answer, eligible for + negative caching, only when every A/AAAA query performed came + back answering that the name does not exist. A query that + failed transiently or never returned is not an answer. */ + { + const uint8_t ip_queries = + async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA); + bool negative = (async->dns_responses & ip_queries) == ip_queries; + if(thrdd->res_A && (async->ai_A || !thrdd->res_A->negative)) + negative = FALSE; + if(thrdd->res_AAAA && (async->ai_AAAA || !thrdd->res_AAAA->negative)) + negative = FALSE; + if(!thrdd->res_A && !thrdd->res_AAAA) + negative = FALSE; + async->negative_answer = negative; + } - Curl_expire_done(data, EXPIRE_ASYNC_NAME); if(async->result) { result = async->result; goto out; } - if((thrdd->res_A && thrdd->res_A->res) || - (thrdd->res_AAAA && thrdd->res_AAAA->res)) { - dns = Curl_dnscache_mk_entry2( - data, async->dns_queries, - thrdd->res_A ? &thrdd->res_A->res : NULL, - thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL, - async->hostname, async->port); + if(async->ai_A || async->ai_AAAA) { + dns = Curl_dnsc_mk_addr2( + data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); if(!dns) { result = CURLE_OUT_OF_MEMORY; goto out; } + } #ifdef USE_HTTPSRR_ARES - if(thrdd->rr.channel) { - struct Curl_https_rrinfo *lhrr = NULL; - if(thrdd->rr.hinfo.complete) { - lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); - if(!lhrr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - Curl_httpsrr_trace(data, lhrr); - Curl_dns_entry_set_https_rr(dns, lhrr); + if(!dns && thrdd->rr.channel) { + Curl_httpsrr_trace(data, async->httpsrr); + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } -#endif } +#endif if(dns) { *pdns = dns; @@ -756,73 +924,9 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, (result != CURLE_COULDNT_RESOLVE_HOST) && (result != CURLE_COULDNT_RESOLVE_PROXY)) { CURL_TRC_DNS(data, "Error %d resolving %s:%d", - result, async->hostname, async->port); + (int)result, async->peer->hostname, async->peer->port); } return result; } -static const struct Curl_addrinfo *async_thrdd_get_ai( - const struct Curl_addrinfo *ai, - int ai_family, unsigned int index) -{ - unsigned int i = 0; - for(i = 0; ai; ai = ai->ai_next) { - if(ai->ai_family == ai_family) { - if(i == index) - return ai; - ++i; - } - } - return NULL; -} - -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index) -{ - struct async_thrdd_ctx *thrdd = &async->thrdd; - - (void)data; - switch(ai_family) { - case AF_INET: - if(thrdd->res_A) - return async_thrdd_get_ai(thrdd->res_A->res, ai_family, index); - break; - case AF_INET6: - if(thrdd->res_AAAA) - return async_thrdd_get_ai(thrdd->res_AAAA->res, ai_family, index); - break; - default: - break; - } - return NULL; -} - -#ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async) -{ -#ifdef USE_HTTPSRR_ARES - if(Curl_async_knows_https(data, async)) - return &async->thrdd.rr.hinfo; -#else - (void)data; - (void)async; -#endif - return NULL; -} - -bool Curl_async_knows_https(struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - (void)data; - if(async->dns_queries & CURL_DNSQ_HTTPS) - return ((async->dns_responses & CURL_DNSQ_HTTPS) || async->done); - return TRUE; /* we know it will never come */ -} - -#endif /* USE_HTTPSRR */ - #endif /* USE_RESOLV_THREADED */ diff --git a/lib/asyn.h b/lib/vdns/asyn.h similarity index 90% rename from lib/asyn.h rename to lib/vdns/asyn.h index ed5093365439..7eade656a52b 100644 --- a/lib/asyn.h +++ b/lib/vdns/asyn.h @@ -26,7 +26,7 @@ #include "curl_setup.h" #if defined(USE_HTTPSRR) && defined(USE_ARES) -#include "httpsrr.h" +#include "vdns/httpsrr.h" #endif struct Curl_easy; @@ -82,15 +82,7 @@ void Curl_async_global_cleanup(void); CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async); -const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, - struct Curl_resolv_async *async, - int ai_family, - unsigned int index); - #ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_async_get_https( - struct Curl_easy *data, - struct Curl_resolv_async *async); bool Curl_async_knows_https(struct Curl_easy *data, struct Curl_resolv_async *async); #endif /* USE_HTTPSRR */ @@ -114,14 +106,10 @@ int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms); /* async resolving implementation using c-ares alone */ struct async_ares_ctx { ares_channel channel; - struct Curl_addrinfo *res_A; - struct Curl_addrinfo *res_AAAA; int ares_status; /* ARES_SUCCESS, ARES_ENOTFOUND, etc. */ CURLcode result; /* CURLE_OK or error handling response */ - struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ -#ifdef USE_HTTPSRR - struct Curl_https_rrinfo hinfo; -#endif + BIT(transient_err); /* an A/AAAA query failed without the resolver + answering that the name does not exist */ }; void Curl_async_ares_shutdown(struct Curl_easy *data, @@ -137,14 +125,15 @@ struct async_thrdd_item; /* Context for threaded resolver */ struct async_thrdd_ctx { - struct async_thrdd_item *res_A; /* ipv4 result */ - struct async_thrdd_item *res_AAAA; /* ipv6 result */ + struct async_thrdd_item *res_A; /* IPv4 final result */ + struct async_thrdd_item *res_AAAA; /* IPv6 final result */ #if defined(USE_HTTPSRR) && defined(USE_ARES) struct { ares_channel channel; - struct Curl_https_rrinfo hinfo; } rr; #endif + BIT(processed_A); + BIT(processed_AAAA); }; void Curl_async_thrdd_shutdown(struct Curl_easy *data, @@ -210,11 +199,9 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, /* convert these functions if an asynch resolver is not used */ #define Curl_async_global_init() CURLE_OK #define Curl_async_global_cleanup() Curl_nop_stmt -#define Curl_async_get_ai(a, b, c, d) NULL #define Curl_async_await(a, b, c) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_pollset(x, y, z) CURLE_OK -#define Curl_async_get_https(x, y) NULL #define Curl_async_knows_https(x, y) TRUE #endif /* !CURLRES_ASYNCH */ @@ -226,6 +213,12 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, struct Curl_resolv_async { struct Curl_resolv_async *next; + struct Curl_peer *peer; + struct Curl_addrinfo *ai_A; + struct Curl_addrinfo *ai_AAAA; +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *httpsrr; +#endif #ifdef USE_RESOLV_ARES struct async_ares_ctx ares; #elif defined(USE_RESOLV_THREADED) @@ -240,8 +233,6 @@ struct Curl_resolv_async { CURLcode result; uint32_t poll_interval; uint32_t id; /* unique id per easy handle of the resolve operation */ - /* what is being resolved */ - uint16_t port; uint8_t dns_queries; /* what queries are being performed */ uint8_t dns_responses; /* what queries had responses so far. */ uint8_t transport; @@ -251,7 +242,10 @@ struct Curl_resolv_async { BIT(for_proxy); BIT(done); BIT(shutdown); - char hostname[1]; + BIT(negative_answer); /* resolver answered that the name does not + exist. Only such failures may be cached as + negative entries, not transient or local + resolver failures. */ }; timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, diff --git a/lib/vdns/cf-dns.c b/lib/vdns/cf-dns.c new file mode 100644 index 000000000000..8a08912d9348 --- /dev/null +++ b/lib/vdns/cf-dns.c @@ -0,0 +1,691 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "curl_addrinfo.h" +#include "cfilters.h" +#include "connect.h" +#include "curl_trc.h" +#include "multiif.h" +#include "progress.h" +#include "url.h" +#include "vdns/cf-dns.h" +#include "vdns/dnscache.h" +#include "vdns/httpsrr.h" +#include "curlx/strparse.h" + +/* Max time to wait for AAAA before connecting sub-filters, e.g. + * letting cf-ip-happy.c do its work. */ +#define CURL_HE_AAAA_AWAIT_MS 25 + +struct cf_dns_ctx { + struct Curl_dns_entry *dns; + struct Curl_peer *peer; + CURLcode resolv_result; + timediff_t he_aaaa_await_ms; + uint32_t resolv_id; + uint8_t dns_queries; + uint8_t transport; + BIT(started); + BIT(announced); + BIT(for_proxy); +}; + +static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport, + bool for_proxy) +{ + struct cf_dns_ctx *ctx; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) + return NULL; + + Curl_peer_link(&ctx->peer, peer); + ctx->dns_queries = dns_queries; + ctx->transport = transport; + ctx->for_proxy = for_proxy; + ctx->he_aaaa_await_ms = CURL_HE_AAAA_AWAIT_MS; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_HE_AAAA_AWAIT_MS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, UINT32_MAX)) { + ctx->he_aaaa_await_ms = (uint32_t)l; + } + } + } +#endif + + CURL_TRC_DNS(data, "[%s] created DNS filter for %s:%u, transport=%x", + Curl_resolv_query_str(ctx->dns_queries), + peer->hostname, peer->port, ctx->transport); + return ctx; +} + +static void cf_dns_ctx_destroy(struct Curl_easy *data, + struct cf_dns_ctx *ctx) +{ + if(ctx) { + Curl_peer_unlink(&ctx->peer); + Curl_dns_entry_unlink(data, &ctx->dns); + curlx_free(ctx); + } +} + +#ifdef CURLVERBOSE +static void cf_dns_report_addr(struct Curl_easy *data, + struct dynbuf *tmp, + const char *label, + int ai_family, + const struct Curl_addrinfo *ai) +{ + char buf[MAX_IPADR_LEN]; + const char *sep = ""; + CURLcode result; + + curlx_dyn_reset(tmp); + for(; ai; ai = ai->ai_next) { + if(ai->ai_family == ai_family) { + Curl_printable_address(ai, buf, sizeof(buf)); + result = curlx_dyn_addf(tmp, "%s%s", sep, buf); + if(result) { + infof(data, "too many IP, cannot show"); + return; + } + sep = ", "; + } + } + + infof(data, "%s%s", label, + (curlx_dyn_len(tmp) ? curlx_dyn_ptr(tmp) : "(none)")); +} + +static void cf_dns_report(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_dns_entry *dns) +{ + struct cf_dns_ctx *ctx = cf->ctx; + struct dynbuf tmp; + + if(!Curl_trc_is_verbose(data) || + /* ignore no name or numerical IP addresses */ + !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname)) + return; + + if(ctx->peer->unix_socket) { +#ifdef USE_UNIX_SOCKETS + CURL_TRC_CF(data, cf, "resolved unix://%s", ctx->peer->hostname); +#else + DEBUGASSERT(0); +#endif + } + else { + curlx_dyn_init(&tmp, 1024); + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); +#ifdef CURLRES_IPV6 + cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); +#endif + cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); + } +#ifdef USE_HTTPSRR + else if(ctx->dns_queries & CURL_DNSQ_HTTPS) { + if(!dns->hinfo) + infof(data, "HTTPS-RR %s:%u: -", dns->hostname, dns->port); + else if(!Curl_httpsrr_applicable(data, dns->hinfo)) + infof(data, "HTTPS-RR %s:%u: not applicable", + dns->hostname, dns->port); + else { + CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); + if(!result) + infof(data, "HTTPS-RR %s:%u: %s", + dns->hostname, dns->port, curlx_dyn_ptr(&tmp)); + else + infof(data, "Error printing HTTPS-RR information"); + } + } +#endif + curlx_dyn_free(&tmp); + } +} +#else +#define cf_dns_report(x, y, z) Curl_nop_stmt +#endif + +/************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ +static CURLcode cf_dns_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_dns_entry **pdns) +{ + struct cf_dns_ctx *ctx = cf->ctx; + timediff_t timeout_ms = Curl_timeleft_ms(data); + CURLcode result = CURLE_OK; + + *pdns = NULL; + + CURL_TRC_CF(data, cf, "[%s] cf_dns_start %s %s:%u", + Curl_resolv_query_str(ctx->dns_queries), + ctx->peer->unix_socket ? "unix-domain-socket" : "host", + ctx->peer->hostname, ctx->peer->port); + if(ctx->peer->unix_socket) + ctx->dns_queries = CURL_DNSQ_A; /* treat it like an A resolve */ + + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + if(Curl_is_ipv4addr(ctx->peer->hostname)) + ctx->dns_queries |= CURL_DNSQ_A; +#ifdef USE_IPV6 + else if(ctx->peer->ipv6) + ctx->dns_queries |= CURL_DNSQ_AAAA; +#endif + + result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport, + (bool)ctx->for_proxy, timeout_ms, + &ctx->resolv_id, pdns); + } +#ifdef USE_HTTPSRR + else if(ctx->dns_queries == CURL_DNSQ_HTTPS) { + result = Curl_resolv_https(data, ctx->peer, (bool)ctx->for_proxy, + timeout_ms, &ctx->resolv_id, pdns); + } +#endif + else { + failf(data, "unsupported DNS queries %x", ctx->dns_queries); + return CURLE_FAILED_INIT; + } + + DEBUGASSERT(!result || !*pdns); + if(!result) { /* resolved right away, either sync or from dnscache */ + DEBUGASSERT(*pdns); + return CURLE_OK; + } + else if(result == CURLE_AGAIN) { /* async resolv in progress */ + return CURLE_OK; + } + else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */ + failf(data, "Failed to resolve '%s' with timeout after %" + FMT_TIMEDIFF_T " ms", ctx->peer->hostname, + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); + return CURLE_OPERATION_TIMEDOUT; + } + else { + DEBUGASSERT(result); + if(ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s", ctx->peer->hostname); + return result; + } +} + +static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_dns_ctx *ctx = cf->ctx; + + if(ctx->resolv_result) + return TRUE; + else if(ctx->dns) + return TRUE; +#ifdef USE_CURL_ASYNC + else if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + const struct curltime *pnow; + timediff_t remain_ms; + /* For Happy Eyeballing, we can start on either A or AAAA resolves, + * but AAAA is preferred. We enforce a small delay for missing + * AAAA to arrive, then we let the connect continue. + * Note: if AAAA was never started (-4), it is considered to have + * an answer (e.g. a negative one). */ + if(Curl_resolv_has_answers(data, ctx->resolv_id, CURL_DNSQ_AAAA)) + return TRUE; + pnow = Curl_pgrs_now(data); + remain_ms = ctx->he_aaaa_await_ms - + Curl_resolv_elapsed_ms(data, ctx->resolv_id, pnow); + if(remain_ms <= 0) + return TRUE; + CURL_TRC_CF(data, cf, "[%s] still waiting %" FMT_TIMEDIFF_T + "ms for AAAA result", + Curl_resolv_query_str(ctx->dns_queries), remain_ms); + Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, remain_ms, pnow); + return FALSE; + } + else { + return TRUE; + } +#else + (void)data; + DEBUGASSERT(0); /* We should not come here */ + return FALSE; +#endif /* USE_CURL_ASYNC */ +} + +static CURLcode cf_dns_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct cf_dns_ctx *ctx = cf->ctx; + bool ip_query = (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)); + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!ctx->started) { + ctx->started = TRUE; + ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns); + } + + if(!ctx->dns && !ctx->resolv_result) { + ctx->resolv_result = + Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns); + } + + if(ctx->resolv_result && ip_query) { + /* failing A|AAAA resolves is a hard failure. */ + CURL_TRC_CF(data, cf, "[%s] error resolving: %d", + Curl_resolv_query_str(ctx->dns_queries), + (int)ctx->resolv_result); + return ctx->resolv_result; + } + + if(ctx->dns && !ctx->announced) { + ctx->announced = TRUE; + if((cf->sockindex == FIRSTSOCKET) && ip_query) { + cf->conn->bits.dns_resolved = TRUE; + Curl_pgrsTime(data, TIMER_NAMELOOKUP); + } + cf_dns_report(cf, data, ctx->dns); + } + + /* Delay connection sub-filters when we are still waiting for AAAA */ + if(!cf_dns_ready_to_connect(cf, data)) { + return CURLE_OK; + } + + if(cf->next && !cf->next->connected) { + bool sub_done; + CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done); + if(result || !sub_done) + return result; + DEBUGASSERT(sub_done); + } + + /* sub filter chain is connected, this means the filter has done + * its work and is connected as well, if it has results or not. */ + CURL_TRC_CF(data, cf, "connected filter chain below"); + *done = TRUE; + cf->connected = TRUE; + Curl_resolv_destroy(data, ctx->resolv_id); + return CURLE_OK; +} + +static void cf_dns_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_dns_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "destroy"); + cf_dns_ctx_destroy(data, ctx); +} + +static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ +#ifdef USE_CURL_ASYNC + if(!cf->connected) { + /* The pollset only works when we have started the resolving. + * Otherwise we might wait on the WAKEUP socketpair forever. + * Since DNS filters can be added in the middle of a connect + * attempt, they are not always started that way. */ + struct cf_dns_ctx *ctx = cf->ctx; + if(!ctx->started) { + CURL_TRC_CF(data, cf, "adjust_pollset, starting %s:%u queries=%s", + ctx->peer->hostname, ctx->peer->port, + Curl_resolv_query_str(ctx->dns_queries)); + ctx->started = TRUE; + ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns); + if(ctx->resolv_result || ctx->dns) { + Curl_multi_mark_dirty(data); + } + } + return Curl_resolv_pollset(data, ps); + } +#else + (void)cf; + (void)data; + (void)ps; +#endif + return CURLE_OK; +} + +static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_dns_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_DONE: + if(ctx->dns) { + /* Should only come here when the connect attempt failed and + * `data` is giving up on it. On a successful connect, we already + * unlinked the DNS entry. */ + Curl_dns_entry_unlink(data, &ctx->dns); + } + break; + default: + break; + } + return result; +} + +struct Curl_cftype Curl_cft_dns = { + "DNS", + CF_TYPE_SETUP | CF_TYPE_DNS, + CURL_LOG_LVL_NONE, + cf_dns_destroy, + cf_dns_connect, + Curl_cf_def_shutdown, + cf_dns_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + cf_dns_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_dns_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport, + bool for_proxy) +{ + struct Curl_cfilter *cf = NULL; + struct cf_dns_ctx *ctx; + CURLcode result = CURLE_OK; + + (void)data; + ctx = cf_dns_ctx_create(data, peer, dns_queries, transport, for_proxy); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_cf_create(&cf, &Curl_cft_dns, ctx); + +out: + *pcf = result ? NULL : cf; + if(result) + cf_dns_ctx_destroy(data, ctx); + return result; +} + +/* Adds a "resolv" filter at the top of the connection's filter chain. + * The filter will resolve the peer on the first connect attempt. */ +static CURLcode cf_dns_add(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + struct Curl_cfilter *cf = NULL; + bool for_proxy = FALSE; + CURLcode result; + + if(!peer) + return CURLE_FAILED_INIT; +#ifndef CURL_DISABLE_PROXY + for_proxy = (peer == conn->socks_proxy.peer) || + (peer == conn->http_proxy.peer); +#endif + + result = cf_dns_create(&cf, data, peer, dns_queries, transport, for_proxy); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +/* Insert a new "resolv" filter directly after `cf`. It will + * start a DNS resolve for the given peer on the + * first connect attempt. + * See socks.c on how this is used to make a non-blocking DNS + * resolve during connect. + */ +static CURLcode cf_dns_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_dns_create(&cf, data, peer, dns_queries, transport, FALSE); + if(result) + return result; + + Curl_conn_cf_insert_after(cf_at, cf); + return CURLE_OK; +} + +static CURLcode cf_dns_add_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + struct Curl_cfilter *cf_dns = NULL; + bool is_addr = CURL_DNSQ_IS_ADDR(dns_queries); + + if((dns_queries & CURL_DNSQ_HTTPS) && + Curl_is_ipaddr(peer->hostname)) { + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_HTTPS); + } + + for(; cf && dns_queries; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + cf_dns = cf; + if(Curl_peer_same_destination(ctx->peer, peer)) { + /* subtract queries already being scheduled/ongoing */ + dns_queries = (uint8_t)(~ctx->dns_queries & dns_queries); + if(!dns_queries) { /* already there */ + return CURLE_OK; + } + else if(is_addr && !ctx->started && + CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + ctx->dns_queries |= dns_queries; + CURL_TRC_DNS(data, "[%s] added queries=%s for %s:%u", + Curl_resolv_query_str(ctx->dns_queries), + Curl_resolv_query_str(dns_queries), + peer->hostname, peer->port); + return CURLE_OK; + } + } + } + } + + if(dns_queries) { + /* No existing filter is handling these, add a new DNS filter + * (after the last one if there was one already. FCFS. */ + if(cf_dns) + return cf_dns_insert_after(cf_dns, data, peer, dns_queries, transport); + else + return cf_dns_add(data, conn, sockindex, peer, dns_queries, transport); + } + return CURLE_OK; +} + +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + /* should only have address query bits */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + return cf_dns_add_resolve(data, conn, sockindex, peer, + (dns_queries & CURL_DNSQ_ADDR), transport); +} + +/* Return the result of the DNS address resolution for peer. + * Searches for a DNS filter from the top of the filter chain down. Returns + * - CURLE_AGAIN when not done yet + * - CURLE_OK when DNS was successfully resolved + * - CURLR_FAILED_INIT when no resolv filter was found + * - error returned by the DNS resolv + */ +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer) +{ + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA))) { + if(ctx->dns || ctx->resolv_result) + return ctx->resolv_result; + return CURLE_AGAIN; + } + } + } + return CURLE_FAILED_INIT; /* no one is resolving */ +} + +/* Return the addrinfo at `index` for the given `family` from the + * first "resolve" filter at the connection. If the DNS resolving is + * not done yet or if no address for the family exists, returns NULL. + */ +const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, + struct Curl_peer *peer, + int8_t sockindex, + int ai_family, + unsigned int index) +{ + struct connectdata *conn = data->conn; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA))) { + CURL_TRC_CF(data, cf, "get %uth result for %s:%u, family=%d, dns=%d", + index, peer->hostname, peer->port, ai_family, !!ctx->dns); + if(ctx->resolv_result) + return NULL; + else if(ctx->dns) { + /* A cached DNS entry may contain address families that we + * here never queried for. We want to give no results for those. */ + if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A)) + return NULL; +#ifdef USE_IPV6 + if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA)) + return NULL; +#endif + return Curl_addrinfo_get(ctx->dns->addr, ai_family, index); + } + else + return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index); + } + } + } + return NULL; +} + +#ifdef USE_HTTPSRR +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer) +{ + return cf_dns_add_resolve(data, conn, sockindex, peer, + CURL_DNSQ_HTTPS, conn->transport_wanted); +} + +/* Return the HTTPS-RR info from the first "resolve" filter at the + * connection. If the DNS resolving is not done yet or if there + * is no HTTPS-RR info, returns NULL. + */ +const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data, + int8_t sockindex, + struct Curl_peer *peer) +{ + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { + if(ctx->dns) + return ctx->dns->hinfo; + else + return Curl_resolv_get_https(data, ctx->resolv_id); + } + } + } + return NULL; +} + +bool Curl_conn_dns_resolved_https(struct Curl_easy *data, + int8_t sockindex, + struct Curl_peer *peer) +{ + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { + if(ctx->dns) + return TRUE; + else + return Curl_resolv_knows_https(data, ctx->resolv_id); + } + } + } + return TRUE; +} + +#endif /* USE_HTTPSRR */ diff --git a/lib/cf-dns.h b/lib/vdns/cf-dns.h similarity index 52% rename from lib/cf-dns.h rename to lib/vdns/cf-dns.h index 3c46b1bf3dc5..6226d2161afb 100644 --- a/lib/cf-dns.h +++ b/lib/vdns/cf-dns.h @@ -29,41 +29,41 @@ struct Curl_easy; struct connectdata; struct Curl_dns_entry; struct Curl_addrinfo; +struct Curl_peer; -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - uint8_t dns_queries, - uint8_t transport, - struct Curl_dns_entry *dns); +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport); -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - const char *hostname, - uint16_t port, - uint8_t transport, - bool complete_resolve); - -CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex); +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer); const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, - int sockindex, + struct Curl_peer *peer, + int8_t sockindex, int ai_family, unsigned int index); -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - int ai_family, - unsigned int index); - #ifdef USE_HTTPSRR -const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data, - int sockindex); -bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex); +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex, + struct Curl_peer *peer); + +const struct Curl_https_rrinfo * +Curl_conn_dns_get_https(struct Curl_easy *data, + int8_t sockindex, + struct Curl_peer *peer); +bool Curl_conn_dns_resolved_https(struct Curl_easy *data, + int8_t sockindex, + struct Curl_peer *peer); #else -#define Curl_conn_dns_get_https(a, b) NULL -#define Curl_conn_dns_resolved_https(a, b) TRUE +#define Curl_conn_dns_get_https(a, b, c) NULL +#define Curl_conn_dns_resolved_https(a, b, c) TRUE #endif extern struct Curl_cftype Curl_cft_dns; diff --git a/lib/dnscache.c b/lib/vdns/dnscache.c similarity index 66% rename from lib/dnscache.c rename to lib/vdns/dnscache.c index 20f6b2171441..bfd935ed472e 100644 --- a/lib/dnscache.c +++ b/lib/vdns/dnscache.c @@ -44,13 +44,13 @@ #include "curl_addrinfo.h" #include "curl_share.h" #include "curl_trc.h" -#include "dnscache.h" #include "hash.h" -#include "hostip.h" -#include "httpsrr.h" #include "progress.h" #include "rand.h" #include "strcase.h" +#include "vdns/dnscache.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/inet_ntop.h" #include "curlx/inet_pton.h" #include "curlx/strcopy.h" @@ -60,33 +60,57 @@ #define MAX_DNS_CACHE_SIZE 29999 -static void dnscache_entry_free(struct Curl_dns_entry *dns) +struct dnsc_id { + struct Curl_str name; + uint16_t port; + char type; +}; + +static void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer) { - Curl_freeaddrinfo(dns->addr); -#ifdef USE_HTTPSRR - if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); - } -#endif - curlx_free(dns); + curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname)); + pid->port = peer->port; + pid->type = type; } +static void dnsc_str2id(struct dnsc_id *pid, char type, + struct Curl_str *name, uint16_t port) +{ + pid->name = *name; + pid->port = port; + pid->type = type; +} + +struct dnsc_key { + char data[MAX_HOSTCACHE_LEN]; + size_t len; +}; + /* * Create a hostcache id string for the provided host + port, to be used by * the DNS caching. Without alloc. Return length of the id string. */ -static size_t create_dnscache_id(const char *name, - size_t nlen, /* 0 or actual name length */ - uint16_t port, char *ptr, size_t buflen) -{ - size_t len = nlen ? nlen : strlen(name); - DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); - if(len > (buflen - 7)) - len = buflen - 7; +static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) +{ + size_t namelen = curlx_strlen(&id->name); + if(namelen > (sizeof(key->data) - 8)) + namelen = sizeof(key->data) - 8; /* store and lower case the name */ - Curl_strntolower(ptr, name, len); - return curl_msnprintf(&ptr[len], 7, ":%u", port) + len; + key->data[0] = id->type; + Curl_strntolower(key->data + 1, curlx_str(&id->name), namelen); + /* include the terminating 0 in key length */ + key->len = namelen + 2 + + curl_msnprintf(&key->data[namelen + 1], 7, ":%u", id->port); +} + +static void dnscache_entry_free(struct Curl_dns_entry *dns) +{ + Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + Curl_httpsrr_destroy(dns->hinfo); +#endif + curlx_free(dns); } struct dnscache_prune_data { @@ -212,32 +236,36 @@ void Curl_dnscache_clear(struct Curl_easy *data) static CURLcode fetch_addr(struct Curl_easy *data, struct Curl_dnscache *dnscache, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pdns) { struct Curl_dns_entry *dns = NULL; - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; + struct dnsc_id id; + struct dnsc_key key; + char type = CURL_DNSQ_IS_ADDR(dns_queries) ? + CURL_DNST_ADDR : CURL_DNST_HTTPS; CURLcode result = CURLE_OK; *pdns = NULL; if(!dnscache) return CURLE_OK; - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, 0, port, - entry_id, sizeof(entry_id)); + dnsc_peer2id(&id, type, peer); + dnsc_id2key(&key, &id); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); /* No entry found in cache, check if we might have a wildcard entry */ - if(!dns && data->state.wildcard_resolve) { - entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); + if(!dns && (type == CURL_DNST_ADDR) && data->state.wildcard_resolve) { + struct Curl_str wildname; + + curlx_str_assign(&wildname, "*", 1); + dnsc_str2id(&id, CURL_DNST_ADDR, &wildname, peer->port); + dnsc_id2key(&key, &id); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); } if(dns && (data->set.dns_cache_timeout_ms != -1)) { @@ -251,13 +279,15 @@ static CURLcode fetch_addr(struct Curl_easy *data, if(dnscache_entry_is_stale(&user, dns)) { infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ - Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); } } - if(dns) { - if((dns->dns_queries & dns_queries) != dns_queries) { - /* The entry does not cover all wanted DNS queries, a miss. */ + /* We need to cache address information and HTTPS-RR separately. */ + if(dns && CURL_DNSQ_IS_ADDR(dns_queries)) { + if((uint8_t)(dns->dns_queries & dns_queries) != + (uint8_t)(dns_queries & CURL_DNSQ_ADDR)) { + /* The entry does not cover all wanted address queries, a miss. */ dns = NULL; } else if(!(dns->dns_responses & dns_queries)) { @@ -267,12 +297,12 @@ static CURLcode fetch_addr(struct Curl_easy *data, dns = NULL; result = CURLE_COULDNT_RESOLVE_HOST; } + else if(dns && !dns->addr) { /* negative entry */ + dns = NULL; + result = CURLE_COULDNT_RESOLVE_HOST; + } } - if(dns && !dns->addr) { /* negative entry */ - dns = NULL; - result = CURLE_COULDNT_RESOLVE_HOST; - } *pdns = dns; return result; } @@ -293,8 +323,7 @@ static CURLcode fetch_addr(struct Curl_easy *data, */ CURLcode Curl_dnscache_get(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pentry) { struct Curl_dnscache *dnscache = dnscache_get(data); @@ -302,7 +331,7 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, CURLcode result = CURLE_OK; dnscache_lock(data, dnscache); - result = fetch_addr(data, dnscache, dns_queries, hostname, port, &dns); + result = fetch_addr(data, dnscache, dns_queries, peer, &dns); if(!result && dns) dns->refcount++; /* we pass out a reference */ else if(result) { @@ -311,6 +340,9 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, } dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cache lookup %s:%u queries=%s -> %d %sfound", + peer->hostname, peer->port, Curl_resolv_query_str(dns_queries), + (int)result, dns ? "" : "not "); *pentry = dns; return result; } @@ -351,7 +383,7 @@ UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data, if(num_addrs > 1) { struct Curl_addrinfo **nodes; - CURL_TRC_DNS(data, "Shuffling %i addresses", num_addrs); + CURL_TRC_DNS(data, "Shuffling %d addresses", num_addrs); nodes = curlx_malloc(num_addrs * sizeof(*nodes)); if(nodes) { @@ -397,8 +429,7 @@ UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data, } #endif -static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, - int ai_family) +static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family) { for(; ai; ai = ai->ai_next) { if(ai->ai_family == ai_family) @@ -407,28 +438,23 @@ static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, return FALSE; } -static struct Curl_dns_entry *dnscache_entry_create( - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - size_t hostlen, - uint16_t port, - bool permanent) +static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data, + struct dnsc_id *pid, + bool permanent) { struct Curl_dns_entry *dns = NULL; /* Create a new cache entry, struct already has the hostname NUL */ - dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen); + dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + + curlx_strlen(&pid->name)); if(!dns) goto out; dns->refcount = 1; /* the cache has the first reference */ - dns->dns_queries = dns_queries; - dns->port = port; - if(hostlen) - memcpy(dns->hostname, hostname, hostlen); + dns->hostlen = curlx_strlen(&pid->name); + dns->port = pid->port; + if(dns->hostlen) + memcpy(dns->hostname, curlx_str(&pid->name), dns->hostlen); if(permanent) { dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ @@ -438,6 +464,28 @@ static struct Curl_dns_entry *dnscache_entry_create( dns->timestamp = *Curl_pgrs_now(data); } +out: + return dns; +} + +static struct Curl_dns_entry *dnsc_entry_assign_addr( + struct Curl_easy *data, + struct Curl_dns_entry *dns, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2) +{ + if(!dns) + goto out; + /* only do this when this is the only reference */ + DEBUGASSERT(dns->refcount == 1); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + + dns->type = CURL_DNST_ADDR; + /* queries should only be about addresses */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + dns->dns_queries = (dns_queries & CURL_DNSQ_ADDR); + /* Take the given address lists into the entry */ if(paddr1 && *paddr1) { dns->addr = *paddr1; @@ -488,79 +536,160 @@ static struct Curl_dns_entry *dnscache_entry_create( return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - uint16_t port) +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct Curl_peer *peer) { - return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct dnsc_id id; + struct Curl_dns_entry *dns; + + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - uint16_t port) +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2, + struct Curl_peer *peer) { - return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct dnsc_id id; + struct Curl_dns_entry *dns; + + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2); + return dns; } #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo) +static struct Curl_dns_entry *dnsc_entry_assign_https( + struct Curl_dns_entry *dns, + struct Curl_https_rrinfo **phinfo) { + if(!dns) + goto out; /* only do this when this is the only reference */ DEBUGASSERT(dns->refcount == 1); - /* it should have been in the queries */ - DEBUGASSERT(dns->dns_queries & CURL_DNSQ_HTTPS); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); + Curl_httpsrr_destroy(dns->hinfo); + dns->hinfo = NULL; } - dns->hinfo = hinfo; - dns->dns_responses |= CURL_DNSQ_HTTPS; + dns->type = CURL_DNST_HTTPS; + dns->dns_responses = dns->dns_queries = CURL_DNSQ_HTTPS; + if(phinfo) { + dns->hinfo = *phinfo; + *phinfo = NULL; + } +out: + if(phinfo && *phinfo) { + Curl_httpsrr_destroy(*phinfo); + *phinfo = NULL; + } + return dns; +} + +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + struct Curl_peer *peer) +{ + struct dnsc_id id; + struct Curl_dns_entry *dns; + + dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); + dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_assign_https(dns, phinfo); + return dns; +} + +static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_https_rrinfo **phinfo, + struct dnsc_id *id, + bool permanent) +{ + struct Curl_dns_entry *dns, *dns2; + struct dnsc_key key; + + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_https(dns, phinfo); + if(!dns) + return NULL; + + /* Store the resolved data in our DNS cache. */ + dnsc_id2key(&key, id); + dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + if(!dns2) { + dnscache_entry_free(dns); + return NULL; + } + + dns = dns2; + dns->refcount++; /* mark entry as in-use */ + return dns; } #endif /* USE_HTTPSRR */ -static struct Curl_dns_entry *dnscache_add_addr(struct Curl_easy *data, - struct Curl_dnscache *dnscache, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - size_t hlen, - uint16_t port, - bool permanent) +static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct dnsc_id *id, + struct dnsc_key *key, + bool permanent) { - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - dns = dnscache_entry_create(data, dns_queries, paddr, NULL, - hostname, hlen, port, permanent); + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, hlen, port, - entry_id, sizeof(entry_id)); + /* Store the resolved data in our DNS cache. */ + dns2 = Curl_hash_add(&dnscache->entries, key->data, key->len, (void *)dns); + if(!dns2) { + dnscache_entry_free(dns); + return NULL; + } + + dns = dns2; + dns->refcount++; /* mark entry as in-use */ + return dns; +} + +static struct Curl_dns_entry *dnsc_add_peer_addr( + struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct dnsc_id *id, + bool permanent) +{ + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + struct dnsc_key key; + + dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + if(!dns) + return NULL; /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, - (void *)dns); + dnsc_id2key(&key, id); + dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); if(!dns2) { dnscache_entry_free(dns); return NULL; } dns = dns2; - dns->refcount++; /* mark entry as in-use */ + dns->refcount++; /* mark entry as in-use */ return dns; } @@ -568,66 +697,78 @@ 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; + struct Curl_str name; + struct dnsc_id id; + struct dnsc_key key; if(!dnscache) return CURLE_FAILED_INIT; - /* Create an entry id, based upon the hostname and port */ - idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id)); + if(!entry || (entry->type == CURL_DNST_INIT)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + curlx_str_assign(&name, entry->hostname, entry->hostlen); + dnsc_str2id(&id, entry->type, &name, entry->port); + dnsc_id2key(&key, &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)) { + if(!Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)entry)) { dnscache_unlock(data, dnscache); return CURLE_OUT_OF_MEMORY; } entry->refcount++; dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cached entry for %s:%u queries=%s", + entry->hostname, entry->port, + Curl_resolv_query_str(entry->dns_queries)); return CURLE_OK; } CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, uint8_t dns_queries, - const char *host, - uint16_t port) + struct Curl_peer *peer) { struct Curl_dnscache *dnscache = dnscache_get(data); - struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns = NULL; + struct dnsc_id id; + CURLcode result = CURLE_OK; + DEBUGASSERT(dnscache); if(!dnscache) return CURLE_FAILED_INIT; dnscache_lock(data, dnscache); - /* put this new host in the cache */ - dns = dnscache_add_addr(data, dnscache, dns_queries, NULL, - host, strlen(host), port, FALSE); + if(dns_queries & CURL_DNSQ_ADDR) { + /* put this new host in the cache */ + dnsc_peer2id(&id, CURL_DNST_ADDR, peer); + dns = dnsc_add_peer_addr(data, dnscache, dns_queries, NULL, &id, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#ifdef USE_HTTPSRR + else if(dns_queries == CURL_DNSQ_HTTPS) { + dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); + dns = dnsc_add_https(data, dnscache, NULL, &id, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#endif + else { + /* a query we do not know, just cache nothing */ + DEBUGASSERT(0); + } + if(dns) { /* release the returned reference; the cache itself will keep the * entry alive: */ dns->refcount--; - dnscache_unlock(data, dnscache); CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s", - host, port, Curl_resolv_query_str(dns_queries)); - return CURLE_OK; + peer->hostname, peer->port, + Curl_resolv_query_str(dns_queries)); } dnscache_unlock(data, dnscache); - return CURLE_OUT_OF_MEMORY; -} - -struct Curl_dns_entry *Curl_dns_entry_link(struct Curl_easy *data, - struct Curl_dns_entry *dns) -{ - if(!dns) - return NULL; - else { - struct Curl_dnscache *dnscache = dnscache_get(data); - dnscache_lock(data, dnscache); - dns->refcount++; - dnscache_unlock(data, dnscache); - return dns; - } + return result; } /* @@ -679,6 +820,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) { struct Curl_dnscache *dnscache = dnscache_get(data); struct curl_slist *hostp; + struct dnsc_id id; + struct dnsc_key key; if(!dnscache) return CURLE_FAILED_INIT; @@ -687,14 +830,12 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { - char entry_id[MAX_HOSTCACHE_LEN]; const char *host = hostp->data; struct Curl_str source; if(!host) continue; if(*host == '-') { curl_off_t num = 0; - size_t entry_len; host++; if(!curlx_str_single(&host, '[')) { if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || @@ -711,19 +852,17 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(!curlx_str_number(&host, &num, 0xffff)) { /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), - curlx_strlen(&source), (uint16_t)num, - entry_id, sizeof(entry_id)); + dnsc_str2id(&id, CURL_DNST_ADDR, &source, (uint16_t)num); + dnsc_id2key(&key, &id); dnscache_lock(data, dnscache); /* delete entry, ignore if it did not exist */ - Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); dnscache_unlock(data, dnscache); } } else { struct Curl_dns_entry *dns; struct Curl_addrinfo *head = NULL, *tail = NULL; - size_t entry_len; char address[64]; curl_off_t tmpofft = 0; uint16_t port = 0; @@ -789,7 +928,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) result = Curl_str2addr(address, port, &ai); if(result) { - infof(data, "Resolve address '%s' found illegal", address); + infof(data, "Resolve IP address '%s' found is illegal", address); goto err; } @@ -815,19 +954,16 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) return CURLE_SETOPT_OPTION_SYNTAX; } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), - port, entry_id, sizeof(entry_id)); - + dnsc_str2id(&id, CURL_DNST_ADDR, &source, port); + dnsc_id2key(&key, &id); dnscache_lock(data, dnscache); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); if(dns) { infof(data, "RESOLVE %.*s:%u - old addresses discarded", - (int)curlx_strlen(&source), - curlx_str(&source), port); + (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, @@ -839,13 +975,12 @@ 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(&dnscache->entries, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, key.data, key.len); } - /* put this new host in the cache, an overridy for ALL dns queries */ - dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL, - &head, curlx_str(&source), - curlx_strlen(&source), port, permanent); + /* put this new host in the cache, override all address queries */ + dns = dnsc_add_addr(data, dnscache, CURL_DNSQ_ADDR, &head, + &id, &key, permanent); if(dns) /* release the returned reference; the cache itself will keep the * entry alive: */ @@ -856,9 +991,9 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(!dns) return CURLE_OUT_OF_MEMORY; - infof(data, "Added %.*s:%u:%s to DNS cache%s", - (int)curlx_strlen(&source), curlx_str(&source), port, addresses, - permanent ? "" : " (non-permanent)"); + infof(data, "[DNS] added %.*s:%u:%s to cache%s", + (int)curlx_strlen(&id.name), curlx_str(&id.name), id.port, + addresses, permanent ? "" : " (non-permanent)"); /* Wildcard hostname */ if(curlx_str_casecompare(&source, "*")) { diff --git a/lib/dnscache.h b/lib/vdns/dnscache.h similarity index 74% rename from lib/dnscache.h rename to lib/vdns/dnscache.h index ebe25f6dd416..73dcff60d70f 100644 --- a/lib/dnscache.h +++ b/lib/vdns/dnscache.h @@ -35,6 +35,11 @@ struct connectdata; struct easy_pollset; struct Curl_https_rrinfo; struct Curl_multi; +struct Curl_peer; + +#define CURL_DNST_INIT '\0' +#define CURL_DNST_ADDR 'A' +#define CURL_DNST_HTTPS 'H' struct Curl_dns_entry { struct Curl_addrinfo *addr; @@ -43,10 +48,12 @@ struct Curl_dns_entry { #endif /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ struct curltime timestamp; + size_t hostlen; /* reference counter, entry is freed on reaching 0 */ uint32_t refcount; /* hostname port number that resolved to addr. */ uint16_t port; + char type; /* CURL_DNST_ADDR or CURL_DNST_HTTPS */ uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */ uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ @@ -62,29 +69,23 @@ struct Curl_dns_entry { * * Returns entry or NULL on OOM. */ -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - uint16_t port); - -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, - const char *hostname, - uint16_t port); +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + struct Curl_peer *peer); + +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2, + struct Curl_peer *peer); #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo); +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + struct Curl_peer *peer); #endif /* USE_HTTPSRR */ -/* Increase the ref counter and return it for storing in another place. - * May be called with NULL, in which case it returns NULL. */ -struct Curl_dns_entry *Curl_dns_entry_link(struct Curl_easy *data, - struct Curl_dns_entry *dns); - /* unlink a dns entry, frees all resources if it was the last reference. * Always clears `*pdns`` */ void Curl_dns_entry_unlink(struct Curl_easy *data, @@ -117,8 +118,7 @@ void Curl_dnscache_clear(struct Curl_easy *data); */ CURLcode Curl_dnscache_get(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, struct Curl_dns_entry **pentry); /* @@ -132,8 +132,7 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, * it could not be resolved. */ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, uint8_t dns_queries, - const char *host, - uint16_t port); + struct Curl_peer *peer); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. diff --git a/lib/doh.c b/lib/vdns/doh.c similarity index 68% rename from lib/doh.c rename to lib/vdns/doh.c index 30441358ca54..3b52ed92f362 100644 --- a/lib/doh.c +++ b/lib/vdns/doh.c @@ -27,12 +27,12 @@ #include "urldata.h" #include "curl_addrinfo.h" -#include "doh.h" #include "curl_trc.h" -#include "httpsrr.h" #include "multiif.h" #include "url.h" #include "connect.h" +#include "vdns/doh.h" +#include "vdns/httpsrr.h" #include "curlx/strdup.h" #include "curlx/dynbuf.h" #include "escape.h" /* for Curl_hexencode() */ @@ -44,7 +44,7 @@ static void doh_close(struct Curl_easy *data, struct Curl_resolv_async *async); #ifdef CURLVERBOSE -static const char * const errors[] = { +static const char * const doh_code_str[] = { "", "Bad label", "Out of range", @@ -58,16 +58,35 @@ static const char * const errors[] = { "Unexpected CLASS", "No content", "Bad ID", - "Name too long" + "Name too long", + "No such name", + "Transport failed", + "Out Of Memory" }; static const char *doh_strerror(DOHcode code) { - if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG)) - return errors[code]; + if((size_t)code < CURL_ARRAYSIZE(doh_code_str)) + return doh_code_str[code]; return "bad error code"; } +static const char *doh_type2name(DNStype dnstype) +{ + switch(dnstype) { + case CURL_DNS_TYPE_A: + return "A"; + case CURL_DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case CURL_DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } +} + #endif /* CURLVERBOSE */ /* @unittest 1655 @@ -189,89 +208,8 @@ static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb, return realsize; } -#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) && defined(CURLVERBOSE) - -/* 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) -{ - 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); -} -#endif - -/* called from multi when a sub transfer, e.g. doh probe, is done. - * This looks up the probe response at its meta CURL_EZM_DOH_PROBE - * and copies the response body over to the struct at the master's - * meta at CURL_EZM_DOH_MASTER. */ -static void doh_probe_done(struct Curl_easy *data, - struct Curl_easy *doh, CURLcode result) -{ - struct Curl_resolv_async *async = NULL; - struct doh_probes *dohp = NULL; - struct doh_request *doh_req = NULL; - int i; - - doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); - if(!doh_req) { - DEBUGASSERT(0); - return; - } - - async = Curl_async_get(data, doh_req->resolv_id); - if(!async) { - CURL_TRC_DNS(data, "[%u] ignoring outdated DoH response", - doh_req->resolv_id); - return; - } - dohp = async->doh; - - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - if(dohp->probe_resp[i].probe_mid == doh->mid) - break; - } - /* We really should have found the slot where to store the response */ - if(i >= DOH_SLOT_COUNT) { - DEBUGASSERT(0); - failf(data, "DoH: unknown sub request done"); - return; - } - - dohp->pending--; - infof(doh, "a DoH request is completed, %u to go", dohp->pending); - dohp->probe_resp[i].result = result; - /* We expect either the meta data still to exist or the sub request - * to have already failed. */ - if(!result) { - dohp->probe_resp[i].dnstype = doh_req->dnstype; - result = curlx_dyn_addn(&dohp->probe_resp[i].body, - curlx_dyn_ptr(&doh_req->resp_body), - curlx_dyn_len(&doh_req->resp_body)); - curlx_dyn_free(&doh_req->resp_body); - } - Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); - - if(result) - infof(doh, "DoH request %s", curl_easy_strerror(result)); - - if(!dohp->pending) { - /* DoH completed, run the transfer picking up the results */ - Curl_multi_mark_dirty(data); - } -} - +static void doh_probe_done(struct Curl_easy *doh, + struct Curl_easy *master, CURLcode result); static void doh_probe_dtor(void *key, size_t klen, void *e) { (void)key; @@ -305,6 +243,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, timediff_t timeout_ms; struct doh_request *doh_req; DOHcode d; + bool maybe_https = !curl_strnequal(url, STRCONST("http:")); *pmid = UINT32_MAX; @@ -319,7 +258,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, sizeof(doh_req->req_body), &doh_req->req_body_len); if(d) { - failf(data, "Failed to encode DoH packet [%d]", d); + failf(data, "Failed to encode DoH packet [%d]", (int)d); result = CURLE_OUT_OF_MEMORY; goto error; } @@ -344,7 +283,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, /* pass in the struct pointer via a local variable to please coverity and the gcc typecheck helpers */ - VERBOSE(doh->state.feat = &Curl_trc_feat_dns); + VERBOSE(doh->state.feat = &Curl_trc_feat_doh); ERROR_CHECK_SETOPT(CURLOPT_URL, url); ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb); @@ -353,8 +292,10 @@ static CURLcode doh_probe_run(struct Curl_easy *data, 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_PIPEWAIT, 1L); + if(maybe_https) { + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); + } #endif #ifndef DEBUGBUILD /* enforce HTTPS if not debug */ @@ -367,61 +308,68 @@ static CURLcode doh_probe_run(struct Curl_easy *data, 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)) + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh)) 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. - */ - doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; - doh->set.ssl.custom_capath = data->set.ssl.custom_capath; - doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; - 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]); - } - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, - (long)data->set.ssl.primary.ssl_options); + if(maybe_https) { + 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. + */ + doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; + doh->set.ssl.custom_capath = data->set.ssl.custom_capath; + doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; + if(CURL_EASY_STR(data, STRING_SSL_CAFILE)) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO, + CURL_EASY_STR(data, STRING_SSL_CAFILE)); + } + if(data->set.blobs[BLOB_CAINFO]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]); + } + if(CURL_EASY_STR(data, STRING_SSL_CAPATH)) { + ERROR_CHECK_SETOPT(CURLOPT_CAPATH, + CURL_EASY_STR(data, STRING_SSL_CAPATH)); + } + if(CURL_EASY_STR(data, STRING_SSL_CRLFILE)) { + ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, + CURL_EASY_STR(data, 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(CURL_EASY_STR(data, STRING_SSL_EC_CURVES)) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, + CURL_EASY_STR(data, STRING_SSL_EC_CURVES)); + } + + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, + ((long)data->set.ssl.primary.ssl_options & + ~CURLSSLOPT_AUTO_CLIENT_CERT)); + } doh->state.internal = TRUE; doh->master_mid = data->mid; /* master transfer of this one */ + doh->sub_xfer_done = doh_probe_done; result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor); doh_req = NULL; /* call took ownership */ @@ -434,7 +382,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, private_data via CURLOPT_PRIVATE if they so choose. */ DEBUGASSERT(!doh->set.private_data); - if(curl_multi_add_handle(multi, doh)) + if(Curl_multi_add_handle(multi, doh)) goto error; *pmid = doh->mid; @@ -460,69 +408,83 @@ CURLcode Curl_doh(struct Curl_easy *data, size_t i; DEBUGASSERT(!async->doh); - DEBUGASSERT(async->hostname[0]); + DEBUGASSERT(async->peer->hostname[0]); if(async->doh) { DEBUGASSERT(0); /* should not happen */ Curl_doh_cleanup(data, async); } + if(!async->dns_queries) + return CURLE_BAD_FUNCTION_ARGUMENT; +#ifdef USE_HTTPSRR + if(CURL_DNSQ_IS_ADDR(async->dns_queries) && + (async->dns_queries & CURL_DNSQ_HTTPS)) { + /* Can't mix those in the same async resolve */ + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } +#else + if(async->dns_queries & CURL_DNSQ_HTTPS) { + DEBUGASSERT(0); + return CURLE_NOT_BUILT_IN; + } +#endif + /* start clean, consider allocating this struct on demand */ async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes)); if(!dohp) return CURLE_OUT_OF_MEMORY; for(i = 0; i < DOH_SLOT_COUNT; ++i) { - dohp->probe_resp[i].probe_mid = UINT32_MAX; - curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); - } - - dohp->host = async->hostname; - dohp->port = async->port; - /* We are making sub easy handles and want to be called back when - * one is done. */ - data->sub_xfer_done = doh_probe_done; - - /* create IPv4 DoH request */ - if(async->dns_queries & CURL_DNSQ_A) { - result = doh_probe_run(data, CURL_DNS_TYPE_A, - async->hostname, data->set.str[STRING_DOH], - data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); - if(result) - goto error; - dohp->pending++; + dohp->probe_rc[i] = DOH_OK; + dohp->probe_mid[i] = UINT32_MAX; } #ifdef USE_IPV6 + /* AAAA results have preference in happy eyeballing, trigger first */ if(async->dns_queries & CURL_DNSQ_AAAA) { /* create IPv6 DoH request */ result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, - async->hostname, data->set.str[STRING_DOH], + async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); + &dohp->probe_mid[DOH_SLOT_IPV6]); if(result) goto error; - dohp->pending++; + async->queries_ongoing++; } #endif + /* create IPv4 DoH request */ + if(async->dns_queries & CURL_DNSQ_A) { + result = doh_probe_run(data, CURL_DNS_TYPE_A, + async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), + data->multi, async->id, + &dohp->probe_mid[DOH_SLOT_IPV4]); + if(result) + goto error; + async->queries_ongoing++; + } + #ifdef USE_HTTPSRR if(async->dns_queries & CURL_DNSQ_HTTPS) { char *qname = NULL; - if(async->port != PORT_HTTPS) { - qname = curl_maprintf("_%d._https.%s", async->port, async->hostname); + if(async->peer->port != PORT_HTTPS) { + qname = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); if(!qname) goto error; } result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS, - qname ? qname : async->hostname, - data->set.str[STRING_DOH], data->multi, + qname ? qname : async->peer->hostname, + CURL_EASY_STR(data, STRING_DOH), data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); + &dohp->probe_mid[DOH_SLOT_HTTPS_RR]); curlx_free(qname); if(result) goto error; - dohp->pending++; + async->queries_ongoing++; } #endif return CURLE_OK; @@ -616,58 +578,7 @@ static DOHcode doh_store_https(const unsigned char *doh, int index, } #endif -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 */ - unsigned char length; - - if(d->numcname == DOH_MAX_CNAME) - return DOH_OK; /* skip! */ - - c = &d->cname[d->numcname++]; - do { - if(index >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - length = doh[index]; - if((length & 0xc0) == 0xc0) { - int newpos; - /* name pointer, get the new offset (14 bits) */ - if((index + 1) >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - - /* move to the new index */ - newpos = (length & 0x3f) << 8 | doh[index + 1]; - index = (unsigned int)newpos; - continue; - } - else if(length & 0xc0) - return DOH_DNS_BAD_LABEL; /* bad input */ - else - index++; - - if(length) { - 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(curlx_dyn_addn(c, &doh[index], length)) - return DOH_OUT_OF_MEM; - index += length; - } - } while(length && --loop); - - if(!loop) - return DOH_DNS_LABEL_LOOP; - return DOH_OK; -} - static DOHcode doh_rdata(const unsigned char *doh, - size_t dohlen, unsigned short rdlength, unsigned short type, int index, @@ -676,10 +587,7 @@ static DOHcode doh_rdata(const unsigned char *doh, /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes - HTTPS (TYPE 65): N bytes */ - DOHcode rc; - switch(type) { case CURL_DNS_TYPE_A: if(rdlength != 4) @@ -692,22 +600,15 @@ static DOHcode doh_rdata(const unsigned char *doh, doh_store_aaaa(doh, index, d); break; #ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - rc = doh_store_https(doh, index, d, rdlength); + case CURL_DNS_TYPE_HTTPS: { + DOHcode rc = doh_store_https(doh, index, d, rdlength); if(rc) return rc; break; + } #endif - case CURL_DNS_TYPE_CNAME: - rc = doh_store_cname(doh, dohlen, (unsigned int)index, d); - if(rc) - return rc; - break; - case CURL_DNS_TYPE_DNAME: - /* explicit for clarity; skip; rely on synthesized CNAME */ - break; default: - /* unsupported type, skip it */ + /* unsupported type, or type we do not store, skip it */ break; } return DOH_OK; @@ -717,14 +618,13 @@ static DOHcode doh_rdata(const unsigned char *doh, UNITTEST void de_init(struct dohentry *de); UNITTEST void de_init(struct dohentry *de) { - int i; memset(de, 0, sizeof(*de)); de->ttl = INT_MAX; - for(i = 0; i < DOH_MAX_CNAME; i++) - curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME); } -/* @unittest 1655 */ +/* TTL value cap */ +#define MAX_DNS_TTL 86400U /* 24 hours */ +/* @unittest 1650 */ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, size_t dohlen, DNStype dnstype, @@ -749,6 +649,8 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(!doh || doh[0] || doh[1]) return DOH_DNS_BAD_ID; /* bad ID */ rcode = doh[3] & 0x0f; + if(rcode == 3) + return DOH_DNS_NXDOMAIN; /* name does not exist */ if(rcode) return DOH_DNS_BAD_RCODE; /* bad rcode */ @@ -779,7 +681,7 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if((type != CURL_DNS_TYPE_CNAME) && /* may be synthesized from DNAME */ (type != CURL_DNS_TYPE_DNAME) && /* if present, accept and ignore */ (type != dnstype)) - /* Not the same type as was asked for nor CNAME nor DNAME */ + /* Not the same type as was asked for, nor CNAME nor DNAME */ return DOH_DNS_UNEXPECTED_TYPE; index += 2; @@ -794,6 +696,8 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, return DOH_DNS_OUT_OF_RANGE; ttl = doh_get32bit(doh, index); + if(ttl > MAX_DNS_TTL) + ttl = MAX_DNS_TTL; if(ttl < d->ttl) d->ttl = ttl; index += 4; @@ -806,9 +710,9 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; - rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); + rc = doh_rdata(doh, rdlength, type, (int)index, d); if(rc) - return rc; /* bad doh_rdata */ + return rc; index += rdlength; ancount--; } @@ -860,69 +764,10 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ -#ifdef USE_HTTPSRR - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr && - !d->numhttps_rrs) -#else - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr) -#endif - /* nothing stored! */ - return DOH_NO_CONTENT; - return DOH_OK; /* ok */ } -#ifdef CURLVERBOSE -static void doh_show(struct Curl_easy *data, - const struct dohentry *d) -{ - int i; - 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 == CURL_DNS_TYPE_A) { - 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 == CURL_DNS_TYPE_AAAA) { - int j; - 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; - curl_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; - ptr += l; - } - infof(data, "%s", buffer); - } - } -#ifdef USE_HTTPSRR - for(i = 0; i < d->numhttps_rrs; i++) { -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - 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 /* USE_HTTPSRR */ - for(i = 0; i < d->numcname; i++) { - infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i])); - } -} -#else -#define doh_show(x, y) -#endif - /* - * doh2ai() - * * 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 @@ -932,7 +777,6 @@ static void doh_show(struct Curl_easy *data, * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). */ - static CURLcode doh2ai(const struct dohentry *de, const char *hostname, int port, struct Curl_addrinfo **aip) { @@ -943,14 +787,9 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, #ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif + size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ CURLcode result = CURLE_OK; int i; - size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ - - DEBUGASSERT(de); - - if(!de->numaddr) - return CURLE_COULDNT_RESOLVE_HOST; for(i = 0; i < de->numaddr; i++) { size_t ss_size; @@ -1028,35 +867,16 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, return result; } -#ifdef CURLVERBOSE -static const char *doh_type2name(DNStype dnstype) -{ - switch(dnstype) { - case CURL_DNS_TYPE_A: - return "A"; - case CURL_DNS_TYPE_AAAA: - return "AAAA"; -#ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - return "HTTPS"; -#endif - default: - return "unknown"; - } -} -#endif - /* @unittest 1655 */ UNITTEST void de_cleanup(struct dohentry *d); UNITTEST void de_cleanup(struct dohentry *d) { - int i = 0; - for(i = 0; i < d->numcname; i++) { - curlx_dyn_free(&d->cname[i]); - } #ifdef USE_HTTPSRR + int i = 0; for(i = 0; i < d->numhttps_rrs; i++) curlx_safefree(d->https_rrs[i].val); +#else + (void)d; #endif } @@ -1087,10 +907,14 @@ static CURLcode doh_decode_rdata_name(const unsigned char **buf, DEBUGASSERT(buf && remaining && dnsname); if(!buf || !remaining || !dnsname || !*remaining) return CURLE_OUT_OF_MEMORY; - curlx_dyn_init(&thename, CURL_MAXLEN_host_name); + curlx_dyn_init(&thename, CURL_MAXLEN_HOST_NAME); rem = *remaining; cp = *buf; clen = *cp++; + /* RFC 9460 says it must be uncompressed */ + if(clen > 63) + return CURLE_WEIRD_SERVER_REPLY; + if(clen == 0) { /* special case - return "." as name */ if(curlx_dyn_addn(&thename, ".", 1)) @@ -1112,6 +936,11 @@ static CURLcode doh_decode_rdata_name(const unsigned char **buf, return CURLE_OUT_OF_MEMORY; } clen = *cp++; + if(clen > 63) { + /* invalid format */ + curlx_dyn_free(&thename); + return CURLE_WEIRD_SERVER_REPLY; + } } *buf = cp; *remaining = rem - 1; @@ -1169,51 +998,149 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, len -= plen; expected_min_pcode = pcode + 1; } - DEBUGASSERT(!len); *hrr = lhrr; return CURLE_OK; err: - Curl_httpsrr_cleanup(lhrr); - curlx_safefree(lhrr); + Curl_httpsrr_destroy(lhrr); return result; } -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) -static void doh_print_httpsrr(struct Curl_easy *data, - struct Curl_https_rrinfo *hrr) +#endif /* USE_HTTPSRR */ + +/* called from multi when a sub transfer, e.g. doh probe, is done. + * Parse the response and set the results in the `async` context + * of master, using the id from the probe's CURL_EZM_DOH_PROBE + * meta data. */ +static void doh_probe_done(struct Curl_easy *doh, + struct Curl_easy *master, CURLcode result) { - 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); + struct Curl_resolv_async *async = NULL; + struct doh_probes *dohp = NULL; + struct doh_request *doh_req = NULL; + struct Curl_addrinfo **pdest_ai; + struct dohentry de; + int slot, httpcode; + + de_init(&de); + doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + if(!doh_req) { + /* transfer `doh` is not a DoH probe. */ + DEBUGASSERT(0); + goto out; } - else - infof(data, "HTTPS RR: no ipv4hints"); - if(hrr->echconfiglist) { - doh_print_buf(data, "HTTPS RR: ECHConfigList", - hrr->echconfiglist, hrr->echconfiglist_len); + + async = Curl_async_get(master, doh_req->resolv_id); + if(!async) { + CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response", + doh_req->resolv_id); + goto out; } - else - infof(data, "HTTPS RR: no ECHConfigList"); - if(hrr->ipv6hints) { - doh_print_buf(data, "HTTPS RR: ipv6hint", - hrr->ipv6hints, hrr->ipv6hints_len); + dohp = async->doh; + + for(slot = 0; slot < DOH_SLOT_COUNT; ++slot) { + if(dohp->probe_mid[slot] == doh->mid) + break; } + /* We really should have found the slot where to store the response */ + if(slot >= DOH_SLOT_COUNT) { + failf(master, "DoH: unknown sub request done"); + DEBUGASSERT(0); + goto out; + } + + async->queries_ongoing--; + dohp = async->doh; + httpcode = doh->info.httpcode; + switch(slot) { + case DOH_SLOT_IPV4: + async->dns_responses |= CURL_DNSQ_A; + break; +#ifdef USE_IPV6 + case DOH_SLOT_IPV6: + async->dns_responses |= CURL_DNSQ_AAAA; + break; +#endif +#ifdef USE_HTTPSRR + case DOH_SLOT_HTTPS_RR: + async->dns_responses |= CURL_DNSQ_HTTPS; + break; +#endif + default: + DEBUGASSERT(0); + break; + } + + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + goto out; + } + else if((httpcode < 200) || (httpcode >= 300)) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: HTTP status %d", + doh_type2name(doh_req->dnstype), httpcode); + goto out; + } + + dohp->probe_rc[slot] = doh_resp_decode(curlx_dyn_uptr(&doh_req->resp_body), + curlx_dyn_len(&doh_req->resp_body), + doh_req->dnstype, &de); + if(dohp->probe_rc[slot]) { +#ifdef USE_HTTPSRR + if((dohp->probe_rc[slot] == DOH_NO_CONTENT) && + (doh_req->dnstype == CURL_DNS_TYPE_HTTPS)) { + dohp->probe_rc[slot] = DOH_DNS_NXDOMAIN; + } +#endif + infof(doh, "[DoH] [%s] error decoding response: %s", + doh_type2name(doh_req->dnstype), + doh_strerror(dohp->probe_rc[slot])); + goto out; + } + + if(doh_req->dnstype == CURL_DNS_TYPE_A) + pdest_ai = &async->ai_A; + else if(doh_req->dnstype == CURL_DNS_TYPE_AAAA) + pdest_ai = &async->ai_AAAA; else - infof(data, "HTTPS RR: no ipv6hints"); + pdest_ai = NULL; + + if(pdest_ai && de.numaddr) { + if(*pdest_ai) { + Curl_freeaddrinfo(*pdest_ai); + *pdest_ai = NULL; + } + result = doh2ai(&de, async->peer->hostname, async->peer->port, pdest_ai); + if(result) { /* hard failure on our side, fail completely */ + infof(doh, "[DoH] [%s] error creating addrinfo: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + dohp->probe_rc[slot] = DOH_OOM; + async->result = result; + } + } +#ifdef USE_HTTPSRR + else if((doh_req->dnstype == CURL_DNS_TYPE_HTTPS) && de.numhttps_rrs) { + CURL_TRC_DNS(doh, "[HTTPS] got %d records", de.numhttps_rrs); + result = doh_resp_decode_httpsrr(doh, de.https_rrs->val, + de.https_rrs->len, &async->httpsrr); + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] error decoding HTTPS RR: %s", + curl_easy_strerror(result)); + goto out; + } + } +#endif /* USE_HTTPSRR */ + + /* DoH request complete, run master to act on results */ + infof(doh, "DoH request complete, %u to go", async->queries_ongoing); + +out: + Curl_multi_mark_dirty(master); + de_cleanup(&de); + Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); } -# endif -#endif CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_resolv_async *async, @@ -1221,97 +1148,77 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, { struct doh_probes *dohp = async->doh; CURLcode result = CURLE_OK; - struct dohentry de; *pdns = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; - if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT32_MAX && - dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT32_MAX) { - failf(data, "Could not DoH-resolve: %s", dohp->host); + async->negative_answer = FALSE; + if(async->result) { + result = async->result; + goto out; + } + + if(CURL_DNSQ_IS_ADDR(async->dns_queries) && + dohp->probe_mid[DOH_SLOT_IPV4] == UINT32_MAX && + dohp->probe_mid[DOH_SLOT_IPV6] == UINT32_MAX) { + failf(data, "Could not DoH-resolve: %s", async->peer->hostname); return async->for_proxy ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } - else if(!dohp->pending) { - DOHcode rc[DOH_SLOT_COUNT]; + else if(!async->queries_ongoing) { + struct Curl_dns_entry *dns = NULL; + bool negative = TRUE; int slot; - memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ doh_close(data, async); /* parse the responses, create the struct and return it! */ - de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - struct doh_response *p = &dohp->probe_resp[slot]; - if(!p->dnstype) - continue; - rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body), - curlx_dyn_len(&p->body), - p->dnstype, &de); - if(rc[slot]) { - CURL_TRC_DNS(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), - doh_type2name(p->dnstype), dohp->host); - } + /* Failing without an NXDOMAIN answer - a SERVFAIL-class rcode or + an undecodable response - says nothing about the name. Such a + failure must not be cached as a negative entry. */ + if(dohp->probe_rc[slot] && (dohp->probe_rc[slot] != DOH_DNS_NXDOMAIN)) + negative = FALSE; } /* next slot */ - 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; - - if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { - CURL_TRC_DNS(data, "hostname: %s", dohp->host); - doh_show(data, &de); - } - - result = doh2ai(&de, dohp->host, dohp->port, &ai); - if(result) - goto error; - - /* we got a response, create a dns entry. */ - dns = Curl_dnscache_mk_entry(data, async->dns_queries, - &ai, dohp->host, dohp->port); + if(async->ai_A || async->ai_AAAA) { + dns = Curl_dnsc_mk_addr2( + data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); if(!dns) { result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } - - /* Now add and HTTPSRR information if we have */ + } #ifdef USE_HTTPSRR - if(de.numhttps_rrs > 0 && result == CURLE_OK) { - struct Curl_https_rrinfo *hrr = NULL; - result = doh_resp_decode_httpsrr(data, de.https_rrs->val, - de.https_rrs->len, &hrr); - if(result) { - infof(data, "Failed to decode HTTPS RR"); - Curl_dns_entry_unlink(data, &dns); - goto error; - } - infof(data, "Some HTTPS RR to process"); -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - doh_print_httpsrr(data, hrr); -#endif - Curl_dns_entry_set_https_rr(dns, hrr); + else if((async->dns_queries & CURL_DNSQ_HTTPS) && + !dohp->probe_rc[DOH_SLOT_HTTPS_RR]) { + Curl_httpsrr_trace(data, async->httpsrr); + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + } #endif /* USE_HTTPSRR */ - - /* and add the entry to the cache */ - result = Curl_dnscache_add(data, dns); - *pdns = dns; - } /* address processing done */ else { + /* every query failed. Only NXDOMAIN answers for all of them + make this a negative answer, eligible for caching. */ + async->negative_answer = negative; result = async->for_proxy ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } - } /* !dohp->pending */ + /* and add the entry to the cache */ + if(dns) + result = Curl_dnscache_add(data, dns); + *pdns = dns; + } /* !async->queries_ongoing */ else /* wait for pending DoH transactions to complete */ return CURLE_AGAIN; -error: - de_cleanup(&de); +out: Curl_doh_cleanup(data, async); return result; } @@ -1325,23 +1232,23 @@ static void doh_close(struct Curl_easy *data, uint32_t mid; size_t slot; for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - mid = doh->probe_resp[slot].probe_mid; + mid = doh->probe_mid[slot]; if(mid == UINT32_MAX) continue; - doh->probe_resp[slot].probe_mid = UINT32_MAX; + doh->probe_mid[slot] = UINT32_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)); + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", mid)); continue; } + probe_data->sub_xfer_done = NULL; /* No longer interested in result */ /* data->multi might already be reset at this time */ - curl_multi_remove_handle(data->multi, probe_data); + Curl_multi_remove_handle(data->multi, probe_data); Curl_close(&probe_data); } - data->sub_xfer_done = NULL; + CURL_TRC_DNS(data, "[DoH] probe done"); } } @@ -1350,11 +1257,7 @@ void Curl_doh_cleanup(struct Curl_easy *data, { struct doh_probes *dohp = async->doh; if(dohp) { - int i; doh_close(data, async); - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - curlx_dyn_free(&dohp->probe_resp[i].body); - } curlx_safefree(async->doh); } } diff --git a/lib/doh.h b/lib/vdns/doh.h similarity index 91% rename from lib/doh.h rename to lib/vdns/doh.h index 428b230a5817..a6358027a7c4 100644 --- a/lib/doh.h +++ b/lib/vdns/doh.h @@ -39,12 +39,16 @@ typedef enum { DOH_OUT_OF_MEM, /* 5 */ DOH_DNS_RDATA_LEN, /* 6 */ DOH_DNS_MALFORMAT, /* 7 */ - DOH_DNS_BAD_RCODE, /* 8 - no such name */ + DOH_DNS_BAD_RCODE, /* 8 - unsuccessful rcode, not NXDOMAIN */ DOH_DNS_UNEXPECTED_TYPE, /* 9 */ DOH_DNS_UNEXPECTED_CLASS, /* 10 */ DOH_NO_CONTENT, /* 11 */ DOH_DNS_BAD_ID, /* 12 */ - DOH_DNS_NAME_TOO_LONG /* 13 */ + DOH_DNS_NAME_TOO_LONG, /* 13 */ + DOH_DNS_NXDOMAIN, /* 14 - no such name */ + DOH_HTTP_FAILED, /* failure at the HTTP level */ + DOH_OOM, /* out of memory */ + DOH_CODE_LAST /* Not used, limit */ } DOHcode; typedef enum { @@ -97,20 +101,11 @@ struct doh_request { DNStype dnstype; }; -struct doh_response { - uint32_t probe_mid; - struct dynbuf body; - DNStype dnstype; - CURLcode result; -}; - /* 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 */ - uint16_t port; - const char *host; + uint32_t probe_mid[DOH_SLOT_COUNT]; + DOHcode probe_rc[DOH_SLOT_COUNT]; }; /* @@ -125,7 +120,6 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns); #define DOH_MAX_ADDR 24 -#define DOH_MAX_CNAME 4 #define DOH_MAX_HTTPS 4 struct dohaddr { @@ -152,11 +146,9 @@ struct dohhttps_rr { #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; @@ -167,7 +159,6 @@ void Curl_doh_cleanup(struct Curl_easy *data, struct Curl_resolv_async *async); #define Curl_doh_wanted(d) (!!(d)->set.doh) - #else /* CURL_DISABLE_DOH */ #define Curl_doh(a, b) NULL #define Curl_doh_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST diff --git a/lib/hostip.c b/lib/vdns/hostip.c similarity index 77% rename from lib/hostip.c rename to lib/vdns/hostip.c index 85f53c4ef4a4..8464bd291773 100644 --- a/lib/hostip.c +++ b/lib/vdns/hostip.c @@ -46,16 +46,16 @@ #include "urldata.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "dnscache.h" -#include "hostip.h" -#include "httpsrr.h" #include "url.h" #include "multiif.h" #include "progress.h" -#include "doh.h" #include "select.h" #include "strcase.h" #include "easy_lock.h" +#include "vdns/dnscache.h" +#include "vdns/doh.h" +#include "vdns/hostip.h" +#include "vdns/httpsrr.h" #include "curlx/inet_ntop.h" #include "curlx/inet_pton.h" #include "curlx/strcopy.h" @@ -70,10 +70,6 @@ #define USE_ALARM_TIMEOUT #endif -#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */ - -#define MAX_DNS_CACHE_SIZE 29999 - #define RESOLV_FAIL(for_proxy) \ ((for_proxy) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST) @@ -346,11 +342,11 @@ static bool tailmatch(const char *full, size_t flen, } static CURLcode hostip_resolv_failed(struct Curl_easy *data, - const char *hostname, + struct Curl_peer *peer, bool for_proxy) { failf(data, "Could not resolve %s: %s", - for_proxy ? "proxy" : "host", hostname); + for_proxy ? "proxy" : "host", peer->hostname); return RESOLV_FAIL(for_proxy); } @@ -358,7 +354,7 @@ static bool can_resolve_dns_queries(struct Curl_easy *data, uint8_t dns_queries) { (void)data; - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) return FALSE; return TRUE; } @@ -367,13 +363,14 @@ CURLcode Curl_resolv_announce_start(struct Curl_easy *data, void *resolver) { if(data->set.resolver_start) { + struct Curl_mapi_guard guard; int rc; CURL_TRC_DNS(data, "announcing resolve to application"); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_resolver_start); rc = data->set.resolver_start(resolver, NULL, data->set.resolver_start_client); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc) { CURL_TRC_DNS(data, "application aborted resolve"); return CURLE_ABORTED_BY_CALLBACK; @@ -386,14 +383,12 @@ CURLcode Curl_resolv_announce_start(struct Curl_easy *data, static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms) { struct Curl_resolv_async *async; - size_t hostlen = strlen(hostname); if(!data->multi) { DEBUGASSERT(0); @@ -401,7 +396,7 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, } /* struct size already includes the NUL for hostname */ - async = curlx_calloc(1, sizeof(*async) + hostlen); + async = curlx_calloc(1, sizeof(*async)); if(!async) return NULL; @@ -415,18 +410,15 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, else data->multi->last_resolv_id++; async->id = data->multi->last_resolv_id; + Curl_peer_link(&async->peer, peer); async->dns_queries = dns_queries; - async->port = port; async->transport = transport; async->for_proxy = for_proxy; async->start = *Curl_pgrs_now(data); async->timeout_ms = timeout_ms; - if(hostlen) { - memcpy(async->hostname, hostname, hostlen); - async->is_ipaddr = Curl_is_ipaddr(async->hostname); - if(async->is_ipaddr) - async->is_ipv4addr = Curl_is_ipv4addr(async->hostname); - } + async->is_ipaddr = Curl_is_ipaddr(peer->hostname); + if(async->is_ipaddr) + async->is_ipv4addr = Curl_is_ipv4addr(peer->hostname); return async; } @@ -449,19 +441,28 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, result = Curl_async_take_result(data, async, pdns); if(result == CURLE_AGAIN) { - CURL_TRC_DNS(data, "resolve incomplete, queries=%s, responses=%s, " + CURL_TRC_DNS(data, "[%s] resolve incomplete, responses=%s, " "ongoing=%d for %s:%d", Curl_resolv_query_str(async->dns_queries), Curl_resolv_query_str(async->dns_responses), - async->queries_ongoing, async->hostname, async->port); + async->queries_ongoing, + async->peer->hostname, async->peer->port); result = CURLE_OK; } - else if(result) { + else if(IS_RESOLV_FAIL(result)) { result = Curl_async_failed(data, async, NULL); } + else if(result) { + /* a local failure, not a resolve answer. Keep the error as it + is so it does not get treated as one. */ + CURL_TRC_DNS(data, "[%s] resolve error %d for %s:%u", + Curl_resolv_query_str(async->dns_queries), + (int)result, async->peer->hostname, async->peer->port); + } else { - CURL_TRC_DNS(data, "resolve complete for %s:%u", - async->hostname, async->port); + CURL_TRC_DNS(data, "[%s] resolve complete for %s:%u", + Curl_resolv_query_str(async->dns_queries), + async->peer->hostname, async->peer->port); DEBUGASSERT(*pdns); } @@ -469,12 +470,13 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, } timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, - uint32_t resolv_id) + uint32_t resolv_id, + const struct curltime *pnow) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); if(!async) return CURL_TIMEOUT_RESOLVE_MS; - return curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + return curlx_ptimediff_ms(pnow, &async->start); } bool Curl_resolv_has_answers(struct Curl_easy *data, @@ -499,99 +501,122 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, unsigned int index) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - (void)index; - if(!async) - return NULL; - if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A)) + if(!async || !CURL_DNSQ_IS_ADDR(async->dns_queries)) return NULL; + switch(ai_family) { + case AF_INET: + return Curl_addrinfo_get(async->ai_A, ai_family, index); #ifdef USE_IPV6 - if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA)) - return NULL; + case AF_INET6: + return Curl_addrinfo_get(async->ai_AAAA, ai_family, index); #endif - return Curl_async_get_ai(data, async, ai_family, index); + default: + return NULL; + } } - #ifdef USE_HTTPSRR + +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns) +{ + return Curl_resolv(data, peer, CURL_DNSQ_HTTPS, TRNSPRT_TCP, + for_proxy, timeout_ms, presolv_id, pdns); +} + const struct Curl_https_rrinfo * Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id) { - struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(!async) + struct Curl_resolv_async *async; + if(!Curl_resolv_knows_https(data, resolv_id)) return NULL; - return Curl_async_get_https(data, async); + async = Curl_async_get(data, resolv_id); + return async ? async->httpsrr : NULL; } bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(!async) - return TRUE; - return Curl_async_knows_https(data, async); + if(async && (async->dns_queries & CURL_DNSQ_HTTPS)) + return ((async->dns_responses & CURL_DNSQ_HTTPS) || + !async->queries_ongoing); + return TRUE; /* we know it will never come */ } #endif /* USE_HTTPSRR */ #endif /* USE_CURL_ASYNC */ +/* Start resolving. `*pnegative` is only meaningful when this returns + a CURLE_COULDNT_RESOLVE_* failure: TRUE when the resolver answered + that the name does not exist, FALSE on transient or local failures + that must not be cached as negative entries. */ static CURLcode hostip_resolv_start(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, bool allowDOH, uint32_t *presolv_id, - struct Curl_dns_entry **pdns) + struct Curl_dns_entry **pdns, + bool *pnegative) { #ifdef USE_CURL_ASYNC struct Curl_resolv_async *async = NULL; #endif struct Curl_addrinfo *addr = NULL; size_t hostname_len; + bool addr_queries = (dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)); CURLcode result = CURLE_OK; + *pnegative = FALSE; + (void)timeout_ms; /* not in all ifdefs */ *presolv_id = 0; *pdns = NULL; /* Check for "known" things to resolve ourselves. */ + if(addr_queries) { #ifndef USE_RESOLVE_ON_IPS - if(Curl_is_ipaddr(hostname)) { - /* test655 verifies that the announce is done, even though there - * is no real resolving. So, keep doing this. */ - result = Curl_resolv_announce_start(data, NULL); - if(result) + if(Curl_is_ipaddr(peer->hostname)) { + /* test655 verifies that the announce is done, even though there + * is no real resolving. So, keep doing this. */ + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + /* shortcut literal IP addresses, if we are not told to resolve them. */ + result = Curl_str2addr(peer->hostname, peer->port, &addr); goto out; - /* shortcut literal IP addresses, if we are not told to resolve them. */ - result = Curl_str2addr(hostname, port, &addr); - goto out; - } + } #endif - hostname_len = strlen(hostname); - if(curl_strequal(hostname, "localhost") || - curl_strequal(hostname, "localhost.") || - tailmatch(hostname, hostname_len, STRCONST(".localhost")) || - tailmatch(hostname, hostname_len, STRCONST(".localhost."))) { - result = Curl_resolv_announce_start(data, NULL); - if(result) + hostname_len = strlen(peer->hostname); + if(curl_strequal(peer->hostname, "localhost") || + curl_strequal(peer->hostname, "localhost.") || + tailmatch(peer->hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(peer->hostname, hostname_len, STRCONST(".localhost."))) { + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + addr = get_localhost(peer->port, peer->hostname); + if(!addr) + result = CURLE_OUT_OF_MEMORY; goto out; - addr = get_localhost(port, hostname); - if(!addr) - result = CURLE_OUT_OF_MEMORY; - goto out; + } } - #ifndef CURL_DISABLE_DOH - if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { + if(!Curl_is_ipaddr(peer->hostname) && allowDOH && data->set.doh) { result = Curl_resolv_announce_start(data, NULL); if(result) goto out; if(!async) { - async = hostip_async_new(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms); + async = hostip_async_new(data, dns_queries, peer, transport, + for_proxy, timeout_ms); if(!async) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -613,8 +638,8 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, #ifdef CURLRES_ASYNCH (void)addr; if(!async) { - async = hostip_async_new(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms); + async = hostip_async_new(data, dns_queries, peer, transport, + for_proxy, timeout_ms); if(!async) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -633,9 +658,14 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, result = Curl_resolv_announce_start(data, NULL); if(result) goto out; - addr = Curl_sync_getaddrinfo(data, dns_queries, hostname, port, transport); - if(!addr) + addr = Curl_sync_getaddrinfo(data, dns_queries, peer->hostname, peer->port, + transport); + if(!addr) { result = RESOLV_FAIL(for_proxy); + /* the synchronous resolvers do not tell a transient failure from + an authoritative negative answer, treat it as before */ + *pnegative = TRUE; + } #endif out: @@ -643,7 +673,7 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, if(addr) { /* we got a response, create a dns entry, add to cache, return */ DEBUGASSERT(!*pdns); - *pdns = Curl_dnscache_mk_entry(data, dns_queries, &addr, hostname, port); + *pdns = Curl_dnsc_mk_addr(data, dns_queries, &addr, peer); if(!*pdns) result = CURLE_OUT_OF_MEMORY; } @@ -663,6 +693,7 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, data->state.async = async; } else { + *pnegative = !!async->negative_answer; Curl_async_destroy(data, async); } } @@ -672,8 +703,7 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, static CURLcode hostip_resolv(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, @@ -684,6 +714,7 @@ static CURLcode hostip_resolv(struct Curl_easy *data, size_t hostname_len; CURLcode result = RESOLV_FAIL(for_proxy); bool cache_dns = FALSE; + bool negative = FALSE; (void)timeout_ms; /* not used in all ifdefs */ *presolv_id = 0; @@ -694,53 +725,57 @@ static CURLcode hostip_resolv(struct Curl_easy *data, #endif /* We should intentionally error and not resolve .onion TLDs */ - hostname_len = strlen(hostname); + hostname_len = strlen(peer->hostname); DEBUGASSERT(hostname_len); if(hostname_len >= 7 && - (curl_strequal(&hostname[hostname_len - 6], ".onion") || - curl_strequal(&hostname[hostname_len - 7], ".onion."))) { + (curl_strequal(&peer->hostname[hostname_len - 6], ".onion") || + curl_strequal(&peer->hostname[hostname_len - 7], ".onion."))) { failf(data, "Not resolving .onion address (RFC 7686)"); goto out; } #ifdef DEBUGBUILD - CURL_TRC_DNS(data, "hostip_resolv(%s:%u, queries=%s)", - hostname, port, Curl_resolv_query_str(dns_queries)); - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && + CURL_TRC_DNS(data, "[%s] hostip_resolv(%s:%u)", + Curl_resolv_query_str(dns_queries), peer->hostname, peer->port); + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && getenv("CURL_DBG_RESOLV_FAIL_IPV6")) { infof(data, "DEBUG fail ipv6 resolve"); - result = hostip_resolv_failed(data, hostname, for_proxy); + result = hostip_resolv_failed(data, peer, for_proxy); goto out; } #endif /* Let's check our DNS cache first */ - result = Curl_dnscache_get(data, dns_queries, hostname, port, pdns); + result = Curl_dnscache_get(data, dns_queries, peer, pdns); if(*pdns) { - infof(data, "Hostname %s was found in DNS cache", hostname); + infof(data, "Hostname %s was found in DNS cache", peer->hostname); result = CURLE_OK; } else if(result) { infof(data, "Negative DNS entry"); - result = hostip_resolv_failed(data, hostname, for_proxy); + result = hostip_resolv_failed(data, peer, for_proxy); } else { /* No luck, we need to start resolving. */ cache_dns = TRUE; - result = hostip_resolv_start(data, dns_queries, hostname, port, - transport, for_proxy, timeout_ms, allowDOH, - presolv_id, pdns); + result = hostip_resolv_start(data, dns_queries, peer, transport, + for_proxy, timeout_ms, allowDOH, + presolv_id, pdns, &negative); + CURL_TRC_DNS(data, "[%s] hostip_resolv started -> %d", + Curl_resolv_query_str(dns_queries), (int)result); } out: if(result && (result != CURLE_AGAIN)) { Curl_dns_entry_unlink(data, pdns); if(IS_RESOLV_FAIL(result)) { - if(cache_dns) - Curl_dnscache_add_negative(data, dns_queries, hostname, port); - failf(data, "Could not resolve: %s:%u", hostname, port); + if(cache_dns && negative) + Curl_dnscache_add_negative(data, dns_queries, peer); + if(dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", peer->hostname, peer->port); } else { - failf(data, "Error %d resolving %s:%u", result, hostname, port); + failf(data, "Error %d resolving %s:%u", + (int)result, peer->hostname, peer->port); } } else if(cache_dns && *pdns) { @@ -759,13 +794,19 @@ CURLcode Curl_resolv_blocking(struct Curl_easy *data, uint8_t transport, struct Curl_dns_entry **pdns) { + struct Curl_peer *peer = NULL; CURLcode result; uint32_t resolv_id; + DEBUGASSERT(hostname && *hostname); *pdns = NULL; + + result = Curl_peer_create(data, data->conn->scheme, hostname, port, &peer); + if(result) + goto out; + /* We cannot do a blocking resolve using DoH currently */ - result = hostip_resolv(data, dns_queries, - hostname, port, transport, FALSE, 0, FALSE, + result = hostip_resolv(data, dns_queries, peer, transport, FALSE, 0, FALSE, &resolv_id, pdns); switch(result) { case CURLE_OK: @@ -781,6 +822,9 @@ CURLcode Curl_resolv_blocking(struct Curl_easy *data, default: break; } + +out: + Curl_peer_unlink(&peer); return result; } @@ -795,14 +839,10 @@ CURL_NORETURN static void alarmfunc(int sig) (void)sig; siglongjmp(curl_jmpenv, 1); } -#endif /* USE_ALARM_TIMEOUT */ - -#ifdef USE_ALARM_TIMEOUT static CURLcode resolv_alarm_timeout(struct Curl_easy *data, uint8_t dns_queries, - const char *hostname, - uint16_t port, + struct Curl_peer *peer, uint8_t transport, bool for_proxy, timediff_t timeout_ms, @@ -822,7 +862,7 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, volatile unsigned int prev_alarm = 0; CURLcode result; - DEBUGASSERT(hostname && *hostname); + DEBUGASSERT(peer->hostname && *peer->hostname); DEBUGASSERT(timeout_ms > 0); DEBUGASSERT(!data->set.no_signal); #ifndef CURL_DISABLE_DOH @@ -883,7 +923,7 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - result = hostip_resolv(data, dns_queries, hostname, port, transport, + result = hostip_resolv(data, dns_queries, peer, transport, for_proxy, timeout_ms, FALSE, presolv_id, entry); clean_up: @@ -935,6 +975,32 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, #endif /* USE_ALARM_TIMEOUT */ +#ifdef USE_UNIX_SOCKETS +static CURLcode resolv_unix(struct Curl_easy *data, + struct Curl_peer *peer, + struct Curl_dns_entry **pdns) +{ + struct Curl_addrinfo *addr; + CURLcode result; + + DEBUGASSERT(peer->unix_socket); + *pdns = NULL; + + result = Curl_unix2addr(peer->hostname, (bool)peer->abstract_uds, &addr); + if(result) { + if(result == CURLE_TOO_LARGE) { + /* Long paths are not supported for now */ + failf(data, "Unix socket path too long: '%s'", peer->hostname); + result = CURLE_COULDNT_RESOLVE_HOST; + } + return result; + } + + *pdns = Curl_dnsc_mk_addr(data, 0, &addr, peer); + return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} +#endif /* USE_UNIX_SOCKETS */ + /* * Curl_resolv() is the main name resolve function within libcurl. It resolves * a name and returns a pointer to the entry in the 'entry' argument. This @@ -956,16 +1022,14 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, * any other CURLcode error, *pdns == NULL */ CURLcode Curl_resolv(struct Curl_easy *data, + struct Curl_peer *peer, uint8_t dns_queries, - const char *hostname, - uint16_t port, uint8_t transport, bool for_proxy, timediff_t timeout_ms, uint32_t *presolv_id, struct Curl_dns_entry **pdns) { - DEBUGASSERT(hostname && *hostname); *presolv_id = 0; *pdns = NULL; @@ -975,14 +1039,24 @@ CURLcode Curl_resolv(struct Curl_easy *data, else if(!timeout_ms) timeout_ms = CURL_TIMEOUT_RESOLVE_MS; +#ifdef USE_UNIX_SOCKETS + if((dns_queries & CURL_DNSQ_ADDR) && peer->unix_socket) + return resolv_unix(data, peer, pdns); +#else + if(peer->unix_socket) + return hostip_resolv_failed(data, peer, for_proxy); +#endif + #ifdef USE_ALARM_TIMEOUT - if(timeout_ms && data->set.no_signal) { - /* Cannot use ALARM when signals are disabled */ - timeout_ms = 0; - } - if(timeout_ms && !Curl_doh_wanted(data)) { - return resolv_alarm_timeout(data, dns_queries, hostname, port, transport, - for_proxy, timeout_ms, presolv_id, pdns); + if(dns_queries & CURL_DNSQ_ADDR) { + if(timeout_ms && data->set.no_signal) { + /* Cannot use ALARM when signals are disabled */ + timeout_ms = 0; + } + if(timeout_ms && !Curl_doh_wanted(data)) { + return resolv_alarm_timeout(data, dns_queries, peer, transport, + for_proxy, timeout_ms, presolv_id, pdns); + } } #endif /* !USE_ALARM_TIMEOUT */ @@ -991,7 +1065,7 @@ CURLcode Curl_resolv(struct Curl_easy *data, infof(data, "timeout on name lookup is not supported"); #endif - return hostip_resolv(data, dns_queries, hostname, port, transport, + return hostip_resolv(data, dns_queries, peer, transport, for_proxy, timeout_ms, TRUE, presolv_id, pdns); } @@ -1019,11 +1093,10 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, return CURLE_FAILED_INIT; /* check if we have the name resolved by now (from someone else) */ - result = Curl_dnscache_get(data, async->dns_queries, - async->hostname, async->port, pdns); + result = Curl_dnscache_get(data, async->dns_queries, async->peer, pdns); if(*pdns) { /* Tell a possibly async resolver we no longer need the results. */ - infof(data, "Hostname '%s' was found in DNS cache", async->hostname); + infof(data, "Hostname '%s' was found in DNS cache", async->peer->hostname); Curl_async_shutdown(data, async); return CURLE_OK; } @@ -1041,13 +1114,20 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, Curl_dns_entry_unlink(data, pdns); } else if(IS_RESOLV_FAIL(result)) { - Curl_dnscache_add_negative(data, async->dns_queries, - async->hostname, async->port); - failf(data, "Could not resolve: %s:%u", async->hostname, async->port); + /* Only cache the failure when the resolver answered that the + name does not exist. Transient failures, e.g. an unreachable + or overloaded DNS server or local resource shortages, say + nothing about the name and would poison the cache for every + transfer using it. */ + if(async->negative_answer) + Curl_dnscache_add_negative(data, async->dns_queries, async->peer); + if(async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", + async->peer->hostname, async->peer->port); } else if(result) { failf(data, "Error %d resolving %s:%u", - result, async->hostname, async->port); + (int)result, async->peer->hostname, async->peer->port); } return result; } @@ -1101,30 +1181,3 @@ void Curl_resolv_destroy_all(struct Curl_easy *data) } #endif /* USE_CURL_ASYNC */ - -#ifdef USE_UNIX_SOCKETS -CURLcode Curl_resolv_unix(struct Curl_easy *data, - const char *unix_path, - bool abstract_path, - struct Curl_dns_entry **pdns) -{ - struct Curl_addrinfo *addr; - CURLcode result; - - DEBUGASSERT(unix_path); - *pdns = NULL; - - result = Curl_unix2addr(unix_path, abstract_path, &addr); - if(result) { - if(result == CURLE_TOO_LARGE) { - /* Long paths are not supported for now */ - failf(data, "Unix socket path too long: '%s'", unix_path); - result = CURLE_COULDNT_RESOLVE_HOST; - } - return result; - } - - *pdns = Curl_dnscache_mk_entry(data, 0, &addr, NULL, 0); - return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY; -} -#endif /* USE_UNIX_SOCKETS */ diff --git a/lib/hostip.h b/lib/vdns/hostip.h similarity index 86% rename from lib/hostip.h rename to lib/vdns/hostip.h index 780fb4dc13df..43172101f2a0 100644 --- a/lib/hostip.h +++ b/lib/vdns/hostip.h @@ -45,14 +45,15 @@ struct easy_pollset; struct Curl_https_rrinfo; struct Curl_multi; struct Curl_dns_entry; +struct Curl_peer; /* DNS query types */ #define CURL_DNSQ_A (1U << 0) #define CURL_DNSQ_AAAA (1U << 1) #define CURL_DNSQ_HTTPS (1U << 2) -#define CURL_DNSQ_ALL (CURL_DNSQ_A | CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS) -#define CURL_DNSQ_IP(x) (uint8_t)((x)&(CURL_DNSQ_A | CURL_DNSQ_AAAA)) +#define CURL_DNSQ_ADDR (uint8_t)(CURL_DNSQ_A | CURL_DNSQ_AAAA) +#define CURL_DNSQ_IS_ADDR(x) (uint8_t)((x)&(CURL_DNSQ_ADDR)) #ifdef CURLVERBOSE const char *Curl_resolv_query_str(uint8_t dns_queries); @@ -96,9 +97,8 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, * - other: the operation failed, `*pdns` is NULL, `*presolv_id` is 0. */ CURLcode Curl_resolv(struct Curl_easy *data, + struct Curl_peer *peer, uint8_t dns_queries, - const char *hostname, - uint16_t port, uint8_t transport, bool for_proxy, timediff_t timeout_ms, @@ -140,7 +140,8 @@ void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id); /* How much time has gone by since start of resolve. * Returns CURL_TIMEOUT_RESOLVE_MS if `resolv_id` is no longer valid. */ timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, - uint32_t resolv_id); + uint32_t resolv_id, + const struct curltime *pnow); /* Return TRUE if `resolv_id` has answers (positive or negative) to * all queries in `dns_queries`. @@ -153,6 +154,22 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, int ai_family, unsigned int index); #ifdef USE_HTTPSRR + +/* Start DNS resolving for HTTPS records. Returns + * - CURLE_OK: `*pdns` is the resolved DNS entry (needs to be unlinked). + * `*presolv_id` is 0. + * - CURLE_AGAIN: resolve is asynchronous and not finished yet. + * `presolv_id` is the identifier for querying results later. + * - other: the operation failed, `*pdns` is NULL, `*presolv_id` is 0. + */ +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns); + + const struct Curl_https_rrinfo *Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id); bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); @@ -162,7 +179,7 @@ bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); #define Curl_resolv_shutdown_all(x) Curl_nop_stmt #define Curl_resolv_destroy_all(x) Curl_nop_stmt #define Curl_resolv_take_result(x, y, z) CURLE_NOT_BUILT_IN -#define Curl_resolv_elapsed_ms(x, y) CURL_TIMEOUT_RESOLVE_MS +#define Curl_resolv_elapsed_ms(x, y, z) CURL_TIMEOUT_RESOLVE_MS #define Curl_resolv_has_answers(x, y, z) TRUE #define Curl_resolv_get_ai(x, y, z, a) NULL #define Curl_resolv_get_https(x, y) NULL @@ -184,11 +201,4 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, uint8_t transport); #endif -#ifdef USE_UNIX_SOCKETS -CURLcode Curl_resolv_unix(struct Curl_easy *data, - const char *unix_path, - bool abstract_path, - struct Curl_dns_entry **pdns); -#endif - #endif /* HEADER_CURL_HOSTIP_H */ diff --git a/lib/hostip4.c b/lib/vdns/hostip4.c similarity index 98% rename from lib/hostip4.c rename to lib/vdns/hostip4.c index fb35e3992ca4..83896a82b27f 100644 --- a/lib/hostip4.c +++ b/lib/vdns/hostip4.c @@ -45,7 +45,7 @@ #include "urldata.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" +#include "vdns/hostip.h" #include "url.h" @@ -64,7 +64,7 @@ * 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 - * flavours have thread-safe versions of the plain gethostbyname() etc. + * flavors have thread-safe versions of the plain gethostbyname() etc. * */ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, @@ -91,7 +91,7 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, !defined(CURLRES_AMIGA) /* - * Curl_ipv4_resolve_r() - ipv4 thread-safe resolver function. + * Curl_ipv4_resolve_r() - IPv4 thread-safe resolver function. * * This is used for both synchronous and asynchronous resolver builds, * implying that only thread-safe code and function calls may be used. diff --git a/lib/hostip6.c b/lib/vdns/hostip6.c similarity index 99% rename from lib/hostip6.c rename to lib/vdns/hostip6.c index 7412f428a4fd..e33a154d2f42 100644 --- a/lib/hostip6.c +++ b/lib/vdns/hostip6.c @@ -46,8 +46,8 @@ #include "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" -#include "hostip.h" #include "url.h" +#include "vdns/hostip.h" #include "curlx/inet_pton.h" #include "connect.h" diff --git a/lib/httpsrr.c b/lib/vdns/httpsrr.c similarity index 88% rename from lib/httpsrr.c rename to lib/vdns/httpsrr.c index 90fcb524e868..98a3c894964c 100644 --- a/lib/httpsrr.c +++ b/lib/vdns/httpsrr.c @@ -26,9 +26,9 @@ #ifdef USE_HTTPSRR #include "urldata.h" -#include "httpsrr.h" #include "connect.h" #include "curl_trc.h" +#include "vdns/httpsrr.h" #include "curlx/strdup.h" #include "curlx/inet_ntop.h" @@ -82,7 +82,7 @@ static CURLcode httpsrr_print_addr(struct dynbuf *dyn, CURLcode result = CURLE_OK; for(i = 0; (i < (total_len / alen)) && !result; ++i) { - if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) + if(curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) result = curlx_dyn_add(dyn, ""); else result = curlx_dyn_addf(dyn, "%s%s", sep, buf); @@ -97,16 +97,16 @@ void Curl_httpsrr_trace(struct Curl_easy *data, struct dynbuf tmp; CURLcode result; - if(!rr || !rr->complete) { - CURL_TRC_DNS(data, "[HTTPS-RR] not available"); + if(!rr) { + CURL_TRC_DNS(data, "[HTTPS] no record available"); return; } curlx_dyn_init(&tmp, 1024); result = Curl_httpsrr_print(&tmp, rr); if(!result) - CURL_TRC_DNS(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); + CURL_TRC_DNS(data, "[HTTPS] record: %s", curlx_dyn_ptr(&tmp)); else - CURL_TRC_DNS(data, "Error printing HTTPS-RR information"); + CURL_TRC_DNS(data, "[HTTPS] error printing information"); curlx_dyn_free(&tmp); } @@ -225,23 +225,15 @@ CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, return result; } -struct Curl_https_rrinfo *Curl_httpsrr_dup_move( - struct Curl_https_rrinfo *rrinfo) +void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) { - struct Curl_https_rrinfo *dup = curlx_memdup(rrinfo, sizeof(*rrinfo)); - if(dup) - memset(rrinfo, 0, sizeof(*rrinfo)); - return dup; -} - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) -{ - curlx_safefree(rrinfo->target); - curlx_safefree(rrinfo->echconfiglist); - curlx_safefree(rrinfo->ipv4hints); - curlx_safefree(rrinfo->ipv6hints); - curlx_safefree(rrinfo->rrname); - rrinfo->complete = FALSE; + if(rrinfo) { + curlx_free(rrinfo->target); + curlx_free(rrinfo->echconfiglist); + curlx_free(rrinfo->ipv4hints); + curlx_free(rrinfo->ipv6hints); + curlx_free(rrinfo); + } } bool Curl_httpsrr_applicable(struct Curl_easy *data, @@ -251,7 +243,7 @@ bool Curl_httpsrr_applicable(struct Curl_easy *data, return FALSE; return (!rr->target || !rr->target[0] || (rr->target[0] == '.' && !rr->target[1])) && - (!rr->port_set || rr->port == data->conn->remote_port); + (!rr->port_set || rr->port == data->conn->origin->port); } #ifdef USE_ARES @@ -269,11 +261,16 @@ static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, } CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo) + struct Curl_https_rrinfo **phinfo) { - CURLcode result = CURLE_OK; + struct Curl_https_rrinfo *hinfo = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; size_t i; + hinfo = curlx_calloc(1, sizeof(*hinfo)); + if(!hinfo) + goto out; + for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { const char *target; size_t opt; @@ -297,12 +294,20 @@ CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, opt++) { result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); if(result) - break; + goto out; } } + result = CURLE_OK; + out: - hinfo->complete = !result; - curlx_safefree(hinfo->rrname); + if(result) { + *phinfo = NULL; + Curl_httpsrr_destroy(hinfo); + } + else { + DEBUGASSERT(hinfo); + *phinfo = hinfo; + } return result; } diff --git a/lib/httpsrr.h b/lib/vdns/httpsrr.h similarity index 89% rename from lib/httpsrr.h rename to lib/vdns/httpsrr.h index 28a790d17f07..8d6eed1c936c 100644 --- a/lib/httpsrr.h +++ b/lib/vdns/httpsrr.h @@ -31,14 +31,13 @@ #ifdef USE_HTTPSRR -#define CURL_MAXLEN_host_name 253 +#define CURL_MAXLEN_HOST_NAME 253 #define MAX_HTTPSRR_ALPNS 4 struct Curl_easy; struct dynbuf; struct Curl_https_rrinfo { - char *rrname; /* if NULL, the same as the URL hostname */ /* * Fields from HTTPS RR. The only mandatory fields are priority and target. * See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 @@ -57,16 +56,12 @@ struct Curl_https_rrinfo { BIT(no_def_alpn); /* keytag = 2 */ BIT(mandatory); /* keytag = 0 */ BIT(port_set); /* port value has been assigned */ - BIT(complete); /* values have been successfully assigned */ }; CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, uint16_t rrkey, const uint8_t *val, size_t vlen); -struct Curl_https_rrinfo *Curl_httpsrr_dup_move( - struct Curl_https_rrinfo *rrinfo); - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); +void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo); /* TRUE if the record is applicable to the transfer and its connection. */ bool Curl_httpsrr_applicable(struct Curl_easy *data, @@ -85,7 +80,7 @@ bool Curl_httpsrr_applicable(struct Curl_easy *data, #ifdef USE_ARES CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo); + struct Curl_https_rrinfo **phinfo); #endif /* USE_ARES */ #ifdef CURLVERBOSE diff --git a/lib/version.c b/lib/version.c index b3b0a46abbb1..c4cdde6377bb 100644 --- a/lib/version.c +++ b/lib/version.c @@ -242,7 +242,9 @@ char *curl_version(void) src[i++] = gsasl_buf; #endif #ifdef HAVE_GSSAPI -#ifdef HAVE_GSSGNU +#ifdef HAVE_GSSAPPLE + curl_msnprintf(gss_buf, sizeof(gss_buf), "AppleGSS"); +#elif defined(HAVE_GSSGNU) curl_msnprintf(gss_buf, sizeof(gss_buf), "libgss/%s", GSS_VERSION); #elif defined(CURL_KRB5_VERSION) curl_msnprintf(gss_buf, sizeof(gss_buf), "mit-krb5/%s", CURL_KRB5_VERSION); @@ -388,7 +390,7 @@ static int idn_present(curl_version_info_data *info) (void)info; return TRUE; #else - return info->libidn != NULL; + return !!info->libidn; #endif } #endif @@ -467,6 +469,9 @@ static const struct feat features_table[] = { !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif +#ifndef CURL_DISABLE_HTTPSIG + FEATURE("HTTPSIG", NULL, 0), +#endif #ifdef USE_HTTPSRR FEATURE("HTTPSRR", NULL, 0), #endif @@ -491,6 +496,9 @@ static const struct feat features_table[] = { #ifdef USE_NTLM FEATURE("NTLM", NULL, CURL_VERSION_NTLM), #endif +#ifdef USE_PROXY_HTTP3 + FEATURE("proxy-HTTP3", NULL, 0), +#endif #ifdef USE_LIBPSL FEATURE("PSL", NULL, CURL_VERSION_PSL), #endif @@ -516,9 +524,6 @@ static const struct feat features_table[] = { #ifdef GLOBAL_INIT_IS_THREADSAFE FEATURE("threadsafe", NULL, CURL_VERSION_THREADSAFE), #endif -#ifdef USE_TLS_SRP - FEATURE("TLS-SRP", NULL, CURL_VERSION_TLSAUTH_SRP), -#endif #if defined(_WIN32) && defined(UNICODE) && defined(_UNICODE) FEATURE("Unicode", NULL, CURL_VERSION_UNICODE), #endif diff --git a/lib/vquic/capsule.c b/lib/vquic/capsule.c new file mode 100644 index 000000000000..4bbdae3ace44 --- /dev/null +++ b/lib/vquic/capsule.c @@ -0,0 +1,301 @@ +/*************************************************************************** + * _ _ ____ _ + * 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_PROXY) && !defined(CURL_DISABLE_HTTP) + +#ifdef HAVE_ARPA_INET_H +#include /* for htons() */ +#endif + +#include +#include "urldata.h" +#include "curlx/dynbuf.h" +#include "cfilters.h" +#include "curl_trc.h" +#include "bufq.h" +#include "vquic/capsule.h" + + +/** + * Convert 64-bit value from network byte order to host byte order + */ +static uint64_t capsule_ntohll(uint64_t value) +{ +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return value; +#elif (defined(__GNUC__) || defined(__clang__)) && \ + defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + return __builtin_bswap64(value); +#else + union { + uint64_t u64; + uint32_t u32[2]; + } src, dst; + + src.u64 = value; + dst.u32[0] = ntohl(src.u32[1]); + dst.u32[1] = ntohl(src.u32[0]); + return dst.u64; +#endif +} + +/** + * Encode a variable-length integer into a plain buffer. + * @param buf Output buffer (must have at least 8 bytes) + * @param value Value to encode (must be <= 0x3FFFFFFFFFFFFFFF) + * @return Number of bytes written + */ +static size_t capsule_encode_varint_buf(uint8_t *buf, uint64_t value) +{ + DEBUGASSERT(value <= 0x3FFFFFFFFFFFFFFF); + + if(value <= 0x3F) { + buf[0] = (uint8_t)value; + return 1; + } + else if(value <= 0x3FFF) { + uint16_t encoded = (uint16_t)value & 0x3FFF; + encoded = ntohs(encoded | 0x4000); + memcpy(buf, &encoded, 2); + return 2; + } + else if(value <= 0x3FFFFFFF) { + uint32_t encoded = (uint32_t)value & 0x3FFFFFFF; + encoded = ntohl(encoded | 0x80000000); + memcpy(buf, &encoded, 4); + return 4; + } + else { + uint64_t encoded = (uint64_t)value & 0x3FFFFFFFFFFFFFFF; + encoded = capsule_ntohll(encoded | 0xC000000000000000); + memcpy(buf, &encoded, 8); + return 8; + } +} + +static CURLcode capsule_peek_u8(struct bufq *recvbufq, + size_t offset, + uint8_t *pbyte) +{ + const unsigned char *peek = NULL; + size_t peeklen = 0; + + if(!Curl_bufq_peek_at(recvbufq, offset, &peek, &peeklen) || !peeklen) + return CURLE_AGAIN; + *pbyte = peek[0]; + return CURLE_OK; +} + +static CURLcode capsule_decode_varint_at(struct bufq *recvbufq, + size_t offset, + uint64_t *pvalue, + size_t *pconsumed) +{ + uint8_t first_byte, byte; + uint64_t value; + size_t nbytes; + size_t i; + CURLcode result; + + result = capsule_peek_u8(recvbufq, offset, &first_byte); + if(result) + return result; + + nbytes = (size_t)1 << (first_byte >> 6); /* 1, 2, 4 or 8 bytes */ + value = first_byte & 0x3F; + + for(i = 1; i < nbytes; ++i) { + result = capsule_peek_u8(recvbufq, offset + i, &byte); + if(result) + return result; + value = (value << 8) | byte; + } + + *pvalue = value; + *pconsumed = nbytes; + return CURLE_OK; +} + +/** + * Write the capsule header (type + varint length + context ID) into `hdr`. + * @param hdr Output buffer (must be >= HTTP_CAPSULE_HEADER_MAX_SIZE) + * @param hdrlen Size of `hdr` in bytes + * @param payload_len Length of the UDP payload that follows + * @return Number of header bytes written, or 0 on error + * + * @unittest 3400 + */ +UNITTEST size_t capsule_encap_udp_hdr(uint8_t *hdr, size_t hdrlen, + size_t payload_len); +UNITTEST size_t capsule_encap_udp_hdr(uint8_t *hdr, size_t hdrlen, + size_t payload_len) +{ + size_t off = 0; + DEBUGASSERT(hdrlen >= HTTP_CAPSULE_HEADER_MAX_SIZE); + if(hdrlen < HTTP_CAPSULE_HEADER_MAX_SIZE) + return 0; + hdr[off++] = 0; /* capsule type: HTTP Datagram */ + off += capsule_encode_varint_buf(hdr + off, (uint64_t)payload_len + 1); + hdr[off++] = 0; /* context ID */ + return off; +} + +CURLcode Curl_capsule_encap_udp_datagram(struct bufq *q, + const void *buf, size_t blen) +{ + CURLcode result; + uint8_t hdr[HTTP_CAPSULE_HEADER_MAX_SIZE]; + size_t hdr_len, nwritten; + + hdr_len = capsule_encap_udp_hdr(hdr, sizeof(hdr), blen); + DEBUGASSERT(hdr_len); + if(!hdr_len) + return CURLE_FAILED_INIT; + + result = Curl_bufq_write(q, hdr, hdr_len, &nwritten); + if(!result && (nwritten != hdr_len)) + return CURLE_WRITE_ERROR; + if(!result) { + result = Curl_bufq_write(q, buf, blen, &nwritten); + if(!result && (nwritten != blen)) + return CURLE_WRITE_ERROR; + } + if(result == CURLE_AGAIN) + return CURLE_WRITE_ERROR; + return result; +} + +size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct bufq *recvbufq, + unsigned char *buf, size_t len, + CURLcode *err) +{ + const unsigned char *context_id, *capsule_type; + size_t read_size, varint_len; + uint64_t capsule_length; + size_t offset, payload_len; + size_t bytes_read = 0; + CURLcode result = CURLE_OK; + + if(!len) { + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0; + } + + if(Curl_bufq_is_empty(recvbufq)) { + *err = CURLE_AGAIN; + return 0; + } + + if(!Curl_bufq_peek(recvbufq, &capsule_type, &read_size) || !read_size) { + *err = CURLE_AGAIN; + return 0; + } + + if(capsule_type[0]) { + infof(data, "Error! Invalid capsule type: %d", capsule_type[0]); + Curl_bufq_skip(recvbufq, 1); + *err = CURLE_RECV_ERROR; + return 0; + } + + offset = 1; + result = capsule_decode_varint_at(recvbufq, offset, &capsule_length, + &varint_len); + if(result == CURLE_AGAIN) { + *err = CURLE_AGAIN; + return 0; + } + else if(result) { + *err = CURLE_RECV_ERROR; + return 0; + } + offset += varint_len; + + if(!Curl_bufq_peek_at(recvbufq, offset, &context_id, &read_size) || + !read_size) { + *err = CURLE_AGAIN; + return 0; + } + + if(*context_id) { + infof(data, "Error! Invalid context ID: %02x", *context_id); + Curl_bufq_skip(recvbufq, offset + 1); + *err = CURLE_RECV_ERROR; + return 0; + } + offset += 1; + + if(!capsule_length) { + infof(data, "Error! Invalid capsule length: 0"); + Curl_bufq_skip(recvbufq, offset); + *err = CURLE_RECV_ERROR; + return 0; + } + if(capsule_length - 1 >= (uint64_t)SIZE_MAX) { + infof(data, "Error! Capsule length too large: %" CURL_FORMAT_CURL_OFF_T, + (curl_off_t)capsule_length); + *err = CURLE_RECV_ERROR; + return 0; + } + payload_len = (size_t)(capsule_length - 1); + + if(Curl_bufq_len(recvbufq) < offset + payload_len) { + *err = CURLE_AGAIN; + return 0; + } + + if(payload_len > len) { + infof(data, "UDP payload does not fit destination buffer: %zu > %zu", + payload_len, len); + Curl_bufq_skip(recvbufq, offset + payload_len); + *err = CURLE_RECV_ERROR; + return 0; + } + + Curl_bufq_skip(recvbufq, offset); + if(!payload_len) { + *err = CURLE_OK; + return 0; + } + result = Curl_bufq_read(recvbufq, buf, payload_len, &bytes_read); + if(result || (bytes_read != payload_len)) { + infof(data, "Error! Read less than expected %zu %zu", + payload_len, bytes_read); + *err = CURLE_RECV_ERROR; + return 0; + } + + if(cf && data) { + CURL_TRC_CF(data, cf, "Processed UDP capsule raw: size=%zu " + "length_left %zu", payload_len, Curl_bufq_len(recvbufq)); + } + *err = CURLE_OK; + return bytes_read; +} + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ diff --git a/lib/vquic/capsule.h b/lib/vquic/capsule.h new file mode 100644 index 000000000000..7861e6fd739f --- /dev/null +++ b/lib/vquic/capsule.h @@ -0,0 +1,70 @@ +#ifndef HEADER_CURL_CAPSULE_H +#define HEADER_CURL_CAPSULE_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_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include "curlx/dynbuf.h" +#include "bufq.h" + +/* HTTP Capsule constants */ +#define HTTP_CAPSULE_HEADER_MAX_SIZE 10 + +/* HTTP Capsule function prototypes */ + +/** + * Encapsulate UDP payload into HTTP Datagram capsule format + * @param q the bufq to write the capsule to + * @param buf Payload buffer + * @param blen Payload buffer length + * @return CURLE_OK on success, error code on failure + */ +CURLcode Curl_capsule_encap_udp_datagram(struct bufq *q, + const void *buf, size_t blen); + +struct Curl_easy; +struct Curl_cfilter; + +/** + * Process one UDP capsule from buffer into raw datagram payload bytes. + * @param cf Connection filter + * @param data Easy handle + * @param recvbufq Buffer queue containing capsule data + * @param buf Output buffer for one datagram payload + * @param len Size of output buffer in bytes + * @param err Error code output + * @return Number of payload bytes written. Check `err` for status. + */ +size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct bufq *recvbufq, + unsigned char *buf, size_t len, + CURLcode *err); + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + +#endif /* HEADER_CURL_CAPSULE_H */ diff --git a/lib/vquic/cf-capsule.c b/lib/vquic/cf-capsule.c new file mode 100644 index 000000000000..3ff7827bba93 --- /dev/null +++ b/lib/vquic/cf-capsule.c @@ -0,0 +1,306 @@ +/*************************************************************************** + * _ _ ____ _ + * 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_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include "urldata.h" +#include "cfilters.h" +#include "curl_trc.h" +#include "bufq.h" +#include "select.h" +#include "vquic/capsule.h" +#include "vquic/cf-capsule.h" + +/* send/recv buffer: 4 chunks of 16KB = 64KB, enough for large datagrams */ +#define CAPSULE_RECV_CHUNKS 4 +#define CAPSULE_SEND_CHUNKS 4 +#define CAPSULE_CHUNK_SIZE (16 * 1024) + +struct cf_capsule_ctx { + struct bufq recvbuf; + struct bufq sendbuf; +}; + +static void cf_capsule_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + (void)data; + if(ctx) { + Curl_bufq_free(&ctx->recvbuf); + Curl_bufq_free(&ctx->sendbuf); + curlx_safefree(ctx); + } +} + +static CURLcode cf_capsule_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + if(cf->next) { + CURLcode result = cf->next->cft->do_connect(cf->next, data, done); + if(!result && *done) + cf->connected = TRUE; + return result; + } + *done = FALSE; + return CURLE_OK; +} + +static CURLcode cf_capsule_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + size_t nwritten; + + if(Curl_bufq_is_empty(&ctx->sendbuf)) + return CURLE_OK; + + result = Curl_cf_send_bufq(cf->next, data, &ctx->sendbuf, NULL, 0, + &nwritten); + if(result) { + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "flush send buffer(%zu) -> EAGAIN", + Curl_bufq_len(&ctx->sendbuf)); + } + return result; + } + return Curl_bufq_is_empty(&ctx->sendbuf) ? CURLE_OK : CURLE_AGAIN; +} + +static CURLcode cf_capsule_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const uint8_t *buf, size_t len, + bool eos, size_t *pnwritten) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + CURLcode result; + + (void)eos; + *pnwritten = 0; + + if(Curl_bufq_is_full(&ctx->sendbuf)) { + result = cf_capsule_flush(cf, data); + if(result) + return result; + } + + /* encapsulate new payload into a capsule */ + result = Curl_capsule_encap_udp_datagram(&ctx->sendbuf, buf, len); + if(result) + return result; + + result = cf_capsule_flush(cf, data); + if(result == CURLE_AGAIN) { + /* Could not send it (or all), report success nevertheless as we + * have the payload buffered now and will flush it later. */ + result = CURLE_OK; + } + + if(!result) + *pnwritten = len; + return result; +} + +static CURLcode cf_capsule_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + size_t *pnread) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + CURLcode result; + size_t nread; + + *pnread = 0; + + /* fill our receive buffer from the filter below */ + while(!Curl_bufq_is_full(&ctx->recvbuf)) { + result = Curl_cf_recv_bufq(cf->next, data, &ctx->recvbuf, 0, &nread); + if(result == CURLE_AGAIN) + break; + if(result) + return result; + if(!nread) + break; + } + + /* try to extract a complete capsule datagram */ + *pnread = Curl_capsule_process_udp_raw(cf, data, &ctx->recvbuf, + (unsigned char *)buf, len, + &result); + return result; +} + +static bool cf_capsule_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + + if(ctx && !Curl_bufq_is_empty(&ctx->recvbuf)) + return TRUE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +static CURLcode cf_capsule_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_FLUSH: + result = cf_capsule_flush(cf, data); + break; + default: + break; + } + return result; +} + +static CURLcode cf_capsule_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + + (void)pres2; + switch(query) { + case CF_QUERY_NEED_FLUSH: { + if(!Curl_bufq_is_empty(&ctx->sendbuf)) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static CURLcode cf_capsule_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_capsule_ctx *ctx = cf->ctx; + + if(!Curl_bufq_is_empty(&ctx->sendbuf)) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + if(sock != CURL_SOCKET_BAD) + return Curl_pollset_add_out(data, ps, sock); + } + return CURLE_OK; +} + +static CURLcode cf_capsule_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + + if(!cf->connected || cf->shutdown) { + *done = TRUE; + } + else { + result = cf_capsule_flush(cf, data); + *done = !result; + if(result == CURLE_AGAIN) + result = CURLE_OK; + } + return result; +} + +struct Curl_cftype Curl_cft_capsule = { + "CAPSULE", + 0, + 0, + cf_capsule_destroy, + cf_capsule_connect, + cf_capsule_shutdown, + cf_capsule_adjust_pollset, + cf_capsule_data_pending, + cf_capsule_send, + cf_capsule_recv, + cf_capsule_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_capsule_query, +}; + +static CURLcode cf_capsule_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf = NULL; + struct cf_capsule_ctx *ctx; + CURLcode result; + + (void)data; + (void)conn; + *pcf = NULL; + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + Curl_bufq_init2(&ctx->recvbuf, CAPSULE_CHUNK_SIZE, CAPSULE_RECV_CHUNKS, + BUFQ_OPT_SOFT_LIMIT); + Curl_bufq_init2(&ctx->sendbuf, CAPSULE_CHUNK_SIZE, CAPSULE_SEND_CHUNKS, + BUFQ_OPT_SOFT_LIMIT); + + result = Curl_cf_create(&cf, &Curl_cft_capsule, ctx); + +out: + *pcf = (!result) ? cf : NULL; + if(result && ctx) { + Curl_bufq_free(&ctx->recvbuf); + Curl_bufq_free(&ctx->sendbuf); + curlx_free(ctx); + } + return result; +} + +CURLcode Curl_cf_capsule_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_capsule_create(&cf, data, cf_at->conn); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ diff --git a/lib/vquic/cf-capsule.h b/lib/vquic/cf-capsule.h new file mode 100644 index 000000000000..437c9681b6cc --- /dev/null +++ b/lib/vquic/cf-capsule.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_CF_CAPSULE_H +#define HEADER_CURL_CF_CAPSULE_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_PROXY) && !defined(CURL_DISABLE_HTTP) + +/* Insert a capsule protocol filter after `cf_at` in the filter chain. + * The capsule filter encapsulates/decapsulates UDP datagrams using + * the HTTP Datagram capsule format (RFC 9297). */ +CURLcode Curl_cf_capsule_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_capsule; + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + +#endif /* HEADER_CURL_CF_CAPSULE_H */ diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/cf-ngtcp2-cmn.c similarity index 52% rename from lib/vquic/curl_ngtcp2.c rename to lib/vquic/cf-ngtcp2-cmn.c index 8cf3886d2241..b1cfb68cbdfa 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -24,12 +24,12 @@ #include "curl_setup.h" #if !defined(CURL_DISABLE_HTTP) && defined(USE_NGTCP2) && defined(USE_NGHTTP3) + #include -#include #ifdef USE_OPENSSL #include -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) #include #elif defined(OPENSSL_QUIC_API2) #include @@ -45,6 +45,8 @@ #include "vtls/wolfssl.h" #endif +#include + #include "urldata.h" #include "url.h" #include "uint-hash.h" @@ -52,7 +54,6 @@ #include "rand.h" #include "multiif.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-socket.h" #include "connect.h" #include "progress.h" @@ -60,41 +61,16 @@ #include "curlx/dynbuf.h" #include "http1.h" #include "select.h" +#include "sockaddr.h" #include "transfer.h" #include "bufref.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" #include "vtls/vtls.h" #include "vtls/vtls_scache.h" -#include "vquic/curl_ngtcp2.h" - - -#define QUIC_MAX_STREAMS (256 * 1024) -#define QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) - -/* We announce a small window size in transport param to the server, - * and grow that immediately to max when no rate limit is in place. - * We need to start small as we are not able to decrease it. */ -#define H3_STREAM_WINDOW_SIZE_INITIAL (32 * 1024) -#define H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) -#define H3_CONN_WINDOW_SIZE_MAX (100 * H3_STREAM_WINDOW_SIZE_MAX) - -#define H3_STREAM_CHUNK_SIZE (64 * 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. Memory consumption goes down when streams run empty, - * have a large upload done, etc. */ -#define H3_STREAM_POOL_SPARES 2 -/* The max amount of un-acked upload data we keep around per stream */ -#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) -#define H3_STREAM_SEND_CHUNKS \ - (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) +#include "vquic/cf-ngtcp2-cmn.h" /* * Store ngtcp2 version info in this buffer. @@ -107,144 +83,7 @@ void Curl_ngtcp2_ver(char *p, size_t len) ng2->version_str, ht3->version_str); } -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; - ngtcp2_cid scid; - uint32_t version; - ngtcp2_settings settings; - ngtcp2_transport_params transport_params; - ngtcp2_ccerr last_error; - ngtcp2_crypto_conn_ref conn_ref; - 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 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` */ - 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(initialized); - BIT(tls_handshake_complete); /* TLS handshake is done */ - BIT(use_earlydata); /* Using 0RTT data */ - BIT(earlydata_accepted); /* 0RTT was accepted 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; - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); - 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_uint32_hash_destroy(&ctx->streams); - Curl_ssl_peer_cleanup(&ctx->peer); - } - curlx_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 closes the - * connection when it does not hear from us for that time. - * - * Some servers use this as a keep-alive timer at a rather low - * 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_uint32_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 %" PRIu64 "ms, " - "set keep-alive to %" PRIu64 " ms.", - (rp->max_idle_timeout / NGTCP2_MILLISECONDS), - (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 h3_stream_ctx { - int64_t id; /* HTTP/3 protocol identifier */ - struct bufq sendbuf; /* h3 request body */ - struct h1_req_parser h1; /* h1 request parsing */ - size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - uint64_t rx_offset; /* current receive offset */ - uint64_t rx_offset_max; /* allowed receive offset */ - uint64_t window_size_max; /* max flow control window set for stream */ - int status_code; /* HTTP status code */ - CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ - BIT(resp_hds_complete); /* we have a complete, final response */ - BIT(closed); /* TRUE on stream close */ - 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) +void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream) { Curl_bufq_free(&stream->sendbuf); Curl_h1_req_parse_free(&stream->h1); @@ -255,165 +94,65 @@ 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 h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - if(!data) - return CURLE_FAILED_INIT; - - if(stream) - return CURLE_OK; - - stream = curlx_calloc(1, sizeof(*stream)); - if(!stream) - return CURLE_OUT_OF_MEMORY; - - stream->id = -1; - stream->rx_offset = 0; - stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; - - /* 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; - stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; - Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { - h3_stream_ctx_free(stream); - return CURLE_OUT_OF_MEMORY; - } - - if(Curl_uint32_hash_count(&ctx->streams) == 1) - cf_ngtcp2_setup_keep_alive(cf, data); - - return CURLE_OK; -} - -#if NGTCP2_VERSION_NUM < 0x011100 -struct cf_ngtcp2_sfind_ctx { - int64_t stream_id; - struct h3_stream_ctx *stream; - uint32_t mid; -}; - -static bool cf_ngtcp2_sfind(uint32_t mid, void *value, void *user_data) -{ - struct cf_ngtcp2_sfind_ctx *fctx = user_data; - struct h3_stream_ctx *stream = value; - - if(fctx->stream_id == stream->id) { - fctx->mid = mid; - fctx->stream = stream; - return FALSE; - } - return TRUE; /* continue */ -} - -static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, - int64_t stream_id) -{ - struct cf_ngtcp2_sfind_ctx fctx; - fctx.stream_id = stream_id; - fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); - return fctx.stream; -} -#else -static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, - int64_t stream_id) -{ - struct Curl_easy *data = - ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - - if(!data) { - return NULL; - } - - return H3_STREAM_CTX(ctx, data); + Curl_cf_ngtcp2_h3_stream_ctx_free((struct h3_stream_ctx *)stream); } -#endif -static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_stream_ctx *stream) +static bool cf_ngtcp2_h3_err_is_fatal(int code) { - 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, "[%" PRId64 "] cancel stream -> %d", - stream->id, result); - } + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); } -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)cf; - if(stream) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); - if(!Curl_uint32_hash_count(&ctx->streams)) - cf_ngtcp2_setup_keep_alive(cf, data); + 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)) + Curl_cf_ngtcp2_cmn_conn_close(cf, 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 Curl_easy *data, - struct pkt_io_ctx *pktx, - struct Curl_cfilter *cf) +CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + cf_ngtcp2_init_h3_conn *init_h3_conn_cb) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - - vquic_ctx_update_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); + DEBUGASSERT(!ctx->initialized); + ctx->qlogfd = -1; + ctx->tunnel_inbuf = NULL; + ctx->tunnel_inbuf_len = 0; + ctx->version = NGTCP2_PROTO_VER_MAX; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->init_h3_conn_cb = init_h3_conn_cb; + ctx->initialized = TRUE; + return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); } -static void pktx_init(struct pkt_io_ctx *pktx, - struct Curl_cfilter *cf, - struct Curl_easy *data) +void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - - pktx->cf = cf; - pktx->data = data; - ngtcp2_path_storage_zero(&pktx->ps); - vquic_ctx_set_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); + if(ctx && ctx->initialized) { + Curl_vquic_tls_cleanup(&ctx->tls); + Curl_vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + curlx_dyn_free(&ctx->scratch); + Curl_uint32_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); + curlx_safefree(ctx->tunnel_inbuf); + ctx->tunnel_inbuf_len = 0; + if(ctx->qlogfd != -1) { + curlx_close(ctx->qlogfd); + ctx->qlogfd = -1; + } + } } -static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data); - static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { struct Curl_cfilter *cf = conn_ref->user_data; @@ -424,11 +163,8 @@ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) #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; /* need an easy handle to infof() message */ va_list ap; + (void)user_data; va_start(ap, fmt); curl_mvfprintf(stderr, fmt, ap); va_end(ap); @@ -454,7 +190,7 @@ static void qlog_callback(void *user_data, uint32_t flags, static void quic_settings(struct cf_ngtcp2_ctx *ctx, struct Curl_easy *data, - struct pkt_io_ctx *pktx) + struct cf_ngtcp2_io_ctx *pktx) { ngtcp2_settings *s = &ctx->settings; ngtcp2_transport_params *t = &ctx->transport_params; @@ -490,10 +226,12 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, } } -static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, - struct Curl_easy *data); +#if defined(_MSC_VER) && defined(_DLL) +#pragma warning(push) +#pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif -static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) +static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; @@ -504,42 +242,56 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) data = CF_DATA_CURRENT(cf); DEBUGASSERT(data); if(!ctx || !data) - return NGHTTP3_ERR_CALLBACK_FAILURE; + return NGTCP2_ERR_CALLBACK_FAILURE; ctx->handshake_at = *Curl_pgrs_now(data); ctx->tls_handshake_complete = TRUE; Curl_vquic_report_handshake(&ctx->tls, cf, data); ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, - data, &ctx->peer); + data, &ctx->ssl_peer); + if(ctx->tls_vrfy_result) + return NGTCP2_ERR_CALLBACK_FAILURE; + #ifdef CURLVERBOSE if(Curl_trc_is_verbose(data)) { const ngtcp2_transport_params *rp; rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); CURL_TRC_CF(data, cf, "handshake complete after %" FMT_TIMEDIFF_T "ms, remote transport[max_udp_payload=%" PRIu64 - ", initial_max_data=%" PRIu64 - "]", - curlx_ptimediff_ms(&ctx->handshake_at, &ctx->started_at), - rp->max_udp_payload_size, rp->initial_max_data); + ", initial_max_data=%" PRIu64 "]", + curlx_ptimediff_ms(&ctx->handshake_at, &ctx->started_at), + rp->max_udp_payload_size, rp->initial_max_data); } #endif /* In case of earlydata, where we simulate being connected, update * the handshake time when we really did connect */ - if(ctx->use_earlydata) + if(ctx->use_earlydata && !ctx->stats_reported && + !(cf->cft->flags & CF_TYPE_PROXY)) { Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } 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); + /* Check for bug that OpenSSL did not even send the early data. */ + if(SSL_get_early_data_status(ctx->tls.ossl.ssl) == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); #endif -#ifdef USE_GNUTLS + +#if NGTCP2_VERSION_NUM >= 0x011700 + ctx->earlydata_accepted = + !ngtcp2_conn_get_tls_early_data_rejected2(ctx->qconn); +#else /* older NGTCP2 */ +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); + if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); + ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); +#elif defined(USE_GNUTLS) int flags = gnutls_session_get_flags(ctx->tls.gtls.session); ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); -#endif -#ifdef USE_WOLFSSL +#elif defined(USE_WOLFSSL) #ifdef WOLFSSL_EARLY_DATA ctx->earlydata_accepted = (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != @@ -548,7 +300,8 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) DEBUGASSERT(0); /* should not come here if ED is disabled. */ ctx->earlydata_accepted = FALSE; #endif /* WOLFSSL_EARLY_DATA */ -#endif +#endif /* OPENSSL or GNUTLS or WOLFSSL */ +#endif /* older NGTCP2 */ CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? @@ -558,58 +311,12 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) return 0; } -static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, - struct Curl_easy *data); - -static bool cf_ngtcp2_err_is_fatal(int code) -{ - return (NGTCP2_ERR_FATAL >= code) || - (NGTCP2_ERR_DROP_CONN == code) || - (NGTCP2_ERR_IDLE_CLOSE == code); -} - -static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) +static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) { - 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); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); - } - } - if(cf_ngtcp2_err_is_fatal(code)) - cf_ngtcp2_conn_close(cf, data); -} - -static bool cf_ngtcp2_h3_err_is_fatal(int code) -{ - return (NGHTTP3_ERR_FATAL >= code) || - (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); -} - -static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - 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, - const uint8_t *buf, size_t buflen, - void *user_data, void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; + struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; nghttp3_ssize rc; uint64_t nconsumed; @@ -687,12 +394,48 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" PRIu64 ") -> %d", stream_id, app_error_code, rv); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - cf_ngtcp2_h3_err_set(cf, data, rv); + Curl_cf_ngtcp2_h3_err_set(cf, data, rv); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +#ifdef NGTCP2_CALLBACKS_V5 /* ngtcp2 v1.25.0+ */ +static int cb_stream_close2(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream_id, + uint64_t rx_app_error_code, + uint64_t tx_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; + uint64_t h3_app_error_code = NGHTTP3_H3_NO_ERROR; + int rv; + + (void)tconn; + (void)tx_app_error_code; + /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; + + if(flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET) + h3_app_error_code = rx_app_error_code; + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, h3_app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" + PRIu64 ") -> %d", stream_id, h3_app_error_code, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + Curl_cf_ngtcp2_h3_err_set(cf, data, rv); return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } +#endif static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, uint64_t final_size, uint64_t app_error_code, @@ -811,7 +554,8 @@ static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, } #ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */ -static int cb_get_new_connection_id2(ngtcp2_conn *tconn, ngtcp2_cid *cid, +static int cb_get_new_connection_id2( + ngtcp2_conn *tconn, ngtcp2_cid *cid, struct ngtcp2_stateless_reset_token *token, size_t cidlen, void *user_data) { CURLcode result; @@ -844,23 +588,22 @@ static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, DEBUGASSERT(ctx); DEBUGASSERT(data); - if(ctx && data && !ctx->h3conn) { - if(init_ngh3_conn(cf, data)) + if(ctx && data && !ctx->h3conn && ctx->init_h3_conn_cb) { + if(ctx->init_h3_conn_cb(cf, data, ctx)) return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } -#if defined(_MSC_VER) && defined(_DLL) -#pragma warning(push) -#pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif - static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_client_initial_cb, NULL, /* recv_client_initial */ ngtcp2_crypto_recv_crypto_data_cb, - cf_ngtcp2_handshake_completed, + cb_ngtcp2_handshake_completed, NULL, /* recv_version_negotiation */ ngtcp2_crypto_encrypt_cb, ngtcp2_crypto_decrypt_cb, @@ -906,1000 +649,773 @@ static ngtcp2_callbacks ng_callbacks = { NULL, /* dcid_status2 */ ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */ #endif +#ifdef NGTCP2_CALLBACKS_V4 /* ngtcp2 v1.24.0+ */ + NULL, /* recv_stop_sending */ +#endif +#ifdef NGTCP2_CALLBACKS_V5 /* ngtcp2 v1.25.0+ */ + cb_stream_close2, /* is called instead of cb_stream_close when set */ +#endif }; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif #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) +static bool cf_ngtcp2_need_httpsrr(struct Curl_easy *data) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct pkt_io_ctx local_pktx; - ngtcp2_tstamp expiry; +#ifdef USE_OPENSSL + return Curl_ossl_need_httpsrr(data); +#elif defined(USE_WOLFSSL) + return Curl_wssl_need_httpsrr(data); +#else + (void)data; + return FALSE; +#endif +} - if(!pktx) { - pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - pktx_update_time(data, pktx, cf); - } +#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; - 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); - } + 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]; - 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); + 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->tls.ossl, ctx->ssl_peer.scache_key, + ssl_sessionid, "h3", quic_tp, quic_tp_len, NULL); } - return CURLE_OK; + return 0; } +#endif /* USE_OPENSSL */ -static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - bool want_recv, want_send; - CURLcode result = CURLE_OK; - - if(!ctx->qconn) - return CURLE_OK; - - 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); +#ifdef USE_GNUTLS - result = Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); - CF_DATA_RESTORE(cf, save); +#ifdef CURLVERBOSE +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 result; + return "Unknown"; } +#endif -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) +static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) { - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)conn; - (void)stream_id; + 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; - /* we might be called by nghttp3 after we already cleaned up */ - if(!stream) - return 0; + (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 [%u]", + 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; - 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, "[%" PRId64 "] RESET: error %" PRIu64, - stream->id, stream->error3); - } - else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + 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->ssl_peer.scache_key, + session, 0, "h3", quic_tp, quic_tp_len, + NULL); + break; + } + default: + break; + } } - Curl_multi_mark_dirty(data); return 0; } +#endif /* USE_GNUTLS */ -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) +#ifdef USE_WOLFSSL +static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) { - /* This function returns no error intentionally, but records - * the result at the stream, skipping further writes once the - * `result` of the transfer is known. - * The stream is subsequently cancelled "higher up" in the filter's - * send/recv callbacks. Closing the stream here leads to SEND/RECV - * errors in other places that then overwrite the transfer's result. */ - 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, "[%" PRId64 "] error %d writing %zu " - "bytes of headers", stream->id, stream->xfer_result, blen); - } -} + ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; -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) -{ - /* This function returns no error intentionally, but records - * the result at the stream, skipping further writes once the - * `result` of the transfer is known. - * The stream is subsequently cancelled "higher up" in the filter's - * send/recv callbacks. Closing the stream here leads to SEND/RECV - * errors in other places that then overwrite the transfer's result. */ - 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, "[%" PRId64 "] error %d writing %zu bytes of data", - stream->id, stream->xfer_result, blen); + DEBUGASSERT(cf); + 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->ssl_peer.scache_key, + session, wolfSSL_version(ssl), + "h3", quic_tp, quic_tp_len, NULL); } } + return 0; } +#endif /* USE_WOLFSSL */ -static void cf_ngtcp2_upd_rx_win(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_stream_ctx *stream) +static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; - - /* how much does rate limiting allow us to acknowledge? */ - if(Curl_rlimit_active(&data->progress.dl.rlimit)) { - int64_t avail; - - /* start rate limit updates only after first bytes arrived */ - if(!stream->rx_offset) - return; - - avail = Curl_rlimit_avail(&data->progress.dl.rlimit, - Curl_pgrs_now(data)); - if(avail <= 0) { - /* nothing available, do not extend the rx offset */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 - " tokens)", stream->id, avail); - return; - } - wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); - } - - if(stream->rx_offset_max < stream->rx_offset) { - DEBUGASSERT(0); - return; - } - cur_win = stream->rx_offset_max - stream->rx_offset; + struct curl_tls_ctx *ctx = user_data; - if(wanted_win > cur_win) { - uint64_t delta = wanted_win - cur_win; - - if(UINT64_MAX - delta < stream->rx_offset_max) - delta = UINT64_MAX - stream->rx_offset_max; - if(delta) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] rx window, extend by %" PRIu64 - " bytes", stream->id, delta); - stream->rx_offset_max += delta; - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, delta); - } +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) + 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_AWSLC && !OPENSSL_IS_BORINGSSL */ + if(Curl_ssl_scache_use(cf, data)) { + /* 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); } -} - -static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, - 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; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - (void)conn; - (void)stream3_id; - - if(!stream) - return NGHTTP3_ERR_CALLBACK_FAILURE; - - h3_xfer_write_resp(cf, data, stream, (const char *)buf, blen, FALSE); - ngtcp2_conn_extend_max_offset(ctx->qconn, blen); - stream->rx_offset += blen; - if(stream->rx_offset_max < stream->rx_offset) - stream->rx_offset_max = stream->rx_offset; +#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(Curl_ssl_scache_use(cf, data)) { + gnutls_handshake_set_hook_function(ctx->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + quic_gtls_handshake_cb); + } - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, rx win=%" PRIu64, - stream->id, blen, stream->rx_offset_max - stream->rx_offset); - cf_ngtcp2_upd_rx_win(cf, data, stream); - return 0; +#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(Curl_ssl_scache_use(cf, data)) { + /* 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 int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, - size_t consumed, void *user_data, - void *stream_user_data) +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 Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)conn; + CURLcode result = CURLE_OK; - /* nghttp3 has consumed bytes on the QUIC stream and we need to - * tell the QUIC connection to increase its flow control */ - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); - ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); - if(stream) { - stream->rx_offset += consumed; - stream->rx_offset_max += consumed; + *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"); } - return 0; + 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 if(ctx->init_h3_conn_cb) { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", ctx->earlydata_max, scs->alpn); + result = ctx->init_h3_conn_cb(cf, data, ctx); + if(!result) { + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; + } + } + else { /* init_h3_conn_cb not set, assume done */ + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; + } + } +#else /* not supported in the TLS backend */ + (void)data; + (void)ctx; + (void)scs; + (void)alpns; +#endif + return result; } -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, - int fin, void *user_data, void *stream_user_data) +/* + * Might be called twice for happy eyeballs. + */ +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) { - struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)conn; - (void)stream_id; - (void)fin; - (void)cf; + int rc; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr = NULL; + int qfd; + static const struct alpn_spec ALPN_SPEC_H3 = { { "h3", "h3-29" }, 2 }; - if(!stream) - return 0; - /* add a CRLF only if we have received some headers */ - h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), - (bool)stream->closed); + DEBUGASSERT(ctx->initialized); + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; - CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", - stream_id, stream->status_code); - if(stream->status_code / 100 != 1) { - stream->resp_hds_complete = TRUE; - } - Curl_multi_mark_dirty(data); - return 0; -} + ctx->scid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, - 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; - 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; + (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); + ctx->qlogfd = qfd; /* -1 if failure above */ + quic_settings(ctx, data, pktx); - /* we might have cleaned up this transfer already */ - if(!stream) - return 0; + result = Curl_vquic_ctx_init(data, &ctx->q); + if(result) + return result; - if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + /* Query socket and remote address from sub-chain */ + if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL)) { + /* No direct socket - must be tunneled QUIC (CONNECT-UDP through proxy) */ + ctx->q.sockfd = CURL_SOCKET_BAD; + } - result = Curl_http_decode_status(&stream->status_code, - (const char *)h3val.base, h3val.len); - if(result) - return NGHTTP3_ERR_CALLBACK_FAILURE; - 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, "[%" PRId64 "] status: %s", - stream_id, curlx_dyn_ptr(&ctx->scratch)); - if(result) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket - get local address for ngtcp2 */ + 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) + return CURLE_QUIC_CONNECT_ERROR; + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); } else { - /* store as an HTTP1-style header */ - CURL_TRC_CF(data, cf, "[%" 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); + /* Tunneled QUIC (e.g. CONNECT-UDP): get remote address + from the connected filter below */ + const struct Curl_sockaddr_ex *remote = NULL; + if(cf->next->cft->query(cf->next, data, CF_QUERY_REMOTE_ADDR, NULL, + CURL_UNCONST(&remote))) + return CURLE_QUIC_CONNECT_ERROR; + if(!remote) + return CURLE_QUIC_CONNECT_ERROR; + + memset(&ctx->q.local_addr, 0, sizeof(ctx->q.local_addr)); + switch(remote->family) { + case AF_INET: + ((struct sockaddr_in *)&ctx->q.local_addr)->sin_family = AF_INET; + ctx->q.local_addrlen = sizeof(struct sockaddr_in); + break; +#ifdef USE_IPV6 + case AF_INET6: + ((struct sockaddr_in6 *)&ctx->q.local_addr)->sin6_family = AF_INET6; + ctx->q.local_addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + return CURLE_QUIC_CONNECT_ERROR; + } + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &remote->curl_sa_addr, + (socklen_t)remote->addrlen); } - return 0; -} -static int cb_h3_stop_sending(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_ngtcp2_ctx *ctx = cf->ctx; - int rv; - (void)conn; - (void)stream_user_data; + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + Curl_ngtcp2_mem(), cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; - rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, - app_error_code); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; - return 0; -} + result = Curl_vquic_tls_init(&ctx->tls, cf, data, + &ctx->ssl_peer, &ALPN_SPEC_H3, + cf_ngtcp2_tls_ctx_setup, &ctx->tls, + &ctx->conn_ref, + cf_ngtcp2_on_session_reuse); + if(result) + return result; -static int cb_h3_reset_stream(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_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - int rv; - (void)conn; +#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 +#error "ngtcp2 TLS backend not defined" +#endif - rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, - app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGHTTP3_ERR_CALLBACK_FAILURE; +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) && \ + defined(OPENSSL_QUIC_API2) + /* We need to tell OpenSSL to *really* use Early Data for QUIC and + * this only works *after* ngtcp2 has tweaked all SSL parameters, + * otherwise OpenSSL does not accept it. */ + if(ctx->use_earlydata && + !SSL_set_quic_tls_early_data_enabled(ctx->tls.ossl.ssl, 1)) { + CURL_TRC_CF(data, cf, "OpenSSL refused to use early data"); + ctx->use_earlydata = FALSE; + cf->connected = FALSE; } +#endif + ngtcp2_ccerr_default(&ctx->last_error); - return 0; + return CURLE_OK; } -static nghttp3_callbacks ngh3_callbacks = { - cb_h3_acked_req_body, /* 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 (deprecated) */ -#ifdef NGHTTP3_CALLBACKS_V2 /* nghttp3 v1.11.0+ */ - NULL, /* recv_origin */ - NULL, /* end_origin */ - NULL, /* rand */ -#endif -#ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ - NULL, /* recv_settings2 */ -#endif -}; - -static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, - struct Curl_easy *data) +CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; - int rc; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; - 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; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; } - nghttp3_settings_default(&ctx->h3settings); - - rc = nghttp3_conn_client_new(&ctx->h3conn, - &ngh3_callbacks, - &ctx->h3settings, - Curl_nghttp3_mem(), - cf); - if(rc) { - failf(data, "error creating nghttp3 connection instance"); - return CURLE_OUT_OF_MEMORY; + /* Connect the sub-chain */ + if(cf->next && !cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, done); + if(result || !*done) + return result; } - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } + *done = FALSE; - rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; + if(cf_ngtcp2_need_httpsrr(data) && + !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { + CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); + return CURLE_OK; } - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack encoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + CF_DATA_SAVE(save, cf, data); - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack decoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; + if(!ctx->qconn) { + ctx->started_at = *Curl_pgrs_now(data); + result = cf_connect_start(cf, data, &pktx); + if(result) + goto out; + if(cf->connected) { + *done = TRUE; + goto out; + } + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + /* we do not expect to be able to recv anything yet */ + goto out; } - rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, - qpack_dec_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 qpack streams: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + if(result) + goto out; + + if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { + result = ctx->tls_vrfy_result; + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + *done = TRUE; + } } - return CURLE_OK; -} +out: + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; + if(ctx->qconn && + ((result == CURLE_RECV_ERROR) || (result == CURLE_SEND_ERROR)) && + ngtcp2_conn_in_draining_period(ctx->qconn)) { + const ngtcp2_ccerr *cerr = ngtcp2_conn_get_ccerr(ctx->qconn); -static CURLcode recv_closed_stream(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_stream_ctx *stream, - size_t *pnread) -{ - (void)cf; - *pnread = 0; - if(stream->reset) { - if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { - infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again " - "on a new connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ - data->state.refused_stream = TRUE; - return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ + result = CURLE_COULDNT_CONNECT; + if(cerr) { + CURL_TRC_CF(data, cf, "connect error, type=%d, code=%" PRIu64, + (int)cerr->type, cerr->error_code); + switch(cerr->type) { + case NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION: + CURL_TRC_CF(data, cf, "error in version negotiation"); + break; + default: + if(cerr->error_code >= NGTCP2_CRYPTO_ERROR) { + CURL_TRC_CF(data, cf, "crypto error, tls alert=%u", + (unsigned int)(cerr->error_code & 0xffU)); + } + else if(cerr->error_code == NGTCP2_CONNECTION_REFUSED) { + CURL_TRC_CF(data, cf, "connection refused by server"); + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE with this code right away. We want + * to keep on trying in this case. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + } } - else if(stream->resp_hds_complete && data->req.no_body) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, " - "but we did not want a body anyway, ignore error 0x%" - PRIx64 " %s", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); - return CURLE_OK; + } + +#ifdef CURLVERBOSE + if(result) { + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket - get IP info for error reporting */ + struct ip_quadruple ip; + + if(!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)); } - failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 - " %s)", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); - return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; - } - else if(!stream->resp_hds_complete) { - failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before " - "getting all response header fields, treated as error", - stream->id); - return CURLE_HTTP3; } - return CURLE_OK; +#endif + if(!result && ctx->qconn) { + result = Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx); + } + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", (int)result, *done); + CF_DATA_RESTORE(cf, save); + return result; } -/* incoming data frames on the h3 stream */ -static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t blen, size_t *pnread) +CURLcode Curl_cf_ngtcp2_cmn_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; - struct pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx pktx; CURLcode result = CURLE_OK; - int i; - - (void)ctx; - (void)buf; - NOVERBOSE((void)blen); - CF_DATA_SAVE(save, cf, data); - DEBUGASSERT(cf->connected); - DEBUGASSERT(ctx); - DEBUGASSERT(ctx->qconn); - DEBUGASSERT(ctx->h3conn); - *pnread = 0; + if(cf->shutdown || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } - /* handshake verification failed in callback, do not recv anything */ - if(ctx->tls_vrfy_result) { - result = ctx->tls_vrfy_result; - goto denied; + if(!cf->next) { + Curl_bufq_reset(&ctx->q.sendbuf); + *done = TRUE; + return CURLE_OK; } - pktx_init(&pktx, cf, data); + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); - if(!stream || ctx->shutdown_started) { - result = CURLE_RECV_ERROR; - goto out; - } + if(!ctx->shutdown_started) { + char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + ngtcp2_ssize nwritten; - cf_ngtcp2_upd_rx_win(cf, data, stream); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = Curl_cf_ngtcp2_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", + (int)result); + *done = TRUE; + goto out; + } + } - /* first check for results/closed already known without touching - * the connection. For an already failed/closed stream, errors on - * the connection do not count. - * Then handle incoming data and check for failed/closed again. - */ - for(i = 0; i < 2; ++i) { - if(stream->xfer_result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); - result = stream->xfer_result; - goto out; + 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=%" + PRIu64 ") -> %zd", (int)ctx->last_error.type, + ctx->last_error.error_code, (ssize_t)nwritten); + /* there are cases listed in ngtcp2 documentation where this call + * may fail. Since we are doing a connection shutdown as graceful + * as we can, such an error is ignored here. */ + 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. */ + size_t n; + result = Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, + (size_t)nwritten, &n); + if(result) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " + "aborting shutdown", (int)result); + goto out; + } + + ctx->q.no_gso = TRUE; + ctx->q.gsolen = (size_t)nwritten; + ctx->q.split_len = 0; } - else if(stream->closed) { - result = recv_closed_stream(cf, data, stream, pnread); + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing egress"); + result = Curl_vquic_flush(cf, data, &ctx->q); + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; goto out; } - - if(!i && cf_progress_ingress(cf, data, &pktx)) { - result = CURLE_RECV_ERROR; + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", + (int)result); + *done = TRUE; goto out; } } - result = CURLE_AGAIN; - + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* Sent everything off. ngtcp2 seems to have no support for graceful + * shutdowns. We are done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + result = CURLE_OK; + } out: - result = Curl_1st_fatal(result, cf_progress_egress(cf, data, &pktx)); - result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx)); -denied: - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(blen=%zu) -> %d, %zu", - stream ? stream->id : -1, blen, result, *pnread); CF_DATA_RESTORE(cf, save); return result; } -static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data) +void Curl_cf_ngtcp2_cmn_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - size_t skiplen; + bool done; + Curl_cf_ngtcp2_cmn_shutdown(cf, data, &done); +} - (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 bool cf_ngtcp2_err_is_fatal(int code) +{ + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); } -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) +void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { - struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - 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; + 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); + } + else { + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); } - 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, "[%" PRId64 "] read req body -> AGAIN", stream->id); - return NGHTTP3_ERR_WOULDBLOCK; } - - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " - "%d vecs%s with %zd (buffered=%zu, left=%" FMT_OFF_T ")", - stream->id, (int)nvecs, - *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", - nwritten, Curl_bufq_len(&stream->sendbuf), - stream->upload_left); - return (nghttp3_ssize)nvecs; + if(cf_ngtcp2_err_is_fatal(code)) + Curl_cf_ngtcp2_cmn_conn_close(cf, data); } -static CURLcode h3_stream_open(struct Curl_cfilter *cf, - struct Curl_easy *data, - const uint8_t *buf, size_t len, - size_t *pnwritten) +void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = NULL; - int64_t sid; - struct dynhds h2_headers; - size_t nheader; - nghttp3_nv *nva = NULL; - int rc = 0; - unsigned int i; - nghttp3_data_reader reader; - nghttp3_data_reader *preader = NULL; - CURLcode result; - - *pnwritten = 0; - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - - result = h3_data_setup(cf, data); - if(result) - goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } - result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, pnwritten); - if(result) - goto out; - if(!stream->h1.done) { - /* need more data */ - goto out; - } - DEBUGASSERT(stream->h1.req); + io_ctx->cf = cf; + io_ctx->data = data; + io_ctx->now = *Curl_pgrs_now(data); + ngtcp2_path_storage_zero(&io_ctx->ps); + Curl_vquic_ctx_set_time(&ctx->q, &io_ctx->now); + io_ctx->ts = ((ngtcp2_tstamp)io_ctx->now.tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)io_ctx->now.tv_usec * NGTCP2_MICROSECONDS); +} - result = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); - if(result) - goto out; +void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; - /* no longer needed */ - Curl_h1_req_parse_free(&stream->h1); + io_ctx->now = *Curl_pgrs_now(data); + Curl_vquic_ctx_update_time(&ctx->q, &io_ctx->now); + io_ctx->ts = ((ngtcp2_tstamp)io_ctx->now.tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)io_ctx->now.tv_usec * NGTCP2_MICROSECONDS); +} - nheader = Curl_dynhds_count(&h2_headers); - nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } +#if NGTCP2_VERSION_NUM < 0x011100 +struct cf_ngtcp2_sfind_ctx { + int64_t stream_id; + struct h3_stream_ctx *stream; + uint32_t mid; +}; - 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; - } +static bool cf_ngtcp2_sfind(uint32_t mid, void *value, void *user_data) +{ + struct cf_ngtcp2_sfind_ctx *fctx = user_data; + struct h3_stream_ctx *stream = value; - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); - if(rc) { - failf(data, "can get bidi streams"); - result = CURLE_SEND_ERROR; - goto out; - } - stream->id = sid; - ++ctx->used_bidi_streams; - - 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; + if(fctx->stream_id == stream->id) { + fctx->mid = mid; + fctx->stream = stream; + return FALSE; } + return TRUE; /* continue */ +} - 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: - CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, " - "connection is closing", stream->id); - break; - default: - CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> " - "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); - break; - } - cf_ngtcp2_stream_close(cf, data, stream); - result = CURLE_SEND_ERROR; - goto out; - } - - cf_ngtcp2_upd_rx_win(cf, data, stream); - - if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", - stream->id, Curl_bufref_ptr(&data->state.url)); - for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, - (int)nva[i].namelen, nva[i].name, - (int)nva[i].valuelen, nva[i].value); - } - } - -out: - curlx_free(nva); - Curl_dynhds_free(&h2_headers); - return result; -} - -static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const uint8_t *buf, size_t len, bool eos, - size_t *pnwritten) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - struct cf_call_data save; - struct pkt_io_ctx pktx; - CURLcode result = CURLE_OK; - - CF_DATA_SAVE(save, cf, data); - DEBUGASSERT(cf->connected); - DEBUGASSERT(ctx->qconn); - DEBUGASSERT(ctx->h3conn); - pktx_init(&pktx, cf, data); - *pnwritten = 0; - - /* handshake verification failed in callback, do not send anything */ - if(ctx->tls_vrfy_result) { - result = ctx->tls_vrfy_result; - goto denied; - } - - (void)eos; /* use for stream EOF and block handling */ - result = cf_progress_ingress(cf, data, &pktx); - if(result) - goto out; - - if(!stream || stream->id < 0) { - if(ctx->shutdown_started) { - CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); - result = CURLE_SEND_ERROR; - goto out; - } - result = h3_stream_open(cf, data, buf, len, pnwritten); - if(result) { - CURL_TRC_CF(data, cf, "failed to open stream -> %d", result); - goto out; - } - VERBOSE(stream = H3_STREAM_CTX(ctx, data)); - } - else if(stream->xfer_result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); - result = 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, "[%" PRId64 "] discarding data" - "on closed stream with response", stream->id); - result = CURLE_OK; - *pnwritten = len; - goto out; - } - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " - "-> stream closed", stream->id, len); - result = CURLE_HTTP3; - goto out; - } - else if(ctx->shutdown_started) { - CURL_TRC_CF(data, cf, "cannot send on closed connection"); - result = CURLE_SEND_ERROR; - goto out; - } - else { - result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " - "sendbuf(len=%zu) -> %d, %zu", - stream->id, len, result, *pnwritten); - if(result) - goto out; - (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); - } - - if(*pnwritten > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata) - ctx->earlydata_skip += *pnwritten; - - DEBUGASSERT(!result); - result = cf_progress_egress(cf, data, &pktx); - -out: - result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx)); -denied: - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu", - stream ? stream->id : -1, len, result, *pnwritten); - CF_DATA_RESTORE(cf, save); - return result; -} - -struct cf_ngtcp2_recv_ctx { - struct pkt_io_ctx *pktx; - size_t pkt_count; -}; - -static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, - size_t gso_size, - struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, - void *userp) +static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, + int64_t stream_id) { - struct cf_ngtcp2_recv_ctx *rctx = userp; - struct pkt_io_ctx *pktx = rctx->pktx; - struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; - ngtcp2_pkt_info pi; - ngtcp2_path path; - size_t offset, pktlen; - int rv; - - if(!rctx->pkt_count) { - pktx_update_time(pktx->data, pktx, pktx->cf); - ngtcp2_path_storage_zero(&pktx->ps); - } - - if(ecn) - CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", - buflen, gso_size, ecn); - ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, - remote_addrlen); - pi.ecn = (uint8_t)ecn; - - for(offset = 0; offset < buflen; offset += gso_size) { - rctx->pkt_count++; - pktlen = ((offset + gso_size) <= buflen) ? gso_size : (buflen - offset); - rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, - buf + offset, pktlen, pktx->ts); - if(rv) { - 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 - is a common reason for this */ - return CURLE_PEER_FAILED_VERIFICATION; - return CURLE_RECV_ERROR; - } - } - return CURLE_OK; + struct cf_ngtcp2_sfind_ctx fctx; + fctx.stream_id = stream_id; + fctx.stream = NULL; + Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); + return fctx.stream; } - -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) +#else +static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, + int64_t stream_id) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct pkt_io_ctx local_pktx; - struct cf_ngtcp2_recv_ctx rctx; - CURLcode result = CURLE_OK; + struct Curl_easy *data = + ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - if(!pktx) { - pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; + if(!data) { + return NULL; } - result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); - if(result) - return result; - - rctx.pktx = pktx; - rctx.pkt_count = 0; - return vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_ngtcp2_recv_pkts, &rctx); + return H3_STREAM_CTX(ctx, data); } +#endif /** * Read a network packet to send from ngtcp2 into `buf`. @@ -1909,7 +1425,7 @@ static CURLcode read_pkt_to_send(void *userp, unsigned char *buf, size_t buflen, size_t *pnread) { - struct pkt_io_ctx *x = userp; + struct cf_ngtcp2_io_ctx *x = userp; struct cf_ngtcp2_ctx *ctx = x->cf->ctx; nghttp3_vec vec[16]; nghttp3_ssize veccnt; @@ -1939,7 +1455,7 @@ static CURLcode read_pkt_to_send(void *userp, if(veccnt < 0) { failf(x->data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); - cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); + Curl_cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); return CURLE_SEND_ERROR; } } @@ -1983,7 +1499,7 @@ static CURLcode read_pkt_to_send(void *userp, DEBUGASSERT(ndatalen == -1); failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", ngtcp2_strerror((int)n)); - cf_ngtcp2_err_set(x->cf, x->data, (int)n); + Curl_cf_ngtcp2_cmn_err_set(x->cf, x->data, (int)n); return CURLE_SEND_ERROR; } } @@ -2006,9 +1522,9 @@ static CURLcode read_pkt_to_send(void *userp, } } -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) +CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; size_t nread; @@ -2016,25 +1532,25 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, size_t pktcnt = 0; size_t gsolen = 0; /* this disables gso until we have a clue */ size_t send_quantum; - CURLcode curlcode; - struct pkt_io_ctx local_pktx; + CURLcode result; + struct cf_ngtcp2_io_ctx local_pktx; if(!pktx) { - pktx_init(&local_pktx, cf, data); + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); pktx = &local_pktx; } else { - pktx_update_time(data, pktx, cf); + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); ngtcp2_path_storage_zero(&pktx->ps); } - curlcode = vquic_flush(cf, data, &ctx->q); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + result = Curl_vquic_flush(cf, data, &ctx->q); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } - return curlcode; + return result; } /* In UDP, there is a maximum theoretical packet payload length and @@ -2056,12 +1572,12 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, send_quantum); for(;;) { /* add the next packet to send, if any, to our buffer */ - curlcode = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, - read_pkt_to_send, pktx, &nread); - if(curlcode == CURLE_AGAIN) + result = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, + read_pkt_to_send, pktx, &nread); + if(result == CURLE_AGAIN) break; - else if(curlcode) - return curlcode; + else if(result) + return result; else { size_t buflen = Curl_bufq_len(&ctx->q.sendbuf); if((buflen >= send_quantum) || @@ -2076,17 +1592,17 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, } else if(nread > gsolen || (gsolen > path_max_payload_size && nread != gsolen)) { - /* The added packet is a PMTUD *or* the one(s) before the - * added were PMTUD and the last one is smaller. - * Flush the buffer before the last add. */ - curlcode = vquic_send_tail_split(cf, data, &ctx->q, - gsolen, nread, nread); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + /* The added packet is a PMTUD *or* the one(s) before the added were + * PMTUD and the last one is smaller. Flush the buffer before the last + * add. */ + result = Curl_vquic_send_tail_split(cf, data, &ctx->q, + gsolen, nread, nread); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } - return curlcode; + return result; } pktcnt = 0; } @@ -2102,797 +1618,339 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, /* time to send */ CURL_TRC_CF(data, cf, "egress, send collected %zu packets in %zu bytes", pktcnt, Curl_bufq_len(&ctx->q.sendbuf)); - curlcode = vquic_send(cf, data, &ctx->q, gsolen); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire_set(data, EXPIRE_QUIC, 1, &pktx->now); return CURLE_OK; } - return curlcode; + return result; } - pktx_update_time(data, pktx, cf); + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); ngtcp2_conn_update_pkt_tx_time(ctx->qconn, pktx->ts); } return CURLE_OK; } -static CURLcode h3_data_pause(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool pause) -{ - /* There seems to exist no API in ngtcp2 to shrink/enlarge the streams - * windows. As we do in HTTP/2. */ - (void)cf; - if(!pause) - Curl_multi_mark_dirty(data); - return CURLE_OK; -} +struct cf_ngtcp2_recv_ctx { + struct cf_ngtcp2_io_ctx *pktx; + size_t pkt_count; +}; -static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2) +static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, + size_t gso_size, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, uint8_t ecn, + void *userp) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct cf_call_data save; + struct cf_ngtcp2_recv_ctx *rctx = userp; + struct cf_ngtcp2_io_ctx *pktx = rctx->pktx; + struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; + ngtcp2_pkt_info pi; + ngtcp2_path path; + size_t offset, pktlen; + int rv; - 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->h3conn, stream->id); - } - break; - } - case CF_CTRL_CONN_INFO_UPDATE: - if(!cf->sockindex && cf->connected) { - cf->conn->httpversion_seen = 30; - Curl_conn_set_multiplex(cf->conn); - } - break; - default: - break; + if(!rctx->pkt_count) { + Curl_cf_ngtcp2_io_ctx_update_time(pktx->data, pktx, pktx->cf); + ngtcp2_path_storage_zero(&pktx->ps); } - CF_DATA_RESTORE(cf, save); - return result; -} -static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) -{ - struct cf_call_data save = ctx->call_data; + if(ecn) + CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", + buflen, gso_size, (unsigned)ecn); + ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, + remote_addrlen); + pi.ecn = (uint8_t)ecn; - if(!ctx->initialized) - return; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - } - ctx->qlogfd = -1; - Curl_vquic_tls_cleanup(&ctx->tls); - vquic_ctx_free(&ctx->q); - if(ctx->h3conn) { - nghttp3_conn_del(ctx->h3conn); - ctx->h3conn = NULL; - } - if(ctx->qconn) { - ngtcp2_conn_del(ctx->qconn); - ctx->qconn = NULL; - } -#ifdef OPENSSL_QUIC_API2 - if(ctx->ossl_ctx) { - ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); - ctx->ossl_ctx = NULL; + for(offset = 0; offset < buflen; offset += gso_size) { + rctx->pkt_count++; + pktlen = ((offset + gso_size) <= buflen) ? gso_size : (buflen - offset); + rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, + buf + offset, pktlen, pktx->ts); + if(rv) { + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); + Curl_cf_ngtcp2_cmn_err_set(pktx->cf, pktx->data, rv); + + if(rv == NGTCP2_ERR_CRYPTO) + /* this is a "TLS problem", but a failed certificate verification + is a common reason for this */ + return CURLE_PEER_FAILED_VERIFICATION; + return CURLE_RECV_ERROR; + } } -#endif - ctx->call_data = save; + return CURLE_OK; } -static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) +CURLcode Curl_cf_ngtcp2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct cf_call_data save; - struct pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx local_pktx; + struct cf_ngtcp2_recv_ctx rctx; CURLcode result = CURLE_OK; - if(cf->shutdown || !ctx->qconn) { - *done = TRUE; - return CURLE_OK; + if(!pktx) { + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); + pktx = &local_pktx; } - CF_DATA_SAVE(save, cf, data); - *done = FALSE; - pktx_init(&pktx, cf, data); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; - if(!ctx->shutdown_started) { - char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_ssize nwritten; + rctx.pktx = pktx; + rctx.pkt_count = 0; - 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; + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket (via happy eyeballs) */ + CURL_TRC_CF(data, cf, "progress_ingress(socket)"); + return Curl_vquic_recv_packets(cf, data, &ctx->q, 1000, + cf_ngtcp2_recv_pkts, &rctx); + } + else { + /* Tunneled QUIC (CONNECT-UDP through proxy) */ + unsigned char *buf; + size_t max_udp_payload = QUIC_TUNNEL_INBUF_SIZE; + size_t pkt_limit = QUIC_TUNNEL_INGRESS_PKT_LIMIT; + size_t nread; + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + + CURL_TRC_CF(data, cf, "progress_ingress(sub-filters)"); + if(ctx->qconn) { + size_t max_path_payload; + max_path_payload = + ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); + if(max_path_payload > max_udp_payload) + max_udp_payload = max_path_payload; + } + + if(ctx->tunnel_inbuf_len < max_udp_payload) { + unsigned char *newbuf = curlx_realloc(ctx->tunnel_inbuf, + max_udp_payload); + if(!newbuf) + return CURLE_OUT_OF_MEMORY; + ctx->tunnel_inbuf = newbuf; + ctx->tunnel_inbuf_len = max_udp_payload; + } + buf = ctx->tunnel_inbuf; + + while(pkt_limit--) { + result = Curl_conn_cf_recv(cf->next, data, (char *)buf, + ctx->tunnel_inbuf_len, &nread); + if(result == CURLE_AGAIN) { + /* no more data available at the moment */ + return CURLE_OK; } - else if(result) { - CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); - *done = TRUE; - goto out; + if(result) { + CURL_TRC_CF(data, cf, "ingress, recv from tunnel failed: %d", + (int)result); + return result; + } + if(nread == 0) { + /* tunnel closed */ + return CURLE_OK; + } + + memcpy(&remote_addr, ctx->connected_path.remote.addr, + ctx->connected_path.remote.addrlen); + remote_addrlen = (socklen_t)ctx->connected_path.remote.addrlen; + result = cf_ngtcp2_recv_pkts(buf, nread, nread, &remote_addr, + remote_addrlen, 0, &rctx); + if(result) + return result; + + if(!ctx->q.got_first_byte) { + ctx->q.got_first_byte = TRUE; + ctx->q.first_byte_at = ctx->q.last_op; } + ctx->q.last_io = ctx->q.last_op; } + return CURLE_OK; + } +} - 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=%" - PRIu64 ") -> %zd", ctx->last_error.type, - ctx->last_error.error_code, (ssize_t)nwritten); - /* there are cases listed in ngtcp2 documentation where this call - * may fail. Since we are doing a connection shutdown as graceful - * as we can, such an error is ignored here. */ - 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. */ - size_t n; - result = Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, - (size_t)nwritten, &n); - 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; - } - } - - 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. 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_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); - 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. +/** + * 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 int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) { - 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]; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_ngtcp2_io_ctx local_pktx; + ngtcp2_tstamp expiry; - 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); + if(!pktx) { + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); + pktx = &local_pktx; } - return 0; -} -#endif /* USE_OPENSSL */ - -#ifdef USE_GNUTLS - -#ifdef CURLVERBOSE -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"; + else { + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); } - return "Unknown"; -} -#endif -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 [%u]", - 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; + 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)); + Curl_cf_ngtcp2_cmn_err_set(cf, data, rv); + return CURLE_SEND_ERROR; } - (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; + result = Curl_cf_ngtcp2_progress_ingress(cf, data, pktx); + if(!result) + result = Curl_cf_ngtcp2_progress_egress(cf, data, pktx); + if(result) { + /* a verify failure during ingress must win over generic errors */ + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; + return result; } - (void)Curl_wssl_cache_session(cf, data, ctx->peer.scache_key, - session, wolfSSL_version(ssl), - "h3", quic_tp, quic_tp_len); + /* ask again, things might have changed */ + expiry = ngtcp2_conn_get_expiry(ctx->qconn); } - } - 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; - -#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(Curl_ssl_scache_use(cf, data)) { - /* 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(Curl_ssl_scache_use(cf, data)) { - 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(Curl_ssl_scache_use(cf, data)) { - /* 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; + if(expiry > pktx->ts) { + ngtcp2_duration timeout = expiry - pktx->ts; + if(timeout % NGTCP2_MILLISECONDS) { + timeout += NGTCP2_MILLISECONDS; } + Curl_expire_set(data, EXPIRE_QUIC, + (timediff_t)(timeout / NGTCP2_MILLISECONDS), + &pktx->now); } } -#else /* not supported in the TLS backend */ - (void)data; - (void)ctx; - (void)scs; - (void)alpns; -#endif - return result; -} - -static bool cf_ngtcp2_need_httpsrr(struct Curl_easy *data) -{ -#ifdef USE_OPENSSL - return Curl_ossl_need_httpsrr(data); -#elif defined(USE_WOLFSSL) - return Curl_wssl_need_httpsrr(data); -#else - (void)data; - return FALSE; -#endif + return CURLE_OK; } -/* - * Might be called twice for happy eyeballs. - */ -static CURLcode cf_connect_start(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) +static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - int rc; - int rv; - CURLcode result; - const struct Curl_sockaddr_ex *sockaddr = NULL; - int qfd; - static const struct alpn_spec ALPN_SPEC_H3 = { { "h3", "h3-29" }, 2 }; - - DEBUGASSERT(ctx->initialized); - ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - ctx->scid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); - ctx->qlogfd = qfd; /* -1 if failure above */ - quic_settings(ctx, data, pktx); - - result = vquic_ctx_init(data, &ctx->q); - if(result) - return result; - - if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL)) - 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); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - ngtcp2_addr_init(&ctx->connected_path.local, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&ctx->connected_path.remote, - &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - 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; + const ngtcp2_transport_params *rp; + /* Peer should have sent us its transport parameters. If it + * announces a positive `max_idle_timeout` it closes the + * connection when it does not hear from us for that time. + * + * Some servers use this as a keep-alive timer at a rather low + * 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; -#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; + 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"); } - 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; + else if(!Curl_uint32_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 %" PRIu64 "ms, " + "set keep-alive to %" PRIu64 " ms.", + (rp->max_idle_timeout / NGTCP2_MILLISECONDS), + (keep_ns / NGTCP2_MILLISECONDS)); } -#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 -#error "ngtcp2 TLS backend not defined" -#endif - - ngtcp2_ccerr_default(&ctx->last_error); - - return CURLE_OK; } -static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) +CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct cf_call_data save; - struct pkt_io_ctx pktx; - - 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; - } + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - *done = FALSE; + if(!data) + return CURLE_FAILED_INIT; - if(cf_ngtcp2_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { - CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); + if(stream) return CURLE_OK; - } - - pktx_init(&pktx, cf, data); - CF_DATA_SAVE(save, cf, data); - if(!ctx->qconn) { - ctx->started_at = *Curl_pgrs_now(data); - result = cf_connect_start(cf, data, &pktx); - if(result) - goto out; - if(cf->connected) { - *done = TRUE; - goto out; - } - result = cf_progress_egress(cf, data, &pktx); - /* we do not expect to be able to recv anything yet */ - goto out; - } + stream = curlx_calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; - result = cf_progress_ingress(cf, data, &pktx); - if(result) - goto out; + stream->id = -1; + stream->rx_offset = 0; + stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; + stream->tx_in_flight_ideal = H3_STREAM_SEND_BUF_INITIAL; - result = cf_progress_egress(cf, data, &pktx); - if(result) - goto out; + /* 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->tx_in_flight_size = 0; + stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { - result = ctx->tls_vrfy_result; - if(!result) { - CURL_TRC_CF(data, cf, "peer verified"); - cf->connected = TRUE; - *done = TRUE; - } + if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + Curl_cf_ngtcp2_h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; } -out: - if(ctx->qconn && - ((result == CURLE_RECV_ERROR) || (result == CURLE_SEND_ERROR)) && - ngtcp2_conn_in_draining_period(ctx->qconn)) { - const ngtcp2_ccerr *cerr = ngtcp2_conn_get_ccerr(ctx->qconn); + if(Curl_uint32_hash_count(&ctx->streams) == 1) + cf_ngtcp2_setup_keep_alive(cf, data); - result = CURLE_COULDNT_CONNECT; - if(cerr) { - CURL_TRC_CF(data, cf, "connect error, type=%d, code=%" PRIu64, - cerr->type, cerr->error_code); - switch(cerr->type) { - case NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION: - CURL_TRC_CF(data, cf, "error in version negotiation"); - break; - default: - if(cerr->error_code >= NGTCP2_CRYPTO_ERROR) { - CURL_TRC_CF(data, cf, "crypto error, tls alert=%u", - (unsigned int)(cerr->error_code & 0xffU)); - } - else if(cerr->error_code == NGTCP2_CONNECTION_REFUSED) { - CURL_TRC_CF(data, cf, "connection refused by server"); - /* When a QUIC server instance is shutting down, it may send us a - * CONNECTION_CLOSE with this code right away. We want - * to keep on trying in this case. */ - result = CURLE_WEIRD_SERVER_REPLY; - } - } - } - } + return CURLE_OK; +} -#ifdef CURLVERBOSE - if(result) { - struct ip_quadruple ip; +void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; - if(!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 && ctx->qconn) { - result = check_and_set_expiry(cf, data, &pktx); + 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 = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" PRId64 "] cancel stream -> %d", + stream->id, (int)result); } - if(result || *done) - CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); - CF_DATA_RESTORE(cf, save); - return result; } -static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2) +void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct cf_call_data save; - - switch(query) { - case CF_QUERY_MAX_CONCURRENT: { - DEBUGASSERT(pres1); - CF_DATA_SAVE(save, cf, data); - /* 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 = cf->conn->attached_xfers; - 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, cf->conn->attached_xfers); - CF_DATA_RESTORE(cf, save); - return CURLE_OK; - } - case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->q.got_first_byte) { - timediff_t ms = curlx_ptimediff_ms(&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->q.got_first_byte) - *when = ctx->q.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; - case CF_QUERY_SSL_INFO: - case CF_QUERY_SSL_CTX_INFO: { - struct curl_tlssessioninfo *info = pres2; - if(Curl_vquic_tls_get_ssl_info(&ctx->tls, - (query == CF_QUERY_SSL_CTX_INFO), info)) - return CURLE_OK; - break; - } - case CF_QUERY_ALPN_NEGOTIATED: { - const char **palpn = pres2; - DEBUGASSERT(palpn); - *palpn = cf->connected ? "h3" : NULL; - return CURLE_OK; - } - default: - break; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + Curl_uint32_hash_remove(&ctx->streams, data->mid); + if(!Curl_uint32_hash_count(&ctx->streams)) + cf_ngtcp2_setup_keep_alive(cf, data); } - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; } -static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *input_pending) +bool Curl_cf_ngtcp2_cmn_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) { struct cf_ngtcp2_ctx *ctx = cf->ctx; bool alive = FALSE; @@ -2928,8 +1986,8 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, 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, NULL); - CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + result = Curl_cf_ngtcp2_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", (int)result); alive = result ? FALSE : TRUE; } @@ -2938,60 +1996,103 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, return alive; } -struct Curl_cftype Curl_cft_http3 = { - "HTTP/3", - 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, - cf_ngtcp2_adjust_pollset, - Curl_cf_def_data_pending, - cf_ngtcp2_send, - cf_ngtcp2_recv, - cf_ngtcp2_cntrl, - cf_ngtcp2_conn_is_alive, - Curl_cf_def_conn_keep_alive, - cf_ngtcp2_query, -}; - -CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - struct connectdata *conn, - struct Curl_sockaddr_ex *addr) +CURLcode Curl_cf_ngtcp2_h3_init_ctrls(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data) { - struct cf_ngtcp2_ctx *ctx = NULL; - struct Curl_cfilter *cf = NULL; - CURLcode result; + int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; + int rc; - ctx = curlx_calloc(1, sizeof(*ctx)); - if(!ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); + if(rc) { + 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) { + 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) { + 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) { + 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) { + failf(data, "error binding HTTP/3 qpack streams: %s", ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } - cf_ngtcp2_ctx_init(ctx); + return CURLE_OK; +} - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); - if(result) - goto out; - cf->conn = conn; +CURLcode Curl_cf_ngtcp2_cmn_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; - result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC); - if(result) - goto out; - cf->next->conn = cf->conn; - cf->next->sockindex = cf->sockindex; + switch(query) { + case CF_QUERY_CONNECT_REPLY_MS: + if((ctx->q.sockfd != CURL_SOCKET_BAD) && ctx->q.got_first_byte) { + timediff_t ms = curlx_ptimediff_ms(&ctx->q.first_byte_at, + &ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + return CURLE_OK; + } + break; + case CF_QUERY_REALLY_CONNECTED: + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + *pres1 = ctx->q.got_first_byte; + return CURLE_OK; + } + break; + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} -out: - *pcf = (!result) ? cf : NULL; - if(result) { - if(cf) - Curl_conn_cf_discard_chain(&cf, data); - else if(ctx) - cf_ngtcp2_ctx_free(ctx); +CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported) { + if((cf->cft->flags & CF_TYPE_PROXY) && + (ctx->q.sockfd != CURL_SOCKET_BAD) && ctx->q.got_first_byte) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + ctx->stats_reported = TRUE; + } + else if(ctx->handshake_at.tv_sec || ctx->handshake_at.tv_usec) { + if(ctx->q.sockfd != CURL_SOCKET_BAD) + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } + } + break; + default: + break; } return result; } -#endif +#endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h new file mode 100644 index 000000000000..e0c27a95f7c0 --- /dev/null +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -0,0 +1,253 @@ +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_CMN_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_NGTCP2) && defined(USE_NGHTTP3) + +#include +#include + +#ifdef USE_OPENSSL +#include +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) +#include +#elif defined(OPENSSL_QUIC_API2) +#include +#else +#include +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#include +#include "vtls/wolfssl.h" +#endif + +#ifdef HAVE_NETINET_UDP_H +#include +#endif + +#include + +#include "http1.h" +#include "uint-hash.h" +#include "vtls/vtls.h" +#include "vquic/vquic_int.h" +#include "vquic/vquic-tls.h" + +struct Curl_cfilter; +struct Curl_easy; +struct cf_ngtcp2_ctx; +struct cf_quic_ctx; + +#define QUIC_MAX_STREAMS (256 * 1024) +#define QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) +#define QUIC_TUNNEL_INBUF_SIZE (64 * 1024) + +/* We announce a small window size in transport param to the server, + * and grow that immediately to max when no rate limit is in place. + * We need to start small as we are not able to decrease it. */ +#define H3_STREAM_WINDOW_SIZE_INITIAL (32 * 1024) +#define H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) +#define H3_CONN_WINDOW_SIZE_MAX (100 * H3_STREAM_WINDOW_SIZE_MAX) + +#define H3_STREAM_CHUNK_SIZE (64 * 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 window + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffers do not keep + * spares. Memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES 2 +/* The max amount of un-acked upload data we keep around per stream */ +#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) +/* How much data we initially want to buffer un-acked */ +#define H3_STREAM_SEND_BUF_INITIAL (32 * 1024) + +#define QUIC_TUNNEL_INGRESS_PKT_LIMIT 1000 + + +void Curl_ngtcp2_ver(char *p, size_t len); + +typedef CURLcode cf_ngtcp2_init_h3_conn(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx); + +struct cf_ngtcp2_ctx { + struct cf_quic_ctx q; + struct ssl_peer ssl_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; + ngtcp2_cid scid; + uint32_t version; + ngtcp2_settings settings; + ngtcp2_transport_params transport_params; + ngtcp2_ccerr last_error; + ngtcp2_crypto_conn_ref conn_ref; + struct cf_call_data call_data; + cf_ngtcp2_init_h3_conn *init_h3_conn_cb; + nghttp3_conn *h3conn; + nghttp3_settings h3settings; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ + struct uint_hash streams; /* hash data->mid to h3_stream_ctx */ + 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; + unsigned char *tunnel_inbuf; /* ingress buffer for tunneled packets */ + size_t tunnel_inbuf_len; + BIT(initialized); + BIT(tls_handshake_complete); /* TLS handshake is done */ + BIT(use_earlydata); /* Using 0RTT data */ + BIT(earlydata_accepted); /* 0RTT was accepted by server */ + BIT(shutdown_started); /* queued shutdown packets */ + BIT(stats_reported); /* connect statistics reported */ +}; + +/* 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 + +CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + cf_ngtcp2_init_h3_conn *init_h3_conn_cb); +void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx); +void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code); + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + int64_t id; /* HTTP/3 stream identifier */ + struct bufq sendbuf; /* h3 request body */ + struct h1_req_parser h1; /* h1 request parsing */ + uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t rx_total; /* total number of bytes received */ + uint64_t rx_offset; /* current receive offset */ + uint64_t rx_offset_max; /* allowed receive offset */ + uint64_t window_size_max; /* max flow control window set for stream */ + size_t tx_in_flight_size; /* sendbuf data "in flight" */ + size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ + int status_code; /* HTTP status code */ + 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 */ +}; + +void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream); +void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code); + +CURLcode Curl_cf_ngtcp2_h3_init_ctrls(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data); + +CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + +CURLcode Curl_cf_ngtcp2_cmn_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); +void Curl_cf_ngtcp2_cmn_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +struct cf_ngtcp2_io_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct curltime now; + ngtcp2_tstamp ts; + ngtcp2_path_storage ps; +}; + +void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); +void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf); + +CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, + struct Curl_easy *data); +void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream); +void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, + struct Curl_easy *data); + +bool Curl_cf_ngtcp2_cmn_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending); + +CURLcode Curl_cf_ngtcp2_cmn_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); + +CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); + +#endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ + +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c new file mode 100644 index 000000000000..89bf19cc2782 --- /dev/null +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -0,0 +1,1329 @@ +/*************************************************************************** + * _ _ ____ _ + * 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(CURL_DISABLE_PROXY) && \ + defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \ + defined(USE_NGTCP2) && defined(USE_OPENSSL) + +#include "urldata.h" +#include "url.h" +#include "curl_trc.h" +#include "sendf.h" +#include "multiif.h" +#include "cfilters.h" +#include "connect.h" +#include "progress.h" +#include "curlx/dynbuf.h" +#include "http_proxy.h" +#include "vquic/vquic.h" +#include "vquic/cf-ngtcp2-cmn.h" +#include "vquic/cf-ngtcp2-proxy.h" +#include "capsule.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 PROXY_H3_STREAM_RECV_CHUNKS ((512 * 1024) / H3_STREAM_CHUNK_SIZE) + +typedef enum { + H3_TUNNEL_INIT, /* init/default/no tunnel state */ + H3_TUNNEL_CONNECT, /* CONNECT request is being sent */ + H3_TUNNEL_RESPONSE, /* CONNECT response received completely */ + H3_TUNNEL_ESTABLISHED, + H3_TUNNEL_FAILED +} h3_tunnel_state; + +struct h3_tunnel_stream { + struct Curl_peer *peer; /* where the tunnel goes to */ + struct http_resp *resp; + struct bufq recvbuf; + char *authority; + struct h3_stream_ctx *stream; + h3_tunnel_state state; + BIT(udp); + BIT(has_final_response); + BIT(closed); +}; + +static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, + struct Curl_peer *peer, + bool udp) +{ + ts->state = H3_TUNNEL_INIT; + Curl_peer_link(&ts->peer, peer); + Curl_bufq_init2(&ts->recvbuf, H3_STREAM_CHUNK_SIZE, + PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + ts->udp = udp; + /* host:port with IPv6 support */ + ts->authority = curl_maprintf("%s%s%s:%u", peer->ipv6 ? "[" : "", + peer->hostname, + peer->ipv6 ? "]" : "", + peer->port); + if(!ts->authority) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static void h3_tunnel_stream_reset(struct h3_tunnel_stream *ts) +{ + Curl_bufq_reset(&ts->recvbuf); + Curl_http_resp_free(ts->resp); + ts->resp = NULL; + ts->stream = NULL; + ts->has_final_response = FALSE; + ts->closed = FALSE; + ts->state = H3_TUNNEL_INIT; +} + +static void h3_tunnel_stream_cleanup(struct h3_tunnel_stream *ts) +{ + Curl_peer_unlink(&ts->peer); + Curl_bufq_free(&ts->recvbuf); + Curl_http_resp_free(ts->resp); + curlx_safefree(ts->authority); + ts->state = H3_TUNNEL_INIT; +} + +static void h3_tunnel_go_state(struct Curl_cfilter *cf, + struct h3_tunnel_stream *ts, + h3_tunnel_state new_state, + struct Curl_easy *data) +{ + VERBOSE(int64_t stream_id = ts->stream ? ts->stream->id : -1); + (void)cf; + + if(ts->state == new_state) + return; + + /* leaving this one */ + switch(ts->state) { + case H3_TUNNEL_CONNECT: + data->req.ignorebody = FALSE; + break; + default: + break; + } + + /* entering this one */ + switch(new_state) { + case H3_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [init]", stream_id); + h3_tunnel_stream_reset(ts); + break; + case H3_TUNNEL_CONNECT: + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [connect]", stream_id); + ts->state = H3_TUNNEL_CONNECT; + break; + case H3_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [response]", stream_id); + ts->state = H3_TUNNEL_RESPONSE; + break; + case H3_TUNNEL_ESTABLISHED: + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [established]", stream_id); + infof(data, "CONNECT%s phase completed for HTTP/3 proxy", + ts->udp ? "-UDP" : ""); + data->state.authproxy.done = TRUE; + data->state.authproxy.multipass = FALSE; + ts->state = new_state; + curlx_safefree(data->req.hd_proxy_auth); + break; + case H3_TUNNEL_FAILED: + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [failed]", stream_id); + ts->state = new_state; + /* If a proxy-authorization header was used for the proxy, then we should + make sure that it is not accidentally used for the document request + after we have connected. So let's free and clear it here. */ + curlx_safefree(data->req.hd_proxy_auth); + break; + } +} + +struct cf_h3_proxy_ctx { + struct cf_ngtcp2_ctx ngtcp2_ctx; + struct h3_tunnel_stream tunnel; /* our tunnel CONNECT stream */ + BIT(connected); +}; + +static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx); + +static CURLcode cf_h3_proxy_ctx_init(struct cf_h3_proxy_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) +{ + CURLcode result; + result = Curl_cf_ngtcp2_ctx_init(&ctx->ngtcp2_ctx, origin, peer, + sslc, cf_ngtcp2_proxy_h3_init); + if(!result) + result = h3_tunnel_stream_init(&ctx->tunnel, tunnel_peer, + TRNSPRT_IS_DGRAM(tunnel_transport)); + return result; +} + +static void cf_h3_proxy_ctx_free(struct cf_h3_proxy_ctx *ctx) +{ + if(ctx) { + Curl_cf_ngtcp2_ctx_cleanup(&ctx->ngtcp2_ctx); + h3_tunnel_stream_cleanup(&ctx->tunnel); + curlx_free(ctx); + } +} + +static int cb_h3_proxy_acked_req_body(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_h3_proxy_ctx *pctx = cf->ctx; + struct h3_stream_ctx *stream; + size_t skiplen; + (void)stream_user_data; + + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) + 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->tx_in_flight_size) + skiplen = stream->tx_in_flight_size; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->tx_in_flight_size -= skiplen; + + /* Resume upload processing if we have more data to send */ + if(stream->tx_in_flight_size < 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 int cb_h3_proxy_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_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; + + (void)conn; + (void)stream_user_data; + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) + 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, "[%" PRId64 "] RESET: error %" PRIu64, + stream->id, stream->error3); + } + else + CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + pctx->tunnel.stream = NULL; + pctx->tunnel.closed = TRUE; + Curl_multi_mark_dirty(data); + return 0; +} + +static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; + + /* how much does rate limiting allow us to acknowledge? */ + if(Curl_rlimit_active(&data->progress.dl.rlimit)) { + int64_t avail; + + /* start rate limit updates only after first bytes arrived */ + if(!stream->rx_offset) + return; + + avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); + if(avail <= 0) { + /* nothing available, do not extend the rx offset */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 + " tokens)", stream->id, avail); + return; + } + wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); + } + + if(stream->rx_offset_max < stream->rx_offset) { + DEBUGASSERT(0); + return; + } + cur_win = stream->rx_offset_max - stream->rx_offset; + if(cur_win < wanted_win) { + /* We have exhausted the credit we gave the QUIC peer for DATA. + * We extend it with the amount we can give (rate limit) */ + uint64_t ext = wanted_win - cur_win; + + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, ext); + ngtcp2_conn_extend_max_offset(ctx->qconn, ext); + stream->rx_offset_max += ext; + if(stream->rx_offset_max > stream->window_size_max) { + stream->window_size_max = stream->rx_offset_max; + CURL_TRC_CF(data, cf, "[%" PRId64 "] max window now -> %" PRIu64, + stream->id, stream->window_size_max); + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] rx_offset_max -> %" PRIu64 + " (ext %" PRIu64 ", win %" PRIu64 ")", + stream->id, stream->rx_offset_max, ext, wanted_win); + } +} + +static int cb_h3_proxy_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_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; + size_t nwritten; + CURLcode result = CURLE_OK; + (void)conn; + (void)stream3_id; + (void)stream_user_data; + + stream = pctx->tunnel.stream; + if(!data || !stream || (stream->id != stream3_id)) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + stream->rx_total += (curl_off_t)buflen; + CURL_TRC_CF(data, cf, "[cb_h3_proxy_recv_data] " + "[%" PRId64 "] DATA len=%zu, total=%" FMT_OFF_T, + stream->id, buflen, stream->rx_total); + + result = Curl_bufq_write(&pctx->tunnel.recvbuf, buf, buflen, &nwritten); + if(result || (nwritten < buflen)) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + /* DATA has been moved into our local recv buffer. Update stream offsets + * and give QUIC read credit back so long transfers over proxy tunnels + * do not stall on stream/connection flow-control limits. */ + stream->rx_offset += buflen; + if(stream->rx_offset_max < stream->rx_offset) + stream->rx_offset_max = stream->rx_offset; + + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, rx win=%" PRIu64, + stream->id, buflen, stream->rx_offset_max - stream->rx_offset); + cf_h3_proxy_upd_rx_win(cf, data, stream); + + Curl_multi_mark_dirty(data); + return 0; +} + +static int cb_h3_proxy_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_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + (void)conn; + (void)stream_user_data; + + if(!ctx) + return 0; + + /* nghttp3 has consumed bytes on the QUIC stream and we need to + * tell the QUIC connection to increase its flow control */ + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream_id, consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + + return 0; +} + +static int cb_h3_proxy_recv_header(nghttp3_conn *conn, int64_t stream_id, + 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_h3_proxy_ctx *pctx = cf->ctx; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; + CURLcode result = CURLE_OK; + int http_status; + struct http_resp *resp; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)stream_user_data; + + /* stream_user_data might be NULL for control streams */ + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] recv_header: stream lookup " + "failed for data=%p mid=%u", + stream_id, (void *)data, data ? data->mid : 0); + return 0; + } + + if(pctx->tunnel.has_final_response) { + /* we do not do anything with trailers for tunnel streams */ + return 0; + } + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return NGHTTP3_ERR_CALLBACK_FAILURE; + http_status = stream->status_code; + result = Curl_http_resp_make(&resp, http_status, NULL); + if(result) + return NGHTTP3_ERR_CALLBACK_FAILURE; + if(pctx->tunnel.resp) + Curl_http_resp_free(pctx->tunnel.resp); + pctx->tunnel.resp = resp; + } + else { + if(!pctx->tunnel.resp) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", stream_id, + (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); + result = Curl_dynhds_add(&pctx->tunnel.resp->headers, + (const char *)h3name.base, h3name.len, + (const char *)h3val.base, h3val.len); + if(result) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int cb_h3_proxy_end_headers(nghttp3_conn *conn, int64_t stream_id, + int fin, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; + (void)conn; + (void)stream_id; + (void)fin; + (void)stream_user_data; + + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers: stream lookup " + "failed for data=%p mid=%u", + stream_id, (void *)data, data ? data->mid : 0); + return 0; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", stream_id, + stream->status_code); + + if(!pctx->tunnel.has_final_response) { + if(stream->status_code / 100 != 1) { + pctx->tunnel.has_final_response = TRUE; + } + } + + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + + Curl_multi_mark_dirty(data); + return 0; +} + +static int cb_h3_proxy_stop_sending(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_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + (void)conn; + (void)stream_user_data; + + if(ctx) { + int rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, + app_error_code); + + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int cb_h3_proxy_reset_stream(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_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + int rv; + + (void)conn; + (void)stream_user_data; + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + if(!pctx->tunnel.stream || + (stream_id != pctx->tunnel.stream->id)) + return 0; + + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, + app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + pctx->tunnel.stream = NULL; + pctx->tunnel.closed = TRUE; + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static nghttp3_ssize cb_h3_tunnel_read_data(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_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; + size_t nwritten = 0; + size_t nvecs = 0; + const unsigned char *buf_base; + + (void)conn; + (void)stream_id; + (void)veccnt; + (void)stream_user_data; + (void)pflags; + + stream = pctx->tunnel.stream; + if(!data || !stream || (stream->id != stream_id)) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + /* nghttp3 keeps references to the sendbuf data until it is ACKed + * by the server (see `cb_h3_proxy_acked_req_body()` for updates). + * `tx_in_flight_size` is the amount of bytes in `sendbuf` + * that we have already passed to nghttp3, but which have not been + * ACKed yet. + * Any amount beyond `tx_in_flight_size` we need still to pass + * to nghttp3. Do that now, if we can. */ + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt) { + if(!Curl_bufq_peek_at(&stream->sendbuf, + stream->tx_in_flight_size, + &buf_base, + &vec[nvecs].len)) + break; + vec[nvecs].base = (uint8_t *)(uintptr_t)buf_base; + stream->tx_in_flight_size += vec[nvecs].len; + nwritten += vec[nvecs].len; + ++nvecs; + } + DEBUGASSERT(nvecs > 0); /* we SHOULD have been able to peek */ + } + + if(!nwritten) { + /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + stream->id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + "%zu vecs%s with %zu (buffered=%zu)", + stream->id, nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf)); + return (nghttp3_ssize)nvecs; +} + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif +static nghttp3_callbacks ngh3_proxy_callbacks = { + cb_h3_proxy_acked_req_body, /* acked_stream_data */ + cb_h3_proxy_stream_close, + cb_h3_proxy_recv_data, + cb_h3_proxy_deferred_consume, + NULL, /* begin_headers */ + cb_h3_proxy_recv_header, + cb_h3_proxy_end_headers, + NULL, /* begin_trailers */ + cb_h3_proxy_recv_header, + NULL, /* end_trailers */ + cb_h3_proxy_stop_sending, + NULL, /* end_stream */ + cb_h3_proxy_reset_stream, + NULL, /* shutdown */ + NULL, /* recv_settings (deprecated) */ +#ifdef NGHTTP3_CALLBACKS_V2 /* nghttp3 v1.11.0+ */ + NULL, /* recv_origin */ + NULL, /* end_origin */ + NULL, /* rand */ +#endif +#ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ + NULL, /* recv_settings2 */ +#endif +#ifdef NGHTTP3_CALLBACKS_V4 /* nghttp3 v1.18.0+ */ + NULL, /* stream_close2 */ +#endif +}; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif + +static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx) +{ + 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; + } + + nghttp3_settings_default(&ctx->h3settings); + + rc = nghttp3_conn_client_new(&ctx->h3conn, + &ngh3_proxy_callbacks, + &ctx->h3settings, + Curl_nghttp3_mem(), + cf); + if(rc) { + failf(data, "error creating nghttp3 connection instance"); + return CURLE_OUT_OF_MEMORY; + } + + return Curl_cf_ngtcp2_h3_init_ctrls(ctx, data); +} + +static ssize_t cf_h3_proxy_recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) +{ + ssize_t nread = -1; + *err = CURLE_OK; + + if(stream->reset) { + if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { + infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again " + "on a new connection", stream->id); + connclose(cf->conn); + data->state.refused_stream = TRUE; + *err = CURLE_RECV_ERROR; + goto out; + } + else if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, " + "but we did not want a body anyway, ignore error 0x%" + PRIx64 " %s", stream->id, stream->error3, + Curl_vquic_h3_err_str(stream->error3)); + nread = 0; + goto out; + } + failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 + " %s)", stream->id, stream->error3, + Curl_vquic_h3_err_str(stream->error3)); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before " + "getting all response header fields, treated as error", + stream->id); + *err = CURLE_HTTP3; + goto out; + } + nread = 0; + +out: + return nread; +} + +static CURLcode cf_h3_proxy_sendbuf_add(struct Curl_easy *data, + struct h3_stream_ctx *stream, + const uint8_t *buf, size_t len, + size_t *pnwritten) +{ + CURLcode result; + *pnwritten = 0; + (void)data; + + result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten); + return result; +} + +static CURLcode cf_h3_proxy_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const uint8_t *buf, size_t len, + bool eos, size_t *pnwritten) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = NULL; + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + *pnwritten = 0; + + /* handshake verification failed in callback, do not send anything */ + if(ctx->tls_vrfy_result) { + result = ctx->tls_vrfy_result; + goto denied; + } + + (void)eos; /* use for stream EOF and block handling */ + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + if(pctx->tunnel.closed) { + result = CURLE_SEND_ERROR; + goto denied; + } + + stream = pctx->tunnel.stream; + if(!stream) { + result = CURLE_SEND_ERROR; + goto denied; + } + + 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, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->id); + result = CURLE_OK; + *pnwritten = len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + result = CURLE_HTTP3; + goto out; + } + else { + result = cf_h3_proxy_sendbuf_add(data, stream, buf, len, pnwritten); + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %d, %zu", + stream->id, len, (int)result, *pnwritten); + if(result) + goto out; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + + if(*pnwritten > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata) + ctx->earlydata_skip += *pnwritten; + + DEBUGASSERT(!result); + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + +out: + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); +denied: + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu", + stream ? stream->id : -1, len, (int)result, *pnwritten); + CF_DATA_RESTORE(cf, save); + return result; +} + +/* incoming data frames on the h3 stream */ +static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, size_t *pnread) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = pctx->tunnel.stream; + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + *pnread = 0; + + /* handshake verification failed in callback, do not recv anything */ + if(ctx->tls_vrfy_result) { + result = ctx->tls_vrfy_result; + goto denied; + } + + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + + if(!stream || ctx->shutdown_started) { + result = CURLE_RECV_ERROR; + goto out; + } + + if(!Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) { + result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); + if(result) + goto out; + } + + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + /* inbufq had nothing before, maybe after progressing ingress? */ + if(!*pnread && !Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) { + result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); + if(result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] read inbufq(len=%zu) -> %zu, %d", + stream->id, len, *pnread, (int)result); + goto out; + } + } + + if(*pnread) { + Curl_multi_mark_dirty(data); + } + else { + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + result = stream->xfer_result; + goto out; + } + else if(stream->closed) { + ssize_t nread = + cf_h3_proxy_recv_closed_stream(cf, data, stream, &result); + if(nread > 0) + *pnread = (size_t)nread; + goto out; + } + result = CURLE_AGAIN; + } + +out: + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_progress_egress(cf, data, &pktx)); + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); +denied: + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %d, %zu", + stream ? stream->id : -1, len, (int)result, *pnread); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts, + struct httpreq *req) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = NULL; + struct dynhds h2_headers; + nghttp3_nv *nva = NULL; + size_t nheader; + int rc = 0; + unsigned int i; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + CURLcode result; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + result = Curl_http_req_to_h2(&h2_headers, req, data); + if(result) + goto out; + + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + if(result) + goto out; + stream = H3_STREAM_CTX(ctx, data); + DEBUGASSERT(stream); + if(!stream) { + result = CURLE_FAILED_INIT; + goto out; + } + + nheader = Curl_dynhds_count(&h2_headers); + nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); + 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 = NGHTTP3_NV_FLAG_NONE; + } + + /* Open a bidirectional stream */ + { + int64_t sid; + int rv; + + DEBUGASSERT(stream->id == -1); + /* Do NOT set `data` as stream user data. The transfer `data` may + * get cleaned up long before the tunnel goes down. */ + rv = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, NULL); + if(rv) { + failf(data, "cannot get bidi streams: %s", ngtcp2_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + stream->id = sid; + ts->stream = stream; + ++ctx->used_bidi_streams; + CURL_TRC_CF(data, cf, "[%" PRId64 "] opened bidi stream", sid); + } + + /* CONNECT-UDP request stream remains open for capsules, no fixed EOF. */ + stream->send_closed = 0; + reader.read_data = cb_h3_tunnel_read_data; + preader = &reader; + + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, + nva, nheader, preader, data); + + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, " + "connection is closing", stream->id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> %d (%s)", + stream->id, rc, nghttp3_strerror(rc)); + break; + } + result = CURLE_SEND_ERROR; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + CURL_TRC_CF(data, cf, "[H3-PROXY] [%" PRId64 "] OPENED stream " + "for %s", stream->id, + Curl_bufref_ptr(&data->state.url)); + } + +out: + curlx_free(nva); + Curl_dynhds_free(&h2_headers); + return result; +} + +static CURLcode cf_h3_proxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + bool want_recv, want_send; + CURLcode result = CURLE_OK; + curl_socket_t sock = (ctx->q.sockfd != CURL_SOCKET_BAD) ? + ctx->q.sockfd : Curl_conn_cf_get_socket(cf, data); + + if(!ctx->qconn || !pctx->tunnel.stream || (sock == CURL_SOCKET_BAD)) + return CURLE_OK; + + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + + if(want_recv || want_send || !Curl_bufq_is_empty(&ctx->q.sendbuf)) { + struct h3_stream_ctx *stream = pctx->tunnel.stream; + bool c_exhaust, s_exhaust; + + 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); + + result = Curl_pollset_set(data, ps, sock, want_recv, want_send); + } + return result; +} + +static bool cf_h3_proxy_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + if(!Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) + return TRUE; + return cf->next ? + cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +static CURLcode cf_h3_proxy_submit_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts) +{ + CURLcode result; + struct httpreq *req = NULL; + + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, + ts->peer, + PROXY_HTTP_V3, + (bool)ts->udp); + if(!result) + result = Curl_creader_set_null(data); + if(!result) + result = cf_h3_proxy_submit(cf, data, ts, req); + + if(req) + Curl_http_req_free(req); + if(result) + failf(data, "Failed sending CONNECT to proxy"); + return result; +} + +static CURLcode cf_h3_proxy_inspect_response(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + proxy_inspect_result res; + CURLcode result; + + result = Curl_http_proxy_inspect_tunnel_response( + cf, data, ts->resp, (bool)pctx->tunnel.udp, &res); + if(result) + return result; + switch(res) { + case PROXY_INSPECT_OK: + h3_tunnel_go_state(cf, ts, H3_TUNNEL_ESTABLISHED, data); + break; + case PROXY_INSPECT_FAILED: + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); + result = CURLE_COULDNT_CONNECT; + break; + case PROXY_INSPECT_AUTH_RETRY: + h3_tunnel_go_state(cf, ts, H3_TUNNEL_INIT, data); + break; + } + return result; +} + +static CURLcode cf_h3_proxy_tunnel(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts, + bool *pdone) +{ + struct cf_h3_proxy_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(ts); + DEBUGASSERT(ts->authority); + *pdone = FALSE; + do { + switch(ts->state) { + case H3_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority); + result = cf_h3_proxy_submit_CONNECT(cf, data, ts); + if(result) + goto out; + h3_tunnel_go_state(cf, ts, H3_TUNNEL_CONNECT, data); + + result = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); + if(result) + goto out; + FALLTHROUGH(); + + case H3_TUNNEL_CONNECT: + /* Non-blocking: call ingress/egress once and return. + * The multi interface will call us again when ready. */ + result = Curl_cf_ngtcp2_progress_ingress(cf, data, NULL); + if(result) + goto out; + result = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); + if(result && result != CURLE_AGAIN) { + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); + goto out; + } + + if(ts->has_final_response) { + h3_tunnel_go_state(cf, ts, H3_TUNNEL_RESPONSE, data); + } + else { + /* Not done yet, return and let multi interface call us again */ + result = CURLE_OK; + goto out; + } + FALLTHROUGH(); + + case H3_TUNNEL_RESPONSE: + DEBUGASSERT(ts->has_final_response); + result = cf_h3_proxy_inspect_response(cf, data, ts); + if(result) + goto out; + ctx->connected = TRUE; + break; + + case H3_TUNNEL_ESTABLISHED: + *pdone = TRUE; + return CURLE_OK; + + case H3_TUNNEL_FAILED: + return CURLE_RECV_ERROR; + + default: + break; + } + + } while(ts->state == H3_TUNNEL_INIT); + +out: + if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed) + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); + return result; +} + +static CURLcode cf_h3_proxy_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_call_data save = { 0 }; + CURLcode result = CURLE_OK; + struct h3_tunnel_stream *ts = &pctx->tunnel; + bool data_saved = FALSE; + + result = Curl_cf_ngtcp2_cmn_connect(cf, data, done); + if(result || !*done) + goto out; + + CF_DATA_SAVE(save, cf, data); + data_saved = TRUE; + + /* At this point the QUIC is connected, but the proxy is not connected */ + result = cf_h3_proxy_tunnel(cf, data, ts, done); + +out: + 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); + } + + if(data_saved) + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_h3_proxy_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h3_proxy_ctx *ctx = cf->ctx; + + (void)data; + if(ctx) { + CURL_TRC_CF(data, cf, "cf_h3_proxy_destroy()"); + cf_h3_proxy_ctx_free(ctx); + cf->ctx = NULL; + } +} + +static CURLcode cf_h3_proxy_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + return Curl_cf_ngtcp2_cmn_shutdown(cf, data, done); +} + +struct Curl_cftype Curl_cft_h3_proxy = { + "H3-PROXY", + CF_TYPE_IP_CONNECT | CF_TYPE_PROXY | CF_TYPE_SSL, + CURL_LOG_LVL_NONE, + cf_h3_proxy_destroy, + cf_h3_proxy_connect, + cf_h3_proxy_shutdown, + cf_h3_proxy_adjust_pollset, + cf_h3_proxy_data_pending, + cf_h3_proxy_send, + cf_h3_proxy_recv, + Curl_cf_def_cntrl, + Curl_cf_ngtcp2_cmn_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_ngtcp2_cmn_query, +}; + +CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) +{ + struct Curl_cfilter *cf = NULL; + struct cf_h3_proxy_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(!tunnel_peer) + return CURLE_FAILED_INIT; + if((transport_peer != TRNSPRT_QUIC) || (!conn->http_proxy.peer)) + return CURLE_FAILED_INIT; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = cf_h3_proxy_ctx_init(ctx, origin, peer, &conn->proxy_ssl_config, + tunnel_peer, tunnel_transport); + if(result) + goto out; + + result = Curl_cf_create(&cf, &Curl_cft_h3_proxy, ctx); + if(result) + goto out; + cf->conn = conn; + + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); + if(result) + goto out; + cf->next->conn = cf->conn; + cf->next->sockindex = cf->sockindex; + +out: + *pcf = (!result) ? cf : NULL; + if(result) { + if(cf) + Curl_conn_cf_discard_chain(&cf, data); + else if(ctx) + cf_h3_proxy_ctx_free(ctx); + } + else + CURL_TRC_CF(data, cf, "created, udp_tunnel=%d", ctx->tunnel.udp); + return result; +} + +CURLcode Curl_cf_ngtcp2_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) +{ + struct Curl_cfilter *cf = NULL; + struct cf_h3_proxy_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + (void)data; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + result = cf_h3_proxy_ctx_init(ctx, origin, peer, + &cf_at->conn->proxy_ssl_config, + tunnel_peer, tunnel_transport); + if(result) + goto out; + + result = Curl_cf_create(&cf, &Curl_cft_h3_proxy, ctx); + if(result) + goto out; + + /* H3-PROXY uses the UDP socket created by happy eyeballs below it. + Curl_conn_cf_insert_after chains the existing sub-filters, i.e. + "HAPPY-EYEBALLS -> UDP" as cf->next of H3-PROXY. */ + Curl_conn_cf_insert_after(cf_at, cf); + +out: + if(result) { + if(cf) + Curl_conn_cf_discard_chain(&cf, data); + else if(ctx) + cf_h3_proxy_ctx_free(ctx); + } + return result; +} + +#endif diff --git a/lib/vquic/cf-ngtcp2-proxy.h b/lib/vquic/cf-ngtcp2-proxy.h new file mode 100644 index 000000000000..acdee0e46338 --- /dev/null +++ b/lib/vquic/cf-ngtcp2-proxy.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_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(CURL_DISABLE_PROXY) && \ + defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \ + defined(USE_NGTCP2) && defined(USE_OPENSSL) + +CURLcode Curl_cf_ngtcp2_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); + +CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); + +#endif + +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_H */ diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c new file mode 100644 index 000000000000..e3bca8774e40 --- /dev/null +++ b/lib/vquic/cf-ngtcp2.c @@ -0,0 +1,1179 @@ +/*************************************************************************** + * _ _ ____ _ + * 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_NGTCP2) && defined(USE_NGHTTP3) + +#include "urldata.h" +#include "url.h" +#include "uint-hash.h" +#include "curl_trc.h" +#include "rand.h" +#include "multiif.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "curlx/fopen.h" +#include "curlx/dynbuf.h" +#include "http1.h" +#include "select.h" +#include "transfer.h" +#include "bufref.h" +#include "vdns/cf-dns.h" +#include "vquic/vquic.h" +#include "vquic/vquic_int.h" +#include "vquic/cf-ngtcp2-cmn.h" +#include "vquic/cf-ngtcp2.h" + + +static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data); + +static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool want_recv, want_send; + CURLcode result = CURLE_OK; + curl_socket_t sock = (ctx->q.sockfd != CURL_SOCKET_BAD) ? + ctx->q.sockfd : Curl_conn_cf_get_socket(cf, data); + + if(!ctx->qconn || (sock == CURL_SOCKET_BAD)) + return CURLE_OK; + + Curl_pollset_check(data, ps, sock, &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 || + stream->tx_in_flight_size >= stream->tx_in_flight_ideal); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + result = Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + return result; +} + +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_ngtcp2_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, "[%" PRId64 "] RESET: error %" PRIu64, + stream->id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + } + Curl_multi_mark_dirty(data); + return 0; +} + +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 buflen, bool eos) +{ + /* This function returns no error intentionally, but records + * the result at the stream, skipping further writes once the + * `result` of the transfer is known. + * The stream is subsequently cancelled "higher up" in the filter's + * send/recv callbacks. Closing the stream here leads to SEND/RECV + * errors in other places that then overwrite the transfer's result. */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, buflen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%" PRId64 "] error %d writing %zu " + "bytes of headers", stream->id, (int)stream->xfer_result, + buflen); + } +} + +static void h3_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t buflen, bool eos) +{ + /* This function returns no error intentionally, but records + * the result at the stream, skipping further writes once the + * `result` of the transfer is known. + * The stream is subsequently cancelled "higher up" in the filter's + * send/recv callbacks. Closing the stream here leads to SEND/RECV + * errors in other places that then overwrite the transfer's result. */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp(data, buf, buflen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] error %d writing %zu bytes of data", + stream->id, (int)stream->xfer_result, buflen); + } + } +} + +static void cf_ngtcp2_upd_rx_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; + + /* how much does rate limiting allow us to acknowledge? */ + if(Curl_rlimit_active(&data->progress.dl.rlimit)) { + int64_t avail; + + /* start rate limit updates only after first bytes arrived */ + if(!stream->rx_offset) + return; + + avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); + if(avail <= 0) { + /* nothing available, do not extend the rx offset */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 + " tokens)", stream->id, avail); + return; + } + wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); + } + + if(stream->rx_offset_max < stream->rx_offset) { + DEBUGASSERT(0); + return; + } + cur_win = stream->rx_offset_max - stream->rx_offset; + + if(wanted_win > cur_win) { + uint64_t delta = wanted_win - cur_win; + + if(UINT64_MAX - delta < stream->rx_offset_max) + delta = UINT64_MAX - stream->rx_offset_max; + if(delta) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] rx window, extend by %" PRIu64 + " bytes", stream->id, delta); + stream->rx_offset_max += delta; + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, delta); + } + } +} + +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_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + h3_xfer_write_resp(cf, data, stream, (const char *)buf, buflen, FALSE); + + ngtcp2_conn_extend_max_offset(ctx->qconn, buflen); + stream->rx_offset += buflen; + if(stream->rx_offset_max < stream->rx_offset) + stream->rx_offset_max = stream->rx_offset; + + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, rx win=%" PRIu64, + stream->id, buflen, stream->rx_offset_max - stream->rx_offset); + cf_ngtcp2_upd_rx_win(cf, data, stream); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + + /* nghttp3 has consumed bytes on the QUIC stream and we need to + * tell the QUIC connection to increase its flow control */ + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + if(stream) { + stream->rx_offset += consumed; + stream->rx_offset_max += consumed; + } + return 0; +} + +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we have received some headers */ + h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), + (bool)stream->closed); + + CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + Curl_multi_mark_dirty(data); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, + 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; + 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) { + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return NGHTTP3_ERR_CALLBACK_FAILURE; + 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, "[%" PRId64 "] status: %s", + stream_id, curlx_dyn_ptr(&ctx->scratch)); + if(result) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + else { + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" 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; +} + +static int cb_h3_stop_sending(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_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)conn; + (void)stream_user_data; + + rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, + app_error_code); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_h3_reset_stream(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_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)conn; + + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, + app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_req_body, /* 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 (deprecated) */ +#ifdef NGHTTP3_CALLBACKS_V2 /* nghttp3 v1.11.0+ */ + NULL, /* recv_origin */ + NULL, /* end_origin */ + NULL, /* rand */ +#endif +#ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ + NULL, /* recv_settings2 */ +#endif +#ifdef NGHTTP3_CALLBACKS_V4 /* nghttp3 v1.18.0+ */ + NULL, /* stream_close2 */ +#endif +}; +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif + +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx) +{ + 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; + } + + nghttp3_settings_default(&ctx->h3settings); + + rc = nghttp3_conn_client_new(&ctx->h3conn, + &ngh3_callbacks, + &ctx->h3settings, + Curl_nghttp3_mem(), + cf); + if(rc) { + failf(data, "error creating nghttp3 connection instance"); + return CURLE_OUT_OF_MEMORY; + } + + return Curl_cf_ngtcp2_h3_init_ctrls(ctx, data); +} + +static CURLcode recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + size_t *pnread) +{ + (void)cf; + *pnread = 0; + if(stream->reset) { + if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { + infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again " + "on a new connection", stream->id); + connclose(cf->conn); /* do not use this anymore */ + data->state.refused_stream = TRUE; + return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ + } + else if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, " + "but we did not want a body anyway, ignore error 0x%" + PRIx64 " %s", stream->id, stream->error3, + Curl_vquic_h3_err_str(stream->error3)); + return CURLE_OK; + } + failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 + " %s)", stream->id, stream->error3, + Curl_vquic_h3_err_str(stream->error3)); + return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before " + "getting all response header fields, treated as error", + stream->id); + return CURLE_HTTP3; + } + return CURLE_OK; +} + +/* incoming data frames on the h3 stream */ +static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t buflen, size_t *pnread) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + CURLcode result = CURLE_OK; + int i; + + (void)ctx; + (void)buf; + NOVERBOSE((void)buflen); + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + *pnread = 0; + + /* handshake verification failed in callback, do not recv anything */ + if(ctx->tls_vrfy_result) { + result = ctx->tls_vrfy_result; + goto denied; + } + + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + + if(!stream || ctx->shutdown_started) { + result = CURLE_RECV_ERROR; + goto out; + } + + cf_ngtcp2_upd_rx_win(cf, data, stream); + + /* first check for results/closed already known without touching + * the connection. For an already failed/closed stream, errors on + * the connection do not count. + * Then handle incoming data and check for failed/closed again. + */ + for(i = 0; i < 2; ++i) { + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + result = stream->xfer_result; + goto out; + } + else if(stream->closed) { + result = recv_closed_stream(cf, data, stream, pnread); + goto out; + } + + if(!i && Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx)) { + result = CURLE_RECV_ERROR; + goto out; + } + } + + result = CURLE_AGAIN; + +out: + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_progress_egress(cf, data, &pktx)); + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; +denied: + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(buflen=%zu) -> %d, %zu", + stream ? stream->id : -1, buflen, (int)result, *pnread); + CF_DATA_RESTORE(cf, save); + return result; +} + +static int cb_h3_acked_req_body(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_ngtcp2_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->tx_in_flight_size) + skiplen = stream->tx_in_flight_size; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->tx_in_flight_size -= skiplen; + + /* Resume upload processing if we have more data to send */ + if(stream->tx_in_flight_size < 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_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_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + size_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). + * `tx_in_flight_size` is the amount of bytes in `sendbuf` + * that we have already passed to nghttp3, but which have not been + * ACKed yet. + * Any amount beyond `tx_in_flight_size` we need still to pass + * to nghttp3. Do that now, if we can. */ + if(stream->tx_in_flight_size < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt && + Curl_bufq_peek_at(&stream->sendbuf, + stream->tx_in_flight_size, + CURL_UNCONST(&vec[nvecs].base), + &vec[nvecs].len)) { + stream->tx_in_flight_size += 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, "[%" PRId64 "] read req body -> AGAIN", stream->id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + "%zu vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", + stream->id, nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +static CURLcode h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const uint8_t *buf, size_t len, + size_t *pnwritten) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = NULL; + int64_t sid; + struct dynhds h2_headers; + size_t nheader; + nghttp3_nv *nva = NULL; + int rc = 0; + unsigned int i; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + CURLcode result; + + *pnwritten = 0; + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + if(result) + goto out; + stream = H3_STREAM_CTX(ctx, data); + DEBUGASSERT(stream); + if(!stream) { + result = CURLE_FAILED_INIT; + goto out; + } + + result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, pnwritten); + if(result) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + result = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(result) + goto out; + + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); + if(rc) { + failf(data, "cannot open bidi streams"); + result = CURLE_SEND_ERROR; + goto out; + } + stream->id = sid; + ++ctx->used_bidi_streams; + + 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 no 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->h3conn, stream->id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, " + "connection is closing", stream->id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> " + "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); + break; + } + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + result = CURLE_SEND_ERROR; + goto out; + } + + cf_ngtcp2_upd_rx_win(cf, data, stream); + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + stream->id, Curl_bufref_ptr(&data->state.url)); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } + +out: + curlx_free(nva); + Curl_dynhds_free(&h2_headers); + return result; +} + +static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const uint8_t *buf, size_t len, bool eos, + size_t *pnwritten) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + *pnwritten = 0; + + /* handshake verification failed in callback, do not send anything */ + if(ctx->tls_vrfy_result) { + result = ctx->tls_vrfy_result; + goto denied; + } + + (void)eos; /* use for stream EOF and block handling */ + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + if(!stream || stream->id < 0) { + if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); + result = CURLE_SEND_ERROR; + goto out; + } + result = h3_stream_open(cf, data, buf, len, pnwritten); + if(result) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", (int)result); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + result = 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, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->id); + result = CURLE_OK; + *pnwritten = len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + result = CURLE_HTTP3; + goto out; + } + else if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot send on closed connection"); + result = CURLE_SEND_ERROR; + goto out; + } + else if(stream->tx_in_flight_size >= stream->tx_in_flight_ideal) { + result = CURLE_AGAIN; + goto out; + } + else { + result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten); + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %d, %zu", + stream->id, len, (int)result, *pnwritten); + if(result) + goto out; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + + if(*pnwritten > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata) + ctx->earlydata_skip += *pnwritten; + + DEBUGASSERT(!result); + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + + if(*pnwritten > 0) { + const size_t delta = (2 * 1024); + if(stream->tx_in_flight_size > (stream->tx_in_flight_ideal + delta)) { + /* ngtcp2 keeps more in flight than we try to provide for, + * increase our "ideal" tx buffer length */ + stream->tx_in_flight_ideal += delta; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(%zu), added %zu, " + "inflight=%zu, increase tx_win=%zu", + stream->id, len, *pnwritten, + stream->tx_in_flight_size, stream->tx_in_flight_ideal); + } + else if((stream->tx_in_flight_size + delta) < stream->tx_in_flight_ideal) { + /* ngtcp2 keeps less in flight than we try to provide for, + * decrease our "ideal" tx buffer length */ + stream->tx_in_flight_ideal -= delta; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(%zu), added %zu, " + "inflight=%zu, reduce tx_win=%zu", + stream->id, len, *pnwritten, + stream->tx_in_flight_size, stream->tx_in_flight_ideal); + } + } + +out: + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; +denied: + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu", + stream ? stream->id : -1, len, (int)result, *pnwritten); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + /* There seems to exist no API in ngtcp2 to shrink/enlarge the streams + * windows. As we do in HTTP/2. */ + (void)cf; + if(!pause) + Curl_multi_mark_dirty(data); + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + 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: + Curl_cf_ngtcp2_h3_stream_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->tx_in_flight_size; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + break; + } + case CF_CTRL_CONN_INFO_UPDATE: + if(!cf->sockindex && cf->connected) { + cf->conn->httpversion_seen = 30; + Curl_conn_set_multiplex(cf->conn); + } + break; + default: + result = Curl_cf_ngtcp2_cmn_cntrl(cf, data, event, arg1, arg2); + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(!ctx->initialized) + return; + if(ctx->qlogfd != -1) { + curlx_close(ctx->qlogfd); + } + ctx->qlogfd = -1; + Curl_vquic_tls_cleanup(&ctx->tls); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); + Curl_vquic_ctx_free(&ctx->q); + if(ctx->h3conn) { + nghttp3_conn_del(ctx->h3conn); + ctx->h3conn = NULL; + } + if(ctx->qconn) { + ngtcp2_conn_del(ctx->qconn); + ctx->qconn = NULL; + } +#ifdef OPENSSL_QUIC_API2 + if(ctx->ossl_ctx) { + ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); + ctx->ossl_ctx = NULL; + } +#endif + ctx->call_data = save; +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + if(ctx->qconn) { + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); + Curl_cf_ngtcp2_cmn_conn_close(cf, data); + cf_ngtcp2_ctx_close(ctx); + CF_DATA_RESTORE(cf, save); + } + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); + cf->ctx = NULL; + } +} + +static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return Curl_cf_ngtcp2_cmn_connect(cf, data, done); +} + +static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + DEBUGASSERT(pres1); + CF_DATA_SAVE(save, cf, data); + /* 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 = cf->conn->attached_xfers; + 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, cf->conn->attached_xfers); + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + } + case CF_QUERY_HTTP_VERSION: + *pres1 = 30; + return CURLE_OK; + case CF_QUERY_SSL_INFO: + case CF_QUERY_SSL_CTX_INFO: { + struct curl_tlssessioninfo *info = pres2; + if(Curl_vquic_tls_get_ssl_info(&ctx->tls, + (query == CF_QUERY_SSL_CTX_INFO), info)) + return CURLE_OK; + break; + } + case CF_QUERY_ALPN_NEGOTIATED: { + const char **palpn = pres2; + DEBUGASSERT(palpn); + *palpn = cf->connected ? "h3" : NULL; + return CURLE_OK; + } + default: + break; + } + return Curl_cf_ngtcp2_cmn_query(cf, data, query, pres1, pres2); +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, + 0, + cf_ngtcp2_destroy, + cf_ngtcp2_connect, + Curl_cf_ngtcp2_cmn_shutdown, + cf_ngtcp2_adjust_pollset, + Curl_cf_def_data_pending, + cf_ngtcp2_send, + cf_ngtcp2_recv, + cf_ngtcp2_cntrl, + Curl_cf_ngtcp2_cmn_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_ngtcp2_query, +}; + +CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr) +{ + struct cf_ngtcp2_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = Curl_cf_ngtcp2_ctx_init(ctx, origin, peer, + &conn->ssl_config, init_ngh3_conn); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + cf->conn = conn; + + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); + if(result) + goto out; + cf->next->conn = cf->conn; + cf->next->sockindex = cf->sockindex; + +out: + *pcf = (!result) ? cf : NULL; + if(result) { + if(cf) + Curl_conn_cf_discard_chain(&cf, data); + else if(ctx) { + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); + } + } + return result; +} + +CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer) +{ + struct cf_ngtcp2_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = Curl_cf_ngtcp2_ctx_init(ctx, origin, peer, + &cf_at->conn->ssl_config, init_ngh3_conn); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + if(result) { + curlx_safefree(cf); + if(ctx) { + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); + } + } + return result; +} + +#endif diff --git a/lib/vquic/curl_ngtcp2.h b/lib/vquic/cf-ngtcp2.h similarity index 81% rename from lib/vquic/curl_ngtcp2.h rename to lib/vquic/cf-ngtcp2.h index 185272ace030..601efc82245a 100644 --- a/lib/vquic/curl_ngtcp2.h +++ b/lib/vquic/cf-ngtcp2.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H -#define HEADER_CURL_VQUIC_CURL_NGTCP2_H +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -48,12 +48,16 @@ struct Curl_cfilter; #include "urldata.h" -void Curl_ngtcp2_ver(char *p, size_t len); - CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr); + +CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer); #endif -#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */ +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_H */ diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/cf-quiche.c similarity index 91% rename from lib/vquic/curl_quiche.c rename to lib/vquic/cf-quiche.c index 73f664a65344..934a56cadaf1 100644 --- a/lib/vquic/curl_quiche.c +++ b/lib/vquic/cf-quiche.c @@ -32,7 +32,6 @@ #include "uint-hash.h" #include "urldata.h" #include "cfilters.h" -#include "cf-dns.h" #include "cf-socket.h" #include "curl_trc.h" #include "rand.h" @@ -40,11 +39,14 @@ #include "connect.h" #include "progress.h" #include "select.h" +#include "http.h" #include "http1.h" +#include "sockaddr.h" +#include "vdns/cf-dns.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" -#include "vquic/curl_quiche.h" +#include "vquic/cf-quiche.h" #include "transfer.h" #include "url.h" #include "bufref.h" @@ -75,7 +77,7 @@ void Curl_quiche_ver(char *p, size_t len) struct cf_quiche_ctx { struct cf_quic_ctx q; - struct ssl_peer peer; + struct ssl_peer ssl_peer; struct curl_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; @@ -92,6 +94,7 @@ struct cf_quiche_ctx { BIT(goaway); /* got GOAWAY from server */ BIT(x509_store_setup); /* if x509 store has been set up */ BIT(shutdown_started); /* queued shutdown packets */ + BIT(stats_reported); /* connect statistics reported */ }; #ifdef DEBUG_QUICHE @@ -106,7 +109,10 @@ static void quiche_debug_log(const char *line, void *argp) static void h3_stream_hash_free(unsigned int id, void *stream); -static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) +static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc) { DEBUGASSERT(!ctx->initialized); #ifdef DEBUG_QUICHE @@ -121,6 +127,7 @@ static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) BUFQ_OPT_SOFT_LIMIT); ctx->data_recvd = 0; ctx->initialized = TRUE; + return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); } static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) @@ -129,8 +136,8 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) /* quiche freed it */ ctx->tls.ossl.ssl = NULL; Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->peer); - vquic_ctx_free(&ctx->q); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); + Curl_vquic_ctx_free(&ctx->q); Curl_uint32_hash_destroy(&ctx->streams); curlx_dyn_free(&ctx->h1hdr); Curl_bufq_free(&ctx->writebuf); @@ -156,6 +163,7 @@ static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx) quiche_config_free(ctx->cfg); ctx->cfg = NULL; } + Curl_ssl_peer_cleanup(&ctx->ssl_peer); } static CURLcode cf_flush_egress(struct Curl_cfilter *cf, @@ -247,7 +255,6 @@ static bool cf_quiche_do_expire(struct Curl_cfilter *cf, struct h3_stream_ctx *stream, void *user_data) { - (void)stream; (void)user_data; CURL_TRC_CF(sdata, cf, "conn closed, mark as dirty"); stream->xfer_result = CURLE_SEND_ERROR; @@ -298,7 +305,7 @@ static void cf_quiche_stream_close(struct Curl_cfilter *cf, result = cf_flush_egress(cf, data); if(result) CURL_TRC_CF(data, cf, "[%" PRIu64 "] stream close, flush egress -> %d", - stream->id, result); + stream->id, (int)result); } } @@ -338,7 +345,8 @@ static void cf_quiche_write_hd(struct Curl_cfilter *cf, stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); if(stream->xfer_result) CURL_TRC_CF(data, cf, "[%" PRIu64 "] error %d writing %zu " - "bytes of headers", stream->id, stream->xfer_result, blen); + "bytes of headers", stream->id, (int)stream->xfer_result, + blen); } } @@ -418,7 +426,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, if(result) { CURL_TRC_CF(x->data, x->cf, "[%" PRIu64 "] on header error %d", - stream->id, result); + stream->id, (int)result); if(!stream->xfer_result) stream->xfer_result = result; } @@ -459,7 +467,7 @@ static void cf_quiche_flush_body(struct Curl_cfilter *cf, Curl_bufq_skip(&ctx->writebuf, blen); if(stream->xfer_result) { CURL_TRC_CF(data, cf, "[%" PRIu64 "] error %d writing %zu bytes" - " of data", stream->id, stream->xfer_result, blen); + " of data", stream->id, (int)stream->xfer_result, blen); } } else @@ -496,9 +504,9 @@ static void cf_quiche_recv_body(struct Curl_cfilter *cf, break; else if(result) { CURL_TRC_CF(data, cf, "[%" PRIu64 "] recv_body error %d", - stream->id, result); + stream->id, (int)result); failf(data, "[%" PRIu64 "] Error %d in HTTP/3 response body for stream", - stream->id, result); + stream->id, (int)result); stream->closed = TRUE; stream->reset = TRUE; stream->send_closed = TRUE; @@ -514,10 +522,14 @@ static void cf_quiche_process_ev(struct Curl_cfilter *cf, struct h3_stream_ctx *stream, quiche_h3_event *ev) { + enum quiche_h3_event_type type; + if(!stream) return; - switch(quiche_h3_event_type(ev)) { + type = quiche_h3_event_type(ev); + + switch(type) { case QUICHE_H3_EVENT_HEADERS: { struct cb_ctx cb_ctx; stream->resp_got_header = TRUE; @@ -563,12 +575,12 @@ static void cf_quiche_process_ev(struct Curl_cfilter *cf, default: CURL_TRC_CF(data, cf, "[%" PRIu64 "] recv, unhandled event %d", - stream->id, quiche_h3_event_type(ev)); + stream->id, (int)type); break; } } -struct cf_quich_disp_ctx { +struct cf_quiche_disp_ctx { uint64_t stream_id; struct Curl_cfilter *cf; struct Curl_multi *multi; @@ -577,7 +589,7 @@ struct cf_quich_disp_ctx { static bool cf_quiche_disp_event(uint32_t mid, void *val, void *user_data) { - struct cf_quich_disp_ctx *dctx = user_data; + struct cf_quiche_disp_ctx *dctx = user_data; struct h3_stream_ctx *stream = val; if(stream->id == dctx->stream_id) { @@ -618,7 +630,7 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, else { /* another transfer, do not return errors, as they are not for * the calling transfer */ - struct cf_quich_disp_ctx dctx; + struct cf_quiche_disp_ctx dctx; dctx.stream_id = (uint64_t)rv; dctx.cf = cf; dctx.multi = data->multi; @@ -640,7 +652,7 @@ struct recv_ctx { static CURLcode cf_quiche_recv_pkts(const unsigned char *buf, size_t buflen, size_t gso_size, struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, + socklen_t remote_addrlen, uint8_t ecn, void *userp) { struct recv_ctx *r = userp; @@ -715,8 +727,8 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, rctx.data = data; rctx.pkts = 0; - result = vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_quiche_recv_pkts, &rctx); + result = Curl_vquic_recv_packets(cf, data, &ctx->q, 1000, + cf_quiche_recv_pkts, &rctx); if(result) return result; @@ -785,7 +797,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, } } - result = vquic_flush(cf, data, &ctx->q); + result = Curl_vquic_flush(cf, data, &ctx->q); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -807,7 +819,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, if(result != CURLE_AGAIN) return result; /* Nothing more to add, flush and leave */ - result = vquic_send(cf, data, &ctx->q, gsolen); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -820,7 +832,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, ++pkt_count; if(nread < gsolen || pkt_count >= MAX_PKT_BURST) { - result = vquic_send(cf, data, &ctx->q, gsolen); + result = Curl_vquic_send(cf, data, &ctx->q, gsolen); if(result) { if(result == CURLE_AGAIN) { Curl_expire(data, 1, EXPIRE_QUIC); @@ -855,7 +867,7 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) { infof(data, "HTTP/3 stream %" PRIu64 " refused by server, try again " "on a new connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ + connclose(cf->conn); /* do not use this anymore */ data->state.refused_stream = TRUE; return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ } @@ -863,15 +875,15 @@ static CURLcode recv_closed_stream(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%" PRIu64 "] error after response headers, " "but we did not want a body anyway, ignore error 0x%" PRIx64 " %s", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); return CURLE_OK; } failf(data, "HTTP/3 stream %" PRIu64 " reset by server (error 0x%" PRIx64 " %s)", stream->id, stream->error3, - vquic_h3_err_str(stream->error3)); + Curl_vquic_h3_err_str(stream->error3)); result = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_recv, was reset -> %d", - stream->id, result); + stream->id, (int)result); } else if(!stream->resp_got_header) { failf(data, "HTTP/3 stream %" PRIu64 " was closed cleanly, but before " @@ -892,7 +904,7 @@ static CURLcode cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, *pnread = 0; (void)buf; (void)blen; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); if(!stream) return CURLE_RECV_ERROR; @@ -922,7 +934,8 @@ static CURLcode cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(*pnread > 0) ctx->data_recvd += *pnread; CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_recv(len=%zu) -> %d, %zu, total=%" - FMT_OFF_T, stream->id, blen, result, *pnread, ctx->data_recvd); + FMT_OFF_T, stream->id, blen, (int)result, *pnread, + ctx->data_recvd); return result; } @@ -999,9 +1012,9 @@ static CURLcode h3_open_stream(struct Curl_cfilter *cf, DEBUGASSERT(stream); result = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, pnwritten); + !data->state.http_ignorecustom ? + CURL_EASY_STR(data, STRING_CUSTOMREQUEST) : NULL, + 0, pnwritten); if(result) goto out; if(!stream->h1.done) { @@ -1102,7 +1115,7 @@ static CURLcode cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode result; *pnwritten = 0; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); result = cf_process_ingress(cf, data); if(result) @@ -1128,7 +1141,7 @@ static CURLcode cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, * 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, "[%" PRIu64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" PRIu64 "] discarding data " "on closed stream with response", stream->id); result = CURLE_OK; *pnwritten = len; @@ -1148,7 +1161,7 @@ static CURLcode cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_send(len=%zu) -> %d, %zu", stream ? stream->id : (uint64_t)~0, len, - result, *pnwritten); + (int)result, *pnwritten); return result; } @@ -1232,7 +1245,7 @@ static CURLcode cf_quiche_cntrl(struct Curl_cfilter *cf, body[0] = 'X'; result = cf_quiche_send(cf, data, body, 0, TRUE, &sent); CURL_TRC_CF(data, cf, "[%" PRIu64 "] DONE_SEND -> %d, %zu", - stream->id, result, sent); + stream->id, (int)result, sent); } break; } @@ -1242,6 +1255,14 @@ static CURLcode cf_quiche_cntrl(struct Curl_cfilter *cf, Curl_conn_set_multiplex(cf->conn); } break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !ctx->stats_reported && + (ctx->handshake_at.tv_sec || ctx->handshake_at.tv_usec)) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->q.first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + ctx->stats_reported = TRUE; + } + break; default: break; } @@ -1260,7 +1281,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); DEBUGASSERT(ctx->initialized); - result = vquic_ctx_init(data, &ctx->q); + result = Curl_vquic_ctx_init(data, &ctx->q); if(result) return result; @@ -1286,11 +1307,10 @@ static CURLcode cf_quiche_ctx_open(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 *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - - 1); + (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), + CURL_CSTRLEN(QUICHE_H3_APPLICATION_PROTOCOL)); - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->ssl_peer, &ALPN_SPEC_H3, NULL, NULL, cf, NULL); if(result) return result; @@ -1339,7 +1359,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, unsigned alpn_len, offset = 0; /* Replace each ALPN length prefix by a comma. */ - while(offset < sizeof(alpn_protocols) - 1) { + while(offset < CURL_CSTRLEN(alpn_protocols)) { alpn_len = alpn_protocols[offset]; alpn_protocols[offset] = ','; offset += 1 + alpn_len; @@ -1356,7 +1376,7 @@ static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->ssl_peer); } static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, @@ -1380,12 +1400,12 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, *done = FALSE; if(Curl_ossl_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; } - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); if(!ctx->qconn) { result = cf_quiche_ctx_open(cf, data); @@ -1417,6 +1437,10 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, result = CURLE_OUT_OF_MEMORY; goto out; } + /* quiche 0.29.3+ rejects response headers larger than 32 KiB by + default. Allow as much as curl itself accepts. */ + quiche_h3_config_set_max_field_section_size(ctx->h3config, + MAX_HTTP_RESP_HEADER_SIZE); /* Create a new HTTP/3 connection on the QUIC connection. */ ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); @@ -1467,7 +1491,7 @@ static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, int err; ctx->shutdown_started = TRUE; - vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); + Curl_vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data)); err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); if(err) { CURL_TRC_CF(data, cf, "error %d adding shutdown packet, " @@ -1485,8 +1509,8 @@ static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, } 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. */ + /* sent everything, quiche does not seem to support a graceful shutdown + * waiting for a reply, so we are done. */ CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); *done = TRUE; } @@ -1498,16 +1522,6 @@ static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, 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); - cf->connected = FALSE; - } -} - static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { (void)data; @@ -1545,18 +1559,6 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, else *pres1 = -1; return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - if(ctx->q.got_first_byte) - *when = ctx->q.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; @@ -1625,7 +1627,6 @@ struct Curl_cftype Curl_cft_http3 = { 0, cf_quiche_destroy, cf_quiche_connect, - cf_quiche_close, cf_quiche_shutdown, cf_quiche_adjust_pollset, Curl_cf_def_data_pending, @@ -1639,6 +1640,8 @@ struct Curl_cftype Curl_cft_http3 = { CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr) { @@ -1651,14 +1654,15 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - cf_quiche_ctx_init(ctx); - - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + result = cf_quiche_ctx_init(ctx, origin, peer, &conn->ssl_config); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) goto out; cf->conn = conn; - result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC); + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); if(result) goto out; cf->next->conn = cf->conn; @@ -1676,4 +1680,34 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, return result; } +CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer) +{ + struct cf_quiche_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = cf_quiche_ctx_init(ctx, origin, peer, &cf_at->conn->ssl_config); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); + +out: + if(result) { + curlx_safefree(cf); + if(ctx) + cf_quiche_ctx_free(ctx); + } + + return result; +} + #endif diff --git a/lib/vquic/curl_quiche.h b/lib/vquic/cf-quiche.h similarity index 78% rename from lib/vquic/curl_quiche.h rename to lib/vquic/cf-quiche.h index c2c88ddeafe3..88d9161dd768 100644 --- a/lib/vquic/curl_quiche.h +++ b/lib/vquic/cf-quiche.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H -#define HEADER_CURL_VQUIC_CURL_QUICHE_H +#ifndef HEADER_CURL_VQUIC_CF_QUICHE_H +#define HEADER_CURL_VQUIC_CF_QUICHE_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -37,9 +37,14 @@ void Curl_quiche_ver(char *p, size_t len); CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr); +CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer); #endif -#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */ +#endif /* HEADER_CURL_VQUIC_CF_QUICHE_H */ diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index d81f2a9e6bd5..75e2f4e1e2c4 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -49,17 +49,12 @@ #include "vtls/vtls_scache.h" #include "vquic/vquic-tls.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) +CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct ssl_peer *ssl_peer) { char tls_id[80]; - CURLcode result; #ifdef USE_OPENSSL Curl_ossl_version(tls_id, sizeof(tls_id)); @@ -71,22 +66,31 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, #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; + if(ssl_peer->origin || ssl_peer->peer) + Curl_ssl_peer_cleanup(ssl_peer); + return Curl_ssl_peer_init(ssl_peer, origin, peer, sslc, + tls_id, TRNSPRT_QUIC); +} +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *ssl_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) +{ #ifdef USE_OPENSSL - (void)result; - return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, alpns, + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, ssl_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, + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, ssl_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, + return Curl_wssl_ctx_init(&ctx->wssl, cf, data, ssl_peer, alpns, cb_setup, cb_user_data, ssl_user_data, session_reuse_cb); #else @@ -162,9 +166,9 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, (void)conn_config; result = Curl_ossl_check_peer_cert(cf, data, &ctx->ossl, peer); #elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(cf, data, ctx->gtls.session, - conn_config, &data->set.ssl, peer, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + result = Curl_gtls_verifyserver( + cf, data, ctx->gtls.session, conn_config, &data->set.ssl, peer, + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY)); if(result) return result; #elif defined(USE_WOLFSSL) @@ -178,7 +182,7 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, NULL) == WOLFSSL_FAILURE)) result = CURLE_PEER_FAILED_VERIFICATION; else if(!peer->sni && - (wolfSSL_X509_check_ip_asc(cert, peer->hostname, + (wolfSSL_X509_check_ip_asc(cert, peer->origin->hostname, 0) == WOLFSSL_FAILURE)) result = CURLE_PEER_FAILED_VERIFICATION; wolfSSL_X509_free(cert); diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h index c461c1548b17..e8d2418b0611 100644 --- a/lib/vquic/vquic-tls.h +++ b/lib/vquic/vquic-tls.h @@ -66,13 +66,18 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, struct Curl_ssl_session *scs, bool *do_early_data); +CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct ssl_peer *ssl_peer); + /** * 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 to be connected to + * @param ssl_peer the SSL peer to be connected to * @param alpns the ALPN specifications to negotiate, may be NULL * @param cb_setup optional callback for early TLS config * @param cb_user_data user_data param for callback @@ -82,7 +87,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, - struct ssl_peer *peer, + struct ssl_peer *ssl_peer, const struct alpn_spec *alpns, Curl_vquic_tls_ctx_setup *cb_setup, void *cb_user_data, diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 0040ad671fb8..bbc5777eac88 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -24,6 +24,7 @@ #include "curl_setup.h" #include "urldata.h" #include "vquic/vquic.h" +#include "vtls/vtls.h" #include "curl_trc.h" @@ -32,17 +33,30 @@ #ifdef HAVE_NETINET_UDP_H #include #endif +#ifdef HAVE_NETINET_IP_H +#include +#endif #ifdef USE_NGHTTP3 #include #endif +#if defined(USE_APPLE_FAST_UDP) && defined(__APPLE__) +#include +#if defined(SYS_recvmsg_x) && defined(SYS_sendmsg_x) +#define HAVE_APPLE_MSG_X +#endif +#endif + #include "bufq.h" #include "curlx/dynbuf.h" #include "curlx/fopen.h" #include "cfilters.h" -#include "vquic/curl_ngtcp2.h" -#include "vquic/curl_quiche.h" +#include "vdns/cf-dns.h" +#include "vquic/cf-ngtcp2.h" +#include "vquic/cf-ngtcp2-cmn.h" +#include "vquic/cf-ngtcp2-proxy.h" +#include "vquic/cf-quiche.h" #include "multiif.h" #include "progress.h" #include "rand.h" @@ -54,6 +68,31 @@ #define NW_CHUNK_SIZE (64 * 1024) #define NW_SEND_CHUNKS 1 +#ifdef HAVE_APPLE_MSG_X + +/* this is `struct msghdr` with an additional field at the end */ +struct msghdr_x { + void *msg_name; /* optional address */ + socklen_t msg_namelen; /* size of address */ + struct iovec *msg_iov; /* scatter/gather array */ + int msg_iovlen; /* # elements in msg_iov */ + void *msg_control; /* ancillary data, see below */ + socklen_t msg_controllen; /* ancillary data buffer len */ + int msg_flags; /* flags on received message */ + size_t msg_datalen; /* byte length of buffer in msg_iov */ +}; +#endif + +#ifdef CURLVERBOSE +#ifdef HAVE_APPLE_MSG_X +#define VQUIC_SEND_METHOD "sendmsg_x" +#elif defined(HAVE_SENDMSG) +#define VQUIC_SEND_METHOD "sendmsg" +#else +#define VQUIC_SEND_METHOD "send" +#endif +#endif + int Curl_vquic_init(void) { #if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) @@ -64,6 +103,14 @@ int Curl_vquic_init(void) return 1; } +void Curl_vquic_cleanup(void) +{ +#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) && \ + (NGTCP2_VERSION_NUM >= 0x011800) + ngtcp2_crypto_ossl_free(); +#endif +} + void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) @@ -73,13 +120,15 @@ void Curl_quic_ver(char *p, size_t len) #endif } -CURLcode vquic_ctx_init(struct Curl_easy *data, - struct cf_quic_ctx *qctx) +CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, + struct cf_quic_ctx *qctx) { Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS, BUFQ_OPT_SOFT_LIMIT); #if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) qctx->no_gso = FALSE; +#elif defined(HAVE_APPLE_MSG_X) + qctx->no_gso = FALSE; #else qctx->no_gso = TRUE; #endif @@ -93,28 +142,119 @@ CURLcode vquic_ctx_init(struct Curl_easy *data, } } #endif - vquic_ctx_set_time(qctx, Curl_pgrs_now(data)); + Curl_vquic_ctx_set_time(qctx, Curl_pgrs_now(data)); return CURLE_OK; } -void vquic_ctx_free(struct cf_quic_ctx *qctx) +void Curl_vquic_ctx_free(struct cf_quic_ctx *qctx) { Curl_bufq_free(&qctx->sendbuf); } -void vquic_ctx_set_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow) +void Curl_vquic_ctx_set_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow) { qctx->last_op = *pnow; } -void vquic_ctx_update_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow) +void Curl_vquic_ctx_update_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow) { qctx->last_op = *pnow; } +#ifdef HAVE_APPLE_MSG_X +static CURLcode do_sendmsg(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent) +{ +#define MSG_X_SNUM 64 + struct iovec msg_iov[MSG_X_SNUM]; + struct msghdr_x mmsg[MSG_X_SNUM]; + char errstr[STRERROR_LEN]; + size_t n, i = 0, sent = 0; + int rc; + CURLcode result = CURLE_OK; + VERBOSE(size_t calls = 0); + + *psent = 0; + if(!pktlen) + return CURLE_OK; + if(!gsolen || (pktlen < gsolen)) + gsolen = pktlen; + n = (pktlen + gsolen - 1) / gsolen; + while(i < n) { + size_t j, batch = CURLMIN(n - i, MSG_X_SNUM), pkts_sent = 0; + + for(j = 0; j < batch; ++j) { + const size_t offset = (i + j) * gsolen; + msg_iov[j].iov_base = CURL_UNCONST(pkt + offset); + msg_iov[j].iov_len = CURLMIN(gsolen, pktlen - offset); + memset(&mmsg[j], 0, sizeof(mmsg[j])); + mmsg[j].msg_iov = &msg_iov[j]; + mmsg[j].msg_iovlen = 1; + mmsg[j].msg_datalen = msg_iov[j].iov_len; + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((rc = syscall(SYS_sendmsg_x, qctx->sockfd, &mmsg, batch, 0)) == -1 && + (SOCKERRNO == SOCKEINTR)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + + if(rc < 0) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "egress, sendmsg_x -> EAGAIN"); + result = sent ? CURLE_OK : CURLE_AGAIN; + goto out; + } + if(SOCKERRNO != SOCKEMSGSIZE) { + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: sendmsg_x() returned %d (errno=%d; %s)", + rc, SOCKERRNO, errstr); + result = CURLE_SEND_ERROR; + goto out; + } + /* Error was SOCKEMSGSIZE. Network stack does not accept the packet + * length(s). This might be a PMTUD. Just drop it into the void + * and fall through to the success handling. */ + pkts_sent = batch; + } + else + pkts_sent = CURLMIN((size_t)rc, batch); + + VERBOSE(++calls); + if(!pkts_sent) { /* no packets of the current batch were sent */ + result = sent ? CURLE_OK : CURLE_AGAIN; + goto out; + } + i += pkts_sent; /* Some have been sent */ + for(j = 0; j < pkts_sent; ++j) + sent += msg_iov[j].iov_len; + if(pkts_sent < batch) + goto out; /* but not all of them */ + } + +out: + *psent = sent; + if(sent || result) + CURL_TRC_CF(data, cf, + "vquic_sendmsg_x(len=%zu, gso=%zu, packets=%zu, " + "calls=%zu) -> %d", + sent, gsolen, n, calls, (int)result); + return result; +} + +#else /* HAVE_APPLE_MSG_X */ + static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, @@ -149,7 +289,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, * does not seem to like a msg_control of length 0. */ memset(msg_ctrl, 0, sizeof(msg_ctrl)); msg.msg_control = msg_ctrl; - assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); + DEBUGASSERT(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); msg.msg_controllen = CMSG_SPACE(sizeof(int)); cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; @@ -163,12 +303,10 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, ; if(!curlx_sztouz(rv, psent)) { - switch(SOCKERRNO) { - case EAGAIN: -#if EAGAIN != SOCKEWOULDBLOCK - case SOCKEWOULDBLOCK: -#endif + int sockerr = SOCKERRNO; + if(SOCK_EAGAIN(sockerr)) return CURLE_AGAIN; + switch(sockerr) { case SOCKEMSGSIZE: /* UDP datagram is too large; caused by PMTUD. Let it be lost. */ *psent = pktlen; @@ -177,13 +315,13 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, if(pktlen > gsolen) { /* GSO failure */ infof(data, "sendmsg() returned %zd (errno %d); disable GSO", rv, - SOCKERRNO); + sockerr); qctx->no_gso = TRUE; return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } FALLTHROUGH(); default: - failf(data, "sendmsg() returned %zd (errno %d)", rv, SOCKERRNO); + failf(data, "sendmsg() returned %zd (errno %d)", rv, sockerr); result = CURLE_SEND_ERROR; goto out; } @@ -204,7 +342,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, ; if(!curlx_sztouz(rv, psent)) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + if(SOCK_EAGAIN(SOCKERRNO)) { result = CURLE_AGAIN; goto out; } @@ -222,16 +360,13 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, (void)cf; out: + CURL_TRC_CF(data, cf, + "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu", + VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent); return result; } -#ifdef CURLVERBOSE -#ifdef HAVE_SENDMSG -#define VQUIC_SEND_METHOD "sendmsg" -#else -#define VQUIC_SEND_METHOD "send" -#endif -#endif +#endif /* !HAVE_APPLE_MSG_X */ static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -255,9 +390,48 @@ static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, VERBOSE(++calls); } out: - CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=%zu)" - " -> %d, sent=%zu", - VQUIC_SEND_METHOD, pktlen, gsolen, calls, result, *psent); + CURL_TRC_CF(data, cf, + "vquic_%s(len=%zu, gso=%zu, calls=%zu) -> %d, sent=%zu", + VQUIC_SEND_METHOD, pktlen, gsolen, calls, (int)result, *psent); + return result; +} + +/* Split QUIC payload by datagram (gso) boundaries when sending over a + * non-UDP lower filter (for example CONNECT-UDP proxy tunnel). */ +static CURLcode send_packet_no_gso_cf(struct Curl_cfilter *cf, + struct Curl_easy *data, + const uint8_t *pkt, size_t pktlen, + size_t gsolen, size_t *psent) +{ + const uint8_t *p, *end = pkt + pktlen; + size_t sent, len; + CURLcode result = CURLE_OK; + VERBOSE(size_t calls = 0); + + *psent = 0; + + /* Send one datagram-sized chunk per call into the lower filter. */ + for(p = pkt; p < end; p += len) { + len = CURLMIN(gsolen, (size_t)(end - p)); + result = Curl_conn_cf_send(cf->next, data, p, len, FALSE, &sent); + /* Report forward progress even if we return CURLE_AGAIN later. */ + VERBOSE(++calls); + /* Preserve lower-filter errors (including CURLE_AGAIN). */ + if(result) + goto out; + + if(sent != len) { + /* We can only send the complete datagram, not parts. */ + result = CURLE_SEND_ERROR; + goto out; + } + *psent += sent; + } + +out: + CURL_TRC_CF(data, cf, + "vquic_cf_send(len=%zu, gso=%zu, calls=%zu) -> %d, sent=%zu", + pktlen, gsolen, calls, (int)result, *psent); return result; } @@ -285,17 +459,14 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, } else { result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); - CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=1)" - " -> %d, sent=%zu", - VQUIC_SEND_METHOD, pktlen, gsolen, result, *psent); } if(!result) qctx->last_io = qctx->last_op; return result; } -CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx) +CURLcode Curl_vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx) { const unsigned char *buf; size_t blen, sent; @@ -310,7 +481,22 @@ CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, blen = qctx->split_len; } - result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent); + if(qctx->sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket (via happy eyeballs) */ + result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent); + } + else { + /* Tunneled QUIC (CONNECT-UDP through proxy) */ + if(gsolen && (blen > gsolen)) { + /* Send one datagram at a time to preserve packet boundaries. */ + result = send_packet_no_gso_cf(cf, data, buf, blen, gsolen, &sent); + } + else { + /* No GSO aggregate to split, regular lower-filter send is enough. */ + result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &sent); + } + } + if(result) { if(result == CURLE_AGAIN) { Curl_bufq_skip(&qctx->sendbuf, sent); @@ -326,16 +512,17 @@ CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } -CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen) +CURLcode Curl_vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen) { qctx->gsolen = gsolen; - return vquic_flush(cf, data, qctx); + return Curl_vquic_flush(cf, data, qctx); } -CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen, - size_t tail_len, size_t tail_gsolen) +CURLcode Curl_vquic_send_tail_split(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen, + size_t tail_len, size_t tail_gsolen) { DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len); qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; @@ -343,10 +530,11 @@ CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, qctx->gsolen = tail_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); + return Curl_vquic_flush(cf, data, qctx); } -#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +#if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + !defined(HAVE_APPLE_MSG_X) static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) { int gso_size = 0; @@ -369,31 +557,91 @@ static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) break; } } -#endif +#endif /* linux && UDP_GRO */ (void)msg; return (size_t)gso_size; } +#endif /* (HAVE_SENDMMSG || HAVE_SENDMSG) && !HAVE_APPLE_MSG_X */ + +#if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) || \ + defined(HAVE_APPLE_MSG_X)) && \ + (defined(IP_RECVTOS) || defined(IP_TOS)) && defined(IPTOS_ECN_MASK) +static uint8_t vquic_msghdr_get_ecn(struct msghdr *msg, int family) +{ + struct cmsghdr *cmsg; + switch(family) { + case AF_INET: + /* 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 == IPPROTO_IP && +#ifdef __APPLE__ + cmsg->cmsg_type == IP_RECVTOS +#else + cmsg->cmsg_type == IP_TOS +#endif + && cmsg->cmsg_len) { + return *(uint8_t *)(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK; + } + } + break; + case AF_INET6: + /* 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 == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS && + cmsg->cmsg_len) { + unsigned int tos; + + memcpy(&tos, CMSG_DATA(cmsg), sizeof(int)); + + return (uint8_t)(tos & IPTOS_ECN_MASK); + } + } + break; + } + return 0; +} +#else +#define vquic_msghdr_get_ecn(a, b) 0 +#endif /* HAVE_SENDMMSG || HAVE_SENDMSG || HAVE_APPLE_MSG_X ... */ #ifdef HAVE_SENDMMSG + static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { #if defined(__linux__) && defined(UDP_GRO) #define MMSG_NUM 16 #define UDP_GRO_CNT_MAX 64 +#define CMSG_PER_MSG_SIZE (2 * CMSG_SPACE(sizeof(int))) #else #define MMSG_NUM 64 #define UDP_GRO_CNT_MAX 1 +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) #endif #define MSG_BUF_SIZE (UDP_GRO_CNT_MAX * 1500) struct iovec msg_iov[MMSG_NUM]; struct mmsghdr mmsg[MMSG_NUM]; - uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; + uint8_t msg_ctrl[MMSG_NUM * CMSG_PER_MSG_SIZE]; struct sockaddr_storage remote_addr[MMSG_NUM]; size_t total_nread = 0, pkts = 0; #ifdef CURLVERBOSE @@ -405,6 +653,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, size_t gso_size; char *sockbuf = NULL; uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; + uint8_t ecn = 0; DEBUGASSERT(max_pkts > 0); result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE, @@ -419,20 +668,20 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, memset(&mmsg, 0, sizeof(mmsg)); for(i = 0; i < n; ++i) { msg_iov[i].iov_base = bufs[i]; - msg_iov[i].iov_len = (int)sizeof(bufs[i]); + msg_iov[i].iov_len = sizeof(bufs[i]); mmsg[i].msg_hdr.msg_iov = &msg_iov[i]; 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)); + mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_PER_MSG_SIZE]; + mmsg[i].msg_hdr.msg_controllen = CMSG_PER_MSG_SIZE; } while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) ; if(mcount == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + if(SOCK_EAGAIN(SOCKERRNO)) { CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); goto out; } @@ -446,7 +695,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, } curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); failf(data, "QUIC: recvmmsg() unexpectedly returned %d (errno=%d; %s)", - mcount, SOCKERRNO, errstr); + mcount, SOCKERRNO, errstr); result = CURLE_RECV_ERROR; goto out; } @@ -454,17 +703,20 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, VERBOSE(++calls); for(i = 0; i < mcount; ++i) { /* A zero-length UDP packet is no QUIC packet. Ignore. */ - if(!mmsg[i].msg_len) + if(!mmsg[i].msg_len) { + ++pkts; continue; + } total_nread += mmsg[i].msg_len; + ecn = vquic_msghdr_get_ecn(&mmsg[i].msg_hdr, remote_addr[i].ss_family); gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); if(gso_size == 0) gso_size = mmsg[i].msg_len; result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size, mmsg[i].msg_hdr.msg_name, - mmsg[i].msg_hdr.msg_namelen, 0, userp); + mmsg[i].msg_hdr.msg_namelen, ecn, userp); if(result) goto out; pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size; @@ -473,8 +725,119 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, out: if(total_nread || result) - CURL_TRC_CF(data, cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu)" - " -> %d", total_nread, pkts, calls, result); + CURL_TRC_CF(data, cf, + "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); + Curl_multi_xfer_sockbuf_release(data, sockbuf); + return result; +} + +#elif defined(HAVE_APPLE_MSG_X) + +static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, + void *userp) +{ +#define MSG_X_NUM 64 +#define MSG_BUF_SIZE (2048) +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) + struct iovec msg_iov[MSG_X_NUM]; + struct msghdr_x mmsg[MSG_X_NUM]; + uint8_t msg_ctrl[MSG_X_NUM * CMSG_PER_MSG_SIZE]; + struct sockaddr_storage remote_addr[MSG_X_NUM]; + size_t total_nread = 0, pkts = 0; +#ifdef CURLVERBOSE + size_t calls = 0; +#endif + int mcount, i; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + size_t gso_size; + char *sockbuf = NULL; + uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; + uint8_t ecn = 0; + + DEBUGASSERT(max_pkts > 0); + result = Curl_multi_xfer_sockbuf_borrow(data, MSG_X_NUM * MSG_BUF_SIZE, + &sockbuf); + if(result) + goto out; + bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf; + + total_nread = 0; + while(pkts < max_pkts) { + int n = (int)CURLMIN(CURLMIN(MSG_X_NUM, IOV_MAX), max_pkts); + memset(&mmsg, 0, sizeof(mmsg)); + for(i = 0; i < n; ++i) { + msg_iov[i].iov_base = bufs[i]; + msg_iov[i].iov_len = sizeof(bufs[i]); + mmsg[i].msg_iov = &msg_iov[i]; + mmsg[i].msg_iovlen = 1; + mmsg[i].msg_name = &remote_addr[i]; + mmsg[i].msg_namelen = sizeof(remote_addr[i]); + mmsg[i].msg_control = &msg_ctrl[i * CMSG_PER_MSG_SIZE]; + mmsg[i].msg_controllen = CMSG_PER_MSG_SIZE; + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((mcount = syscall(SYS_recvmsg_x, qctx->sockfd, mmsg, n, 0)) == -1 && + (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + if(mcount == -1) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "ingress, recvmsg_x -> EAGAIN"); + goto out; + } + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg_x() unexpectedly returned %d (errno=%d; %s)", + mcount, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + VERBOSE(++calls); + for(i = 0; i < mcount; ++i) { + /* A zero-length UDP packet is no QUIC packet. Ignore. */ + if(!mmsg[i].msg_datalen) { + ++pkts; + continue; + } + total_nread += mmsg[i].msg_datalen; + ecn = vquic_msghdr_get_ecn((struct msghdr *)&mmsg[i], + remote_addr[i].ss_family); + gso_size = mmsg[i].msg_datalen; + + result = recv_cb(bufs[i], mmsg[i].msg_datalen, gso_size, + mmsg[i].msg_name, + mmsg[i].msg_namelen, ecn, userp); + if(result) + goto out; + pkts += (mmsg[i].msg_datalen + gso_size - 1) / gso_size; + } + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, + "vquic_recvmsg_x(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); Curl_multi_xfer_sockbuf_release(data, sockbuf); return result; } @@ -484,8 +847,9 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { +#define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) struct iovec msg_iov; struct msghdr msg; uint8_t buf[64 * 1024]; @@ -495,28 +859,29 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, size_t nread; char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; + uint8_t msg_ctrl[CMSG_PER_MSG_SIZE]; size_t gso_size; + uint8_t ecn = 0; DEBUGASSERT(max_pkts > 0); for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) { - /* fully initialise this on each call to `recvmsg()`. There seem to + /* fully initialize 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_iov.iov_len = 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); + msg.msg_controllen = CMSG_PER_MSG_SIZE; while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 && (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) ; if(!curlx_sztouz(rc, &nread)) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + if(SOCK_EAGAIN(SOCKERRNO)) { goto out; } if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { @@ -538,15 +903,18 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, ++calls; /* A 0-length UDP packet is no QUIC packet */ - if(!nread) + if(!nread) { + ++pkts; continue; + } + ecn = vquic_msghdr_get_ecn(&msg, remote_addr.ss_family); gso_size = vquic_msghdr_get_udp_gro(&msg); if(gso_size == 0) gso_size = nread; result = recv_cb(buf, nread, gso_size, - msg.msg_name, msg.msg_namelen, 0, userp); + msg.msg_name, msg.msg_namelen, ecn, userp); if(result) goto out; pkts += (nread + gso_size - 1) / gso_size; @@ -554,8 +922,9 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, out: if(total_nread || result) - CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu)" - " -> %d", total_nread, pkts, calls, result); + CURL_TRC_CF(data, cf, + "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); return result; } @@ -564,7 +933,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { uint8_t buf[64 * 1024]; int bufsize = (int)sizeof(buf); @@ -583,7 +952,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) ; if(!curlx_sztouz(rv, &nread)) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + if(SOCK_EAGAIN(SOCKERRNO)) { CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); goto out; } @@ -618,21 +987,24 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, out: if(total_nread || result) - CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu)" - " -> %d", total_nread, pkts, calls, result); + CURL_TRC_CF(data, cf, + "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); return result; } #endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ -CURLcode vquic_recv_packets(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct cf_quic_ctx *qctx, - size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp) +CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { CURLcode result; #ifdef HAVE_SENDMMSG result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); +#elif defined(HAVE_APPLE_MSG_X) + result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp); #elif defined(HAVE_SENDMSG) result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #else @@ -699,27 +1071,123 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, return CURLE_OK; } +CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer) +{ + CURLcode result; + + (void)data; /* not used in all cases and compilers are stupid */ +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); +#elif defined(USE_QUICHE) + result = Curl_cf_quiche_insert_after(cf_at, origin, peer); +#else + (void)cf_at; + (void)origin; + (void)peer; + result = CURLE_NOT_BUILT_IN; +#endif + +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if(!result && (origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + result = Curl_conn_dns_add_https_resolve(data, cf_at->conn, + cf_at->sockindex, origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + return result; +} + CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - (void)transport; - DEBUGASSERT(transport == TRNSPRT_QUIC); + (void)transport_peer; + (void)tunnel_transport; + (void)tunnel_peer; + DEBUGASSERT(transport_peer == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_create(pcf, data, conn, addr); + return Curl_cf_ngtcp2_create(pcf, data, origin, peer, conn, addr); #elif defined(USE_QUICHE) - return Curl_cf_quiche_create(pcf, data, conn, addr); + return Curl_cf_quiche_create(pcf, data, origin, peer, conn, addr); #else *pcf = NULL; (void)data; + (void)origin; + (void)peer; (void)conn; (void)addr; + (void)tunnel_peer; + (void)tunnel_transport; return CURLE_NOT_BUILT_IN; #endif } +#if !defined(CURL_DISABLE_PROXY) && defined(USE_PROXY_HTTP3) + +CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) +{ +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + return Curl_cf_ngtcp2_proxy_insert_after(cf_at, data, origin, peer, + tunnel_peer, tunnel_transport); +#else + (void)cf_at; + (void)data; + (void)origin; + (void)peer; + (void)tunnel_peer; + (void)tunnel_transport; + return CURLE_NOT_BUILT_IN; +#endif +} + +CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) +{ + DEBUGASSERT(transport_peer == TRNSPRT_QUIC); +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + return Curl_cf_ngtcp2_proxy_create(pcf, data, origin, peer, transport_peer, + conn, addr, + tunnel_peer, tunnel_transport); +#else + *pcf = NULL; + (void)data; + (void)conn; + (void)addr; + (void)peer; + (void)transport_peer; + (void)tunnel_peer; + (void)tunnel_transport; + return CURLE_NOT_BUILT_IN; +#endif +} + +#endif /* !CURL_DISABLE_PROXY && USE_PROXY_HTTP3 */ + CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn, unsigned char transport) @@ -728,26 +1196,24 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, failf(data, "HTTP/3 cannot be used over UNIX domain sockets"); return CURLE_QUIC_CONNECT_ERROR; } - if(!(conn->scheme->flags & PROTOPT_SSL)) { + if(!(data->state.origin->scheme->flags & PROTOPT_SSL)) { failf(data, "HTTP/3 requested for non-HTTPS URL"); return CURLE_URL_MALFORMAT; } #ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) { + if(conn->socks_proxy.peer) { failf(data, "HTTP/3 is not supported over a SOCKS proxy"); return CURLE_URL_MALFORMAT; } - if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { - failf(data, "HTTP/3 is not supported over an HTTP proxy"); - return CURLE_URL_MALFORMAT; - } +#else + (void)conn; #endif return CURLE_OK; } #ifdef CURLVERBOSE -const char *vquic_h3_err_str(uint64_t error_code) +const char *Curl_vquic_h3_err_str(uint64_t error_code) { if(error_code <= UINT_MAX) { switch((unsigned int)error_code) { diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index 1f0a1ab5e51b..f1363e2c0a76 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -33,22 +33,56 @@ struct Curl_addrinfo; void Curl_quic_ver(char *p, size_t len); int Curl_vquic_init(void); +void Curl_vquic_cleanup(void); CURLcode Curl_qlogdir(struct Curl_easy *data, unsigned char *scid, size_t scidlen, int *qlogfdp); +CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer); + CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_http3; +#if !defined(CURL_DISABLE_PROXY) && defined(USE_PROXY_HTTP3) + +CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); + +CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); + +extern struct Curl_cftype Curl_cft_h3_proxy; + +#endif /* !CURL_DISABLE_PROXY && USE_PROXY_HTTP3 */ + #else #define Curl_vquic_init() 1 +#define Curl_vquic_cleanup() #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h index 82bd5b03581b..23e1102eb0d9 100644 --- a/lib/vquic/vquic_int.h +++ b/lib/vquic/vquic_int.h @@ -53,9 +53,9 @@ typedef enum { } vquic_h3_error; #ifdef CURLVERBOSE -const char *vquic_h3_err_str(uint64_t error_code); +const char *Curl_vquic_h3_err_str(uint64_t error_code); #else -#define vquic_h3_err_str(x) "" +#define Curl_vquic_h3_err_str(x) "" #endif /* CURLVERBOSE */ struct cf_quic_ctx { @@ -80,45 +80,40 @@ struct cf_quic_ctx { #define H3_STREAM_CTX(ctx, data) \ ((data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL) -CURLcode vquic_ctx_init(struct Curl_easy *data, - struct cf_quic_ctx *qctx); -void vquic_ctx_free(struct cf_quic_ctx *qctx); +CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, + struct cf_quic_ctx *qctx); +void Curl_vquic_ctx_free(struct cf_quic_ctx *qctx); -void vquic_ctx_set_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow); +void Curl_vquic_ctx_set_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow); -void vquic_ctx_update_time(struct cf_quic_ctx *qctx, - const struct curltime *pnow); +void Curl_vquic_ctx_update_time(struct cf_quic_ctx *qctx, + const struct curltime *pnow); -void vquic_push_blocked_pkt(struct Curl_cfilter *cf, - struct cf_quic_ctx *qctx, - const uint8_t *pkt, size_t pktlen, size_t gsolen); +CURLcode Curl_vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen); -CURLcode vquic_send_blocked_pkts(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct cf_quic_ctx *qctx); - -CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen); +CURLcode Curl_vquic_send_tail_split(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, size_t gsolen, + size_t tail_len, size_t tail_gsolen); -CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx, size_t gsolen, - size_t tail_len, size_t tail_gsolen); +CURLcode Curl_vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx); -CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, - struct cf_quic_ctx *qctx); +typedef CURLcode Curl_vquic_recv_pkts_cb(const unsigned char *buf, + size_t buflen, + size_t gso_size, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, uint8_t ecn, + void *userp); -typedef CURLcode vquic_recv_pkts_cb(const unsigned char *buf, size_t buflen, - size_t gso_size, - struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, - void *userp); - -CURLcode vquic_recv_packets(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct cf_quic_ctx *qctx, - size_t max_pkts, - vquic_recv_pkts_cb *recv_cb, void *userp); +CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, + void *userp); #ifdef USE_NGTCP2 struct ngtcp2_mem; diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 44094e466c84..03a84d218b7a 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -45,7 +45,6 @@ #include "urldata.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "vssh/ssh.h" @@ -57,6 +56,7 @@ #include "multiif.h" #include "select.h" #include "vssh/vssh.h" +#include "curlx/base64.h" /* for curlx_base64_encode() */ #ifdef HAVE_UNISTD_H #include @@ -109,12 +109,14 @@ static CURLcode sftp_error_to_CURLE(int err) } /* Multiple options: - * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5 - * hash (90s style auth, not sure we should have it here) - * 2. data->set.ssh_keyfunc callback is set. Then we do trust on first + * 1. CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256) is set + * with a SHA256 hash. + * 2. CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5) is set + * with an MD5 hash (90s style auth, not sure we should have it here) + * 3. data->set.ssh_keyfunc callback is set. Then we do trust on first * use. We even save on knownhosts if CURLKHSTAT_FINE_ADD_TO_FILE * is returned by it. - * 3. none of the above. We only accept if it is present on known hosts. + * 4. none of the above. We only accept if it is present on known hosts. * * Returns SSH_OK or SSH_ERROR. */ @@ -122,8 +124,10 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) { int rc; ssh_key pubkey; - size_t hlen; - unsigned char *hash = NULL; + unsigned char *hash_sha256 = NULL; + size_t hlen_sha256; + unsigned char *hash_md5 = NULL; + size_t hlen_md5; char *found_base64 = NULL; char *known_base64 = NULL; int vstate; @@ -139,20 +143,76 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) if(rc != SSH_OK) return rc; - if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { - int i; + if(CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { + const char *pubkey_sha256 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256); + char *fingerprint_b64 = NULL; + size_t fingerprint_b64_len; + size_t pub_pos = 0; + size_t b64_pos = 0; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + &hash_sha256, &hlen_sha256); + if(rc != SSH_OK || hlen_sha256 != 32) { + failf(data, "Denied establishing ssh session: " + "SHA256 fingerprint not available"); + goto cleanup; + } + + if(curlx_base64_encode((const uint8_t *)hash_sha256, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + rc = SSH_ERROR; + goto cleanup; + } + + infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); + + /* Find the position of any = padding characters in the public key */ + while((pubkey_sha256[pub_pos] != '=') && pubkey_sha256[pub_pos]) { + pub_pos++; + } + + /* Find the position of any = padding characters in the base64 coded + * hostkey fingerprint */ + while((fingerprint_b64[b64_pos] != '=') && fingerprint_b64[b64_pos]) { + b64_pos++; + } + + /* Before we authenticate we check the hostkey's SHA256 fingerprint + * against a known fingerprint, if available. + */ + if((pub_pos != b64_pos) || + strncmp(fingerprint_b64, pubkey_sha256, pub_pos)) { + failf(data, + "Denied establishing ssh session: mismatch SHA256 fingerprint. " + "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256); + curlx_free(fingerprint_b64); + rc = SSH_ERROR; + goto cleanup; + } + + curlx_free(fingerprint_b64); + + rc = SSH_OK; + goto cleanup; + } + + if(CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5)) { + const char *pubkey_md5 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5); char md5buffer[33]; - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + int i; - rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen); - if(rc != SSH_OK || hlen != 16) { + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + &hash_md5, &hlen_md5); + if(rc != SSH_OK || hlen_md5 != 16) { failf(data, "Denied establishing ssh session: MD5 fingerprint not available"); goto cleanup; } for(i = 0; i < 16; i++) - curl_msnprintf(&md5buffer[i * 2], 3, "%02x", hash[i]); + curl_msnprintf(&md5buffer[i * 2], 3, "%02x", hash_md5[i]); infof(data, "SSH MD5 fingerprint: %s", md5buffer); @@ -168,7 +228,7 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) goto cleanup; } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { /* Get the known_key from the known hosts file */ vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, @@ -217,6 +277,8 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) keymatch = CURLKHMATCH_OK; break; case SSH_KNOWN_HOSTS_OTHER: + keymatch = CURLKHMATCH_MISMATCH; + break; case SSH_KNOWN_HOSTS_NOT_FOUND: case SSH_KNOWN_HOSTS_UNKNOWN: case SSH_KNOWN_HOSTS_ERROR: @@ -228,6 +290,8 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) } if(func) { /* use callback to determine action */ + struct Curl_mapi_guard guard; + rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); if(rc != SSH_OK) goto cleanup; @@ -259,11 +323,11 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) goto cleanup; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_ssh_keyfunc); 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_CBAPI_END(&guard); switch(rc) { case CURLKHSTAT_FINE_ADD_TO_FILE: @@ -297,8 +361,10 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) /* !checksrc! disable BANNEDFUNC 1 */ free(known_base64); /* allocated by libssh, deallocate with system free */ } - if(hash) - ssh_clean_pubkey_hash(&hash); + if(hash_sha256) + ssh_clean_pubkey_hash(&hash_sha256); + if(hash_md5) + ssh_clean_pubkey_hash(&hash_md5); ssh_key_free(pubkey); if(knownhostsentry) { ssh_knownhosts_entry_free(knownhostsentry); @@ -633,7 +699,8 @@ static int myssh_auth_interactive(struct connectdata *conn, if(nprompts != 1) return SSH_ERROR; - rc = ssh_userauth_kbdint_setanswer(sshc->ssh_session, 0, conn->passwd); + rc = ssh_userauth_kbdint_setanswer(sshc->ssh_session, 0, + Curl_creds_passwd(conn->creds)); if(rc < 0) return SSH_ERROR; @@ -767,8 +834,7 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data, /* For public key auth we need either the private key or CURLSSH_AUTH_AGENT. */ if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) && - (data->set.str[STRING_SSH_PRIVATE_KEY] || - (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { + (sshc->priv_key || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); infof(data, "Authentication using SSH public key file"); } @@ -798,8 +864,8 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - if(sshc->pubkey && !data->set.ssl.key_passwd) { + if(sshc->priv_key) { + if(sshc->pubkey && !data->set.ssl.primary.key_passwd) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -810,13 +876,11 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } } - rc = ssh_pki_import_privkey_file(data-> - set.str[STRING_SSH_PRIVATE_KEY], - data->set.ssl.key_passwd, NULL, + rc = ssh_pki_import_privkey_file(sshc->priv_key, + data->set.ssl.primary.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]); + failf(data, "Could not load private key file %s", sshc->priv_key); rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED); return rc; } @@ -825,7 +889,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } else { rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.key_passwd); + data->set.ssl.primary.key_passwd); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -920,7 +984,8 @@ static int myssh_in_AUTH_PASS_INIT(struct Curl_easy *data, static int myssh_in_AUTH_PASS(struct Curl_easy *data, struct ssh_conn *sshc) { - int rc = ssh_userauth_password(sshc->ssh_session, NULL, data->conn->passwd); + int rc = ssh_userauth_password(sshc->ssh_session, NULL, + Curl_creds_passwd(data->conn->creds)); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; else if(rc == SSH_AUTH_SUCCESS) { @@ -1034,10 +1099,11 @@ static int myssh_in_UPLOAD_INIT(struct Curl_easy *data, 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); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1695,7 +1761,7 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data, return SSH_NO_ERROR; } -static void conn_forget_socket(struct Curl_easy *data, int sockindex) +static void conn_forget_socket(struct Curl_easy *data, int8_t sockindex) { struct connectdata *conn = data->conn; if(conn && CONN_SOCK_IDX_VALID(sockindex)) { @@ -1807,7 +1873,7 @@ static int myssh_in_TRANS_INIT(struct Curl_easy *data, struct ssh_conn *sshc, static void sshc_cleanup(struct ssh_conn *sshc) { - if(sshc->initialised) { + if(sshc->initialized) { if(sshc->sftp_file) { sftp_close(sshc->sftp_file); sshc->sftp_file = NULL; @@ -1822,8 +1888,8 @@ static void sshc_cleanup(struct ssh_conn *sshc) } /* worst-case scenario cleanup */ - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->scp_session == NULL); + DEBUGASSERT(!sshc->ssh_session); + DEBUGASSERT(!sshc->scp_session); if(sshc->readdir_tmp) { ssh_string_free_char(sshc->readdir_tmp); @@ -1850,14 +1916,14 @@ static void sshc_cleanup(struct ssh_conn *sshc) sshc->pubkey = NULL; } - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); + curlx_safefree(sshc->pub_key); + curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); curlx_safefree(sshc->quote_path2); curlx_dyn_free(&sshc->readdir_buf); curlx_safefree(sshc->readdir_linkPath); SSH_STRING_FREE_CHAR(sshc->homedir); - sshc->initialised = FALSE; + sshc->initialized = FALSE; } } @@ -2172,7 +2238,7 @@ static CURLcode myssh_in_SESSION_FREE(struct Curl_easy *data, /* the code we are about to return */ result = sshc->actualcode; memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(data->conn, "SSH session free"); + connclose(data->conn); sshc->state = SSH_SESSION_FREE; /* current */ sshc->nextstate = SSH_NO_STATE; myssh_to(data, sshc, SSH_STOP); @@ -2378,40 +2444,10 @@ static CURLcode myssh_statemachine(struct Curl_easy *data, if(!result && (sshc->state == SSH_STOP)) result = sshc->actualcode; CURL_TRC_SSH(data, "[%s] statemachine() -> %d, block=%d", - Curl_ssh_statename(sshc->state), result, *block); + Curl_ssh_statename(sshc->state), (int)result, *block); return result; } -/* called by the multi interface to figure out what socket(s) to wait for and - for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ -static CURLcode myssh_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int waitfor; - - if(!sshc || (sock == CURL_SOCKET_BAD)) - return CURLE_FAILED_INIT; - - waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; - if(waitfor) { - int flags = 0; - if(waitfor & REQ_IO_RECV) - flags |= CURL_POLL_IN; - if(waitfor & REQ_IO_SEND) - flags |= CURL_POLL_OUT; - DEBUGASSERT(flags); - CURL_TRC_SSH(data, "pollset, flags=%x", flags); - return Curl_pollset_change(data, ps, sock, flags, 0); - } - /* While we still have a session, we listen incoming data. */ - if(sshc->ssh_session) - return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); - return CURLE_OK; -} - /* called repeatedly until done from multi.c */ static CURLcode myssh_multi_statemach(struct Curl_easy *data, bool *done) @@ -2419,8 +2455,8 @@ static CURLcode myssh_multi_statemach(struct Curl_easy *data, struct connectdata *conn = data->conn; 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_pollset() - implementation */ + bool block; /* we store the status and use that to provide + a Curl_ssh_pollset() implementation */ CURLcode result; if(!sshc || !sshp) @@ -2502,7 +2538,7 @@ static CURLcode myssh_setup_connection(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; curlx_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2); - sshc->initialised = TRUE; + sshc->initialized = TRUE; if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor)) return CURLE_OUT_OF_MEMORY; @@ -2511,7 +2547,7 @@ static CURLcode myssh_setup_connection(struct Curl_easy *data, Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - return CURLE_OK; + return Curl_ssh_setup_pkey(data, sshc); } static Curl_recv scp_recv, sftp_recv; @@ -2545,13 +2581,15 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) sshc->ssh_session = ssh_new(); if(!sshc->ssh_session) { - failf(data, "Failure initialising ssh session"); + failf(data, "Failure initializing ssh session"); return CURLE_FAILED_INIT; } + /* For IPv6 origins, use the `user_hostname` that has the "[]" enclosure. + * Otherwise, use `hostname` that is IDN converted. */ rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, - (data->state.up.hostname[0] == '[') ? - data->state.up.hostname : conn->host.name); + conn->origin->ipv6 ? + conn->origin->user_hostname : conn->origin->hostname); if(rc != SSH_OK) { failf(data, "Could not set remote host"); @@ -2571,33 +2609,35 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } - if(conn->user && conn->user[0] != '\0') { - infof(data, "User: %s", conn->user); - rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_USER, conn->user); + if(Curl_creds_has_user(conn->creds)) { + infof(data, "User: %s", conn->creds->user); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_USER, + conn->creds->user); if(rc != SSH_OK) { failf(data, "Could not set user"); return CURLE_FAILED_INIT; } } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { + infof(data, "Known hosts: %s", + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_KNOWNHOSTS, - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); if(rc == SSH_OK) /* libssh has two separate options for this. Set both to the same file to avoid surprises */ rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); if(rc != SSH_OK) { failf(data, "Could not set known hosts file path"); return CURLE_FAILED_INIT; } } - if(conn->remote_port) { + if(conn->origin->port) { rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_PORT, - &conn->remote_port); + &conn->origin->port); if(rc != SSH_OK) { failf(data, "Could not set remote port"); return CURLE_FAILED_INIT; @@ -2616,9 +2656,8 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) 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], - &sshc->pubkey); + if(sshc->pub_key) { + rc = ssh_pki_import_pubkey_file(sshc->pub_key, &sshc->pubkey); if(rc != SSH_OK) { failf(data, "Could not load public key file"); return CURLE_FAILED_INIT; @@ -2741,7 +2780,7 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, return myssh_done(data, sshc, status); } -static CURLcode scp_send(struct Curl_easy *data, int sockindex, +static CURLcode scp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -2775,7 +2814,7 @@ static CURLcode scp_send(struct Curl_easy *data, int sockindex, return CURLE_OK; } -static CURLcode scp_recv(struct Curl_easy *data, int sockindex, +static CURLcode scp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -2901,7 +2940,7 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, } /* return number of sent bytes */ -static CURLcode sftp_send(struct Curl_easy *data, int sockindex, +static CURLcode sftp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -2982,7 +3021,7 @@ static CURLcode sftp_send(struct Curl_easy *data, int sockindex, * Return number of received (decrypted) bytes * or <0 on error */ -static CURLcode sftp_recv(struct Curl_easy *data, int sockindex, +static CURLcode sftp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -3094,23 +3133,23 @@ void Curl_ssh_version(char *buffer, size_t buflen) * SCP. */ const struct Curl_protocol Curl_protocol_scp = { - myssh_setup_connection, /* setup_connection */ - myssh_do_it, /* do_it */ - scp_done, /* done */ - ZERO_NULL, /* do_more */ - myssh_connect, /* connect_it */ - myssh_multi_statemach, /* connecting */ - scp_doing, /* doing */ - myssh_pollset, /* proto_pollset */ - myssh_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - myssh_pollset, /* perform_pollset */ - scp_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_is_dead */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ + myssh_setup_connection, /* setup_connection */ + myssh_do_it, /* do_it */ + scp_done, /* done */ + ZERO_NULL, /* do_more */ + myssh_connect, /* connect_it */ + myssh_multi_statemach, /* connecting */ + scp_doing, /* doing */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + Curl_ssh_pollset, /* perform_pollset */ + scp_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_is_dead */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ }; /* @@ -3124,10 +3163,10 @@ const struct Curl_protocol Curl_protocol_sftp = { myssh_connect, /* connect_it */ myssh_multi_statemach, /* connecting */ sftp_doing, /* doing */ - myssh_pollset, /* proto_pollset */ - myssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - myssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 4e2a72269ff5..24a651d30ebb 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -44,7 +44,6 @@ #include "urldata.h" #include "sendf.h" #include "curl_trc.h" -#include "hostip.h" #include "progress.h" #include "transfer.h" #include "vssh/ssh.h" @@ -57,7 +56,7 @@ #include "curlx/fopen.h" #include "vssh/vssh.h" #include "curlx/strparse.h" -#include "curlx/base64.h" /* for base64 encoding/decoding */ +#include "curlx/base64.h" /* for curlx_base64_encode() */ static const char *sftp_libssh2_strerror(unsigned long err) { @@ -104,10 +103,10 @@ static const char *sftp_libssh2_strerror(unsigned long err) case LIBSSH2_FX_QUOTA_EXCEEDED: return "User quota exceeded"; - case LIBSSH2_FX_UNKNOWN_PRINCIPLE: - return "Unknown principle"; + case LIBSSH2_FX_UNKNOWN_PRINCIPAL: + return "Unknown principal"; - case LIBSSH2_FX_LOCK_CONFlICT: + case LIBSSH2_FX_LOCK_CONFLICT: return "File lock conflict"; case LIBSSH2_FX_DIR_NOT_EMPTY: @@ -148,11 +147,11 @@ static void kbd_callback(const char *name, int name_len, #endif /* CURL_LIBSSH2_DEBUG */ if(num_prompts == 1) { struct connectdata *conn = data->conn; + const char *passwd = Curl_creds_passwd(conn->creds); /* this function must allocate memory that can be freed by libssh2, which uses the LIBSSH2_FREE_FUNC callback */ - responses[0].text = Curl_cstrdup(conn->passwd); - responses[0].length = - responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd)); + responses[0].text = Curl_cstrdup(passwd); + responses[0].length = responses[0].text ? curlx_uztoui(strlen(passwd)) : 0; } (void)prompts; } /* kbd_callback */ @@ -169,7 +168,7 @@ static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err) case LIBSSH2_FX_PERMISSION_DENIED: case LIBSSH2_FX_WRITE_PROTECT: - case LIBSSH2_FX_LOCK_CONFlICT: + case LIBSSH2_FX_LOCK_CONFLICT: return CURLE_REMOTE_ACCESS_DENIED; case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: @@ -275,9 +274,11 @@ static enum curl_khtype convert_ssh2_keytype(int sshkeytype) case LIBSSH2_HOSTKEY_TYPE_RSA: keytype = CURLKHTYPE_RSA; break; - case LIBSSH2_HOSTKEY_TYPE_DSS: +#ifdef LIBSSH2_HOSTKEY_TYPE_DSS + case LIBSSH2_HOSTKEY_TYPE_DSS: /* deprecated upstream */ keytype = CURLKHTYPE_DSS; break; +#endif #ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: keytype = CURLKHTYPE_ECDSA; @@ -305,152 +306,174 @@ static enum curl_khtype convert_ssh2_keytype(int sshkeytype) static CURLcode ssh_knownhost(struct Curl_easy *data, struct ssh_conn *sshc) { + struct connectdata *conn = data->conn; + struct libssh2_knownhost *host = NULL; + const char *remotekey = NULL; + int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE; + int keybit = 0; int sshkeytype = 0; size_t keylen = 0; int rc = 0; CURLcode result = CURLE_OK; - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - /* we are asked to verify the host against a file */ - struct connectdata *conn = data->conn; - struct libssh2_knownhost *host = NULL; - const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, - &keylen, &sshkeytype); - int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE; - int keybit = 0; + if(!CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { + infof(data, "SSH: no knownhosts file configured"); + return CURLE_OK; + } + + remotekey = libssh2_session_hostkey(sshc->ssh_session, + &keylen, &sshkeytype); + if(remotekey) { + /* + * 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? + */ + struct Curl_mapi_guard guard; + enum curl_khmatch keymatch; + curl_sshkeycallback func = + data->set.ssh_keyfunc ? data->set.ssh_keyfunc : sshkeycallback; + struct curl_khkey knownkey; + struct curl_khkey *knownkeyp = NULL; + struct curl_khkey foundkey; + + switch(sshkeytype) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; +#ifdef LIBSSH2_HOSTKEY_TYPE_DSS + case LIBSSH2_HOSTKEY_TYPE_DSS: /* deprecated upstream */ + keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS; + break; +#endif + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521; + break; + case LIBSSH2_HOSTKEY_TYPE_ED25519: + keybit = LIBSSH2_KNOWNHOST_KEY_ED25519; + break; + default: + infof(data, "SSH: unsupported host key type for knownhosts check"); + keybit = 0; + break; + } + if(!keybit) + /* no check means failure! */ + rc = CURLKHSTAT_REJECT; + else { + keycheck = libssh2_knownhost_checkp(sshc->kh, + conn->origin->hostname, + (conn->origin->port != PORT_SSH) ? + conn->origin->port : -1, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW | + keybit, + &host); + + infof(data, "SSH: host check %d, key: %s", keycheck, + (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ? + host->key : ""); + + /* setup 'knownkey' */ + if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { + knownkey.key = host->key; + knownkey.len = 0; + knownkey.keytype = convert_ssh2_keytype(sshkeytype); + knownkeyp = &knownkey; + } + + /* setup 'foundkey' */ + foundkey.key = remotekey; + foundkey.len = keylen; + foundkey.keytype = convert_ssh2_keytype(sshkeytype); - if(remotekey) { /* - * 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? + * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the + * curl_khmatch enum are ever modified, we need to introduce a + * translation table here! */ - enum curl_khmatch keymatch; - curl_sshkeycallback func = - data->set.ssh_keyfunc ? data->set.ssh_keyfunc : sshkeycallback; - struct curl_khkey knownkey; - struct curl_khkey *knownkeyp = NULL; - struct curl_khkey foundkey; - - switch(sshkeytype) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: - keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521; - break; - case LIBSSH2_HOSTKEY_TYPE_ED25519: - keybit = LIBSSH2_KNOWNHOST_KEY_ED25519; - break; - default: - infof(data, "unsupported key type, cannot check knownhosts"); - keybit = 0; - break; - } - if(!keybit) - /* no check means failure! */ - rc = CURLKHSTAT_REJECT; - else { - keycheck = libssh2_knownhost_checkp(sshc->kh, - conn->host.name, - (conn->remote_port != PORT_SSH) ? - conn->remote_port : -1, - remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| - keybit, - &host); - - infof(data, "SSH host check: %d, key: %s", keycheck, - (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ? - host->key : ""); - - /* setup 'knownkey' */ - if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { - knownkey.key = host->key; - knownkey.len = 0; - knownkey.keytype = convert_ssh2_keytype(sshkeytype); - knownkeyp = &knownkey; - } + keymatch = (enum curl_khmatch)keycheck; - /* setup 'foundkey' */ - foundkey.key = remotekey; - foundkey.len = keylen; - foundkey.keytype = convert_ssh2_keytype(sshkeytype); - - /* - * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the - * curl_khmatch enum are ever modified, we need to introduce a - * translation table here! - */ - keymatch = (enum curl_khmatch)keycheck; - - /* Ask the callback how to behave */ - 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); - } + /* Ask the callback how to behave */ + CURL_CBAPI_START(&guard, data, easy_ssh_keyfunc); + rc = func(data, knownkeyp, /* from the knownhosts file */ + &foundkey, /* from the remote host */ + keymatch, data->set.ssh_keyfunc_userp); + CURL_CBAPI_END(&guard); } - else - /* no remotekey means failure! */ - rc = CURLKHSTAT_REJECT; + } + else { + /* no remotekey means failure! */ + infof(data, "SSH: host offers no public key"); + rc = CURLKHSTAT_REJECT; + } - switch(rc) { - default: /* unknown return codes is the same as reject */ - case CURLKHSTAT_REJECT: - myssh_to(data, sshc, SSH_SESSION_FREE); - FALLTHROUGH(); - case CURLKHSTAT_DEFER: - /* DEFER means bail out but keep the SSH_HOSTKEY state */ - result = CURLE_PEER_FAILED_VERIFICATION; - break; - case CURLKHSTAT_FINE_REPLACE: - /* remove old host+key that does not match */ - if(host) - libssh2_knownhost_del(sshc->kh, host); - FALLTHROUGH(); - case CURLKHSTAT_FINE: - case CURLKHSTAT_FINE_ADD_TO_FILE: - /* proceed */ - if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { + switch(rc) { + default: /* unknown return codes is the same as reject */ + case CURLKHSTAT_REJECT: + infof(data, "SSH: knownhost check failed"); + myssh_to(data, sshc, SSH_SESSION_FREE); + FALLTHROUGH(); + case CURLKHSTAT_DEFER: + /* DEFER means bail out but keep the SSH_HOSTKEY state */ + result = CURLE_PEER_FAILED_VERIFICATION; + break; + case CURLKHSTAT_FINE_REPLACE: + /* remove old host+key that does not match */ + if(host) + libssh2_knownhost_del(sshc->kh, host); + FALLTHROUGH(); + case CURLKHSTAT_FINE: + case CURLKHSTAT_FINE_ADD_TO_FILE: + /* proceed */ + if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { + int addrc; + const char *hostbuf; + char *hostport = NULL; + if(conn->origin->port != PORT_SSH) { + hostbuf = hostport = curl_maprintf("[%s]:%u", conn->origin->hostname, + conn->origin->port); + if(!hostbuf) + infof(data, "WARNING: failed allocating buffer for [host]:port"); + } + else + hostbuf = conn->origin->hostname; + if(hostbuf) { /* 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, - remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| - keybit, NULL); + addrc = libssh2_knownhost_addc(sshc->kh, hostbuf, NULL, + remotekey, keylen, NULL, 0, + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW | + keybit, NULL); if(addrc) - infof(data, "WARNING: adding the known host %s failed", - conn->host.name); + infof(data, "WARNING: adding the known host %s failed", hostbuf); else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE || rc == CURLKHSTAT_FINE_REPLACE) { /* now we write the entire in-memory list of known hosts to the known_hosts file */ int wrc = - libssh2_knownhost_writefile(sshc->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); + libssh2_knownhost_writefile( + sshc->kh, CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS), + LIBSSH2_KNOWNHOST_FILE_OPENSSH); if(wrc) { infof(data, "WARNING: writing %s failed", - data->set.str[STRING_SSH_KNOWNHOSTS]); + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } } } - break; + curlx_free(hostport); } + else + infof(data, "SSH: knownhost entry matches host key"); + break; } return result; } @@ -458,13 +481,10 @@ static CURLcode ssh_knownhost(struct Curl_easy *data, static CURLcode ssh_check_fingerprint(struct Curl_easy *data, struct ssh_conn *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"); - infof(data, "SSH SHA256 public key: %s", - pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); + const char *pubkey_md5 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5); + const char *pubkey_sha256 = + CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256); if(pubkey_sha256) { const char *fingerprint = NULL; @@ -473,6 +493,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, size_t pub_pos = 0; size_t b64_pos = 0; + infof(data, "SSH: SHA256 public key '%s'", pubkey_sha256); /* The fingerprint points to static storage (!), do not free() it. */ fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_SHA256); @@ -498,7 +519,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, return CURLE_PEER_FAILED_VERIFICATION; } - infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); + infof(data, "SSH: SHA256 fingerprint '%s'", fingerprint_b64); /* Find the position of any = padding characters in the public key */ while((pubkey_sha256[pub_pos] != '=') && pubkey_sha256[pub_pos]) { @@ -526,13 +547,14 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, curlx_free(fingerprint_b64); - infof(data, "SHA256 checksum match"); + infof(data, "SSH: SHA256 checksum match"); } if(pubkey_md5) { char md5buffer[33]; const char *fingerprint; + infof(data, "SSH: MD5 public key '%s'", pubkey_md5); fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_MD5); @@ -544,7 +566,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, (unsigned char)fingerprint[i]); } - infof(data, "SSH MD5 fingerprint: %s", md5buffer); + infof(data, "SSH: MD5 fingerprint '%s'", md5buffer); } /* This does NOT verify the length of 'pubkey_md5' separately, which @@ -563,7 +585,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, myssh_to(data, sshc, SSH_SESSION_FREE); return CURLE_PEER_FAILED_VERIFICATION; } - infof(data, "MD5 checksum match"); + infof(data, "SSH: MD5 checksum match"); } if(!pubkey_md5 && !pubkey_sha256) { @@ -575,15 +597,18 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); if(remotekey) { + struct Curl_mapi_guard guard; enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype); - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_ssh_hostkeyfunc); rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, (int)keytype, remotekey, keylen); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(rc != CURLKHMATCH_OK) { myssh_to(data, sshc, SSH_SESSION_FREE); + failf(data, "SSH: callback failed host public key verification"); return CURLE_PEER_FAILED_VERIFICATION; } + infof(data, "SSH: verified public key via callback"); } else { myssh_to(data, sshc, SSH_SESSION_FREE); @@ -592,6 +617,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data, return CURLE_OK; } else { + CURL_TRC_SSH(data, "no host key checksum given, checking knownhosts"); return ssh_knownhost(data, sshc); } } @@ -616,12 +642,14 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, static const char hostkey_method_ssh_ecdsa_256[] = "ecdsa-sha2-nistp256"; static const char hostkey_method_ssh_rsa_all[] = "rsa-sha2-256,rsa-sha2-512,ssh-rsa"; +#ifdef LIBSSH2_KNOWNHOST_KEY_SSHDSS static const char hostkey_method_ssh_dss[] = "ssh-dss"; +#endif bool found = FALSE; if(sshc->kh && - !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && - !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { + !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_MD5) && + !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { struct libssh2_knownhost *store = NULL; struct connectdata *conn = data->conn; /* lets try to find our host in the known hosts file */ @@ -636,22 +664,23 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, const char *p; const char *kh_name_end = strstr(store->name, "]:"); if(!kh_name_end) { - infof(data, "Invalid host pattern %s in %s", - store->name, data->set.str[STRING_SSH_KNOWNHOSTS]); + infof(data, "SSH: invalid host pattern %s in %s", + store->name, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); continue; } p = kh_name_end + 2; /* start of port number */ if(!curlx_str_number(&p, &port, 0xffff) && - (kh_name_end && (port == conn->remote_port))) { + (kh_name_end && (port == conn->origin->port))) { kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end); - if(strncmp(store->name + 1, - conn->host.name, kh_name_size) == 0) { + if(!strncmp(store->name + 1, conn->origin->hostname, + kh_name_size)) { found = TRUE; break; } } } - else if(strcmp(store->name, conn->host.name) == 0) { + else if(!strcmp(store->name, conn->origin->hostname)) { found = TRUE; break; } @@ -666,8 +695,9 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, if(found) { int rc; const char *hostkey_method = NULL; - infof(data, "Found host %s in %s", - conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]); + infof(data, "SSH: found host '%s' in '%s'", + conn->origin->hostname, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); switch(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) { case LIBSSH2_KNOWNHOST_KEY_ED25519: @@ -685,19 +715,21 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, case LIBSSH2_KNOWNHOST_KEY_SSHRSA: hostkey_method = hostkey_method_ssh_rsa_all; break; - case LIBSSH2_KNOWNHOST_KEY_SSHDSS: +#ifdef LIBSSH2_KNOWNHOST_KEY_SSHDSS + case LIBSSH2_KNOWNHOST_KEY_SSHDSS: /* deprecated upstream */ hostkey_method = hostkey_method_ssh_dss; break; +#endif case LIBSSH2_KNOWNHOST_KEY_RSA1: failf(data, "Found host key type RSA1 which is not supported"); return CURLE_SSH; default: - failf(data, "Unknown host key type: %i", + failf(data, "Unknown host key type: %d", (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK)); return CURLE_SSH; } - infof(data, "Set \"%s\" as SSH hostkey type", hostkey_method); + infof(data, "SSH: set '%s' as hostkey type", hostkey_method); rc = libssh2_session_method_pref(sshc->ssh_session, LIBSSH2_METHOD_HOSTKEY, hostkey_method); if(rc) { @@ -709,8 +741,9 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, } } else { - infof(data, "Did not find host %s in %s", - conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]); + infof(data, "SSH: did not find host '%s' in '%s'", + conn->origin->hostname, + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } } @@ -778,7 +811,7 @@ static CURLcode sftp_quote(struct Curl_easy *data, cp = strchr(cmd, ' '); if(!cp) { failf(data, "Syntax error command '%s', missing parameter", cmd); - return result; + return CURLE_QUOTE_ERROR; } /* @@ -1001,10 +1034,11 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, 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); + struct Curl_mapi_guard guard; + CURL_CBAPI_START(&guard, data, easy_seek_func); seekerr = data->set.seek_func(data->set.seek_client, data->state.resume_from, SEEK_SET); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1016,6 +1050,7 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, } /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + struct Curl_mapi_guard guard; char scratch[4 * 1024]; size_t readthisamountnow = (data->state.resume_from - passed > @@ -1023,11 +1058,11 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fread_func); actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -1067,8 +1102,7 @@ static CURLcode sftp_upload_init(struct Curl_easy *data, return CURLE_OK; } -static CURLcode ssh_state_pkey_init(struct Curl_easy *data, - struct ssh_conn *sshc) +static void 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 @@ -1077,89 +1111,14 @@ static CURLcode ssh_state_pkey_init(struct Curl_easy *data, sshc->authed = FALSE; if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && - (strstr(sshc->authlist, "publickey") != NULL)) { - bool out_of_memory = FALSE; - - sshc->rsa_pub = sshc->rsa = NULL; - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - sshc->rsa = curlx_strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); - if(!sshc->rsa) - out_of_memory = TRUE; - } - else { - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - char *home = curl_getenv("HOME"); - curlx_struct_stat sbuf; - - /* If no private key file is specified, try some common paths. */ - if(home) { - /* Try ~/.ssh first. */ - sshc->rsa = curl_maprintf("%s/.ssh/id_rsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - sshc->rsa = curl_maprintf("%s/.ssh/id_dsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(curlx_stat(sshc->rsa, &sbuf)) { - curlx_safefree(sshc->rsa); - } - } - curlx_free(home); - } - if(!out_of_memory && !sshc->rsa) { - /* Nothing found; try the current dir. */ - sshc->rsa = curlx_strdup("id_rsa"); - if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - sshc->rsa = curlx_strdup("id_dsa"); - if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) { - curlx_free(sshc->rsa); - /* Out of guesses. Set to the empty string to avoid - * surprising info messages. */ - sshc->rsa = curlx_strdup(""); - } - } - } - } - - /* - * Unless the user explicitly specifies a public key file, let - * libssh2 extract the public key from the private key file. - * This is done by passing sshc->rsa_pub = NULL. - */ - if(!out_of_memory && 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 = curlx_strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); - if(!sshc->rsa_pub) - out_of_memory = TRUE; - } - - if(out_of_memory || !sshc->rsa) { - curlx_safefree(sshc->rsa); - curlx_safefree(sshc->rsa_pub); - myssh_to(data, sshc, SSH_SESSION_FREE); - return CURLE_OUT_OF_MEMORY; - } - - sshc->passphrase = data->set.ssl.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; - - 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); - + strstr(sshc->authlist, "publickey")) { + if(sshc->pub_key) + infof(data, "SSH: trying public key file '%s'", sshc->pub_key); + infof(data, "SSH: trying private key file '%s'", sshc->priv_key); myssh_to(data, sshc, SSH_AUTH_PKEY); } - else { + else myssh_to(data, sshc, SSH_AUTH_PASS_INIT); - } - return CURLE_OK; } static CURLcode sftp_quote_stat(struct Curl_easy *data, @@ -1180,7 +1139,7 @@ static CURLcode sftp_quote_stat(struct Curl_easy *data, sshc->acceptfail = TRUE; } - if(!!strncmp(cmd, "chmod", 5)) { + 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. @@ -1354,7 +1313,7 @@ static CURLcode sftp_download_stat(struct Curl_easy *data, if(data->req.size == 0) { /* no data to transfer */ Curl_xfer_setup_nop(data); - infof(data, "File already completely downloaded"); + infof(data, "SSH: file already completely downloaded"); myssh_to(data, sshc, SSH_STOP); return CURLE_OK; } @@ -1496,15 +1455,15 @@ static CURLcode ssh_state_authlist(struct Curl_easy *data, * Therefore always specify it here. */ struct connectdata *conn = data->conn; + const char *user = Curl_creds_user(conn->creds); sshc->authlist = libssh2_userauth_list(sshc->ssh_session, - conn->user, - curlx_uztoui(strlen(conn->user))); + user, curlx_uztoui(strlen(user))); if(!sshc->authlist) { int rc; if(libssh2_userauth_authenticated(sshc->ssh_session)) { sshc->authed = TRUE; - infof(data, "SSH user accepted with no authentication"); + infof(data, "SSH: user accepted with no authentication"); myssh_to(data, sshc, SSH_AUTH_DONE); return CURLE_OK; } @@ -1515,7 +1474,7 @@ static CURLcode ssh_state_authlist(struct Curl_easy *data, myssh_to(data, sshc, SSH_SESSION_FREE); return libssh2_session_error_to_CURLE(rc); } - infof(data, "SSH authentication methods available: %s", sshc->authlist); + infof(data, "SSH: host offers authentication via: %s", sshc->authlist); myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); return CURLE_OK; @@ -1527,22 +1486,19 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, /* The function below checks if the files exists, no need to stat() here. */ struct connectdata *conn = data->conn; + const char *user = Curl_creds_user(conn->creds); int rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - sshc->rsa_pub, - sshc->rsa, sshc->passphrase); + user, + curlx_uztoui(strlen(user)), + sshc->pub_key, + sshc->priv_key, sshc->passphrase); if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); - if(rc == 0) { sshc->authed = TRUE; - infof(data, "Initialized SSH public key authentication"); + infof(data, "SSH: authenticated via publickey"); myssh_to(data, sshc, SSH_AUTH_DONE); } else { @@ -1556,7 +1512,7 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, else { (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); } - infof(data, "SSH public key authentication failed: %s", err_msg); + infof(data, "SSH: publickey authentication denied: %s", err_msg); myssh_to(data, sshc, SSH_AUTH_PASS_INIT); } return CURLE_OK; @@ -1566,7 +1522,7 @@ 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)) { + strstr(sshc->authlist, "password")) { myssh_to(data, sshc, SSH_AUTH_PASS); } else { @@ -1579,18 +1535,20 @@ static CURLcode ssh_state_auth_pass(struct Curl_easy *data, struct ssh_conn *sshc) { struct connectdata *conn = data->conn; + const char *user = Curl_creds_user(conn->creds); + const char *passwd = Curl_creds_passwd(conn->creds); int rc = - libssh2_userauth_password_ex(sshc->ssh_session, conn->user, - curlx_uztoui(strlen(conn->user)), - conn->passwd, - curlx_uztoui(strlen(conn->passwd)), + libssh2_userauth_password_ex(sshc->ssh_session, user, + curlx_uztoui(strlen(user)), + passwd, + curlx_uztoui(strlen(passwd)), NULL); if(rc == LIBSSH2_ERROR_EAGAIN) { return CURLE_AGAIN; } if(rc == 0) { sshc->authed = TRUE; - infof(data, "Initialized password authentication"); + infof(data, "SSH: initialized password authentication"); myssh_to(data, sshc, SSH_AUTH_DONE); } else { @@ -1603,7 +1561,7 @@ 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)) { + strstr(sshc->authlist, "hostbased")) { myssh_to(data, sshc, SSH_AUTH_HOST); } else { @@ -1617,15 +1575,16 @@ static CURLcode ssh_state_auth_agent_init(struct Curl_easy *data, { int rc = 0; if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) && - (strstr(sshc->authlist, "publickey") != NULL)) { + strstr(sshc->authlist, "publickey")) { + infof(data, "SSH: trying publickey authentication via agent"); /* 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"); + infof(data, "SSH: could not create agent object"); myssh_to(data, sshc, SSH_AUTH_KEY_INIT); return CURLE_OK; @@ -1636,7 +1595,7 @@ static CURLcode ssh_state_auth_agent_init(struct Curl_easy *data, if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; if(rc < 0) { - infof(data, "Failure connecting to agent"); + infof(data, "SSH: failure connecting to agent"); myssh_to(data, sshc, SSH_AUTH_KEY_INIT); } else { @@ -1656,7 +1615,7 @@ static CURLcode ssh_state_auth_agent_list(struct Curl_easy *data, if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; if(rc < 0) { - infof(data, "Failure requesting identities to agent"); + infof(data, "SSH: failure requesting identities to agent"); myssh_to(data, sshc, SSH_AUTH_KEY_INIT); } else { @@ -1679,8 +1638,11 @@ static CURLcode ssh_state_auth_agent(struct Curl_easy *data, return CURLE_AGAIN; if(rc == 0) { - struct connectdata *conn = data->conn; - rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, + CURL_TRC_SSH(data, "[SSH_AUTH_AGENT_LIST] auth user '%s' for key '%s'", + Curl_creds_user(data->conn->creds), + sshc->sshagent_identity->comment); + rc = libssh2_agent_userauth(sshc->ssh_agent, + Curl_creds_user(data->conn->creds), sshc->sshagent_identity); if(rc < 0) { @@ -1694,13 +1656,15 @@ static CURLcode ssh_state_auth_agent(struct Curl_easy *data, } if(rc < 0) - infof(data, "Failure requesting identities to agent"); + infof(data, "SSH: failure requesting identities to agent"); else if(rc == 1) - infof(data, "No identity would match"); + infof(data, "SSH: no agent identity would match"); if(rc == LIBSSH2_ERROR_NONE) { sshc->authed = TRUE; - infof(data, "Agent based authentication successful"); + infof(data, "SSH: agent authenticated user '%s' with key '%s'", + Curl_creds_user(data->conn->creds), + sshc->sshagent_identity->comment); myssh_to(data, sshc, SSH_AUTH_DONE); } else { @@ -1713,7 +1677,7 @@ 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)) { + strstr(sshc->authlist, "keyboard-interactive")) { myssh_to(data, sshc, SSH_AUTH_KEY); } else { @@ -1727,18 +1691,17 @@ static CURLcode ssh_state_auth_key(struct Curl_easy *data, { /* Authentication failed. Continue with keyboard-interactive now. */ struct connectdata *conn = data->conn; + const char *user = Curl_creds_user(conn->creds); int rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), + user, curlx_uztoui(strlen(user)), &kbd_callback); if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; if(rc == 0) { sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication"); + infof(data, "SSH: initialized keyboard interactive authentication"); myssh_to(data, sshc, SSH_AUTH_DONE); return CURLE_OK; } @@ -1758,7 +1721,7 @@ static CURLcode ssh_state_auth_done(struct Curl_easy *data, /* * At this point we have an authenticated ssh session. */ - infof(data, "Authentication complete"); + infof(data, "SSH: authentication complete"); Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ @@ -1769,7 +1732,7 @@ static CURLcode ssh_state_auth_done(struct Curl_easy *data, myssh_to(data, sshc, SSH_SFTP_INIT); return CURLE_OK; } - infof(data, "SSH CONNECT phase done"); + infof(data, "SSH: connection established"); myssh_to(data, sshc, SSH_STOP); return CURLE_OK; } @@ -1809,7 +1772,7 @@ static CURLcode ssh_state_sftp_realpath(struct Curl_easy *data, return CURLE_FAILED_INIT; rc = libssh2_sftp_symlink_ex(sshc->sftp_session, ".", - curlx_uztoui(strlen(".")), + curlx_uztoui(CURL_CSTRLEN(".")), sshp->readdir_filename, CURL_PATH_MAX, LIBSSH2_SFTP_REALPATH); if(rc == LIBSSH2_ERROR_EAGAIN) @@ -1863,7 +1826,7 @@ static CURLcode ssh_state_sftp_quote_init(struct Curl_easy *data, } if(data->set.quote) { - infof(data, "Sending quote commands"); + infof(data, "SSH: sending quote commands"); sshc->quote_item = data->set.quote; myssh_to(data, sshc, SSH_SFTP_QUOTE); } @@ -1877,7 +1840,7 @@ static CURLcode ssh_state_sftp_postquote_init(struct Curl_easy *data, struct ssh_conn *sshc) { if(data->set.postquote) { - infof(data, "Sending quote commands"); + infof(data, "SSH: sending quote commands"); sshc->quote_item = data->set.postquote; myssh_to(data, sshc, SSH_SFTP_QUOTE); } @@ -2585,15 +2548,15 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, } /* 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); - - curlx_safefree(sshc->rsa_pub); - curlx_safefree(sshc->rsa); + DEBUGASSERT(!sshc->ssh_session); + DEBUGASSERT(!sshc->ssh_channel); + DEBUGASSERT(!sshc->sftp_session); + DEBUGASSERT(!sshc->sftp_handle); + DEBUGASSERT(!sshc->kh); + DEBUGASSERT(!sshc->ssh_agent); + + curlx_safefree(sshc->pub_key); + curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); curlx_safefree(sshc->quote_path2); curlx_safefree(sshc->homedir); @@ -2692,7 +2655,7 @@ static CURLcode ssh_state_sftp_create_dirs(struct Curl_easy *data, sshc->slash_pos = strchr(sshc->slash_pos, '/'); if(sshc->slash_pos) { *sshc->slash_pos = 0; - infof(data, "Creating directory '%s'", sshp->path); + infof(data, "SFTP: creating directory '%s'", sshp->path); myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR); return CURLE_OK; } @@ -2817,8 +2780,7 @@ static CURLcode ssh_state_scp_send_eof(struct Curl_easy *data, char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); - infof(data, - "Failed to send libssh2 channel EOF: %d %s", + infof(data, "Failed to send libssh2 channel EOF: %d %s", rc, err_msg); } } @@ -2837,8 +2799,7 @@ static CURLcode ssh_state_scp_wait_eof(struct Curl_easy *data, char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); - infof(data, "Failed to get channel EOF: %d %s", - rc, err_msg); + infof(data, "Failed to get channel EOF: %d %s", rc, err_msg); } } myssh_to(data, sshc, SSH_SCP_WAIT_CLOSE); @@ -2857,8 +2818,7 @@ static CURLcode ssh_state_scp_wait_close(struct Curl_easy *data, char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); - infof(data, "Channel failed to close: %d %s", - rc, err_msg); + infof(data, "Channel failed to close: %d %s", rc, err_msg); } } myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE); @@ -2877,9 +2837,7 @@ static CURLcode ssh_state_scp_channel_free( 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); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", rc, err_msg); } sshc->ssh_channel = NULL; } @@ -2896,7 +2854,7 @@ static CURLcode ssh_state_session_free(struct Curl_easy *data, if(result) return result; memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); + connclose(conn); sshc->state = SSH_SESSION_FREE; /* current */ myssh_to(data, sshc, SSH_STOP); return CURLE_OK; @@ -2939,7 +2897,7 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_AUTH_PKEY_INIT: - result = ssh_state_pkey_init(data, sshc); + ssh_state_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: @@ -3162,41 +3120,11 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, result = CURLE_OK; } CURL_TRC_SSH(data, "[%s] statemachine() -> %d, block=%d", - Curl_ssh_statename(sshc->state), result, *block); + Curl_ssh_statename(sshc->state), (int)result, *block); return result; } -/* called by the multi interface to figure out what socket(s) to wait for and - for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ -static CURLcode ssh_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int waitfor; - - if(!sshc || (sock == CURL_SOCKET_BAD)) - return CURLE_FAILED_INIT; - - waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; - if(waitfor) { - int flags = 0; - if(waitfor & REQ_IO_RECV) - flags |= CURL_POLL_IN; - if(waitfor & REQ_IO_SEND) - flags |= CURL_POLL_OUT; - DEBUGASSERT(flags); - CURL_TRC_SSH(data, "pollset, flags=%x", flags); - return Curl_pollset_change(data, ps, sock, flags, 0); - } - /* While we still have a session, we listen incoming data. */ - if(sshc->ssh_session) - return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); - return CURLE_OK; -} - /* * 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 @@ -3230,8 +3158,8 @@ static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) 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_pollset() - implementation */ + bool block; /* we store the status and use that to provide + a Curl_ssh_pollset() implementation */ if(!sshc || !sshp) return CURLE_FAILED_INIT; @@ -3344,7 +3272,7 @@ static CURLcode ssh_setup_connection(struct Curl_easy *data, if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - return CURLE_OK; + return Curl_ssh_setup_pkey(data, sshc); } static Curl_recv scp_recv, sftp_recv; @@ -3355,7 +3283,7 @@ static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer, size_t length, int flags, void **abstract) { struct Curl_easy *data = (struct Curl_easy *)*abstract; - int sockindex = Curl_conn_sockindex(data, sock); + int8_t sockindex = Curl_conn_sockindex(data, sock); size_t nread; CURLcode result; struct connectdata *conn = data->conn; @@ -3383,7 +3311,7 @@ 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; - int sockindex = Curl_conn_sockindex(data, sock); + int8_t sockindex = Curl_conn_sockindex(data, sock); size_t nwrite; CURLcode result; struct connectdata *conn = data->conn; @@ -3445,27 +3373,27 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) break; } if(crypto_str) - infof(data, "libssh2 cryptography backend: %s", crypto_str); + infof(data, "SSH: libssh2 cryptography backend: %s", crypto_str); } #endif if(!sshc) return CURLE_FAILED_INIT; - infof(data, "User: '%s'", conn->user); + infof(data, "SSH: user '%s'", Curl_creds_user(conn->creds)); #ifdef CURL_LIBSSH2_DEBUG - infof(data, "Password: %s", conn->passwd); + infof(data, "SSH: password %s", Curl_creds_passwd(conn->creds)); sock = conn->sock[FIRSTSOCKET]; #endif /* CURL_LIBSSH2_DEBUG */ - /* libcurl MUST to set custom memory functions so that the kbd_callback + /* libcurl MUST 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); if(!sshc->ssh_session) { - failf(data, "Failure initialising ssh session"); + failf(data, "Failure initializing ssh session"); return CURLE_FAILED_INIT; } @@ -3479,20 +3407,18 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #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); + /* Setup libssh2 callbacks to make it read/write TLS from the socket. - ssize_t - sendcb(libssh2_socket_t sock, const void *buffer, size_t length, - int flags, void **abstract); + 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"); + infof(data, "SSH: using HTTPS proxy"); #if defined(__clang__) && __clang_major__ >= 16 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcast-function-type-strict" @@ -3526,7 +3452,7 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) sshrecv.recvptr = ssh_tls_recv; sshsend.sendptr = ssh_tls_send; - infof(data, "Uses HTTPS proxy"); + infof(data, "SSH: using HTTPS proxy"); libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_RECV, sshrecv.recvp); libssh2_session_callback_set(sshc->ssh_session, @@ -3551,10 +3477,10 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) 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"); + infof(data, "SSH: failed to enable compression for session"); } - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + if(CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)) { int rc; sshc->kh = libssh2_knownhost_init(sshc->ssh_session); if(!sshc->kh) { @@ -3564,18 +3490,18 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) } /* read all known hosts from there */ - rc = libssh2_knownhost_readfile(sshc->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); + rc = libssh2_knownhost_readfile( + sshc->kh, CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS), + LIBSSH2_KNOWNHOST_FILE_OPENSSH); if(rc < 0) - infof(data, "Failed to read known hosts from %s", - data->set.str[STRING_SSH_KNOWNHOSTS]); + infof(data, "SSH: failed to read known hosts from %s", + CURL_EASY_STR(data, STRING_SSH_KNOWNHOSTS)); } #ifdef CURL_LIBSSH2_DEBUG libssh2_trace(sshc->ssh_session, ~0); - infof(data, "SSH socket: %d", (int)sock); -#endif /* CURL_LIBSSH2_DEBUG */ + infof(data, "SSH: socket %d", (int)sock); +#endif myssh_to(data, sshc, SSH_INIT); @@ -3693,7 +3619,7 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, return ssh_done(data, status); } -static CURLcode scp_send(struct Curl_easy *data, int sockindex, +static CURLcode scp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -3725,7 +3651,7 @@ static CURLcode scp_send(struct Curl_easy *data, int sockindex, return result; } -static CURLcode scp_recv(struct Curl_easy *data, int sockindex, +static CURLcode scp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -3823,7 +3749,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, CURL_TRC_SSH(data, "DISCONNECT starts now"); myssh_to(data, sshc, SSH_SFTP_SHUTDOWN); result = ssh_block_statemach(data, sshc, sshp, TRUE); - CURL_TRC_SSH(data, "DISCONNECT is done -> %d", result); + CURL_TRC_SSH(data, "DISCONNECT is done -> %d", (int)result); } sshc_cleanup(sshc, data, TRUE); } @@ -3851,7 +3777,7 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, } /* return number of sent bytes */ -static CURLcode sftp_send(struct Curl_easy *data, int sockindex, +static CURLcode sftp_send(struct Curl_easy *data, int8_t sockindex, const uint8_t *mem, size_t len, bool eos, size_t *pnwritten) { @@ -3882,7 +3808,7 @@ static CURLcode sftp_send(struct Curl_easy *data, int sockindex, * Return number of received (decrypted) bytes * or <0 on error */ -static CURLcode sftp_recv(struct Curl_easy *data, int sockindex, +static CURLcode sftp_recv(struct Curl_easy *data, int8_t sockindex, char *mem, size_t len, size_t *pnread) { struct connectdata *conn = data->conn; @@ -3985,10 +3911,10 @@ const struct Curl_protocol Curl_protocol_scp = { ssh_connect, /* connect_it */ ssh_multi_statemach, /* connecting */ scp_doing, /* doing */ - ssh_pollset, /* proto_pollset */ - ssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - ssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ scp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ @@ -4008,10 +3934,10 @@ const struct Curl_protocol Curl_protocol_sftp = { ssh_connect, /* connect_it */ ssh_multi_statemach, /* connecting */ sftp_doing, /* doing */ - ssh_pollset, /* proto_pollset */ - ssh_pollset, /* doing_pollset */ + Curl_ssh_pollset, /* proto_pollset */ + Curl_ssh_pollset, /* doing_pollset */ ZERO_NULL, /* domore_pollset */ - ssh_pollset, /* perform_pollset */ + Curl_ssh_pollset, /* perform_pollset */ sftp_disconnect, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ diff --git a/lib/vssh/ssh.h b/lib/vssh/ssh.h index 44e72ab802bf..52bc1399901a 100644 --- a/lib/vssh/ssh.h +++ b/lib/vssh/ssh.h @@ -144,8 +144,8 @@ struct ssh_conn { /* common */ const char *passphrase; /* pass-phrase to use */ - char *rsa_pub; /* strdup'ed public key file */ - char *rsa; /* strdup'ed private key file */ + char *pub_key; /* strdup'ed public key file */ + char *priv_key; /* strdup'ed private key file */ sshstate state; /* always use ssh.c:state() to change state! */ sshstate nextstate; /* the state to goto after stopping */ struct curl_slist *quote_item; /* for the quote option */ @@ -195,7 +195,7 @@ struct ssh_conn { const char *readdir_filename; /* points within readdir_attrs */ const char *readdir_longentry; char *readdir_tmp; - BIT(initialised); + BIT(initialized); #elif defined(USE_LIBSSH2) LIBSSH2_SESSION *ssh_session; /* Secure Shell session */ LIBSSH2_CHANNEL *ssh_channel; /* Secure Shell channel handle */ @@ -220,7 +220,7 @@ struct ssh_conn { #ifdef USE_LIBSSH #if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 9, 0) -#error "SCP/SFTP protocols require libssh 0.9.0 or later" +#error "SCP/SFTP protocols require libssh 0.9.0 or greater" #endif #endif @@ -229,9 +229,9 @@ struct ssh_conn { /* Feature detection based on version numbers to better work with non-configure platforms */ -#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 */ +#if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x010900) +#error "SCP/SFTP protocols require libssh2 1.9.0 or greater" +/* 1.9.0 was released on June 20 2019 */ #endif #endif /* USE_LIBSSH2 */ diff --git a/lib/vssh/vssh.c b/lib/vssh/vssh.c index 0ba4a9e69078..9834546987d9 100644 --- a/lib/vssh/vssh.c +++ b/lib/vssh/vssh.c @@ -30,6 +30,9 @@ #include "curlx/strparse.h" #include "curl_trc.h" #include "escape.h" +#include "select.h" /* for Curl_pollset_change() */ +#include "url.h" /* for Curl_conn_meta_get() */ +#include "curlx/fopen.h" #ifdef CURLVERBOSE const char *Curl_ssh_statename(sshstate state) @@ -330,4 +333,118 @@ CURLcode Curl_ssh_range(struct Curl_easy *data, return CURLE_OK; } +/* called by the multi interface to figure out what socket(s) to wait for and + for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ +CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int waitfor; + + if(!sshc || (sock == CURL_SOCKET_BAD)) + return CURLE_FAILED_INIT; + + waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags; + if(waitfor) { + int flags = 0; + if(waitfor & REQ_IO_RECV) + flags |= CURL_POLL_IN; + if(waitfor & REQ_IO_SEND) + flags |= CURL_POLL_OUT; + DEBUGASSERT(flags); + CURL_TRC_SSH(data, "pollset, flags=%x", (unsigned int)flags); + return Curl_pollset_change(data, ps, sock, flags, 0); + } + /* While we still have a session, we listen incoming data. */ + if(sshc->ssh_session) + return Curl_pollset_change(data, ps, sock, CURL_POLL_IN, 0); + return CURLE_OK; +} + +CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc) +{ + char *home = NULL; + if(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) { + const char *str; + + sshc->pub_key = sshc->priv_key = NULL; + + if(CURL_EASY_STR(data, STRING_SSH_PRIVATE_KEY)) { + sshc->priv_key = curlx_strdup( + CURL_EASY_STR(data, STRING_SSH_PRIVATE_KEY)); + if(!sshc->priv_key) + goto fail; + } + else { + /* To ponder about: should really the lib be messing about with the HOME + environment variable etc? */ + curlx_struct_stat sbuf; + home = curl_getenv("HOME"); + + /* If no private key file is specified, try some common paths. */ + if(home) { + /* Try ~/.ssh first. */ + sshc->priv_key = curl_maprintf("%s/.ssh/id_rsa", home); + if(!sshc->priv_key) + goto fail; + else if(curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + sshc->priv_key = curl_maprintf("%s/.ssh/id_dsa", home); + if(!sshc->priv_key) + goto fail; + else if(curlx_stat(sshc->priv_key, &sbuf)) { + curlx_safefree(sshc->priv_key); + } + } + curlx_safefree(home); + } + if(!sshc->priv_key) { + /* Nothing found; try the current dir. */ + sshc->priv_key = curlx_strdup("id_rsa"); + if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + sshc->priv_key = curlx_strdup("id_dsa"); + if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) { + curlx_free(sshc->priv_key); + /* Out of guesses. Set to the empty string to avoid + * surprising info messages. */ + sshc->priv_key = curlx_strdup(""); + } + } + } + } + + /* + * Unless the user explicitly specifies a public key file, let the SSH + * library extract the public key from the private key file. This is done + * by passing sshc->pub_key = NULL. + */ + str = CURL_EASY_STR(data, STRING_SSH_PUBLIC_KEY); + if(str && *str) { /* treat empty string the same way as NULL */ + sshc->pub_key = curlx_strdup(str); + if(!sshc->pub_key) + goto fail; + } + + sshc->passphrase = data->set.ssl.primary.key_passwd; + if(!sshc->passphrase) + sshc->passphrase = ""; + + if(sshc->pub_key) + infof(data, "SSH: public key file '%s'", sshc->pub_key); + if(sshc->priv_key) + infof(data, "SSH: private key file '%s'", sshc->priv_key); + else + infof(data, "SSH: public key auth without private key set!"); + } + return CURLE_OK; + +fail: + curlx_safefree(home); + curlx_safefree(sshc->priv_key); + curlx_safefree(sshc->pub_key); + return CURLE_OUT_OF_MEMORY; +} + #endif /* USE_SSH */ diff --git a/lib/vssh/vssh.h b/lib/vssh/vssh.h index 492108fbd766..4c505c453a3e 100644 --- a/lib/vssh/vssh.h +++ b/lib/vssh/vssh.h @@ -29,6 +29,8 @@ #include "urldata.h" +struct ssh_conn; + CURLcode Curl_getworkingpath(struct Curl_easy *data, const char *homedir, char **path); @@ -38,5 +40,9 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir); CURLcode Curl_ssh_range(struct Curl_easy *data, const char *range, curl_off_t filesize, curl_off_t *startp, curl_off_t *sizep); + +CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps); +CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc); + #endif /* USE_SSH */ #endif /* HEADER_CURL_VSSH_VSSH_H */ diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index e28a40cc0e0a..ce7f0c5e1cd2 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -36,7 +36,7 @@ "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ + */ #include "curl_setup.h" @@ -102,7 +102,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, if(conn_config->verifyhost) { host_str = CFStringCreateWithCString(NULL, - peer->sni ? peer->sni : peer->hostname, kCFStringEncodingUTF8); + peer->sni ? peer->sni : peer->origin->hostname, kCFStringEncodingUTF8); if(!host_str) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -200,25 +200,30 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, goto out; } -#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(ocsp_len > 0) { + bool checked = FALSE; +#if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { CFDataRef ocspdata = CFDataCreate(NULL, ocsp_buf, (CFIndex)ocsp_len); status = SecTrustSetOCSPResponse(trust, ocspdata); CFRelease(ocspdata); if(status != noErr) { - failf(data, "Apple SecTrust: failed to set OCSP response: %i", + failf(data, "Apple SecTrust: failed to set OCSP response: %d", (int)status); result = CURLE_PEER_FAILED_VERIFICATION; goto out; } + checked = TRUE; } - } -#else - (void)ocsp_buf; - (void)ocsp_len; #endif + if(!checked) { + (void)ocsp_buf; + failf(data, "Apple SecTrust: OCSP verification not supported"); + result = CURLE_NOT_BUILT_IN; + goto out; + } + } #ifdef SUPPORTS_SecTrustEvaluateWithError #ifdef HAVE_BUILTIN_AVAILABLE @@ -238,10 +243,8 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, err_desc = curlx_malloc(size + 1); if(err_desc) { if(!CFStringGetCString(error_ref, err_desc, size, - kCFStringEncodingUTF8)) { - curlx_free(err_desc); - err_desc = NULL; - } + kCFStringEncodingUTF8)) + curlx_safefree(err_desc); } } infof(data, "Apple SecTrust failure %ld%s%s", code, @@ -256,7 +259,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, status = SecTrustEvaluate(trust, &sec_result); if(status != noErr) { - failf(data, "Apple SecTrust verification failed: error %i", (int)status); + failf(data, "Apple SecTrust verification failed: error %d", (int)status); result = CURLE_PEER_FAILED_VERIFICATION; } else if((sec_result == kSecTrustResultUnspecified) || diff --git a/lib/vtls/cipher_suite.c b/lib/vtls/cipher_suite.c index 1fc6e9f8c6f9..2c6a0c02d021 100644 --- a/lib/vtls/cipher_suite.c +++ b/lib/vtls/cipher_suite.c @@ -48,7 +48,7 @@ /* Text for cipher suite parts (max 64 entries), keep indexes below in sync with this! */ -static const char *cs_txt = +static const char cs_txt[] = "\0" "TLS" "\0" "WITH" "\0" @@ -639,7 +639,7 @@ uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len) 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) + if(!memcmp(cs_list[i].zip, zip, sizeof(zip))) return cs_list[i].id; } } diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 9b6a4fab2e39..9d17573dc5d1 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -26,7 +26,7 @@ * but vtls.c should ever call or use these functions. * * Note: do not use the GnuTLS' *_t variable type names in this source code, - * since they were not present in 1.0.X. + * since they were not present in 1.0.x. */ #include "curl_setup.h" @@ -37,6 +37,7 @@ #include #include #include +#include #include "urldata.h" #include "curl_trc.h" @@ -71,7 +72,7 @@ static void tls_log_func(int level, const char *str) #endif #undef CURL_GNUTLS_EARLY_DATA -#if GNUTLS_VERSION_NUMBER >= 0x03060d +#if GNUTLS_VERSION_NUMBER >= 0x030702 #define CURL_GNUTLS_EARLY_DATA #endif @@ -94,7 +95,7 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen) DEBUGASSERT(data); result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &nwritten); CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %d, %zu", - blen, result, nwritten); + blen, (int)result, nwritten); backend->gtls.io_result = result; if(result) { /* !checksrc! disable ERRNOVAR 1 */ @@ -127,7 +128,8 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) } result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread); - CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %d, %zu", blen, result, nread); + CURL_TRC_CF(data, cf, "gtls_pull(len=%zu) -> %d, %zu", blen, (int)result, + nread); backend->gtls.io_result = result; if(result) { /* !checksrc! disable ERRNOVAR 1 */ @@ -203,12 +205,12 @@ static gnutls_datum_t load_file(const char *file) f = curlx_fopen(file, "rb"); if(!f) return loaded_file; - if(fseek(f, 0, SEEK_END) != 0) + if(fseek(f, 0, SEEK_END)) goto out; filelen = ftell(f); if(filelen < 0 || filelen > CURL_MAX_INPUT_LENGTH) goto out; - if(fseek(f, 0, SEEK_SET) != 0) + if(fseek(f, 0, SEEK_SET)) goto out; ptr = curlx_malloc((size_t)filelen); if(!ptr) @@ -311,10 +313,6 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) #define GNUTLS_CIPHERS "NORMAL:%PROFILE_MEDIUM:-ARCFOUR-128:" \ "-CTYPE-ALL:+CTYPE-X509" -/* If GnuTLS was compiled without support for SRP it errors out if SRP is - requested in the priority string, so treat it specially - */ -#define GNUTLS_SRP "+SRP" #define QUIC_PRIORITY \ "NORMAL:%PROFILE_MEDIUM:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:" \ @@ -322,12 +320,12 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) "+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ "%DISABLE_TLS13_COMPAT_MODE" -static CURLcode -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) +static CURLcode 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; @@ -408,7 +406,8 @@ CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, rc = gnutls_certificate_allocate_credentials(&shared->creds); if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + failf(data, "gnutls_certificate_allocate_credentials() failed: %s", + gnutls_strerror(rc)); curlx_free(shared); return CURLE_SSL_CONNECT_ERROR; } @@ -458,7 +457,7 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, } infof(data, "SSL Trust Anchors:"); - if(ssl_config->native_ca_store) { + if(config->native_ca_store) { #ifdef USE_APPLE_SECTRUST infof(data, " Native: Apple SecTrust"); creds_are_empty = FALSE; @@ -592,7 +591,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, if(data->multi) { shared_creds = Curl_hash_pick(&data->multi->proto_hash, CURL_UNCONST(MPROTO_GTLS_X509_KEY), - sizeof(MPROTO_GTLS_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); if(shared_creds && shared_creds->creds && !gtls_shared_creds_expired(data, shared_creds) && !gtls_shared_creds_different(cf, shared_creds)) { @@ -605,7 +604,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, 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(key_len == CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -636,7 +635,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, if(!Curl_hash_add2(&data->multi->proto_hash, CURL_UNCONST(MPROTO_GTLS_X509_KEY), - sizeof(MPROTO_GTLS_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_GTLS_X509_KEY), sc, gtls_shared_creds_hash_free)) { Curl_gtls_shared_creds_free(&sc); /* down reference again */ return; @@ -662,7 +661,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store && + !conn_config->native_ca_store && !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ if(cache_criteria_met) @@ -692,6 +691,24 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, } #ifdef CURL_GNUTLS_EARLY_DATA +static int gtls_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; + } +} + CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, @@ -699,14 +716,17 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, curl_off_t valid_until, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **psession) { - struct Curl_ssl_session *sc_session; + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *sdata, *qtp_clone = NULL; size_t sdata_len = 0; size_t earlydata_max = 0; CURLcode result = CURLE_OK; + if(psession) + *psession = NULL; if(!Curl_ssl_scache_use(cf, data)) return CURLE_OK; @@ -740,46 +760,46 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, } result = Curl_ssl_session_create2(sdata, sdata_len, - Curl_glts_get_ietf_proto(session), + gtls_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 && psession && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) + result = Curl_ssl_session_dup(sc_session, &sc_dup); if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; + } + if(!result && psession) { + *psession = sc_dup; + sc_dup = NULL; } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } #endif -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; - } -} - #ifdef CURL_GNUTLS_EARLY_DATA 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); + struct Curl_ssl_session *scs = NULL; + CURLcode result; + + result = Curl_gtls_cache_session(cf, data, connssl->peer.scache_key, + session, 0, connssl->negotiated.alpn, + NULL, 0, &scs); + if(!result) { + Curl_ssl_session_destroy(connssl->session); + connssl->session = scs; + } + return result; } static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, @@ -822,24 +842,11 @@ static CURLcode gtls_set_priority(struct Curl_cfilter *cf, 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 disables 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 */ + /* add it to our own */ if(!curlx_dyn_len(&buf)) { /* not added yet */ result = curlx_dyn_add(&buf, priority); if(result) @@ -891,33 +898,6 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; -#ifdef USE_GNUTLS_SRP - if(config->username && Curl_auth_allowed_to_host(data)) { - infof(data, "Using TLS-SRP username: %s", config->username); - - rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); - if(rc == GNUTLS_E_UNIMPLEMENTED_FEATURE) { - failf(data, "GnuTLS: TLS-SRP support not built in: %s", - gnutls_strerror(rc)); - return CURLE_NOT_BUILT_IN; - } - else if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_allocate_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_OUT_OF_MEMORY; - } - - rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, - config->username, - config->password); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_set_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } -#endif - ssl_config->certverifyresult = 0; /* Initialize TLS session as a client */ @@ -995,10 +975,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; } - if(ssl_config->cert_type && curl_strequal(ssl_config->cert_type, "P12")) { + if(ssl_config->primary.cert_type && + curl_strequal(ssl_config->primary.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 : ""); + ssl_config->primary.key_passwd ? ssl_config->primary.key_passwd : ""); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 potentially-encrypted key or certificate " @@ -1016,39 +997,26 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, rc = gnutls_certificate_set_x509_key_file2( gtls->shared_creds->creds, config->clientcert, - ssl_config->key ? ssl_config->key : config->clientcert, - gnutls_do_file_type(ssl_config->cert_type), - ssl_config->key_passwd, + ssl_config->primary.key ? ssl_config->primary.key : + config->clientcert, + gnutls_do_file_type(ssl_config->primary.cert_type), + ssl_config->primary.key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 %skey file: %s", - ssl_config->key_passwd ? "potentially-encrypted " : "", + ssl_config->primary.key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } } } -#ifdef USE_GNUTLS_SRP - /* put the credentials to the current session */ - if(config->username) { - rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP, - gtls->srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - } - else -#endif - { - 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; - } + 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; } if(config->verifystatus) { @@ -1075,7 +1043,8 @@ static int keylog_callback(gnutls_session_t session, const char *label, return -1; } - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + Curl_tls_keylog_write(label, crandom.data, crandom.size, + secret->data, secret->size); return 0; } @@ -1099,6 +1068,55 @@ static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf, } #endif +static CURLcode gtls_apply_session( + struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct alpn_spec *alpns, + Curl_gtls_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs, + bool *pwas_setup) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + int rc; + + 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; + *pwas_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->bits.connect_only) { + 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); + } + } + } + } +out: + return result; +} + CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, struct Curl_cfilter *cf, struct Curl_easy *data, @@ -1110,57 +1128,40 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, 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); 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); + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + scs = Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + result = gtls_apply_session(gctx, cf, data, peer, &alpns, + sess_reuse_cb, scs, >ls_session_setup); + scs = NULL; + if(!result && gtls_session_setup) { + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + } + } + } /* 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 && !conn_config->verifystatus) { + if(!gtls_session_setup && conn_config->cache_session && + !conn_config->verifystatus) { 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->bits.connect_only) { - 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); - } - } - } - } + result = gtls_apply_session(gctx, cf, data, peer, &alpns, + sess_reuse_cb, scs, >ls_session_setup); } if(!gtls_session_setup) { @@ -1361,11 +1362,12 @@ static void gtls_msg_verify_result(struct Curl_easy *data, if(!was_verified) { if(needs_verified) { failf(data, "SSL: certificate subject name (%s) does not match " - "target hostname '%s'", certname, peer->dispname); + "target hostname '%s'", certname, + peer->origin->user_hostname); } else infof(data, " common name: %s (does not match '%s')", - certname, peer->dispname); + certname, peer->origin->user_hostname); } else infof(data, " common name: %s (matched)", certname); @@ -1425,7 +1427,10 @@ static CURLcode gtls_verify_ocsp_status(struct Curl_easy *data, { gnutls_ocsp_resp_t ocsp_resp = NULL; gnutls_datum_t status_request; - gnutls_ocsp_cert_status_t status; + gnutls_certificate_credentials_t creds = NULL; + gnutls_x509_trust_list_t tlist = NULL; + unsigned int verify_status = 0; + gnutls_ocsp_cert_status_t status = GNUTLS_OCSP_CERT_UNKNOWN; gnutls_x509_crl_reason_t reason; CURLcode result = CURLE_OK; int rc; @@ -1457,6 +1462,20 @@ static CURLcode gtls_verify_ocsp_status(struct Curl_easy *data, goto out; } + if(!gnutls_credentials_get(session, GNUTLS_CRD_CERTIFICATE, (void **)&creds)) + gnutls_certificate_get_trust_list(creds, &tlist); + if(!tlist) { + failf(data, "OCSP response signature verification failed"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto out; + } + rc = gnutls_ocsp_resp_verify(ocsp_resp, tlist, &verify_status, 0); + if(rc < 0 || verify_status) { + failf(data, "OCSP response signature verification failed"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto out; + } + (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, &status, NULL, NULL, NULL, &reason); @@ -1555,21 +1574,75 @@ static CURLcode gtls_chain_get_der(struct Curl_cfilter *cf, *pder_len = (size_t)chain->certs[i].size; return CURLE_OK; } +#endif /* USE_APPLE_SECTRUST */ -static CURLcode glts_apple_verify(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_peer *peer, - struct gtls_cert_chain *chain, - bool *pverified) +/* This function verifies the peer's certificate and returns CURLE_OK on + success or an appropriate CURLcode on error. The certificate verification + status bitmask (trusted, invalid etc.) is stored in + ssl_config->certverifyresult as one or more gnutls_certificate_status_t + enumerated elements bitwise or'd. */ +static CURLcode gtls_verify_cert(struct Curl_easy *data, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + gnutls_session_t session, + struct Curl_cfilter *cf, + struct ssl_peer *peer, + struct gtls_cert_chain *chain) { - CURLcode result; + bool verified = FALSE; + unsigned int verify_status = 0; + long * const certverifyresult = &ssl_config->certverifyresult; + int rc = gnutls_certificate_verify_peers2(session, &verify_status); + if(rc < 0) { + failf(data, "server cert verify failed: %d", rc); + *certverifyresult = rc; + return CURLE_SSL_CONNECT_ERROR; + } + *certverifyresult = verify_status; + verified = !(verify_status & GNUTLS_CERT_INVALID); + if(verified) + infof(data, " SSL certificate verified by GnuTLS"); - result = Curl_vtls_apple_verify(cf, data, peer, chain->num_certs, - gtls_chain_get_der, chain, NULL, 0); - *pverified = !result; - return result; +#ifdef USE_APPLE_SECTRUST + if(!verified && config->native_ca_store) { + CURLcode result = + Curl_vtls_apple_verify(cf, data, peer, chain->num_certs, + gtls_chain_get_der, chain, NULL, 0); + if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) + return result; /* unexpected error */ + verified = !result; + if(verified) { + infof(data, "SSL certificate verified via Apple SecTrust."); + *certverifyresult = 0; + } + } +#else + (void)cf; + (void)peer; + (void)chain; +#endif + + if(!verified) { + /* verify_status is a bitmask of gnutls_certificate_status bits */ + 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, "SSL certificate 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; + } + return CURLE_OK; } -#endif /* USE_APPLE_SECTRUST */ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -1598,21 +1671,10 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, if(config->verifypeer || config->verifyhost || config->issuercert) { -#ifdef USE_GNUTLS_SRP - if(ssl_config->primary.username && !config->verifypeer && - gnutls_cipher_get(session)) { - /* no peer cert, but auth is ok if we have SRP user and cipher and no - peer verify */ - } - else { -#endif - failf(data, "failed to get server cert"); - *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND; - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; -#ifdef USE_GNUTLS_SRP - } -#endif + failf(data, "failed to get server cert"); + *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND; + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; } infof(data, " common name: WARNING could not obtain"); } @@ -1643,58 +1705,10 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, } if(config->verifypeer) { - bool verified = FALSE; - unsigned int verify_status = 0; - /* This function tries to verify the peer's certificate and return - its status (trusted, invalid etc.). The value of status should be - one or more of the gnutls_certificate_status_t enumerated elements - bitwise or'd. To avoid denial of service attacks some default - upper limits regarding the certificate key size and chain size - are set. To override them use - gnutls_certificate_set_verify_limits(). */ - rc = gnutls_certificate_verify_peers2(session, &verify_status); - if(rc < 0) { - failf(data, "server cert verify failed: %d", rc); - *certverifyresult = rc; - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - *certverifyresult = verify_status; - verified = !(verify_status & GNUTLS_CERT_INVALID); - if(verified) - infof(data, " SSL certificate verified by GnuTLS"); - -#ifdef USE_APPLE_SECTRUST - if(!verified && ssl_config->native_ca_store) { - result = glts_apple_verify(cf, data, peer, &chain, &verified); - if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) - goto out; /* unexpected error */ - if(verified) { - infof(data, "SSL certificate verified via Apple SecTrust."); - *certverifyresult = 0; - } - } -#endif - - if(!verified) { - /* verify_status is a bitmask of gnutls_certificate_status bits */ - 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, "SSL certificate verification failed: %s. (CAfile: %s " - "CRLfile: %s)", cause, - config->CAfile ? config->CAfile : "none", - ssl_config->primary.CRLfile ? - ssl_config->primary.CRLfile : "none"); - result = CURLE_PEER_FAILED_VERIFICATION; + result = gtls_verify_cert(data, config, ssl_config, session, + cf, peer, &chain); + if(result) goto out; - } } else infof(data, " SSL certificate verification SKIPPED"); @@ -1791,17 +1805,22 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, } issuerp = load_file(config->issuercert); rc = gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); - if(!rc) - rc = (int)gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); unload_file(issuerp); + if(rc) { + failf(data, "failed to import issuer certificate (%s) (Issuer Cert: %s)", + gnutls_strerror(rc), config->issuercert); + result = CURLE_SSL_ISSUER_ERROR; + goto out; + } + rc = (int)gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); if(rc <= 0) { - failf(data, "server certificate issuer check failed (IssuerCert: %s)", - config->issuercert ? config->issuercert : "none"); + failf(data, "server certificate issuer check failed (Issuer Cert: %s)", + config->issuercert); result = CURLE_SSL_ISSUER_ERROR; goto out; } infof(data, " SSL certificate issuer check OK (Issuer Cert: %s)", - config->issuercert ? config->issuercert : "none"); + config->issuercert); } /* This function checks if the given certificate's subject matches the @@ -1815,7 +1834,7 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, IP addresses) */ rc = (int)gnutls_x509_crt_check_hostname(x509_cert, peer->sni ? peer->sni : - peer->hostname); + peer->origin->hostname); result = (!rc && config->verifyhost) ? CURLE_PEER_FAILED_VERIFICATION : CURLE_OK; gtls_msg_verify_result(data, peer, x509_cert, rc, config->verifyhost); @@ -1849,10 +1868,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char *pinned_key = CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif CURLcode result; @@ -2014,7 +2033,8 @@ static CURLcode gtls_connect_common(struct Curl_cfilter *cf, } *done = ((connssl->state == ssl_connection_complete) || (connssl->state == ssl_connection_deferred)); - CURL_TRC_CF(data, cf, "gtls_connect_common() -> %d, done=%d", result, *done); + CURL_TRC_CF(data, cf, "gtls_connect_common() -> %d, done=%d", (int)result, + *done); return result; } @@ -2089,7 +2109,7 @@ static CURLcode gtls_send(struct Curl_cfilter *cf, out: CURL_TRC_CF(data, cf, "gtls_send(len=%zu) -> %d, %zu", - blen, result, *pnwritten); + blen, (int)result, *pnwritten); return result; } @@ -2187,12 +2207,6 @@ static void gtls_close(struct Curl_cfilter *cf, if(backend->gtls.shared_creds) { Curl_gtls_shared_creds_free(&backend->gtls.shared_creds); } -#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; - } -#endif } static CURLcode gtls_recv(struct Curl_cfilter *cf, @@ -2234,7 +2248,8 @@ static CURLcode gtls_recv(struct Curl_cfilter *cf, } out: - CURL_TRC_CF(data, cf, "gtls_recv(len=%zu) -> 0, %zd", blen, nread); + CURL_TRC_CF(data, cf, "gtls_recv(len=%zu) -> %d, %zd", blen, + (int)result, nread); return result; } @@ -2253,15 +2268,25 @@ static CURLcode gtls_random(struct Curl_easy *data, return rc ? CURLE_FAILED_INIT : CURLE_OK; } -static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, +static CURLcode gtls_sha256sum(const unsigned char *input, + size_t len, unsigned char *sha256sum, /* output */ size_t sha256len) { struct sha256_ctx SHA256pw; sha256_init(&SHA256pw); - sha256_update(&SHA256pw, (unsigned int)tmplen, tmp); + do { + unsigned int ilen = (unsigned int)CURLMIN(len, UINT_MAX); + sha256_update(&SHA256pw, ilen, input); + len -= ilen; + input += ilen; + } while(len); +#if NETTLE_VERSION_MAJOR >= 4 + (void)sha256len; + sha256_digest(&SHA256pw, sha256sum); +#else sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum); +#endif return CURLE_OK; } diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index 49106dd869af..0ad9a7e68c72 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -31,14 +31,6 @@ #include "curlx/timeval.h" -#ifdef HAVE_GNUTLS_SRP -/* the function exists */ -#ifdef USE_TLS_SRP -/* the functionality is not disabled */ -#define USE_GNUTLS_SRP -#endif -#endif - struct Curl_easy; struct Curl_cfilter; struct alpn_spec; @@ -48,8 +40,6 @@ struct ssl_peer; struct ssl_connect_data; struct Curl_ssl_session; -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 */ @@ -66,9 +56,6 @@ void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds); struct gtls_ctx { gnutls_session_t session; 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); }; @@ -107,7 +94,8 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct ssl_peer *peer, const char *pinned_key); -/* Extract TLS session and place in cache, if configured. */ +/* Extract TLS session and place in cache, if configured. Return + * a copy of the session if desired. */ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, @@ -115,7 +103,8 @@ CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, curl_off_t valid_until, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **psession); /* Report properties of a successful handshake */ void Curl_gtls_report_handshake(struct Curl_easy *data, struct gtls_ctx *gctx); diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c index ad09b8319413..0af540993ffd 100644 --- a/lib/vtls/hostcheck.c +++ b/lib/vtls/hostcheck.c @@ -34,8 +34,8 @@ #endif #include "curl_memrchr.h" +#include "vdns/hostip.h" #include "vtls/hostcheck.h" -#include "hostip.h" /* check the two input strings with given length, but do not assume they end in nul-bytes */ diff --git a/lib/vtls/keylog.c b/lib/vtls/keylog.c index 4ae2387a7a72..ac9649a41f78 100644 --- a/lib/vtls/keylog.c +++ b/lib/vtls/keylog.c @@ -66,7 +66,7 @@ void Curl_tls_keylog_close(void) bool Curl_tls_keylog_enabled(void) { - return keylog_file_fp != NULL; + return !!keylog_file_fp; } const char *Curl_tls_keylog_file_name(void) @@ -103,17 +103,19 @@ bool Curl_tls_keylog_write_line(const char *line) } bool Curl_tls_keylog_write(const char *label, - const unsigned char client_random[CLIENT_RANDOM_SIZE], - const unsigned char *secret, size_t secretlen) + const unsigned char *client_random, + size_t random_size, + const unsigned char *secret, size_t secretlen) { size_t pos, i; unsigned char line[KEYLOG_LABEL_MAXLEN + 1 + (2 * CLIENT_RANDOM_SIZE) + 1 + (2 * SECRET_MAXLEN) + 1 + 1]; - - if(!keylog_file_fp) { + DEBUGASSERT(random_size >= CLIENT_RANDOM_SIZE); + if(random_size < CLIENT_RANDOM_SIZE) + return FALSE; + if(!keylog_file_fp) return FALSE; - } pos = strlen(label); if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { diff --git a/lib/vtls/keylog.h b/lib/vtls/keylog.h index 68ded4769d10..d489eb6360a1 100644 --- a/lib/vtls/keylog.h +++ b/lib/vtls/keylog.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) +#define KEYLOG_LABEL_MAXLEN CURL_CSTRLEN("CLIENT_HANDSHAKE_TRAFFIC_SECRET") #define CLIENT_RANDOM_SIZE 32 @@ -62,8 +62,9 @@ const char *Curl_tls_keylog_file_name(void); * Returns true iff the key log file is open and a valid entry was provided. */ bool Curl_tls_keylog_write(const char *label, - const unsigned char client_random[CLIENT_RANDOM_SIZE], - const unsigned char *secret, size_t secretlen); + const unsigned char *client_random, + size_t random_size, + const unsigned char *secret, size_t secretlen); /* * Appends a line to the key log file, ensure it is terminated by an LF. diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index e2822668f085..dbee473d7dbb 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -36,7 +36,7 @@ #include #if MBEDTLS_VERSION_NUMBER < 0x03020000 -#error "mbedTLS 3.2.0 or later required" +#error "mbedTLS 3.2.0 or greater required" #endif #include #include @@ -141,7 +141,7 @@ static int mbedtls_bio_cf_write(void *bio, result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &nwritten); CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %d, %zu", - blen, result, nwritten); + blen, (int)result, nwritten); if(result == CURLE_AGAIN) return MBEDTLS_ERR_SSL_WANT_WRITE; return result ? -1 : (int)nwritten; @@ -163,7 +163,7 @@ static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) result = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &nread); CURL_TRC_CF(data, cf, "mbedtls_bio_cf_in_read(len=%zu) -> %d, %zu", - blen, result, nread); + blen, (int)result, nread); if(result == CURLE_AGAIN) return MBEDTLS_ERR_SSL_WANT_READ; /* nread is never larger than int here */ @@ -179,10 +179,10 @@ static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) #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 -mbed_set_ssl_version_min_max(struct Curl_easy *data, - struct mbed_ssl_backend_data *backend, - struct ssl_primary_config *conn_config) +static CURLcode mbed_set_ssl_version_min_max( + struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) { mbedtls_ssl_protocol_version ver_min = #ifdef MBEDTLS_SSL_PROTO_TLS1_2 @@ -268,22 +268,22 @@ static uint16_t mbed_cipher_suite_walk_str(const char **str, const char **end) static const char ecjpake_suite[] = "TLS_ECJPAKE_WITH_AES_128_CCM_8"; if(!id) { - if((len == sizeof(ecjpake_suite) - 1) && + if((len == CURL_CSTRLEN(ecjpake_suite)) && curl_strnequal(ecjpake_suite, *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_get_str Curl_cipher_suite_get_str #define mbed_cipher_suite_walk_str Curl_cipher_suite_walk_str #endif -static CURLcode -mbed_set_selected_ciphers(struct Curl_easy *data, - struct mbed_ssl_backend_data *backend, - const char *ciphers12, - const char *ciphers13) +static CURLcode mbed_set_selected_ciphers( + struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + const char *ciphers12, + const char *ciphers13) { const char *ciphers = ciphers12; const int *supported; @@ -307,7 +307,7 @@ mbed_set_selected_ciphers(struct Curl_easy *data, /* 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) + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6)) continue; selected[count++] = id; @@ -360,7 +360,7 @@ mbed_set_selected_ciphers(struct Curl_easy *data, /* 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) + if(!strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6)) continue; /* No duplicates allowed (so selected cannot overflow) */ @@ -453,9 +453,11 @@ static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, mbed_extract_certinfo(data, crt); } + /* `verifypeer` and `verifyhost` are independent, so clear the flags of a + disabled check only. The name mismatch belongs to `verifyhost`. */ if(!conn_config->verifypeer) - *flags = 0; - else if(!conn_config->verifyhost) + *flags &= MBEDTLS_X509_BADCERT_CN_MISMATCH; + if(!conn_config->verifyhost) *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; if(*flags) { @@ -464,7 +466,8 @@ static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", *flags); failf(data, "mbedTLS: %s", buf); #else - failf(data, "mbedTLS: certificate verification error 0x%08x", *flags); + failf(data, "mbedTLS: certificate verification error 0x%08x", + (unsigned int)*flags); #endif } @@ -486,7 +489,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, const char * const ssl_capath = conn_config->CApath; #ifdef MBEDTLS_PEM_PARSE_C struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_cert_type = ssl_config->cert_type; + const char * const ssl_cert_type = ssl_config->primary.cert_type; #endif int ret = -1; char errorbuf[128]; @@ -529,7 +532,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error importing CA cert blob: (-0x%04X) %s", - -ret, errorbuf); + (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } } @@ -541,7 +544,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading CA cert file %s: (-0x%04X) %s", - ssl_cafile, -ret, errorbuf); + ssl_cafile, (unsigned int)-ret, errorbuf); return CURLE_SSL_CACERT_BADFILE; } #else @@ -557,7 +560,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading CA cert path %s: (-0x%04X) %s", - ssl_capath, -ret, errorbuf); + ssl_capath, (unsigned int)-ret, errorbuf); if(verifypeer) return CURLE_SSL_CACERT_BADFILE; @@ -581,7 +584,7 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; #ifdef MBEDTLS_PEM_PARSE_C - const char * const ssl_cert_type = ssl_config->cert_type; + const char * const ssl_cert_type = ssl_config->primary.cert_type; #endif int ret = -1; char errorbuf[128]; @@ -595,7 +598,7 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading client cert file %s: (-0x%04X) %s", - ssl_cert, -ret, errorbuf); + ssl_cert, (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } @@ -642,7 +645,7 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading client cert blob: (-0x%04X) %s", - -ret, errorbuf); + (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } } @@ -662,12 +665,12 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, mbedtls_pk_init(&backend->pk); - if(ssl_config->key || ssl_config->key_blob) { - if(ssl_config->key) { + if(ssl_config->primary.key || ssl_config->primary.key_blob) { + if(ssl_config->primary.key) { #ifdef MBEDTLS_FS_IO #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, - ssl_config->key_passwd); + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, + ssl_config->primary.key_passwd); if(ret == 0 && !(mbedtls_pk_can_do_psa(&backend->pk, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH), @@ -677,8 +680,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, - ssl_config->key_passwd, + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, + ssl_config->primary.key_passwd, mbedtls_ctr_drbg_random, &rng.drbg); if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || @@ -689,7 +692,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading private key %s: (-0x%04X) %s", - ssl_config->key, -ret, errorbuf); + ssl_config->primary.key, (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } #else @@ -698,12 +701,18 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, #endif } else { - const struct curl_blob *ssl_key_blob = ssl_config->key_blob; - const unsigned char *key_data = - (const unsigned char *)ssl_key_blob->data; - const char *passwd = ssl_config->key_passwd; + const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; + const char *passwd = ssl_config->primary.key_passwd; + /* Unfortunately, mbedtls_pk_parse_key() requires the data to be + null-terminated if the data is PEM encoded (even when provided the + exact length). */ + unsigned char *newblob = curlx_memdup0(ssl_key_blob->data, + ssl_key_blob->len); + if(!newblob) + return CURLE_OUT_OF_MEMORY; + #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0); if(ret == 0 && @@ -715,7 +724,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0, mbedtls_ctr_drbg_random, @@ -724,11 +733,12 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #endif + curlx_free(newblob); if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error parsing private key: (-0x%04X) %s", - -ret, errorbuf); + (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } } @@ -738,7 +748,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, } static CURLcode mbed_load_crl(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = @@ -757,7 +767,7 @@ static CURLcode mbed_load_crl(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading CRL file %s: (-0x%04X) %s", - ssl_crlfile, -ret, errorbuf); + ssl_crlfile, (unsigned int)-ret, errorbuf); return CURLE_SSL_CRL_BADFILE; } @@ -778,6 +788,38 @@ static CURLcode mbed_load_crl(struct Curl_cfilter *cf, return CURLE_OK; } +static bool mbed_apply_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_ssl_session *sc_session) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + + if(sc_session && sc_session->sdata && sc_session->sdata_len) { + mbedtls_ssl_session session; + int ret; + + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_session_load(&session, sc_session->sdata, + sc_session->sdata_len); + if(ret) { + failf(data, "SSL session error loading: -0x%x", (unsigned int)-ret); + } + else { + ret = mbedtls_ssl_set_session(&backend->ssl, &session); + if(ret) + failf(data, "SSL session error setting: -0x%x", (unsigned int)-ret); + } + mbedtls_ssl_session_free(&session); + if(!ret) { + infof(data, "SSL reusing session ID"); + return TRUE; + } + } + return FALSE; +} + static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -787,11 +829,12 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, 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); int ret; + bool session_applied = FALSE; CURLcode result; char errorbuf[128]; infof(data, "mbedTLS: Connecting to %s:%d", - connssl->peer.hostname, connssl->peer.port); + connssl->peer.origin->hostname, connssl->peer.origin->port); mbedtls_ssl_config_init(&backend->config); ret = mbedtls_ssl_config_defaults(&backend->config, @@ -852,7 +895,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: ssl_setup failed: " - "(-0x%04X) %s", -ret, errorbuf); + "(-0x%04X) %s", (unsigned int)-ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } @@ -891,8 +934,22 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, MBEDTLS_SSL_SESSION_TICKETS_DISABLED); #endif + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + struct Curl_ssl_session *scs = + Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + if(mbed_apply_session(cf, data, scs)) { + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + session_applied = TRUE; + } + } + } + /* Check if there is a cached ID we can/should use here! */ - if(Curl_ssl_scache_use(cf, data)) { + if(!session_applied && Curl_ssl_scache_use(cf, data)) { struct Curl_ssl_session *sc_session = NULL; CURLcode sresult = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key, &sc_session); @@ -903,12 +960,12 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, ret = mbedtls_ssl_session_load(&session, sc_session->sdata, sc_session->sdata_len); if(ret) { - failf(data, "SSL session error loading: -0x%x", -ret); + failf(data, "SSL session error loading: -0x%x", (unsigned int)-ret); } else { ret = mbedtls_ssl_set_session(&backend->ssl, &session); if(ret) - failf(data, "SSL session error setting: -0x%x", -ret); + failf(data, "SSL session error setting: -0x%x", (unsigned int)-ret); else infof(data, "SSL reusing session ID"); } @@ -926,13 +983,14 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif ); - if(ssl_config->key || ssl_config->key_blob) { + if(ssl_config->primary.key || ssl_config->primary.key_blob) { mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ? - connssl->peer.sni : connssl->peer.hostname)) { + connssl->peer.sni : + connssl->peer.origin->hostname)) { /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and the name to set in the SNI extension. Thus even if curl connects to a host specified as an IP address, this function must be used. */ @@ -965,7 +1023,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, result = (*data->set.ssl.fsslctx)(data, &backend->config, data->set.ssl.fsslctxp); if(result) - failf(data, "error signaled by ssl ctx callback"); + failf(data, "error signaled by SSL ctx callback"); } return result; @@ -1017,10 +1075,11 @@ static CURLcode mbed_connect_step2(struct Curl_cfilter *cf, #ifdef HAVE_PINNED_PUBKEY #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char * const pinnedpubkey = + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif #endif @@ -1046,7 +1105,7 @@ static CURLcode mbed_connect_step2(struct Curl_cfilter *cf, mbedtls_ssl_get_version_number(&backend->ssl)); mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "ssl_handshake returned: (-0x%04X) %s", - -ret, errorbuf); + (unsigned int)-ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } @@ -1158,7 +1217,7 @@ static CURLcode mbed_new_session(struct Curl_cfilter *cf, 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); + failf(data, "mbedtls_ssl_get_session returned -0x%x", (unsigned int)-ret); result = CURLE_SSL_CONNECT_ERROR; goto out; } @@ -1177,7 +1236,7 @@ static CURLcode mbed_new_session(struct Curl_cfilter *cf, ret = mbedtls_ssl_session_save(&session, sdata, slen, &slen); if(ret) { - failf(data, "failed to serialize session: -0x%x", -ret); + failf(data, "failed to serialize session: -0x%x", (unsigned int)-ret); goto out; } @@ -1187,13 +1246,22 @@ static CURLcode mbed_new_session(struct Curl_cfilter *cf, connssl->negotiated.alpn, 0, 0, &sc_session); sdata = NULL; /* call took ownership */ - if(!result) + if(!result && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) { + Curl_ssl_session_destroy(connssl->session); + result = Curl_ssl_session_dup(sc_session, &connssl->session); + } + + if(!result) { result = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key, sc_session); + sc_session = NULL; + } out: if(msession_alloced) mbedtls_ssl_session_free(&session); + Curl_ssl_session_destroy(sc_session); curlx_free(sdata); return result; } @@ -1229,7 +1297,7 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else { CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> -0x%04X", - len, -nwritten); + len, (unsigned int)-nwritten); switch(nwritten) { #ifdef MBEDTLS_SSL_PROTO_TLS1_3 case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: @@ -1253,7 +1321,7 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, } CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> %d, %zu", - len, result, *pnwritten); + len, (int)result, *pnwritten); return result; } @@ -1297,7 +1365,8 @@ static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; default: - CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", + (unsigned int)-ret); result = CURLE_RECV_ERROR; goto out; } @@ -1338,7 +1407,8 @@ static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_SEND; } else { - CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", + (unsigned int)-ret); result = CURLE_RECV_ERROR; } @@ -1388,7 +1458,7 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, else { char errorbuf[128]; CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X", - buffersize, -nread); + buffersize, (unsigned int)-nread); switch(nread) { #ifdef MBEDTLS_SSL_SESSION_TICKETS case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: @@ -1408,7 +1478,8 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, break; default: mbedtls_strerror(nread, errorbuf, sizeof(errorbuf)); - failf(data, "ssl_read returned: (-0x%04X) %s", -nread, errorbuf); + failf(data, "ssl_read returned: (-0x%04X) %s", (unsigned int)-nread, + errorbuf); result = CURLE_RECV_ERROR; break; } @@ -1456,19 +1527,19 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { result = mbed_connect_step1(cf, data); if(result) return result; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { result = mbed_connect_step2(cf, data); if(result) return result; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { /* For tls1.3 we get notified about new sessions */ struct ssl_connect_data *ctx = cf->ctx; struct mbed_ssl_backend_data *backend = @@ -1483,7 +1554,7 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_done; } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { connssl->state = ssl_connection_complete; *done = TRUE; } @@ -1555,7 +1626,7 @@ static CURLcode mbedtls_sha256sum(const unsigned char *input, unsigned char *sha256sum, size_t sha256len) { -#if defined(PSA_WANT_ALG_SHA_256) && PSA_WANT_ALG_SHA_256 /* mbedTLS 4+ */ +#if defined(PSA_WANT_ALG_SHA_256) && PSA_WANT_ALG_SHA_256 psa_status_t status; size_t sha256len_actual; status = psa_hash_compute(PSA_ALG_SHA_256, input, inputlen, diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index f3dc8c93bfaf..17ed1485c56e 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -31,13 +31,11 @@ #include "urldata.h" #include "curl_trc.h" -#include "httpsrr.h" #include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ +#include "url.h" /* for the SSL config check function */ #include "curlx/inet_pton.h" #include "vtls/openssl.h" #include "connect.h" -#include "cf-dns.h" #include "progress.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" @@ -51,6 +49,8 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" #include "curlx/strdup.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/apple.h" #ifdef USE_ECH #include "curlx/base64.h" @@ -87,11 +87,11 @@ #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" +# error "LibreSSL 2.9.1 or greater required" # endif #elif !defined(HAVE_BORINGSSL_LIKE) # ifndef HAVE_OPENSSL3 /* 2021-09-07 */ -# error "OpenSSL 3.0.0 or later required" +# error "OpenSSL 3.0.0 or greater required" # endif #endif @@ -127,9 +127,9 @@ #endif /* Whether SSL_CTX_set_ciphersuites is available. - * OpenSSL: supported since 1.1.1 (commit a53b5be6a05) * BoringSSL: no * LibreSSL: supported since 3.4.1 (released 2021-10-14) + * OpenSSL: supported since 1.1.1 (commit a53b5be6a05) */ #if (!defined(LIBRESSL_VERSION_NUMBER) || \ (defined(LIBRESSL_VERSION_NUMBER) && \ @@ -142,9 +142,9 @@ #endif /* 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 + * OpenSSL: supported since 1.0.2 (commit 0b362de5f575) */ #ifndef LIBRESSL_VERSION_NUMBER #define HAVE_SSL_CTX_SET1_SIGALGS @@ -152,10 +152,10 @@ #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(OPENSSL_IS_BORINGSSL) +#define OSSL_PACKAGE "BoringSSL" #elif defined(USE_NGTCP2) && defined(USE_NGHTTP3) && \ !defined(OPENSSL_QUIC_API2) #define OSSL_PACKAGE "quictls" @@ -248,7 +248,7 @@ static CURLcode X509V3_ext(struct Curl_easy *data, if(asn1_object_dump(obj, namebuf, sizeof(namebuf))) /* make sure the name is null-terminated */ - namebuf[sizeof(namebuf) - 1] = 0; + namebuf[CURL_CSTRLEN(namebuf)] = 0; if(!X509V3_EXT_print(bio_out, ext, 0, 0)) ASN1_STRING_print(bio_out, @@ -415,7 +415,7 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(result) break; - BIO_printf(mem, "%lx", X509_get_version(x)); + BIO_printf(mem, "%lx", (unsigned long)X509_get_version(x)); result = push_certinfo(data, mem, "Version", i); if(result) break; @@ -581,7 +581,7 @@ static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) (const uint8_t *)buf, (size_t)blen, FALSE, &nwritten); CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, %zu", - blen, result, nwritten); + blen, (int)result, nwritten); BIO_clear_retry_flags(bio); octx->io_result = result; if(result) { @@ -610,7 +610,7 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) result = Curl_conn_cf_recv(cf->next, data, buf, (size_t)blen, &nread); CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, %zu", - blen, result, nread); + blen, (int)result, nread); BIO_clear_retry_flags(bio); octx->io_result = result; if(result) { @@ -700,6 +700,7 @@ static void ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) *keylog_done = TRUE; Curl_tls_keylog_write("CLIENT_RANDOM", client_random, + sizeof(client_random), master_key, master_key_length); } #endif /* !HAVE_KEYLOG_CALLBACK */ @@ -860,7 +861,7 @@ static int ssl_ui_reader(UI *ui, UI_STRING *uis) default: break; } - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return UI_method_get_reader(UI_OpenSSL())(ui, uis); } /* @@ -879,7 +880,7 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) default: break; } - return (UI_method_get_writer(UI_OpenSSL()))(ui, uis); + return UI_method_get_writer(UI_OpenSSL())(ui, uis); } /* @@ -904,7 +905,7 @@ static int use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, X509 *x = NULL; /* the typecast of blob->len is fine since it is guaranteed to never be larger than CURL_MAX_INPUT_LENGTH */ - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -938,7 +939,7 @@ static int use_privatekey_blob(SSL_CTX *ctx, const struct curl_blob *blob, { int ret = 0; EVP_PKEY *pkey = NULL; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -966,7 +967,7 @@ static int use_certificate_chain_blob(SSL_CTX *ctx, { int ret = 0; X509 *x = NULL; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + BIO *in = BIO_new_mem_buf(blob->data, (int)blob->len); if(!in) return CURLE_OUT_OF_MEMORY; @@ -1176,7 +1177,7 @@ static int engineload(struct Curl_easy *data, } if(data->state.engine) { - const char *cmd_name = "LOAD_CERT_CTRL"; + static const char cmd_name[] = "LOAD_CERT_CTRL"; struct { const char *cert_id; X509 *cert; @@ -1188,13 +1189,13 @@ static int engineload(struct Curl_easy *data, /* Does the engine supports LOAD_CERT_CTRL ? */ if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, 0, CURL_UNCONST(cmd_name), NULL)) { - failf(data, "ssl engine does not support loading certificates"); + failf(data, "SSL engine does not support loading certificates"); return 0; } /* Load the certificate from the engine */ if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name, 0, ¶ms, NULL, 1)) { - failf(data, "ssl engine cannot load client cert with id '%s' [%s]", + failf(data, "SSL engine cannot load client cert with id '%s' [%s]", cert_file, ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer))); @@ -1202,7 +1203,7 @@ static int engineload(struct Curl_easy *data, } if(!params.cert) { - failf(data, "ssl engine did not initialized the certificate properly."); + failf(data, "SSL engine did not initialize the certificate properly."); return 0; } @@ -1318,7 +1319,7 @@ static int pkcs12load(struct Curl_easy *data, int cert_done = 0; STACK_OF(X509) *ca = NULL; if(cert_blob) { - cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len)); + cert_bio = BIO_new_mem_buf(cert_blob->data, (int)cert_blob->len); if(!cert_bio) { failf(data, "BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s", ossl_strerror(ERR_get_error(), error_buffer, @@ -1671,7 +1672,7 @@ static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name) char buf[256]; ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s': %s", + failf(data, "Failed to initialize SSL Engine '%s': %s", name, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); result = CURLE_SSL_ENGINE_INITFAILED; e = NULL; @@ -1898,7 +1899,7 @@ static CURLcode ossl_shutdown(struct Curl_cfilter *cf, *done = TRUE; goto out; } - if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, rc)) { + if(SSL_get_error(octx->ssl, rc) == SSL_ERROR_WANT_WRITE) { CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; @@ -1925,7 +1926,7 @@ static CURLcode ossl_shutdown(struct Curl_cfilter *cf, break; case SSL_ERROR_NONE: /* did not get anything */ case SSL_ERROR_WANT_READ: - /* SSL has send its notify and now wants to read the reply + /* SSL has sent 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; @@ -2024,7 +2025,7 @@ static void ossl_close_all(struct Curl_easy *data) in the certificate and must exactly match the IP in the URI. This function is now used from ngtcp2 (QUIC) as well. -*/ + */ static CURLcode ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, struct ssl_peer *peer, @@ -2042,19 +2043,19 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ - size_t hostlen = strlen(peer->hostname); + size_t hostlen = strlen(peer->origin->hostname); (void)conn; switch(peer->type) { case CURL_SSL_PEER_IPV4: - if(!curlx_inet_pton(AF_INET, peer->hostname, &addr)) + if(!curlx_inet_pton(AF_INET, peer->origin->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)) + if(!curlx_inet_pton(AF_INET6, peer->origin->hostname, &addr)) return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in6_addr); @@ -2065,7 +2066,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, break; default: DEBUGASSERT(0); - failf(data, "unexpected ssl peer type: %d", peer->type); + failf(data, "unexpected SSL peer type: %d", (int)peer->type); return CURLE_PEER_FAILED_VERIFICATION; } @@ -2103,22 +2104,12 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, switch(target) { case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be - assumed that the data returned by ASN1_STRING_data() is null - terminated or does not contain embedded nulls.", but also that - "The actual format of the data depends on the actual string - type itself: for example for an IA5String the data is ASCII" - - It has been however verified that in 0.9.6 and 0.9.7, IA5String - is always null-terminated. - */ - if((altlen == strlen(altptr)) && - /* if this is not true, there was an embedded zero in the name - string and we cannot match it. */ - Curl_cert_hostcheck(altptr, altlen, peer->hostname, hostlen)) { + if(!memchr(altptr, '\0', altlen) && + Curl_cert_hostcheck(altptr, altlen, + peer->origin->hostname, hostlen)) { matched = TRUE; infof(data, " subjectAltName: \"%s\" matches cert's \"%.*s\"", - peer->dispname, (int)altlen, altptr); + peer->origin->user_hostname, (int)altlen, altptr); } break; @@ -2128,7 +2119,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) { matched = TRUE; infof(data, " subjectAltName: \"%s\" matches cert's IP address!", - peer->dispname); + peer->origin->user_hostname); } break; } @@ -2143,10 +2134,11 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, else if(dNSName || iPAddress) { 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); + "IPv4 address" : "IPv6 address"; + infof(data, " subjectAltName does not match %s %s", tname, + peer->origin->user_hostname); failf(data, "SSL: no alternative certificate subject name matches " - "target %s '%s'", tname, peer->dispname); + "target %s '%s'", tname, peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { @@ -2190,7 +2182,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, 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 + /* there was a null-terminator before the end of string, this cannot match and we return failure! */ failf(data, "SSL: illegal cert name field"); result = CURLE_PEER_FAILED_VERIFICATION; @@ -2206,9 +2198,9 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, result = CURLE_PEER_FAILED_VERIFICATION; } else if(!Curl_cert_hostcheck((const char *)cn, cnlen, - peer->hostname, hostlen)) { + peer->origin->hostname, hostlen)) { failf(data, "SSL: certificate subject name '%s' does not match " - "target hostname '%s'", cn, peer->dispname); + "target hostname '%s'", cn, peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { @@ -2492,7 +2484,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, verstr = "TLSv1.3"; break; default: - curl_msnprintf(unknown, sizeof(unknown), "(%x)", ssl_ver); + curl_msnprintf(unknown, sizeof(unknown), "(%x)", (unsigned int)ssl_ver); verstr = unknown; break; } @@ -2628,37 +2620,31 @@ static CURLcode ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, return CURLE_OK; } -#ifdef HAVE_BORINGSSL_LIKE -typedef uint32_t ctx_option_t; -#elif defined(HAVE_OPENSSL3) -typedef uint64_t ctx_option_t; -#elif defined(LIBRESSL_VERSION_NUMBER) -typedef long ctx_option_t; -#else -typedef unsigned long ctx_option_t; -#endif - CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ossl_ctx *octx, const char *ssl_peer_key, SSL_SESSION *session, - int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **psession) { + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *der_session_buf = NULL; unsigned char *qtp_clone = NULL; CURLcode result = CURLE_OK; + if(psession) + *psession = NULL; if(!cf || !data) goto out; if(Curl_ssl_scache_use(cf, data)) { - struct Curl_ssl_session *sc_session = NULL; size_t der_session_size; unsigned char *der_session_ptr; size_t earlydata_max = 0; + int ietf_tls_id = SSL_version(octx->ssl); der_session_size = i2d_SSL_SESSION(session, NULL); if(der_session_size == 0) { @@ -2696,14 +2682,29 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, earlydata_max, qtp_clone, quic_tp_len, &sc_session); der_session_buf = NULL; /* took ownership of sdata */ +#ifdef USE_APPLE_SECTRUST + if(!result) + sc_session->sectrust_verified = octx->sectrust_verified; +#endif + if(!result && psession && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) { + result = Curl_ssl_session_dup(sc_session, &sc_dup); + } if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; } } out: curlx_free(der_session_buf); + if(!result && psession) { + *psession = sc_dup; + sc_dup = NULL; + } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } @@ -2716,8 +2717,15 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) 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); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct Curl_ssl_session *session = NULL; + Curl_ossl_add_session(cf, data, octx, connssl->peer.scache_key, + ssl_sessionid, connssl->negotiated.alpn, NULL, + 0, &session); + if(session) { /* remember current TLS session */ + Curl_ssl_session_destroy(connssl->session); + connssl->session = session; + } } return 0; } @@ -2948,7 +2956,7 @@ static CURLcode ossl_windows_load_anchors(struct Curl_cfilter *cf, https://stackoverflow.com/questions/9507184/ https://github.com/d3x0r/SACK/blob/ff15424d3c581b86d40f818532e5a400c516d39d/src/netlib/ssl_layer.c#L1410 https://datatracker.ietf.org/doc/html/rfc5280 */ - const char *win_stores[] = { + static const char * const win_stores[] = { "ROOT", /* Trusted Root Certification Authorities */ "CA" /* Intermediate Certification Authorities */ }; @@ -2981,7 +2989,6 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, 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; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -2990,7 +2997,7 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, bool have_native_check = FALSE; octx->store_is_empty = TRUE; - if(ssl_config->native_ca_store) { + if(conn_config->native_ca_store) { #ifdef USE_WIN32_CRYPTO bool added = FALSE; result = ossl_windows_load_anchors(cf, data, store, &added); @@ -3010,7 +3017,7 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, result = load_cacert_from_memory(store, conn_config->ca_info_blob); if(result) { failf(data, "error adding trust anchors from certificate blob: %d", - result); + (int)result); return result; } infof(data, " CA Blob from configuration"); @@ -3131,7 +3138,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, OpenSSL do alternate chain checking by default but we do not know how to determine that in a reliable manner. https://web.archive.org/web/20190422050538/rt.openssl.org/Ticket/Display.html?id=3621 - */ + */ x509flags |= X509_V_FLAG_TRUSTED_FIRST; if(!ssl_config->no_partialchain && !ssl_crlfile) { @@ -3141,8 +3148,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, instead of needing the whole chain. Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we - cannot do partial chains with a CRL check. - */ + cannot do partial chains with a CRL check. */ x509flags |= X509_V_FLAG_PARTIAL_CHAIN; } (void)X509_STORE_set_flags(store, x509flags); @@ -3164,7 +3170,7 @@ struct ossl_x509_share { static void oss_x509_share_free(void *key, size_t key_len, void *p) { struct ossl_x509_share *share = p; - DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY) - 1)); + DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -3215,7 +3221,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, *pempty = TRUE; share = multi ? Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1) : NULL; + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)) : NULL; if(share && share->store && !ossl_cached_x509_store_expired(data, share) && !ossl_cached_x509_store_different(cf, data, share)) { @@ -3240,7 +3246,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, return; share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -3248,7 +3254,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_OSSL_X509_KEY), - sizeof(MPROTO_OSSL_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_OSSL_X509_KEY), share, oss_x509_share_free)) { curlx_free(share); return; @@ -3299,7 +3305,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store; + !conn_config->native_ca_store; ERR_set_mark(); @@ -3322,15 +3328,90 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, return result; } -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) +static bool ossl_apply_session( + struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs) { struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + 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 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)) { + VERBOSE(char error_buffer[256]); + infof(data, "SSL: SSL_set_session not accepted, " + "continuing without: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + else { + if(conn_cfg->verifypeer && + (SSL_get_verify_result(octx->ssl) != X509_V_OK) +#ifdef USE_APPLE_SECTRUST + /* if sectrust is used and verified the session before */ + && (!conn_cfg->native_ca_store || !scs->sectrust_verified) +#endif + ) { + /* Session was from unverified connection, cannot reuse here */ + SSL_set_session(octx->ssl, NULL); + infof(data, "SSL session not peer verified, not reusing"); + } + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + octx->reused_session = TRUE; +#ifdef USE_APPLE_SECTRUST + octx->sectrust_session = scs->sectrust_verified; +#endif + infof(data, "SSL verify result: %lx", + (unsigned long)SSL_get_verify_result(octx->ssl)); +#ifdef HAVE_OPENSSL_EARLYDATA + if(ssl_config->earlydata && scs->alpn && + SSL_SESSION_get_max_early_data(ssl_session) && + !cf->conn->bits.connect_only && + (SSL_version(octx->ssl) == TLS1_3_VERSION)) { + bool do_early_data = FALSE; + if(sess_reuse_cb) + (void)sess_reuse_cb(cf, data, alpns, scs, &do_early_data); + 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)alpns; + (void)ssl_config; + (void)sess_reuse_cb; +#endif + } + } + SSL_SESSION_free(ssl_session); + } + else { + infof(data, "SSL session not accepted by OpenSSL, continuing without"); + } + return (bool)octx->reused_session; +} + +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) +{ struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); struct alpn_spec alpns; CURLcode result; @@ -3338,70 +3419,26 @@ ossl_init_session_and_alpns(struct ossl_ctx *octx, Curl_alpn_copy(&alpns, alpns_requested); octx->reused_session = FALSE; - if(Curl_ssl_scache_use(cf, data) && !conn_cfg->verifystatus) { + + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + struct Curl_ssl_session *scs = + Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + if(ossl_apply_session(octx, cf, data, &alpns, sess_reuse_cb, scs)) + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + } + } + + if(!octx->reused_session && + Curl_ssl_scache_use(cf, data) && !conn_cfg->verifystatus) { 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 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)) { - VERBOSE(char error_buffer[256]); - infof(data, "SSL: SSL_set_session not accepted, " - "continuing without: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - } - else { - if(conn_cfg->verifypeer && - (SSL_get_verify_result(octx->ssl) != X509_V_OK)) { - /* Session was from unverified connection, cannot reuse here */ - SSL_set_session(octx->ssl, NULL); - infof(data, "SSL session not peer verified, not reusing"); - } - else { - infof(data, "SSL reusing session with ALPN '%s'", - scs->alpn ? scs->alpn : "-"); - octx->reused_session = TRUE; - infof(data, "SSL verify result: %lx", - SSL_get_verify_result(octx->ssl)); -#ifdef HAVE_OPENSSL_EARLYDATA - if(ssl_config->earlydata && scs->alpn && - SSL_SESSION_get_max_early_data(ssl_session) && - !cf->conn->bits.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) { - SSL_SESSION_free(ssl_session); - 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)ssl_config; - (void)sess_reuse_cb; -#endif - } - } - SSL_SESSION_free(ssl_session); - } - else { - infof(data, "SSL session not accepted by OpenSSL, continuing without"); - } + (void)ossl_apply_session(octx, cf, data, &alpns, sess_reuse_cb, scs); } Curl_ssl_scache_return(cf, data, peer->scache_key, scs); } @@ -3428,9 +3465,9 @@ bool Curl_ossl_need_httpsrr(struct Curl_easy *data) { if(!CURLECH_ENABLED(data)) return FALSE; - if((data->set.tls_ech & CURLECH_GREASE) || - (data->set.tls_ech & CURLECH_CLA_CFG)) - return FALSE; + if((data->set.tls_ech == CURLECH_GREASE) || + CURL_EASY_STR(data, STRING_ECH_CONFIG)) + return FALSE; return TRUE; } @@ -3439,16 +3476,13 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, 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]; + const char *outername = CURL_EASY_STR(data, STRING_ECH_PUBLIC); int trying_ech_now = 0; - CURLcode result = CURLE_OK; if(!CURLECH_ENABLED(data)) return CURLE_OK; - if(data->set.tls_ech & CURLECH_GREASE) { + if(data->set.tls_ech == CURLECH_GREASE) { infof(data, "ECH: will GREASE ClientHello"); #ifdef HAVE_BORINGSSL_LIKE SSL_set_enable_ech_grease(octx->ssl, 1); @@ -3456,10 +3490,13 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); #endif } - else if(data->set.tls_ech & CURLECH_CLA_CFG) { + else if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { #ifdef HAVE_BORINGSSL_LIKE /* have to do base64 decode here for BoringSSL */ - const char *b64 = data->set.str[STRING_ECH_CONFIG]; + const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); + uint8_t *ech_config; + size_t ech_config_len = 0; + CURLcode result; if(!b64) { infof(data, "ECH: ECHConfig from command line empty"); @@ -3469,12 +3506,12 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, 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) + 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) { + if(data->set.tls_ech == CURLECH_HARD) { curlx_free(ech_config); return CURLE_SSL_CONNECT_ERROR; } @@ -3482,15 +3519,18 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, curlx_free(ech_config); trying_ech_now = 1; #else - ech_config = (unsigned char *)data->set.str[STRING_ECH_CONFIG]; + const char *ech_config = CURL_EASY_STR(data, STRING_ECH_CONFIG); + size_t ech_config_len = 0; 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) { + ech_config_len = strlen(ech_config); + if(SSL_set1_ech_config_list(octx->ssl, + (const uint8_t *)ech_config, + ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); - if(data->set.tls_ech & CURLECH_HARD) + if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else @@ -3500,7 +3540,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, } else { const struct Curl_https_rrinfo *rinfo = - Curl_conn_dns_get_https(data, cf->sockindex); + Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); if(rinfo && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; @@ -3509,7 +3549,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, infof(data, "ECH: ECHConfig from HTTPS RR"); if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { infof(data, "ECH: SSL_set1_ech_config_list failed"); - if(data->set.tls_ech & CURLECH_HARD) + if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -3519,7 +3559,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech & CURLECH_HARD) + if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } } @@ -3531,13 +3571,14 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, } #else if(trying_ech_now && outername) { + int ret; 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); + peer->origin->hostname ? peer->origin->hostname : "NULL", outername); + ret = SSL_ech_set1_server_names(octx->ssl, + peer->origin->hostname, outername, + 0 /* do send outer */); + if(ret != 1) { + infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", ret); return CURLE_SSL_CONNECT_ERROR; } } @@ -3675,7 +3716,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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 char * const ssl_cert_type = ssl_config->primary.cert_type; unsigned int ssl_version_min; char error_buffer[256]; @@ -3704,6 +3745,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); return CURLE_OUT_OF_MEMORY; } +#ifdef OPENSSL_HAS_PROVIDERS + if(data->state.libctx) + /* forbid connection reuse with provider/engine use */ + connclose(data->conn); +#endif if(cb_setup) { result = cb_setup(cf, data, cb_user_data); @@ -3711,16 +3757,19 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, return result; } - if(data->set.fdebug && data->set.verbose) { - /* the SSL trace callback is only used for verbose logging */ + if(data->set.fdebug && data->set.verbose && + (peer->transport != TRNSPRT_QUIC)) { + /* the SSL trace callback is only used for verbose logging; + * QUIC connections use a different TLS record format that + * ossl_trace cannot handle */ SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace); SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf); } /* 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 - all the work-arounds and that "It is usually safe to use SSL_OP_ALL to + SSL-implementations. SSL_CTX_set_options() is used to enable those + workarounds. The man page for this option states that SSL_OP_ALL enables + all the workarounds 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." @@ -3745,13 +3794,12 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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 an SSL 3.0/TLS 1.0 CBC vulnerability: + OpenSSL added a workaround 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 + In 0.9.6e they added a bit to SSL_OP_ALL that _disables_ that workaround 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. - */ + workarounds. In order to keep the secure workaround, the + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set. */ ctx_options = SSL_OP_ALL | SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION; @@ -3759,7 +3807,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; /* unless the user explicitly asks to allow the protocol vulnerability we - use the work-around */ + use the workaround */ if(!ssl_config->enable_beast) ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; @@ -3839,8 +3887,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, if(ssl_cert || ssl_cert_blob || ssl_cert_type) { result = client_cert(data, octx->ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); + ssl_config->primary.key, ssl_config->primary.key_blob, + ssl_config->primary.key_type, + ssl_config->primary.key_passwd); if(result) /* failf() is already done in client_cert() */ return result; @@ -3881,31 +3930,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } #endif -#if defined(HAVE_OPENSSL_SRP) && defined(USE_TLS_SRP) - if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) { - char * const ssl_username = ssl_config->primary.username; - char * const ssl_password = ssl_config->primary.password; - infof(data, "Using TLS-SRP username: %s", ssl_username); - - 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(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(octx->ssl_ctx, "SRP")) { - failf(data, "failed setting SRP cipher list"); - return CURLE_SSL_CIPHER; - } - } - } -#endif /* HAVE_OPENSSL_SRP && USE_TLS_SRP */ - /* OpenSSL always tries to verify the peer. By setting the failure mode * to NONE, we allow the connect to complete, regardless of the outcome. * We then explicitly check the result and may try alternatives like @@ -3932,6 +3956,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { + struct Curl_mapi_guard guard; /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. * See: #11800 */ @@ -3941,12 +3966,12 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, return result; octx->x509_store_setup = TRUE; } - Curl_set_in_callback(data, TRUE); + CURL_CBAPI_START(&guard, data, easy_fsslctx); result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, FALSE); + CURL_CBAPI_END(&guard); if(result) { - failf(data, "error signaled by ssl ctx callback"); + failf(data, "error signaled by SSL ctx callback"); return result; } } @@ -4007,8 +4032,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, BIO *bio; CURLcode result; - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_1); DEBUGASSERT(octx); + DEBUGASSERT(connssl->peer.origin); result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, connssl->alpn, NULL, NULL, @@ -4121,7 +4147,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_2); DEBUGASSERT(octx); connssl->io_need = CURL_SSL_IO_NEED_NONE; @@ -4153,25 +4179,25 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, 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) { + if(detail == SSL_ERROR_WANT_READ) { 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) { + if(detail == SSL_ERROR_WANT_WRITE) { 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) { + if(detail == SSL_ERROR_WANT_ASYNC) { CURL_TRC_CF(data, cf, "SSL_connect() -> want async"); connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_AGAIN; } #endif #ifdef SSL_ERROR_WANT_RETRY_VERIFY - if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + if(detail == SSL_ERROR_WANT_RETRY_VERIFY) { CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify"); Curl_xfer_pause_recv(data, TRUE); return CURLE_AGAIN; @@ -4217,7 +4243,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, } #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 */ + OpenSSL version above v1.1.1, not AWS-LC, BoringSSL, or LibreSSL */ else if((lib == ERR_LIB_SSL) && (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { /* If client certificate is required, communicate the @@ -4263,7 +4289,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, curlx_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->peer.hostname, connssl->peer.port); + connssl->peer.origin->hostname, connssl->peer.origin->port); } return result; @@ -4310,7 +4336,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); if(!conn_config->verifypeer && !conn_config->verifyhost && - inner && !strcmp(inner, connssl->peer.hostname)) { + inner && !strcmp(inner, connssl->peer.origin->hostname)) { VERBOSE(status = "bad name (tolerated without peer verification)"); rv = SSL_ECH_STATUS_SUCCESS; } @@ -4333,7 +4359,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, /* 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)) { + if(rv != SSL_ECH_STATUS_SUCCESS && (data->set.tls_ech == CURLECH_HARD)) { infof(data, "ECH: ech-hard failed"); return CURLE_SSL_CONNECT_ERROR; } @@ -4560,21 +4586,24 @@ static CURLcode ossl_check_issuer(struct Curl_cfilter *cf, return result; } +static const char *pinned(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + return +#ifndef CURL_DISABLE_PROXY + Curl_ssl_cf_is_proxy(cf) ? + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : +#endif + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); +} + static CURLcode ossl_check_pinned_key(struct Curl_cfilter *cf, struct Curl_easy *data, X509 *server_cert) { - const char *ptr; CURLcode result = CURLE_OK; - - (void)cf; -#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 + const char *ptr = pinned(cf, data); if(ptr) { result = ossl_pkp_pin_peer_pubkey(data, server_cert, ptr); if(result) @@ -4640,6 +4669,7 @@ static CURLcode ossl_infof_cert(struct Curl_cfilter *cf, struct ossl_certs_ctx { STACK_OF(X509) *sk; size_t num_certs; + unsigned char *last_der; }; static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, @@ -4653,6 +4683,9 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, X509 *cert; int der_len; + OPENSSL_free(chain->last_der); + chain->last_der = NULL; + (void)cf; (void)data; *pder_len = 0; @@ -4666,6 +4699,7 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, der_len = i2d_X509(cert, pder); if(der_len < 0) return CURLE_FAILED_INIT; + chain->last_der = *pder; *pder_len = (size_t)der_len; return CURLE_OK; } @@ -4673,13 +4707,13 @@ static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf, static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, struct Curl_easy *data, struct ossl_ctx *octx, - struct ssl_peer *peer, - bool *pverified) + struct ssl_peer *peer) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ossl_certs_ctx chain; CURLcode result; + octx->sectrust_verified = FALSE; memset(&chain, 0, sizeof(chain)); chain.sk = SSL_get_peer_cert_chain(octx->ssl); chain.num_certs = chain.sk ? sk_X509_num(chain.sk) : 0; @@ -4691,8 +4725,11 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, result = CURLE_PEER_FAILED_VERIFICATION; } else { - /* when session was reused, there is no peer cert chain */ - *pverified = FALSE; + /* When session was reused, there is no peer cert chain. + * We trust it if it came from a SecTrust verified TLS. */ + CURL_TRC_CF(data, cf, "session reused, sectrust_session=%d", + octx->sectrust_session); + octx->sectrust_verified = (bool)octx->sectrust_session; return CURLE_OK; } } @@ -4708,23 +4745,26 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, ocsp_len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &ocsp_data); /* SSL_get_tlsext_status_ocsp_resp() returns the length of the OCSP - response data or -1 if there is no OCSP response data. */ - if(ocsp_len < 0) { + response data or -1 if there is no OCSP response data. + AWS-LC breaks the API and returns 0 when there is no data. */ + if(ocsp_len <= 0) { ocsp_len = 0; /* no data available */ ocsp_missing = TRUE; } result = Curl_vtls_apple_verify(cf, data, peer, chain.num_certs, ossl_chain_get_der, &chain, ocsp_data, ocsp_len); + OPENSSL_free(chain.last_der); + chain.last_der = NULL; if(!result && ocsp_missing && conn_config->verifystatus && !octx->reused_session) { /* verified, but OCSP stapling is required and server sent none */ - *pverified = TRUE; + octx->sectrust_verified = TRUE; failf(data, "No OCSP response received"); return CURLE_SSL_INVALIDCERTSTATUS; } } - *pverified = !result; + octx->sectrust_verified = !result; return result; } #endif /* USE_APPLE_SECTRUST */ @@ -4741,9 +4781,6 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, long ossl_verify; X509 *server_cert; bool verified = FALSE; -#if !defined(OPENSSL_NO_OCSP) && defined(USE_APPLE_SECTRUST) - bool sectrust_verified = FALSE; -#endif if(data->set.ssl.certinfo && !octx->reused_session) { /* asked to gather certificate info. Reused sessions do not have cert @@ -4756,7 +4793,8 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, server_cert = SSL_get1_peer_certificate(octx->ssl); if(!server_cert) { /* no verification at all, this maybe acceptable */ - if(!(conn_config->verifypeer || conn_config->verifyhost)) + if(!(conn_config->verifypeer || conn_config->verifyhost) && + !pinned(cf, data)) goto out; failf(data, "SSL: could not get peer certificate"); @@ -4796,26 +4834,24 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, ossl_verify = SSL_get_verify_result(octx->ssl); ssl_config->certverifyresult = ossl_verify; - infof(data, "OpenSSL verify result: %lx", ossl_verify); + infof(data, "OpenSSL verify result: %lx", (unsigned long)ossl_verify); verified = (ossl_verify == X509_V_OK); if(verified) infof(data, "SSL certificate verified via OpenSSL."); #ifdef USE_APPLE_SECTRUST - if(!verified && conn_config->verifypeer && ssl_config->native_ca_store) { + if(!verified && conn_config->verifypeer && conn_config->native_ca_store) { /* we verify using Apple SecTrust *unless* OpenSSL already verified. * This may happen if the application intercepted the OpenSSL callback * and installed its own. */ - result = ossl_apple_verify(cf, data, octx, peer, &verified); + result = ossl_apple_verify(cf, data, octx, peer); if(result && (result != CURLE_PEER_FAILED_VERIFICATION)) goto out; /* unexpected error */ - if(verified) { + if(octx->sectrust_verified) { infof(data, "SSL certificate verified via Apple SecTrust."); ssl_config->certverifyresult = X509_V_OK; -#ifndef OPENSSL_NO_OCSP - sectrust_verified = TRUE; -#endif + verified = TRUE; } } #endif @@ -4834,9 +4870,9 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, #ifndef OPENSSL_NO_OCSP if(conn_config->verifystatus && #ifdef USE_APPLE_SECTRUST - !sectrust_verified && /* already verified via apple sectrust, cannot - * verifystate via OpenSSL in that case as it - * does not have the trust anchors */ + !octx->sectrust_verified && /* already verified via sectrust, cannot + * verifystate via OpenSSL in that case as it + * does not have the trust anchors */ #endif !octx->reused_session) { /* do not do this after Session ID reuse */ @@ -4864,7 +4900,7 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_3); /* * We check certificates to authenticate the server; otherwise we risk @@ -4976,9 +5012,10 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { if(Curl_ossl_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + !Curl_conn_dns_resolved_https(data, cf->sockindex, + connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; } @@ -4988,7 +5025,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, goto out; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { CURL_TRC_CF(data, cf, "ossl_connect, step2"); #ifdef HAVE_OPENSSL_EARLYDATA if(connssl->earlydata_state == ssl_earlydata_await) { @@ -5009,7 +5046,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, goto out; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { CURL_TRC_CF(data, cf, "ossl_connect, step3"); result = ossl_connect_step3(cf, data); if(result) @@ -5027,7 +5064,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, #endif } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { CURL_TRC_CF(data, cf, "ossl_connect, done"); connssl->state = ssl_connection_complete; } @@ -5256,12 +5293,12 @@ static CURLcode ossl_recv(struct Curl_cfilter *cf, connssl->input_pending = FALSE; } CURL_TRC_CF(data, cf, "ossl_recv(len=%zu) -> %d, %zu (in_pending=%d)", - buffersize, result, *pnread, connssl->input_pending); + buffersize, (int)result, *pnread, connssl->input_pending); return result; } static CURLcode ossl_get_channel_binding(struct Curl_easy *data, - int sockindex, + int8_t sockindex, struct dynbuf *binding) { X509 *cert; @@ -5272,7 +5309,7 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, unsigned int length; unsigned char buf[EVP_MAX_MD_SIZE]; - const char prefix[] = "tls-server-end-point:"; + static const char prefix[] = "tls-server-end-point:"; struct connectdata *conn = data->conn; struct Curl_cfilter *cf = conn->cfilter[sockindex]; struct ossl_ctx *octx = NULL; @@ -5394,7 +5431,7 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, } /* Append "tls-server-end-point:" */ - result = curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1); + result = curlx_dyn_addn(binding, prefix, CURL_CSTRLEN(prefix)); if(result) goto out; @@ -5412,9 +5449,9 @@ size_t Curl_ossl_version(char *buffer, size_t size) char *p; size_t count; const char *ver = OpenSSL_version(OPENSSL_VERSION); - const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ - if(curl_strnequal(ver, expected, sizeof(expected) - 1)) { - ver += sizeof(expected) - 1; + static const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ + if(curl_strnequal(ver, expected, CURL_CSTRLEN(expected))) { + ver += CURL_CSTRLEN(expected); } count = curl_msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver); for(p = buffer; *p; ++p) { @@ -5422,6 +5459,9 @@ size_t Curl_ossl_version(char *buffer, size_t size) *p = '_'; } return count; +#elif defined(OPENSSL_IS_AWSLC) + return curl_msnprintf(buffer, size, "%s/%s", + OSSL_PACKAGE, AWSLC_VERSION_NUMBER_STRING); #elif defined(OPENSSL_IS_BORINGSSL) #ifdef CURL_BORINGSSL_VERSION return curl_msnprintf(buffer, size, "%s/%s", @@ -5429,9 +5469,6 @@ size_t Curl_ossl_version(char *buffer, size_t size) #else return curl_msnprintf(buffer, size, OSSL_PACKAGE); #endif -#elif defined(OPENSSL_IS_AWSLC) - return curl_msnprintf(buffer, size, "%s/%s", - OSSL_PACKAGE, AWSLC_VERSION_NUMBER_STRING); #else /* OpenSSL 3+ */ return curl_msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, OpenSSL_version(OPENSSL_VERSION_STRING)); @@ -5456,26 +5493,28 @@ static CURLcode ossl_random(struct Curl_easy *data, return rc == 1 ? CURLE_OK : CURLE_FAILED_INIT; } -static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, +static CURLcode ossl_sha256sum(const unsigned char *input, + size_t len, unsigned char *sha256sum /* output */, size_t unused) { + CURLcode result = CURLE_OK; EVP_MD_CTX *mdctx; - unsigned int len = 0; (void)unused; - mdctx = EVP_MD_CTX_create(); + mdctx = EVP_MD_CTX_new(); if(!mdctx) return CURLE_OUT_OF_MEMORY; - if(!EVP_DigestInit(mdctx, EVP_sha256())) { - EVP_MD_CTX_destroy(mdctx); - return CURLE_FAILED_INIT; + if(!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) { + result = CURLE_FAILED_INIT; + goto out; } - EVP_DigestUpdate(mdctx, tmp, tmplen); - EVP_DigestFinal_ex(mdctx, sha256sum, &len); - EVP_MD_CTX_destroy(mdctx); - return CURLE_OK; + if(!EVP_DigestUpdate(mdctx, input, len) || + !EVP_DigestFinal_ex(mdctx, sha256sum, NULL)) + result = CURLE_BAD_FUNCTION_ARGUMENT; +out: + EVP_MD_CTX_free(mdctx); + return result; } static bool ossl_cert_status_request(void) diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index 61d4a1757e25..dfac78c21e57 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -33,7 +33,7 @@ * , , or something else, does this: * #define X509_NAME ((LPCSTR)7) * - * In BoringSSL/AWC-LC's there is: + * In AWS-LC/BoringSSL's there is: * typedef struct X509_name_st X509_NAME; * etc. * @@ -74,7 +74,7 @@ #define HAVE_OPENSSL3 /* non-fork OpenSSL 3.x or later */ #endif -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) #define HAVE_BORINGSSL_LIKE #endif @@ -86,9 +86,9 @@ /* * 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. + * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 */ #ifndef LIBRESSL_VERSION_NUMBER #define HAVE_KEYLOG_CALLBACK @@ -100,6 +100,14 @@ #define HAVE_OPENSSL_EARLYDATA #endif +#ifdef LIBRESSL_VERSION_NUMBER +typedef long ctx_option_t; +#elif defined(HAVE_BORINGSSL_LIKE) +typedef uint32_t ctx_option_t; +#else +typedef uint64_t ctx_option_t; +#endif + struct alpn_spec; struct ssl_peer; struct Curl_ssl_session; @@ -107,12 +115,12 @@ 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; + SSL_CTX *ssl_ctx; + SSL *ssl; BIO_METHOD *bio_method; CURLcode io_result; /* result of last BIO cfilter operation */ /* blocked writes need to retry with same length, remember it */ - int blocked_ssl_write_len; + int blocked_ssl_write_len; #if !defined(HAVE_KEYLOG_UPSTREAM) && !defined(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. */ @@ -121,6 +129,10 @@ struct ossl_ctx { BIT(x509_store_setup); /* x509 store has been set up */ BIT(store_is_empty); /* no certs/paths/blobs in x509 store */ BIT(reused_session); /* session-ID was reused for this */ +#ifdef USE_APPLE_SECTRUST + BIT(sectrust_verified); /* peer was verified by sectrust */ + BIT(sectrust_session); /* session from sectrust verified peer */ +#endif }; size_t Curl_ossl_version(char *buffer, size_t size); @@ -174,16 +186,17 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, */ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ossl_ctx *octx, const char *ssl_peer_key, SSL_SESSION *session, - int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **psession); /* * 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 + * SSL config verifypeer or -host is set. Otherwise all this is for * informational purposes only! */ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index d886c8505207..9c0b0d7be3b3 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -32,9 +32,9 @@ #include "curlx/fopen.h" #include "curlx/strerr.h" #include "urldata.h" -#include "cf-dns.h" #include "curl_trc.h" -#include "httpsrr.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" #include "vtls/rustls.h" @@ -45,6 +45,12 @@ #include "curlx/base64.h" #endif +#if EAGAIN != EWOULDBLOCK +#define RAW_EAGAIN(e) ((e) == EWOULDBLOCK || (e) == EAGAIN) +#else +#define RAW_EAGAIN(e) ((e) == EWOULDBLOCK) +#endif + struct rustls_ssl_backend_data { const struct rustls_client_config *config; struct rustls_connection *conn; @@ -118,7 +124,7 @@ static int read_cb(void *userdata, uint8_t *buf, uintptr_t len, connssl->peer_closed = TRUE; *out_n = (uintptr_t)nread; CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %d, %zu", - (size_t)len, result, nread); + (size_t)len, (int)result, nread); return ret; } @@ -141,7 +147,7 @@ static int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, } *out_n = (uintptr_t)nwritten; CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %d, %zu", - len, result, nwritten); + len, (int)result, nwritten); return ret; } @@ -160,7 +166,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, io_ctx.data = data; io_error = rustls_connection_read_tls(backend->conn, read_cb, &io_ctx, &tls_bytes_read); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + if(RAW_EAGAIN(io_error)) { *err = CURLE_AGAIN; return -1; } @@ -252,7 +258,7 @@ static CURLcode cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, out: CURL_TRC_CF(data, cf, "rustls_recv(len=%zu) -> %d, %zu", - plainlen, result, *pnread); + plainlen, (int)result, *pnread); return result; } @@ -270,7 +276,7 @@ static CURLcode cr_flush_out(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) { + if(RAW_EAGAIN(io_error)) { CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", tlswritten_total); return CURLE_AGAIN; @@ -328,7 +334,7 @@ static CURLcode cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(backend->plain_out_buffered) { result = cr_flush_out(cf, data, rconn); CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", - backend->plain_out_buffered, result); + backend->plain_out_buffered, (int)result); if(result) return result; if(blen > backend->plain_out_buffered) { @@ -337,7 +343,7 @@ static CURLcode cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else blen = 0; - *pnwritten += (ssize_t)backend->plain_out_buffered; + *pnwritten += backend->plain_out_buffered; backend->plain_out_buffered = 0; } @@ -370,11 +376,11 @@ static CURLcode cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } else - *pnwritten += (ssize_t)plainwritten; + *pnwritten += plainwritten; out: CURL_TRC_CF(data, cf, "rustls_send(len=%zu) -> %d, %zu", - plainlen, result, *pnwritten); + plainlen, (int)result, *pnwritten); return result; } @@ -511,11 +517,11 @@ static void cr_keylog_log_cb(struct rustls_str label, 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 */ curl_msnprintf(clabel, sizeof(clabel), "%.*s", (int)label.len, label.data); - Curl_tls_keylog_write(clabel, client_random, secret, secret_len); + Curl_tls_keylog_write(clabel, client_random, client_random_len, + secret, secret_len); } static CURLcode @@ -845,14 +851,14 @@ init_config_builder_client_auth(struct Curl_easy *data, const struct rustls_certified_key *certified_key = NULL; CURLcode result = CURLE_OK; - if(conn_config->clientcert && !ssl_config->key) { + if(conn_config->clientcert && !ssl_config->primary.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) { + else if(!conn_config->clientcert && ssl_config->primary.key) { failf(data, "rustls: must provide certificate with key '%s'", - ssl_config->key); + ssl_config->primary.key); return CURLE_SSL_CERTPROBLEM; } @@ -866,8 +872,9 @@ init_config_builder_client_auth(struct Curl_easy *data, goto cleanup; } - if(!read_file_into(ssl_config->key, &key_contents)) { - failf(data, "rustls: failed to read key file: '%s'", ssl_config->key); + if(!read_file_into(ssl_config->primary.key, &key_contents)) { + failf(data, "rustls: failed to read key file: '%s'", + ssl_config->primary.key); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } @@ -915,9 +922,9 @@ static bool cr_ech_need_httpsrr(struct Curl_easy *data) { if(!CURLECH_ENABLED(data)) return FALSE; - if((data->set.tls_ech & CURLECH_GREASE) || - (data->set.tls_ech & CURLECH_CLA_CFG)) - return FALSE; + if((data->set.tls_ech == CURLECH_GREASE) || + CURL_EASY_STR(data, STRING_ECH_CONFIG)) + return FALSE; return TRUE; } @@ -941,7 +948,7 @@ init_config_builder_ech(struct Curl_easy *data, goto cleanup; } - if(data->set.str[STRING_ECH_PUBLIC]) { + if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { failf(data, "rustls: ECH outername not supported"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; @@ -957,8 +964,8 @@ init_config_builder_ech(struct Curl_easy *data, 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]; + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); size_t decode_result; if(!b64) { infof(data, "rustls: ECHConfig from command line empty"); @@ -974,8 +981,9 @@ init_config_builder_ech(struct Curl_easy *data, } } else { + const struct ssl_connect_data *connssl = cf->ctx; const struct Curl_https_rrinfo *rinfo = - Curl_conn_dns_get_https(data, cf->sockindex); + Curl_conn_dns_get_https(data, cf->sockindex, connssl->peer.origin); if(!rinfo || !rinfo->echconfiglist) { failf(data, "rustls: ECH requested but no ECHConfig available"); @@ -997,7 +1005,7 @@ init_config_builder_ech(struct Curl_easy *data, } cleanup: /* if we base64 decoded, we can free now */ - if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { curlx_free(ech_config); } if(dns) { @@ -1041,7 +1049,13 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); } - else if(ssl_config->native_ca_store) { + else if(conn_config->native_ca_store) { + if(conn_config->CRLfile) { + failf(data, "rustls: CRL file not supported with native CA store; " + "the platform verifier has no CRL attachment API"); + rustls_client_config_builder_free(config_builder); + return CURLE_NOT_BUILT_IN; + } result = init_config_builder_platform_verifier(data, config_builder); if(result != CURLE_OK) { rustls_client_config_builder_free(config_builder); @@ -1060,7 +1074,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } } - if(conn_config->clientcert || ssl_config->key) { + if(conn_config->clientcert || ssl_config->primary.key) { result = init_config_builder_client_auth(data, conn_config, ssl_config, @@ -1074,7 +1088,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, #ifdef USE_ECH if(CURLECH_ENABLED(data)) { result = init_config_builder_ech(data, cf, config_builder); - if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) { + if((result != CURLE_OK) && (data->set.tls_ech == CURLECH_HARD)) { rustls_client_config_builder_free(config_builder); return result; } @@ -1093,9 +1107,9 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - DEBUGASSERT(rconn == NULL); + DEBUGASSERT(!rconn); rr = rustls_client_connection_new(backend->config, - connssl->peer.hostname, + connssl->peer.origin->hostname, &rconn); if(rr != RUSTLS_RESULT_OK) { rustls_failf(data, rr, "rustls_client_connection_new"); @@ -1142,14 +1156,15 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); - CURL_TRC_CF(data, cf, "cr_connect, state=%d", connssl->state); + CURL_TRC_CF(data, cf, "cr_connect, state=%d", (int)connssl->state); *done = FALSE; #ifdef USE_ECH /* if we do ECH and need the HTTPS-RR information for it, * we delay the connect until it arrives or DNS resolve fails. */ if(cr_ech_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + !Curl_conn_dns_resolved_https(data, cf->sockindex, + connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); return CURLE_OK; } @@ -1159,7 +1174,7 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, result = cr_init_backend(cf, data, (struct rustls_ssl_backend_data *)connssl->backend); - CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", result); + CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", (int)result); if(result) return result; connssl->state = ssl_connection_negotiating; @@ -1351,7 +1366,7 @@ static CURLcode cr_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } DEBUGASSERT(result); - CURL_TRC_CF(data, cf, "shutdown send failed: %d", result); + CURL_TRC_CF(data, cf, "shutdown send failed: %d", (int)result); goto out; } @@ -1368,7 +1383,7 @@ static CURLcode cr_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(result) { DEBUGASSERT(result); - CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", (int)result); } else if(nread == 0) { /* We got the close notify alert and are done. */ diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index e1c8251af31c..98b738d0c65e 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -48,9 +48,8 @@ #include "curlx/fopen.h" #include "curlx/multibyte.h" #include "vtls/x509asn1.h" -#include "system_win32.h" #include "curlx/version_win32.h" -#include "rand.h" +#include "curlx/winapi.h" #include "curlx/strparse.h" #include "progress.h" #include "curl_sha256.h" @@ -69,7 +68,7 @@ #define SCH_DEV_SHOWBOOL(x) do {} while(0) #endif -/* Offered by mingw-w64 v8+. MS SDK 7.0A+. */ +/* Offered by mingw-w64 v8+, MS SDK 7.0A/VS2010+ */ #ifndef SP_PROT_TLS1_0_CLIENT #define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT #endif @@ -80,15 +79,16 @@ #define SP_PROT_TLS1_2_CLIENT 0x00000800 #endif -/* Offered by mingw-w64 v8+. MS SDK ~10+/~VS2017+. */ +/* Offered by mingw-w64 v8+, MS SDK 10.0.15063.0/VS2017 15.1+ */ #ifndef SP_PROT_TLS1_3_CLIENT #define SP_PROT_TLS1_3_CLIENT 0x00002000 #endif +/* Offered by mingw-w64 v8+, MS SDK 8.1/VS2013+ */ #ifndef SCH_USE_STRONG_CRYPTO #define SCH_USE_STRONG_CRYPTO 0x00400000 #endif -/* Offered by mingw-w64 v10+. MS SDK 7.0A+. */ +/* Offered by mingw-w64 v10+, MS SDK 7.0A/VS2010+ */ #ifndef SECBUFFER_ALERT #define SECBUFFER_ALERT 17 #endif @@ -105,12 +105,12 @@ * #define failf(x, y, ...) curl_mprintf(y, __VA_ARGS__) */ -/* Offered by mingw-w64 v4+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v4+, MS SDK 6.0A/VS2008+ */ #ifndef PKCS12_NO_PERSIST_KEY #define PKCS12_NO_PERSIST_KEY 0x00008000 #endif -/* Offered by mingw-w64 v4+. MS SDK ~10+/~VS2017+. */ +/* Offered by mingw-w64 v4+, MS SDK 8.0/~VS2012+ */ #ifndef CERT_FIND_HAS_PRIVATE_KEY #define CERT_FIND_HAS_PRIVATE_KEY (21 << CERT_COMPARE_SHIFT) #endif @@ -118,15 +118,32 @@ /* key to use at `multi->proto_hash` */ #define MPROTO_SCHANNEL_CERT_SHARE_KEY "tls:schannel:cert:share" -/* ALPN requires version 8.1 of the Windows SDK, which was - shipped with Visual Studio 2013, aka _MSC_VER 1800: - https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/hh831771 - 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; + +/* Offered by mingw-w64 v9+, MS SDK 8.1/VS2013+ */ +#ifndef SECBUFFER_APPLICATION_PROTOCOLS +#define SECBUFFER_APPLICATION_PROTOCOLS 18 +#define SECPKG_ATTR_APPLICATION_PROTOCOL 35 + +typedef enum { + SecApplicationProtocolNegotiationExt_None, + SecApplicationProtocolNegotiationExt_NPN, + SecApplicationProtocolNegotiationExt_ALPN +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT; + +typedef enum { + SecApplicationProtocolNegotiationStatus_None, + SecApplicationProtocolNegotiationStatus_Success, + SecApplicationProtocolNegotiationStatus_SelectedClientOnly +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS; + +/* !checksrc! disable TYPEDEFSTRUCT 1 */ +typedef struct { + SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS ProtoNegoStatus; + SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT ProtoNegoExt; + unsigned char ProtocolIdSize; + unsigned char ProtocolId[0xff]; +} SecPkgContext_ApplicationProtocol; #endif static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, @@ -160,13 +177,12 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, /* Windows Server 2022 and newer (including Windows 11) support TLS 1.3 built-in. Previous builds of Windows 10 had broken TLS 1.3 - implementations that could be enabled via registry. - */ + implementations that could be enabled via registry. */ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; } - else /* Windows 10 and older */ + else /* Windows 10 or older */ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; break; @@ -185,14 +201,14 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, break; case CURL_SSLVERSION_TLSv1_3: - /* Windows Server 2022 and newer */ + /* Windows Server 2022 or newer */ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { *enabled_protocols |= SP_PROT_TLS1_3_CLIENT; break; } - else { /* Windows 10 and older */ - failf(data, "schannel: TLS 1.3 not supported on Windows prior to 11"); + else { /* Windows 10 or older */ + failf(data, "schannel: TLS 1.3 not supported on Windows 10 or older"); return CURLE_SSL_CONNECT_ERROR; } } @@ -252,12 +268,12 @@ static const struct algo algs[] = { CIPHEROPTION(CALG_SHA_384), CIPHEROPTION(CALG_SHA_512), CIPHEROPTION(CALG_ECDH), -/* Offered by mingw-w64 v4+. MS SDK 6.0A+. */ +/* Offered by mingw-w64 v4+, MS SDK 6.0A/VS2008+ */ #ifdef CALG_ECMQV CIPHEROPTION(CALG_ECMQV), #endif CIPHEROPTION(CALG_ECDSA), -/* Offered by mingw-w64 v7+. MS SDK 7.0A+. */ +/* Offered by mingw-w64 v7+, MS SDK 7.0A/VS2010+ */ #ifdef CALG_ECDH_EPHEM CIPHEROPTION(CALG_ECDH_EPHEM), #endif @@ -292,9 +308,9 @@ static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, if(alg) algIds[algCount++] = (ALG_ID)alg; else if(!strncmp(startCur, "USE_STRONG_CRYPTO", - sizeof("USE_STRONG_CRYPTO") - 1) || + CURL_CSTRLEN("USE_STRONG_CRYPTO")) || !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", - sizeof("SCH_USE_STRONG_CRYPTO") - 1)) + CURL_CSTRLEN("SCH_USE_STRONG_CRYPTO"))) schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO; else return CURLE_SSL_CIPHER; @@ -315,34 +331,35 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, TCHAR *store_path_start; size_t store_name_len; - sep = _tcschr(path, TEXT('\\')); + sep = _tcschr(path, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; store_name_len = sep - path; - if(_tcsncmp(path, TEXT("CurrentUser"), store_name_len) == 0) + if(_tcsncmp(path, _TEXT("CurrentUser"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER; - else if(_tcsncmp(path, TEXT("LocalMachine"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachine"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE; - else if(_tcsncmp(path, TEXT("CurrentService"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentService"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE; - else if(_tcsncmp(path, TEXT("Services"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Services"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_SERVICES; - else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("Users"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_USERS; - else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("CurrentUserGroupPolicy"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineGroupPolicy"), store_name_len) == + 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"), store_name_len) == 0) + else if(_tcsncmp(path, _TEXT("LocalMachineEnterprise"), store_name_len) == 0) *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; else return CURLE_SSL_CERTPROBLEM; store_path_start = sep + 1; - sep = _tcschr(store_path_start, TEXT('\\')); + sep = _tcschr(store_path_start, _TEXT('\\')); if(!sep) return CURLE_SSL_CERTPROBLEM; @@ -350,24 +367,26 @@ static CURLcode get_cert_location(TCHAR *path, DWORD *store_name, if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN) return CURLE_SSL_CERTPROBLEM; - *sep = TEXT('\0'); + *sep = _TEXT('\0'); *store_path = curlx_tcsdup(store_path_start); - *sep = TEXT('\\'); + *sep = _TEXT('\\'); if(!*store_path) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } -static CURLcode get_client_cert(struct Curl_easy *data, +static CURLcode get_client_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, HCERTSTORE *out_cert_store, PCCERT_CONTEXT *out_cert_context) { + struct ssl_primary_config *sslc = Curl_ssl_cf_get_primary_config(cf); PCCERT_CONTEXT client_cert = NULL; HCERTSTORE client_cert_store = NULL; CURLcode result = CURLE_OK; - if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { + if(sslc->clientcert || sslc->cert_blob) { DWORD cert_store_name = 0; TCHAR *cert_store_path = NULL; TCHAR *cert_thumbprint_str = NULL; @@ -378,15 +397,15 @@ static CURLcode get_client_cert(struct Curl_easy *data, FILE *fInCert = NULL; void *certdata = NULL; size_t certsize = 0; - bool blob = data->set.ssl.primary.cert_blob != NULL; + bool blob = !!sslc->cert_blob; if(blob) { - certdata = data->set.ssl.primary.cert_blob->data; - certsize = data->set.ssl.primary.cert_blob->len; + certdata = sslc->cert_blob->data; + certsize = sslc->cert_blob->len; } else { TCHAR *cert_path = - curlx_convert_UTF8_to_tchar(data->set.ssl.primary.clientcert); + curlx_convert_UTF8_to_tchar(sslc->clientcert); if(!cert_path) return CURLE_OUT_OF_MEMORY; @@ -403,22 +422,22 @@ static CURLcode get_client_cert(struct Curl_easy *data, } curlx_free(cert_path); - if(result && (data->set.ssl.primary.clientcert[0] != '\0')) - fInCert = curlx_fopen(data->set.ssl.primary.clientcert, "rb"); + if(result && (sslc->clientcert[0] != '\0')) + fInCert = curlx_fopen(sslc->clientcert, "rb"); if(result && !fInCert) { failf(data, "schannel: Failed to get certificate location" " or file for %s", - data->set.ssl.primary.clientcert); + sslc->clientcert); return result; } } - if((fInCert || blob) && data->set.ssl.cert_type && - !curl_strequal(data->set.ssl.cert_type, "P12")) { + if((fInCert || blob) && sslc->cert_type && + !curl_strequal(sslc->cert_type, "P12")) { failf(data, "schannel: certificate format compatibility error " "for %s", - blob ? "(memory blob)" : data->set.ssl.primary.clientcert); + blob ? "(memory blob)" : sslc->clientcert); curlx_free(cert_store_path); if(fInCert) curlx_fclose(fInCert); @@ -428,34 +447,34 @@ static CURLcode get_client_cert(struct Curl_easy *data, if(fInCert || blob) { /* Reading a .p12 or .pfx file, like the example at bottom of https://learn.microsoft.com/archive/msdn-technet-forums/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 - */ + */ CRYPT_DATA_BLOB datablob; WCHAR *pszPassword; size_t pwd_len = 0; int cert_find_flags; const char *cert_showfilename_error = blob ? - "(memory blob)" : data->set.ssl.primary.clientcert; + "(memory blob)" : sslc->clientcert; curlx_free(cert_store_path); if(fInCert) { long cert_tell = 0; bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0; - if(continue_reading) + if(continue_reading) { cert_tell = ftell(fInCert); - if(cert_tell < 0) - continue_reading = FALSE; - else - certsize = (size_t)cert_tell; + if(cert_tell < 0) + continue_reading = FALSE; + else + certsize = (size_t)cert_tell; + } if(continue_reading) continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; if(continue_reading && (certsize < CURL_MAX_INPUT_LENGTH)) certdata = curlx_malloc(certsize + 1); - if((!certdata) || - ((int) fread(certdata, certsize, 1, fInCert) != 1)) + if(!certdata || ((int)fread(certdata, certsize, 1, fInCert) != 1)) continue_reading = FALSE; curlx_fclose(fInCert); if(!continue_reading) { failf(data, "schannel: Failed to read cert file %s", - data->set.ssl.primary.clientcert); + sslc->clientcert); curlx_free(certdata); return CURLE_SSL_CERTPROBLEM; } @@ -465,16 +484,14 @@ static CURLcode get_client_cert(struct Curl_easy *data, datablob.pbData = (BYTE *)certdata; datablob.cbData = (DWORD)certsize; - if(data->set.ssl.key_passwd) - pwd_len = strlen(data->set.ssl.key_passwd); - pszPassword = (WCHAR *)curlx_malloc(sizeof(WCHAR) * (pwd_len + 1)); + if(sslc->key_passwd) + pwd_len = strlen(sslc->key_passwd); + pszPassword = curlx_malloc(sizeof(WCHAR) * (pwd_len + 1)); if(pszPassword) { int str_w_len = 0; if(pwd_len > 0) - str_w_len = MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - data->set.ssl.key_passwd, - (int)pwd_len, + str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + sslc->key_passwd, (int)pwd_len, pszPassword, (int)(pwd_len + 1)); if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) @@ -484,6 +501,7 @@ static CURLcode get_client_cert(struct Curl_easy *data, cert_store = PFXImportCertStore(&datablob, pszPassword, PKCS12_NO_PERSIST_KEY); + curlx_memzero(pszPassword, sizeof(WCHAR) * (pwd_len + 1)); curlx_free(pszPassword); } if(!blob) @@ -502,7 +520,7 @@ static CURLcode get_client_cert(struct Curl_easy *data, } /* CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server - 2012, (NT v6.2). For earlier versions we use CURL_FIND_ANY. */ + 2012, (NT 6.2). For older 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; @@ -591,11 +609,10 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; - /* 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). */ + /* We support TLS 1.3 starting in Windows Server 2022 or later + * (OS build 20348) */ if(!conn_config->cipher_list && - curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, + curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { SCH_CREDENTIALS credentials = { 0 }; @@ -628,8 +645,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, } sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(UNISP_NAME), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(UNISP_NAME), SECPKG_CRED_OUTBOUND, NULL, &credentials, NULL, NULL, &backend->cred->cred_handle, NULL); @@ -653,6 +669,11 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, if(ciphers) { if((enabled_protocols & SP_PROT_TLS1_3_CLIENT)) { + if(!(enabled_protocols & ~SP_PROT_TLS1_3_CLIENT)) { + failf(data, "schannel: TLS 1.3 is not supported with a cipher list; " + "remove the cipher list or allow a lower TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } 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."); @@ -673,8 +694,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, } sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)CURL_UNCONST(UNISP_NAME), + Curl_pSecFn->AcquireCredentialsHandle(NULL, CURL_UNCONST(UNISP_NAME), SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL, &backend->cred->cred_handle, NULL); @@ -716,7 +736,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, DWORD enabled_protocols = 0; struct schannel_ssl_backend_data *backend = - (struct schannel_ssl_backend_data *)(connssl->backend); + (struct schannel_ssl_backend_data *)connssl->backend; DEBUGASSERT(backend); @@ -789,7 +809,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - result = get_client_cert(data, &client_cert_store, &client_cert); + result = get_client_cert(cf, data, &client_cert_store, &client_cert); if(result) return result; @@ -835,21 +855,15 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, SecBufferDesc outbuf_desc; SecBuffer inbuf; SecBufferDesc inbuf_desc; -#ifdef HAS_ALPN_SCHANNEL unsigned char alpn_buffer[128]; -#endif SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", - connssl->peer.hostname, connssl->peer.port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); -#ifdef HAS_ALPN_SCHANNEL backend->use_alpn = connssl->alpn && s_win_has_alpn; -#else - backend->use_alpn = FALSE; -#endif if(conn_config->CAfile || conn_config->ca_info_blob) { if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, @@ -903,7 +917,8 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* A hostname associated with the credential is needed by InitializeSecurityContext for SNI and other reasons. */ - snihost = connssl->peer.sni ? connssl->peer.sni : connssl->peer.hostname; + snihost = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.origin->hostname; backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); if(!backend->cred->sni_hostname) return CURLE_OUT_OF_MEMORY; @@ -914,7 +929,6 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, infof(data, "schannel: using IP address, SNI is not supported by OS."); } -#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { int cur = 0; int list_start_index = 0; @@ -924,7 +938,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* The first four bytes is an unsigned int indicating number of bytes of data in the rest of the buffer. */ - extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]); + extension_len = (unsigned int *)(void *)&alpn_buffer[cur]; cur += (int)sizeof(unsigned int); /* The next four bytes are an indicator that this buffer contains @@ -935,7 +949,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* The next two bytes is an unsigned short indicating the number of bytes used to list the preferred protocols. */ - list_len = (unsigned short *)(void *)(&alpn_buffer[cur]); + list_len = (unsigned short *)(void *)&alpn_buffer[cur]; cur += (int)sizeof(unsigned short); list_start_index = cur; @@ -962,10 +976,6 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); } -#else /* HAS_ALPN_SCHANNEL */ - InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, &inbuf, 1); -#endif /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); @@ -987,12 +997,12 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, } /* Schannel InitializeSecurityContext: - https://learn.microsoft.com/windows/win32/api/rrascfg/nn-rrascfg-ieapproviderconfig + https://learn.microsoft.com/windows/win32/api/sspi/nf-sspi-initializesecuritycontextw 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 = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, backend->req_flags, 0, 0, @@ -1246,7 +1256,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 2/3)", - connssl->peer.hostname, connssl->peer.port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; @@ -1372,7 +1382,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, /* check if the handshake needs to be continued */ result = CURLE_OK; for(i = 0; i < 3; i++) { - /* search for handshake tokens that need to be send */ + /* search for handshake tokens that need to be sent */ if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) { size_t written = 0; DEBUGF(infof(data, "schannel: sending next handshake data: " @@ -1421,16 +1431,15 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { 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 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 - connection and this extra data is part of the handshake. - We should process the data immediately; waiting for the socket to - be ready may fail since the server is done sending handshake data. - */ + /* There are two cases where we could be getting extra data here: + 1. If we are renegotiating a connection and the handshake is already + complete (from the server perspective), it can encrypt app data + (not handshake data) in an extra buffer at this point. + 2. (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a + connection and this extra data is part of the handshake. + We should process the data immediately; waiting for the socket to + be ready may fail since the server is done sending handshake data. + */ /* check if the remaining data is less than the total amount and therefore begins after the already processed data */ if(backend->encdata.offset > inbuf[1].cbBuffer) { @@ -1464,10 +1473,10 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + pubkey_ptr = CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #endif if(pubkey_ptr) { result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); @@ -1499,9 +1508,9 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, static bool valid_cert_encoding(const CERT_CONTEXT *cert_context) { - return (cert_context != NULL) && + return cert_context && ((cert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) && - (cert_context->pbCertEncoded != NULL) && + cert_context->pbCertEncoded && (cert_context->cbCertEncoded > 0); } @@ -1594,18 +1603,17 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); CURLcode result = CURLE_OK; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; -#ifdef HAS_ALPN_SCHANNEL SecPkgContext_ApplicationProtocol alpn_result; -#endif - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(connssl->connecting_state == ssl_connect_3); DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", - connssl->peer.hostname, connssl->peer.port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; @@ -1625,7 +1633,6 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } -#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { sspi_status = Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, @@ -1657,7 +1664,6 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); } } -#endif /* save the current session data for possible reuse */ if(Curl_ssl_scache_use(cf, data)) { @@ -1671,7 +1677,7 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, return result; } - if(data->set.ssl.certinfo) { + if(ssl_config->certinfo) { int certs_count = 0; sspi_status = Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, @@ -1726,40 +1732,45 @@ static CURLcode schannel_connect(struct Curl_cfilter *cf, *done = FALSE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { result = schannel_connect_step1(cf, data); if(result) return result; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { result = schannel_connect_step2(cf, data); if(result) return result; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { result = schannel_connect_step3(cf, data); if(result) return result; } - if(ssl_connect_done == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_done) { + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + + if(Curl_pSecFn->QueryContextAttributes( + &backend->ctxt->ctxt_handle, + SECPKG_ATTR_STREAM_SIZES, + &backend->stream_sizes)) { + failf(data, "schannel: failed getting stream sizes"); + return CURLE_SSL_CONNECT_ERROR; + } + connssl->state = ssl_connection_complete; -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS /* mingw-w64 v9+. MS SDK 7.0A+. */ /* When SSPI is used in combination with Schannel * we need the Schannel context to create the Schannel * binding to pass the IIS extended protection checks. * Available on Windows 7 or later. */ - { - struct schannel_ssl_backend_data *backend = - (struct schannel_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); - cf->conn->sslContext = &backend->ctxt->ctxt_handle; - } -#endif + cf->conn->sslContext = &backend->ctxt->ctxt_handle; *done = TRUE; } @@ -1990,29 +2001,23 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, return result; } - /* check if the maximum stream sizes were queried */ - if(backend->stream_sizes.cbMaximumMessage == 0) { - sspi_status = Curl_pSecFn->QueryContextAttributes( - &backend->ctxt->ctxt_handle, - SECPKG_ATTR_STREAM_SIZES, - &backend->stream_sizes); - if(sspi_status != SEC_E_OK) { - return CURLE_SEND_ERROR; - } - } - /* check if the buffer is longer than the maximum message length */ if(len > backend->stream_sizes.cbMaximumMessage) { len = backend->stream_sizes.cbMaximumMessage; } - /* calculate the complete message length and allocate a buffer for it */ + /* calculate the complete message length and prepare the send buffer */ data_len = backend->stream_sizes.cbHeader + len + backend->stream_sizes.cbTrailer; - ptr = (unsigned char *)curlx_malloc(data_len); - if(!ptr) { - return CURLE_OUT_OF_MEMORY; + if(data_len > backend->send_buffer_len) { + ptr = curlx_realloc(backend->send_buffer, data_len); + if(!ptr) + return CURLE_OUT_OF_MEMORY; + backend->send_buffer = ptr; + backend->send_buffer_len = data_len; } + else + ptr = backend->send_buffer; /* setup output buffers (header, data, trailer, empty) */ InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER, @@ -2038,21 +2043,19 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* send the encrypted message including header, data and trailer */ len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; - /* - 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 - 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 - sent. The unwritten encrypted bytes would be the first bytes to - send on the next invocation. - Here's the catch with this - if we tell the client that all the - bytes have been sent, does the client call this method again to - send the buffered data? Looking at who calls this function, it - seems the answer is NO. - */ + /* 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 + 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 + sent. The unwritten encrypted bytes would be the first bytes to + send on the next invocation. + Here's the catch with this - if we tell the client that all the + bytes have been sent, does the client call this method again to + send the buffered data? Looking at who calls this function, it + seems the answer is NO. */ /* send entire message or fail */ while(len > *pnwritten) { @@ -2103,8 +2106,6 @@ static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, result = CURLE_SEND_ERROR; } - curlx_safefree(ptr); - if(len == *pnwritten) /* Encrypted message including header, data and trailer entirely sent. The return value is the number of unencrypted bytes that were sent. */ @@ -2170,8 +2171,7 @@ static CURLcode schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } /* 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. - */ + decrypt all encrypted cached data) so handle !len later in cleanup. */ else if(len && !backend->recv_connection_closed) { /* the encrypted buffer must be large enough to hold all the bytes requested and some TLS record overhead. 'len' is a buffer size, so this @@ -2198,7 +2198,7 @@ static CURLcode schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(result == CURLE_AGAIN) SCH_DEV(infof(data, "schannel: recv returned CURLE_AGAIN")); else { - infof(data, "schannel: recv returned error %d", result); + infof(data, "schannel: recv returned error %d", (int)result); backend->recv_unrecoverable_err = result; } } @@ -2355,8 +2355,7 @@ static CURLcode schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, 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. - */ + ignoring the close_notify for compatibility reasons. */ if(len && !backend->decdata.offset && backend->recv_connection_closed && !backend->recv_sspi_close_notify) { result = CURLE_RECV_ERROR; @@ -2386,8 +2385,7 @@ static CURLcode schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* 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. - */ + consistent. */ if(!len) return CURLE_OK; @@ -2443,7 +2441,7 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, *done = FALSE; if(backend->ctxt) { infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", - connssl->peer.hostname, connssl->peer.port); + connssl->peer.origin->hostname, connssl->peer.origin->port); } if(!backend->ctxt || cf->shutdown) { @@ -2517,7 +2515,7 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, else { if(!backend->recv_connection_closed) { result = CURLE_SEND_ERROR; - failf(data, "schannel: error sending close msg: %d", result); + failf(data, "schannel: error sending close msg: %d", (int)result); goto out; } /* Looks like server already closed the connection. @@ -2540,7 +2538,7 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_RECV; } else if(result) { - CURL_TRC_CF(data, cf, "SSL shutdown, error %d", result); + CURL_TRC_CF(data, cf, "SSL shutdown, error %d", (int)result); result = CURLE_RECV_ERROR; } else if(nread == 0) { @@ -2582,6 +2580,10 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) backend->cred = NULL; } + /* free the buffer used to encrypt outgoing data */ + curlx_safefree(backend->send_buffer); + backend->send_buffer_len = 0; + /* free internal buffer for received encrypted data */ if(backend->encdata.buffer) { curlx_safefree(backend->encdata.buffer); @@ -2600,7 +2602,6 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) static int schannel_init(void) { -#ifdef HAS_ALPN_SCHANNEL typedef const char *(APIENTRY *WINE_GET_VERSION_FN)(void); #if defined(__clang__) && __clang_major__ >= 16 #pragma clang diagnostic push @@ -2615,17 +2616,16 @@ static int schannel_init(void) if(p_wine_get_version) { /* WINE detected */ curl_off_t ver = 0; const char *wine_version = p_wine_get_version(); /* e.g. "6.0.2" */ - /* Assume ALPN support with WINE 6.0 or upper */ + /* Assume ALPN support with WINE 6.0 or greater */ if(wine_version) curlx_str_number(&wine_version, &ver, 20); s_win_has_alpn = (ver >= 6); } else { - /* ALPN is supported on Windows 8.1 / Server 2012 R2 and above. */ + /* ALPN is supported on Windows 8.1 / Server 2012 R2 or newer. */ s_win_has_alpn = curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL); } -#endif /* HAS_ALPN_SCHANNEL */ return Curl_sspi_global_init() == CURLE_OK ? 1 : 0; } @@ -2645,19 +2645,27 @@ static CURLcode schannel_random(struct Curl_easy *data, { (void)data; - return Curl_win32_random(entropy, length); + return curlx_win32_random(entropy, length); } -static void schannel_checksum(const unsigned char *input, - size_t inputlen, - unsigned char *checksum, - size_t checksumlen, - DWORD provType, - const unsigned int algId) +static CURLcode schannel_checksum(const unsigned char *input, + size_t inputlen, + unsigned char *checksum, + size_t checksumlen, + DWORD provType, + const unsigned int algId) { + CURLcode result = CURLE_FAILED_INIT; + HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; + size_t off; + + DWORD cbHashSize; + DWORD dwHashSizeLen; + DWORD dwChecksumLen; + /* since this can fail in multiple ways, zero memory first so we never * return old data */ @@ -2665,37 +2673,45 @@ static void schannel_checksum(const unsigned char *input, if(!CryptAcquireContext(&hProv, NULL, NULL, provType, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) - return; /* failed */ + goto out; - do { - DWORD cbHashSize = 0; - DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize); - DWORD dwChecksumLen = (DWORD)checksumlen; + if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) + goto out; - if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) - break; /* failed */ + result = CURLE_BAD_FUNCTION_ARGUMENT; - if(!CryptHashData(hHash, input, (DWORD)inputlen, 0)) - break; /* failed */ + off = 0; + while(off < inputlen) { + DWORD chunk = (DWORD)CURLMIN(inputlen - off, 0xffffffffUL); + if(!CryptHashData(hHash, input + off, chunk, 0)) + goto out; + off += chunk; + } - /* get hash size */ - if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize, - &dwHashSizeLen, 0)) - break; /* failed */ + /* get hash size */ + cbHashSize = 0; + dwHashSizeLen = (DWORD)sizeof(cbHashSize); + if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize, + &dwHashSizeLen, 0)) + goto out; - /* check hash size */ - if(checksumlen < cbHashSize) - break; /* failed */ + /* check if hash fits into the return buffer */ + if(checksumlen < cbHashSize) + goto out; - if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0)) - break; /* failed */ - } while(0); + dwChecksumLen = (DWORD)checksumlen; + if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0) && + dwChecksumLen == cbHashSize) + result = CURLE_OK; +out: if(hHash) CryptDestroyHash(hHash); if(hProv) CryptReleaseContext(hProv, 0); + + return result; } static CURLcode schannel_sha256sum(const unsigned char *input, @@ -2703,9 +2719,8 @@ static CURLcode schannel_sha256sum(const unsigned char *input, unsigned char *sha256sum, size_t sha256len) { - schannel_checksum(input, inputlen, sha256sum, sha256len, - PROV_RSA_AES, CALG_SHA_256); - return CURLE_OK; + return schannel_checksum(input, inputlen, sha256sum, sha256len, + PROV_RSA_AES, CALG_SHA_256); } static void *schannel_get_internals(struct ssl_connect_data *connssl, @@ -2737,7 +2752,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1); + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share || !share->cert_store) { return NULL; } @@ -2763,10 +2778,11 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, 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(schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + info_blob_digest, + CURL_SHA256_DIGEST_LENGTH)) + return NULL; if(memcmp(share->CAinfo_blob_digest, info_blob_digest, CURL_SHA256_DIGEST_LENGTH)) { return NULL; @@ -2785,7 +2801,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, 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(key_len == CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); DEBUGASSERT(!memcmp(MPROTO_SCHANNEL_CERT_SHARE_KEY, key, key_len)); (void)key; (void)key_len; @@ -2804,7 +2820,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *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; + unsigned char digest[CURL_SHA256_DIGEST_LENGTH]; char *CAfile = NULL; DEBUGASSERT(multi); @@ -2813,48 +2829,53 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, return FALSE; } + if(ca_info_blob) { + if(schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, digest, sizeof(digest))) { + return FALSE; + } + } + else if(conn_config->CAfile) { + CAfile = curlx_strdup(conn_config->CAfile); + if(!CAfile) { + return FALSE; + } + } + share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1); + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); if(!share) { + curlx_free(CAfile); return FALSE; } if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), - sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY) - 1, + CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY), share, schannel_cert_share_free)) { curlx_free(share); + curlx_free(CAfile); 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 = curlx_strdup(conn_config->CAfile); - if(!CAfile) { - return FALSE; - } - } - } - /* free old cache data */ if(share->cert_store) { CertCloseStore(share->cert_store, 0); } curlx_free(share->CAfile); + if(ca_info_blob) { + memcpy(share->CAinfo_blob_digest, digest, sizeof(digest)); + share->CAinfo_blob_size = ca_info_blob->len; + } + else + share->CAinfo_blob_size = 0; + share->time = curlx_now(); share->cert_store = cert_store; - share->CAinfo_blob_size = CAinfo_blob_size; share->CAfile = CAfile; return TRUE; } diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index 496635f2c2f0..ccbb63d7e20c 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -42,7 +42,7 @@ #define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)(size_t)10) #endif -/* Offered by mingw-w64 v8+, MS SDK ~10+/~VS2022+ */ +/* Offered by mingw-w64 v8+, MS SDK 10.0.17763.0/VS2017 15.8+ */ #ifndef SCH_CREDENTIALS_VERSION #define SCH_CREDENTIALS_VERSION 0x00000005 @@ -56,42 +56,42 @@ typedef enum _eTlsAlgorithmUsage { /* !checksrc! disable TYPEDEFSTRUCT 1 */ typedef struct _CRYPTO_SETTINGS { - eTlsAlgorithmUsage eAlgorithmUsage; - UNICODE_STRING strCngAlgId; - DWORD cChainingModes; - PUNICODE_STRING rgstrChainingModes; /* spellchecker:disable-line */ - DWORD dwMinBitLength; - DWORD dwMaxBitLength; -} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS; + eTlsAlgorithmUsage eAlgorithmUsage; + UNICODE_STRING strCngAlgId; + DWORD cChainingModes; + PUNICODE_STRING rgstrChainingModes; /* spellchecker:disable-line */ + DWORD dwMinBitLength; + DWORD dwMaxBitLength; +} CRYPTO_SETTINGS, *PCRYPTO_SETTINGS; /* !checksrc! disable TYPEDEFSTRUCT 1 */ typedef struct _TLS_PARAMETERS { - DWORD cAlpnIds; - PUNICODE_STRING rgstrAlpnIds; /* spellchecker:disable-line */ - DWORD grbitDisabledProtocols; - DWORD cDisabledCrypto; - PCRYPTO_SETTINGS pDisabledCrypto; - DWORD dwFlags; -} TLS_PARAMETERS, * PTLS_PARAMETERS; + DWORD cAlpnIds; + PUNICODE_STRING rgstrAlpnIds; /* spellchecker:disable-line */ + DWORD grbitDisabledProtocols; + DWORD cDisabledCrypto; + PCRYPTO_SETTINGS pDisabledCrypto; + DWORD dwFlags; +} TLS_PARAMETERS, *PTLS_PARAMETERS; /* !checksrc! disable TYPEDEFSTRUCT 1 */ typedef struct _SCH_CREDENTIALS { - DWORD dwVersion; - DWORD dwCredFormat; - DWORD cCreds; - PCCERT_CONTEXT* paCred; - HCERTSTORE hRootStore; + DWORD dwVersion; + DWORD dwCredFormat; + DWORD cCreds; + PCCERT_CONTEXT *paCred; + HCERTSTORE hRootStore; - DWORD cMappers; + DWORD cMappers; struct _HMAPPER **aphMappers; - DWORD dwSessionLifespan; - DWORD dwFlags; - DWORD cTlsParameters; - PTLS_PARAMETERS pTlsParameters; -} SCH_CREDENTIALS, * PSCH_CREDENTIALS; + DWORD dwSessionLifespan; + DWORD dwFlags; + DWORD cTlsParameters; + PTLS_PARAMETERS pTlsParameters; +} SCH_CREDENTIALS, *PSCH_CREDENTIALS; -#endif /* SCH_CREDENTIALS_VERSION */ +#endif /* !SCH_CREDENTIALS_VERSION */ struct Curl_schannel_cred { CredHandle cred_handle; @@ -112,6 +112,8 @@ struct sbuffer { }; struct schannel_ssl_backend_data { + unsigned char *send_buffer; + size_t send_buffer_len; struct sbuffer encdata; struct sbuffer decdata; struct Curl_schannel_cred *cred; @@ -147,9 +149,7 @@ struct schannel_cert_share { struct curltime time; /* when the cached store was created */ }; -/* -* size of the structure: 20 bytes. -*/ +/* size of the structure: 20 bytes */ struct num_ip_data { DWORD size; /* 04 bytes */ union { diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index fc797349b0eb..8f9ce6c846ea 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -71,7 +71,7 @@ struct cert_chain_engine_config_win8 { DWORD dwExclusiveFlags; }; -/* Offered by mingw-w64 v4+. MS SDK ~10+/~VS2017+. */ +/* Offered by mingw-w64 v4+, MS SDK 8.0/~VS2012+ */ #ifndef CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG #define CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG 0x00000001 #endif @@ -92,11 +92,6 @@ struct cert_chain_engine_config_win7 { HCERTSTORE hExclusiveTrustedPeople; }; -static int is_cr_or_lf(char c) -{ - return c == '\r' || c == '\n'; -} - /* Search the substring needle,needlelen into string haystack,haystacklen * Strings do not need to be terminated by a '\0'. * Similar of macOS/Linux memmem (not available on Visual Studio). @@ -112,7 +107,7 @@ static const char *c_memmem(const void *haystack, size_t haystacklen, return NULL; first = *(const char *)needle; for(p = (const char *)haystack; p <= (str_limit - needlelen); p++) - if(((*p) == first) && (memcmp(p, needle, needlelen) == 0)) + if((*p == first) && !memcmp(p, needle, needlelen)) return p; return NULL; @@ -124,8 +119,8 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, const char *ca_file_text, struct Curl_easy *data) { - const size_t begin_cert_len = strlen(BEGIN_CERT); - const size_t end_cert_len = strlen(END_CERT); + const size_t begin_cert_len = CURL_CSTRLEN(BEGIN_CERT); + const size_t end_cert_len = CURL_CSTRLEN(END_CERT); CURLcode result = CURLE_OK; int num_certs = 0; bool more_certs = 1; @@ -134,10 +129,11 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, while(more_certs && (current_ca_file_ptr < ca_buffer_limit)) { const char *begin_cert_ptr = c_memmem(current_ca_file_ptr, - ca_buffer_limit-current_ca_file_ptr, + ca_buffer_limit - + current_ca_file_ptr - 1, BEGIN_CERT, begin_cert_len); - if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) { + if(!begin_cert_ptr || !ISNEWLINE(begin_cert_ptr[begin_cert_len])) { more_certs = 0; } else { @@ -156,7 +152,6 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, CERT_BLOB cert_blob; const CERT_CONTEXT *cert_context = NULL; BOOL add_cert_result = FALSE; - DWORD actual_content_type = 0; DWORD cert_size = (DWORD)((end_cert_ptr + end_cert_len) - begin_cert_ptr); @@ -166,10 +161,10 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &cert_blob, CERT_QUERY_CONTENT_FLAG_CERT, - CERT_QUERY_FORMAT_FLAG_ALL, + CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED, 0, NULL, - &actual_content_type, + NULL, NULL, NULL, NULL, @@ -186,51 +181,27 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, else { current_ca_file_ptr = begin_cert_ptr + cert_size; - /* Sanity check that the cert_context object is the right type */ - if(CERT_QUERY_CONTENT_CERT != actual_content_type) { + add_cert_result = + CertAddCertificateContextToStore(trust_store, + cert_context, + CERT_STORE_ADD_ALWAYS, + NULL); + if(!add_cert_result) { + char buffer[WINAPI_ERROR_LEN]; failf(data, - "schannel: unexpected content type '%lu' when extracting " - "certificate from CA file '%s'", - actual_content_type, ca_file_text); + "schannel: failed to add certificate from CA file '%s' " + "to certificate store: %s", + ca_file_text, + curlx_winapi_strerror(GetLastError(), buffer, + sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; more_certs = 0; } else { - add_cert_result = - CertAddCertificateContextToStore(trust_store, - cert_context, - CERT_STORE_ADD_ALWAYS, - NULL); - if(!add_cert_result) { - char buffer[WINAPI_ERROR_LEN]; - failf(data, - "schannel: failed to add certificate from CA file '%s' " - "to certificate store: %s", - ca_file_text, - curlx_winapi_strerror(GetLastError(), buffer, - sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - more_certs = 0; - } - else { - num_certs++; - } + num_certs++; } - switch(actual_content_type) { - case CERT_QUERY_CONTENT_CERT: - case CERT_QUERY_CONTENT_SERIALIZED_CERT: - CertFreeCertificateContext(cert_context); - break; - case CERT_QUERY_CONTENT_CRL: - case CERT_QUERY_CONTENT_SERIALIZED_CRL: - CertFreeCRLContext((PCCRL_CONTEXT)cert_context); - break; - case CERT_QUERY_CONTENT_CTL: - case CERT_QUERY_CONTENT_SERIALIZED_CTL: - CertFreeCTLContext((PCCTL_CONTEXT)cert_context); - break; - } + CertFreeCertificateContext(cert_context); } } } @@ -254,73 +225,73 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, struct Curl_easy *data) { CURLcode result; - HANDLE ca_file_handle; - LARGE_INTEGER file_size; + FILE *ca_file_handle; char *ca_file_buffer = NULL; - size_t ca_file_bufsize = 0; - DWORD total_bytes_read = 0; + long ca_file_bufsize = 0; + long total_bytes_read = 0; /* * Read the CA file completely into memory before parsing it. This * optimizes for the common case where the CA file is relatively * small ( < 1 MiB ). */ - ca_file_handle = curlx_CreateFile(ca_file, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if(ca_file_handle == INVALID_HANDLE_VALUE) { - char buffer[WINAPI_ERROR_LEN]; - failf(data, "schannel: failed to open CA file '%s': %s", ca_file, - curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + ca_file_handle = curlx_fopen(ca_file, "rb"); + if(!ca_file_handle) { + failf(data, "schannel: failed to open CA file '%s'", ca_file); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } - if(!GetFileSizeEx(ca_file_handle, &file_size)) { - char buffer[WINAPI_ERROR_LEN]; - failf(data, "schannel: failed to determine size of CA file '%s': %s", - ca_file, - curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + if(curlx_fseek(ca_file_handle, 0, SEEK_END)) { + failf(data, "schannel: failed seeking to end of CA file '%s'", ca_file); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } - if(file_size.QuadPart > MAX_CAFILE_SIZE) { + ca_file_bufsize = ftell(ca_file_handle); + + if(curlx_fseek(ca_file_handle, 0, SEEK_SET)) { + failf(data, "schannel: failed seeking to beginning of CA file '%s'", + ca_file); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(ca_file_bufsize < 0) { + failf(data, "schannel: failed to get length of CA file '%s'", ca_file); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(ca_file_bufsize > MAX_CAFILE_SIZE) { failf(data, "schannel: CA file exceeds max size of %d bytes", MAX_CAFILE_SIZE); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } - ca_file_bufsize = (size_t)file_size.QuadPart; - ca_file_buffer = (char *)curlx_malloc(ca_file_bufsize + 1); + ca_file_buffer = curlx_malloc(ca_file_bufsize + 1); if(!ca_file_buffer) { result = CURLE_OUT_OF_MEMORY; goto cleanup; } while(total_bytes_read < ca_file_bufsize) { - DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read); - DWORD bytes_read = 0; + size_t nread = fread(ca_file_buffer + total_bytes_read, 1, + ca_file_bufsize - total_bytes_read, ca_file_handle); - if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, - bytes_to_read, &bytes_read, NULL)) { - char buffer[WINAPI_ERROR_LEN]; - failf(data, "schannel: failed to read from CA file '%s': %s", ca_file, - curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + if(ferror(ca_file_handle)) { + failf(data, "schannel: failed to read from CA file '%s'", ca_file); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } - if(bytes_read == 0) { + + if(nread == 0) { /* Premature EOF -- adjust the bufsize to the new value */ ca_file_bufsize = total_bytes_read; } else { - total_bytes_read += bytes_read; + total_bytes_read += (long)nread; } } @@ -333,8 +304,8 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, data); cleanup: - if(ca_file_handle != INVALID_HANDLE_VALUE) { - CloseHandle(ca_file_handle); + if(ca_file_handle) { + curlx_fclose(ca_file_handle); } curlx_safefree(ca_file_buffer); @@ -362,7 +333,7 @@ static DWORD cert_get_name_string(struct Curl_easy *data, /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */ if(Win8_compat) { -/* Offered by mingw-w64 v4+. MS SDK ~10+/~VS2017+. */ +/* Offered by mingw-w64 v4+, MS SDK 8.0/~VS2012+ */ #ifndef CERT_NAME_SEARCH_ALL_NAMES_FLAG #define CERT_NAME_SEARCH_ALL_NAMES_FLAG 0x2 #endif @@ -382,7 +353,7 @@ static DWORD cert_get_name_string(struct Curl_easy *data, if(!alt_name_info) return 0; - compute_content = host_names != NULL && length != 0; + compute_content = host_names && length != 0; /* Initialize default return values. */ actual_length = 1; @@ -509,7 +480,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) SECURITY_STATUS sspi_status; TCHAR *cert_hostname_buff = NULL; size_t cert_hostname_buff_index = 0; - const char *conn_hostname = connssl->peer.hostname; + const char *conn_hostname = connssl->peer.origin->hostname; size_t hostlen = strlen(conn_hostname); DWORD len = 0; DWORD actual_len = 0; @@ -572,7 +543,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) /* CertGetNameString guarantees that the returned name does not contain * embedded null bytes. This appears to be undocumented behavior. */ - cert_hostname_buff = (LPTSTR)curlx_malloc(len * sizeof(TCHAR)); + cert_hostname_buff = curlx_malloc(len * sizeof(TCHAR)); if(!cert_hostname_buff) { result = CURLE_OUT_OF_MEMORY; goto cleanup; @@ -734,15 +705,15 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, 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); + (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); + conn_config->CAfile, + data); } if(result == CURLE_OK) { if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) { @@ -790,9 +761,13 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, if(result == CURLE_OK) { CERT_CHAIN_PARA ChainPara; + LPSTR serverAuthOID = CURL_UNCONST(szOID_PKIX_KP_SERVER_AUTH); memset(&ChainPara, 0, sizeof(ChainPara)); ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = 1; + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = &serverAuthOID; if(!CertGetCertificateChain(cert_chain_engine, pCertContextServer, @@ -815,7 +790,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; - if(data->set.ssl.revoke_best_effort) { + if(ssl_config->revoke_best_effort) { /* Ignore errors when root certificates are missing the revocation * list URL, or when the list could not be downloaded because the * server is currently unreachable. */ diff --git a/lib/vtls/unitytls.c b/lib/vtls/unitytls.c index a6eed64e92ae..dcb202809ea5 100644 --- a/lib/vtls/unitytls.c +++ b/lib/vtls/unitytls.c @@ -287,7 +287,7 @@ static unitytls_x509verify_result unitytls_on_verify(void* userData, unitytls_x5 struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); const bool verifypeer = conn_config->verifypeer; const bool verifyhost = conn_config->verifyhost; - const char* const hostname = connssl->peer.hostname; + const char* const hostname = connssl->peer.origin->hostname; unitytls_x509verify_result verify_result = UNITYTLS_X509VERIFY_SUCCESS; #if UNITY_CERTVERIFY @@ -440,8 +440,8 @@ static CURLcode unitytls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy const char* const ssl_capath = conn_config->CApath; char* const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *ssl_key_blob = ssl_config->key_blob; - const char* const hostname = connssl->peer.hostname; + const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; + const char* const hostname = connssl->peer.origin->hostname; unitytls_errorstate err = unitytls->unitytls_errorstate_create(); @@ -507,15 +507,15 @@ static CURLcode unitytls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy } /* Load the client private key */ - if(ssl_config->key) { - backend->pk = unitytls_key_parse_pem_from_file(ssl_config->key, ssl_config->key_passwd, &err); + if(ssl_config->primary.key) { + backend->pk = unitytls_key_parse_pem_from_file(ssl_config->primary.key, ssl_config->primary.key_passwd, &err); if(!backend->pk || err.code != UNITYTLS_SUCCESS) { - failf(data, "Error reading private key %s", ssl_config->key); + failf(data, "Error reading private key %s", ssl_config->primary.key); return CURLE_SSL_CERTPROBLEM; } } else if(ssl_key_blob) { - backend->pk = unitytls_key_parse_pem_from_blob(ssl_key_blob, ssl_config->key_passwd, &err); + backend->pk = unitytls_key_parse_pem_from_blob(ssl_key_blob, ssl_config->primary.key_passwd, &err); if(!backend->pk || err.code != UNITYTLS_SUCCESS) { failf(data, "Error parsing private key blob"); return CURLE_SSL_CERTPROBLEM; diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 416fe9c74cde..ed6346a2f195 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -36,14 +36,10 @@ "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ + */ #include "curl_setup.h" -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include "urldata.h" #include "cfilters.h" @@ -75,6 +71,7 @@ #include "connect.h" #include "select.h" #include "setopt.h" +#include "vdns/cf-dns.h" #include "curlx/strdup.h" #include "curlx/strcopy.h" @@ -82,57 +79,6 @@ #include #endif - -#define CLONE_STRING(var) \ - do { \ - if(source->var) { \ - dest->var = curlx_strdup(source->var); \ - if(!dest->var) \ - return FALSE; \ - } \ - else \ - dest->var = NULL; \ - } while(0) - -#define CLONE_BLOB(var) \ - do { \ - if(blobdup(&dest->var, source->var)) \ - return FALSE; \ - } while(0) - -static CURLcode blobdup(struct curl_blob **dest, struct curl_blob *src) -{ - DEBUGASSERT(dest); - DEBUGASSERT(!*dest); - if(src) { - /* only if there is data to dupe! */ - struct curl_blob *d; - d = curlx_malloc(sizeof(struct curl_blob) + src->len); - if(!d) - return CURLE_OUT_OF_MEMORY; - d->len = src->len; - /* Always duplicate because the connection may survive longer than the - handle that passed in the blob. */ - d->flags = CURL_BLOB_COPY; - d->data = (void *)((char *)d + sizeof(struct curl_blob)); - memcpy(d->data, src->data, src->len); - *dest = d; - } - return CURLE_OK; -} - -/* returns TRUE if the blobs are identical */ -static bool blobcmp(struct curl_blob *first, struct curl_blob *second) -{ - if(!first && !second) /* both are NULL */ - return TRUE; - if(!first || !second) /* one is NULL */ - return FALSE; - if(first->len != second->len) /* different sizes */ - return FALSE; - return !memcmp(first->data, second->data, first->len); /* same data */ -} - #ifdef USE_SSL #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_PROXY) static const struct alpn_spec ALPN_SPEC_H11 = { @@ -181,57 +127,6 @@ static const struct alpn_spec *alpn_get_spec(http_majors wanted, #endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_PROXY */ #endif /* USE_SSL */ -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) && -#if UNITY_CERTVERIFY - (c1->unity_certverify == c2->unity_certverify) && - (c1->unity_certverify_userp == c2->unity_certverify_userp) && -#endif /* UNITY_CERTVERIFY */ - 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(c1->username, c2->username) && - !Curl_timestrcmp(c1->password, c2->password) && -#endif - curl_strequal(c1->cipher_list, c2->cipher_list) && - curl_strequal(c1->cipher_list13, c2->cipher_list13) && - curl_strequal(c1->curves, c2->curves) && - curl_strequal(c1->signature_algorithms, c2->signature_algorithms) && - curl_strequal(c1->CRLfile, c2->CRLfile) && - curl_strequal(c1->pinned_key, c2->pinned_key)) - return TRUE; - - return FALSE; -} - #if UNITY_CERTVERIFY CURLcode Curl_unity_certverify(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -264,222 +159,6 @@ CURLcode Curl_unity_certverify(struct Curl_cfilter *cf, } #endif /* UNITY_CERTVERIFY */ -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->cache_session = source->cache_session; - dest->ssl_options = source->ssl_options; -#if UNITY_CERTVERIFY - dest->unity_certverify = source->unity_certverify; - dest->unity_certverify_userp = source->unity_certverify_userp; -#endif /* UNITY_CERTVERIFY */ - - CLONE_BLOB(cert_blob); - CLONE_BLOB(ca_info_blob); - CLONE_BLOB(issuercert_blob); - CLONE_STRING(CApath); - CLONE_STRING(CAfile); - CLONE_STRING(issuercert); - CLONE_STRING(clientcert); - CLONE_STRING(cipher_list); - 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); - CLONE_STRING(password); -#endif - - return TRUE; -} - -static void free_primary_ssl_config(struct ssl_primary_config *sslc) -{ - curlx_safefree(sslc->CApath); - curlx_safefree(sslc->CAfile); - curlx_safefree(sslc->issuercert); - curlx_safefree(sslc->clientcert); - curlx_safefree(sslc->cipher_list); - curlx_safefree(sslc->cipher_list13); - curlx_safefree(sslc->pinned_key); - curlx_safefree(sslc->cert_blob); - curlx_safefree(sslc->ca_info_blob); - curlx_safefree(sslc->issuercert_blob); - curlx_safefree(sslc->curves); - curlx_safefree(sslc->signature_algorithms); - curlx_safefree(sslc->CRLfile); -#ifdef USE_TLS_SRP - curlx_safefree(sslc->username); - curlx_safefree(sslc->password); -#endif -} - -CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) -{ - struct ssl_config_data *sslc = &data->set.ssl; -#if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) - struct UserDefined *set = &data->set; - CURLcode result; -#endif - - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) - if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->native_ca_store = TRUE; -#endif -#ifdef CURL_CA_PATH - if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); - if(result) - return result; - } -#endif -#ifdef CURL_CA_BUNDLE - if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); - if(result) - return result; - } -#endif - } - sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE]; - sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH]; - sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; - sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; - sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST]; - sslc->primary.signature_algorithms = - data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS]; - sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - sslc->primary.curves = data->set.str[STRING_SSL_EC_CURVES]; -#ifdef USE_TLS_SRP - sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; - sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; -#endif - sslc->cert_type = data->set.str[STRING_CERT_TYPE]; - sslc->key = data->set.str[STRING_KEY]; - sslc->key_type = data->set.str[STRING_KEY_TYPE]; - sslc->key_passwd = data->set.str[STRING_KEY_PASSWD]; - sslc->primary.clientcert = data->set.str[STRING_CERT]; - sslc->key_blob = data->set.blobs[BLOB_KEY]; - -#ifndef CURL_DISABLE_PROXY - sslc = &data->set.proxy_ssl; - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) - if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->native_ca_store = TRUE; -#endif -#ifdef CURL_CA_PATH - if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH_PROXY]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], - CURL_CA_PATH); - if(result) - return result; - } -#endif -#ifdef CURL_CA_BUNDLE - if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE_PROXY]) { - result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], - CURL_CA_BUNDLE); - if(result) - return result; - } -#endif - } - sslc->primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - sslc->primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - sslc->primary.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - sslc->primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; - sslc->primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; - sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY]; - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY]; - sslc->cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - sslc->key = data->set.str[STRING_KEY_PROXY]; - sslc->key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - sslc->key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - sslc->primary.clientcert = data->set.str[STRING_CERT_PROXY]; - sslc->key_blob = data->set.blobs[BLOB_KEY_PROXY]; -#ifdef USE_TLS_SRP - sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - sslc->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 easy handle to - * the connection. They are used for connection cache matching and - * probably outlive the easy handle */ - if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config)) - 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 @@ -572,7 +251,7 @@ static void cf_ctx_free(struct ssl_connect_data *ctx) } } -CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding) { if(Curl_ssl->get_channel_binding) @@ -661,8 +340,7 @@ void Curl_ssl_free_certinfo(struct Curl_easy *data) ci->certinfo[i] = NULL; } - curlx_free(ci->certinfo); /* free the actual array too */ - ci->certinfo = NULL; + curlx_safefree(ci->certinfo); /* free the actual array too */ ci->num_of_certs = 0; } } @@ -841,8 +519,8 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, pinned_hash = pinnedpubkey; while(pinned_hash && - !strncmp(pinned_hash, "sha256//", (sizeof("sha256//") - 1))) { - pinned_hash = pinned_hash + (sizeof("sha256//") - 1); + !strncmp(pinned_hash, "sha256//", CURL_CSTRLEN("sha256//"))) { + pinned_hash = pinned_hash + CURL_CSTRLEN("sha256//"); end_pos = strchr(pinned_hash, ';'); pinned_hash_len = end_pos ? (size_t)(end_pos - pinned_hash) : strlen(pinned_hash); @@ -899,7 +577,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, do { char buffer[1024]; size_t want = left > sizeof(buffer) ? sizeof(buffer) : left; - if(want != fread(buffer, 1, want, fp)) + if(fread(buffer, 1, want, fp) != want) goto end; if(curlx_dyn_addn(&buf, buffer, want)) goto end; @@ -1229,26 +907,14 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, void Curl_ssl_peer_cleanup(struct ssl_peer *peer) { + Curl_peer_unlink(&peer->origin); + Curl_peer_unlink(&peer->peer); curlx_safefree(peer->sni); - if(peer->dispname != peer->hostname) - curlx_free(peer->dispname); - peer->dispname = NULL; - curlx_safefree(peer->hostname); curlx_safefree(peer->scache_key); + peer->transport = TRNSPRT_NONE; 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) { - connssl->ssl_impl->close(cf, data); - connssl->state = ssl_connection_none; - Curl_ssl_peer_cleanup(&connssl->peer); - } - cf->connected = FALSE; -} - static ssl_peer_type get_peer_type(const char *hostname) { if(hostname && hostname[0]) { @@ -1268,100 +934,66 @@ static ssl_peer_type get_peer_type(const char *hostname) return CURL_SSL_PEER_DNS; } -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, - struct Curl_cfilter *cf, +CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, uint8_t 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; - peer->port = cf->conn->http_proxy.port; - } - else -#endif - { - ehostname = cf->conn->host.name; - edispname = cf->conn->host.dispname; - peer->port = (uint16_t)cf->conn->remote_port; - } - - /* hostname MUST exist and not be empty */ - if(!ehostname || !ehostname[0]) { - result = CURLE_FAILED_INIT; - goto out; + if(!ssl_peer || !origin) { + DEBUGASSERT(0); + return CURLE_FAILED_INIT; } + DEBUGASSERT(!ssl_peer->origin); + DEBUGASSERT(!ssl_peer->peer); + DEBUGASSERT(!ssl_peer->sni); + ssl_peer->transport = transport; - peer->hostname = curlx_strdup(ehostname); - if(!peer->hostname) - goto out; - if(!edispname || !strcmp(ehostname, edispname)) - peer->dispname = peer->hostname; - else { - peer->dispname = curlx_strdup(edispname); - if(!peer->dispname) - goto out; - } - peer->type = get_peer_type(peer->hostname); - if(peer->type == CURL_SSL_PEER_DNS) { + Curl_peer_link(&ssl_peer->origin, origin); + Curl_peer_link(&ssl_peer->peer, peer); + ssl_peer->type = get_peer_type(origin->hostname); + if(ssl_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] == '.')) + size_t len = strlen(origin->hostname); + if(len && (origin->hostname[len - 1] == '.')) len--; if(len < USHRT_MAX) { - peer->sni = curlx_calloc(1, len + 1); - if(!peer->sni) + ssl_peer->sni = curlx_calloc(1, len + 1); + if(!ssl_peer->sni) goto out; - Curl_strntolower(peer->sni, peer->hostname, len); - peer->sni[len] = 0; + Curl_strntolower(ssl_peer->sni, origin->hostname, len); + ssl_peer->sni[len] = 0; } } - result = Curl_ssl_peer_key_make(cf, peer, tls_id, &peer->scache_key); + result = Curl_ssl_peer_key_make(ssl_peer, sslc, tls_id, + &ssl_peer->scache_key); out: if(result) - Curl_ssl_peer_cleanup(peer); + Curl_ssl_peer_cleanup(ssl_peer); return result; } static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - cf_close(cf, data); - CF_DATA_RESTORE(cf, save); - cf_ctx_free(cf->ctx); - cf->ctx = NULL; -} - -static void ssl_cf_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - cf_close(cf, data); - if(cf->next) - cf->next->cft->do_close(cf->next, data); - CF_DATA_RESTORE(cf, save); + struct ssl_connect_data *connssl = cf->ctx; + if(connssl) { + connssl->ssl_impl->close(cf, data); + connssl->state = ssl_connection_none; + connssl->connecting_state = ssl_connect_1; + connssl->prefs_checked = FALSE; + Curl_ssl_peer_cleanup(&connssl->peer); + Curl_ssl_session_destroy(connssl->session); + cf_ctx_free(connssl); + cf->ctx = NULL; + } + cf->connected = FALSE; } static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, @@ -1377,7 +1009,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - if(!cf->next) { + if(!cf->next || !connssl->peer.origin) { *done = FALSE; return CURLE_FAILED_INIT; } @@ -1402,14 +1034,6 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, connssl->prefs_checked = TRUE; } - 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; - } - result = connssl->ssl_impl->do_connect(cf, data, done); if(!result && *done) { @@ -1431,7 +1055,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, connssl->earlydata_state > ssl_earlydata_none); } out: - CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", result, *done); + CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", (int)result, *done); CF_DATA_RESTORE(cf, save); return result; } @@ -1481,7 +1105,10 @@ static CURLcode ssl_cf_connect_deferred(struct Curl_cfilter *cf, result = ssl_cf_connect(cf, data, done); if(!result && *done) { - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + if(!connssl->stats_reported && (cf->cft == &Curl_cft_ssl)) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + connssl->stats_reported = TRUE; + } switch(connssl->earlydata_state) { case ssl_earlydata_none: break; @@ -1621,7 +1248,7 @@ static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf, 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); + CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", (int)result, *done); CF_DATA_RESTORE(cf, save); cf->shutdown = (result || *done); } @@ -1649,12 +1276,6 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; switch(query) { - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected && !Curl_ssl_cf_is_proxy(cf)) - *when = connssl->handshake_done; - return CURLE_OK; - } case CF_QUERY_SSL_INFO: case CF_QUERY_SSL_CTX_INFO: if(!Curl_ssl_cf_is_proxy(cf)) { @@ -1704,6 +1325,14 @@ static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, cf->conn->httpversion_seen = 30; } break; + case CF_CTRL_REPORT_STATS: + if(cf->connected && !connssl->stats_reported && + (cf->cft == &Curl_cft_ssl) && + (connssl->handshake_done.tv_sec || connssl->handshake_done.tv_usec)) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + connssl->stats_reported = TRUE; + } + break; } return CURLE_OK; } @@ -1725,7 +1354,6 @@ struct Curl_cftype Curl_cft_ssl = { CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, - ssl_cf_close, ssl_cf_shutdown, ssl_cf_adjust_pollset, ssl_cf_data_pending, @@ -1745,7 +1373,6 @@ struct Curl_cftype Curl_cft_ssl_proxy = { CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, - ssl_cf_close, ssl_cf_shutdown, ssl_cf_adjust_pollset, ssl_cf_data_pending, @@ -1794,28 +1421,65 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, return result; } +static CURLcode cf_ssl_peer_init(struct Curl_cfilter *cf, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc) +{ + struct ssl_connect_data *connssl = cf->ctx; + char tls_id[80]; + connssl->ssl_impl->version(tls_id, sizeof(tls_id) - 1); + return Curl_ssl_peer_init(&connssl->peer, origin, peer, sslc, + tls_id, TRNSPRT_TCP); +} + CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct Curl_peer *origin, struct connectdata *conn, - int sockindex) + int8_t sockindex) { struct Curl_cfilter *cf; + struct Curl_peer *peer = (sockindex == SECONDARYSOCKET) ? + conn->via_peer2 : conn->via_peer; CURLcode result; result = cf_ssl_create(&cf, data, conn); + if(!result) + result = cf_ssl_peer_init(cf, origin, peer, &conn->ssl_config); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer) { struct Curl_cfilter *cf; CURLcode result; result = cf_ssl_create(&cf, data, cf_at->conn); if(!result) + result = cf_ssl_peer_init(cf, origin, peer, &cf_at->conn->ssl_config); + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if((origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + } + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } @@ -1840,7 +1504,7 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, } #endif - ctx = cf_ctx_new(data, alpn_get_spec(wanted, 0, false, use_alpn)); + ctx = cf_ctx_new(data, alpn_get_spec(wanted, 0, FALSE, use_alpn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1855,14 +1519,19 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, } CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *peer) { struct Curl_cfilter *cf; CURLcode result; result = cf_ssl_proxy_create(&cf, data, cf_at->conn); + if(!result) + result = cf_ssl_peer_init(cf, peer, NULL, &cf_at->conn->proxy_ssl_config); if(!result) Curl_conn_cf_insert_after(cf_at, cf); + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } @@ -1929,7 +1598,7 @@ static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, } CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex, bool send_shutdown) + int8_t sockindex, bool send_shutdown) { struct Curl_cfilter *cf, *head; CURLcode result = CURLE_OK; @@ -1945,7 +1614,8 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, if(!result && !done) /* blocking failed? */ result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard(&cf, data); - CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", result); + CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", + (int)result); break; } } @@ -1957,8 +1627,8 @@ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } -struct ssl_config_data * -Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data) +struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, + struct Curl_easy *data) { #ifdef CURL_DISABLE_PROXY (void)cf; @@ -1968,8 +1638,8 @@ Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data) #endif } -struct ssl_primary_config * -Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf) +struct ssl_primary_config *Curl_ssl_cf_get_primary_config( + struct Curl_cfilter *cf) { #ifdef CURL_DISABLE_PROXY return &cf->conn->ssl_config; @@ -2153,4 +1823,30 @@ CURLcode Curl_on_session_reuse(struct Curl_cfilter *cf, return result; } +struct Curl_ssl_session *Curl_ssl_get_cf_session(struct Curl_easy *data, + const struct Curl_cftype *cft, + int8_t sockindex) +{ + if(data->conn && +#ifndef CURL_DISABLE_PROXY + ((cft == &Curl_cft_ssl) || (cft == &Curl_cft_ssl_proxy))) { +#else + (cft == &Curl_cft_ssl)) { +#endif + struct Curl_cfilter *cf1 = data->conn->cfilter[sockindex]; + for(; cf1; cf1 = cf1->next) { + /* A tunneling proxy does not offer end2end encryption, even if + * it does SSL itself (e.g. QUIC H3 proxy) */ + if(cf1->cft == cft) + break; + } + if(cf1) { + struct ssl_connect_data *connssl = cf1->ctx; + if(connssl) + return connssl->session; + } + } + return NULL; +} + #endif /* USE_SSL */ diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 5e415736b373..5eae5bd6a267 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -49,15 +49,13 @@ struct dynbuf; #define SSLSUPP_ISSUERCERT_BLOB (1 << 14) /* CURLOPT_ISSUERCERT_BLOB */ #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) - -#define CURLECH_ENABLED(data) \ - ((data)->set.tls_ech && !((data)->set.tls_ech & CURLECH_DISABLE)) +/* CURLECH_ values for the tls_ech option */ +#define CURLECH_DISABLE 0 +#define CURLECH_GREASE 1 +#define CURLECH_ENABLE 2 +#define CURLECH_HARD 3 + +#define CURLECH_ENABLED(data) ((data)->set.tls_ech) #endif /* USE_ECH */ #define ALPN_ACCEPTED "ALPN: server accepted " @@ -91,12 +89,11 @@ typedef enum { } ssl_peer_type; struct ssl_peer { - char *hostname; /* hostname for verification */ - char *dispname; /* display version of hostname */ + struct Curl_peer *origin; /* the authority we talk to */ + struct Curl_peer *peer; /* the machine we are connected to */ 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 */ - uint16_t port; /* port we are talking to */ uint8_t transport; /* one of TRNSPRT_* defines */ }; @@ -107,43 +104,6 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, 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. Works for `verifypeer`, - * `verifyhost` and `verifystatus`. */ -void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); - #if UNITY_CERTVERIFY /** * Unity: hand the DER-encoded peer certificate to the @@ -160,8 +120,10 @@ CURLcode Curl_unity_certverify(struct Curl_cfilter *cf, /** * Init SSL peer information for filter. Can be called repeatedly. */ -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, - struct Curl_cfilter *cf, +CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, uint8_t transport); /** @@ -207,7 +169,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, bool Curl_ssl_cert_status_request(void); /* 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, + * RFC 5929, Section 4.1. The 'tls-server-end-point:' prefix is 21 bytes, * 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. @@ -222,24 +184,28 @@ bool Curl_ssl_cert_status_request(void); * 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, +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding); #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct Curl_peer *origin, struct connectdata *conn, - int sockindex); + int8_t sockindex); CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer); CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex, bool send_shutdown); + int8_t sockindex, bool send_shutdown); #ifndef CURL_DISABLE_PROXY CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *peer); #endif /* !CURL_DISABLE_PROXY */ /** @@ -258,8 +224,8 @@ struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, /** * 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); +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 @@ -279,7 +245,7 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_random(x, y, z) ((void)(x), CURLE_NOT_BUILT_IN) #define Curl_ssl_cert_status_request() FALSE #define Curl_ssl_supports(a, b) FALSE -#define Curl_ssl_cfilter_add(a, b, c) CURLE_NOT_BUILT_IN +#define Curl_ssl_cfilter_add(a, b, c, d) CURLE_NOT_BUILT_IN #define Curl_ssl_cfilter_remove(a, b, c) CURLE_OK #define Curl_ssl_cf_get_config(a, b) NULL #define Curl_ssl_cf_get_primary_config(a) NULL diff --git a/lib/vtls/vtls_config.c b/lib/vtls/vtls_config.c new file mode 100644 index 000000000000..080d513bb598 --- /dev/null +++ b/lib/vtls/vtls_config.c @@ -0,0 +1,406 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 implementing all "generic" SSL functions that all libcurl + internals should use. It is then responsible for calling the proper + "backend" function. + + SSL-functions in libcurl should call functions in this source file, and not + to any specific SSL-layer. + + Curl_ssl_ - prefix for generic ones + + Note that this source code uses the functions of the configured SSL + backend via the global Curl_ssl instance. + + "SSL/TLS Strong Encryption: An Introduction" + https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html + */ + +#include "curl_setup.h" + +#include "urldata.h" +#include "setopt.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "vtls/vtls_config.h" + + +#define CLONE_STRING(var) \ + do { \ + if(source->var) { \ + dest->var = curlx_strdup(source->var); \ + if(!dest->var) \ + return FALSE; \ + } \ + else \ + dest->var = NULL; \ + } while(0) + +#define CLONE_BLOB(var) \ + do { \ + if(blobdup(&dest->var, source->var)) \ + return FALSE; \ + } while(0) + +static CURLcode blobdup(struct curl_blob **dest, struct curl_blob *src) +{ + DEBUGASSERT(dest); + DEBUGASSERT(!*dest); + if(src) { + /* only if there is data to dupe! */ + struct curl_blob *d; + d = curlx_malloc(sizeof(struct curl_blob) + src->len); + if(!d) + return CURLE_OUT_OF_MEMORY; + d->len = src->len; + /* Always duplicate because the connection may survive longer than the + handle that passed in the blob. */ + d->flags = CURL_BLOB_COPY; + d->data = (void *)((char *)d + sizeof(struct curl_blob)); + memcpy(d->data, src->data, src->len); + *dest = d; + } + return CURLE_OK; +} + +/* returns TRUE if the blobs are identical */ +static bool blobcmp(struct curl_blob *first, struct curl_blob *second) +{ + if(!first && !second) /* both are NULL */ + return TRUE; + if(!first || !second) /* one is NULL */ + return FALSE; + if(first->len != second->len) /* different sizes */ + return FALSE; + return !memcmp(first->data, second->data, first->len); /* same data */ +} + +void Curl_ssl_config_init(struct ssl_primary_config *sslc) +{ + /* + * libcurl 7.10 introduced SSL verification *by default*! This needs to be + * switched off unless wanted. + */ + sslc->verifypeer = TRUE; + sslc->verifyhost = TRUE; + sslc->cache_session = TRUE; /* caching by default */ +} + +void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) +{ + if(sslc->deep_copy) { + curlx_safefree(sslc->CApath); + curlx_safefree(sslc->CAfile); + curlx_safefree(sslc->issuercert); + curlx_safefree(sslc->clientcert); + curlx_safefree(sslc->cipher_list); + curlx_safefree(sslc->cipher_list13); + curlx_safefree(sslc->pinned_key); + curlx_safefree(sslc->cert_blob); + curlx_safefree(sslc->ca_info_blob); + curlx_safefree(sslc->issuercert_blob); + curlx_safefree(sslc->key_blob); + curlx_safefree(sslc->curves); + curlx_safefree(sslc->signature_algorithms); + curlx_safefree(sslc->CRLfile); + curlx_safefree(sslc->cert_type); + curlx_safefree(sslc->key); + curlx_safefree(sslc->key_type); + curlx_safefree(sslc->key_passwd); + sslc->deep_copy = FALSE; + } +} + +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->native_ca_store == c2->native_ca_store) && + (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) && + blobcmp(c1->key_blob, c2->key_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) && + curl_strequal(c1->cipher_list, c2->cipher_list) && + curl_strequal(c1->cipher_list13, c2->cipher_list13) && + curl_strequal(c1->curves, c2->curves) && + curl_strequal(c1->signature_algorithms, c2->signature_algorithms) && + Curl_safecmp(c1->CRLfile, c2->CRLfile) && + Curl_safecmp(c1->pinned_key, c2->pinned_key) && + curl_strequal(c1->cert_type, c2->cert_type) && + Curl_safecmp(c1->key, c2->key) && + curl_strequal(c1->key_type, c2->key_type) && + !Curl_timestrcmp(c1->key_passwd, c2->key_passwd)) + return TRUE; + + return FALSE; +} + +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) +{ + DEBUGASSERT(!dest->deep_copy); + dest->deep_copy = TRUE; + dest->version = source->version; + dest->version_max = source->version_max; + dest->verifypeer = source->verifypeer; + dest->verifyhost = source->verifyhost; + dest->verifystatus = source->verifystatus; + dest->native_ca_store = source->native_ca_store; + dest->cache_session = source->cache_session; + dest->ssl_options = source->ssl_options; + + CLONE_BLOB(cert_blob); + CLONE_BLOB(ca_info_blob); + CLONE_BLOB(issuercert_blob); + CLONE_STRING(CApath); + CLONE_STRING(CAfile); + CLONE_STRING(issuercert); + CLONE_STRING(cipher_list); + CLONE_STRING(cipher_list13); + CLONE_STRING(pinned_key); + CLONE_STRING(curves); + CLONE_STRING(signature_algorithms); + CLONE_STRING(CRLfile); + /* SSL credentials: client certificate */ + CLONE_STRING(clientcert); + CLONE_STRING(cert_type); + CLONE_STRING(key); + CLONE_STRING(key_type); + CLONE_STRING(key_passwd); + CLONE_BLOB(key_blob); + return TRUE; +} + +static void ssl_easy_config_compl_options(struct Curl_peer *origin, + struct Curl_peer *initial_origin, + struct ssl_config_data *sslc) +{ + uint8_t options = sslc->primary.ssl_options; + /* If set via CURLOPT_(PROXY_)SSL_OPTIONS, we definitely use it. + * If not, we switch it on for supported backends if no custom + * CA settings exist. */ + sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); + sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); + sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); + sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); + sslc->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); + sslc->earlydata = !!(options & CURLSSLOPT_EARLYDATA); + + sslc->auto_client_cert = Curl_peer_equal(origin, initial_origin) && + !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); +} + +static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) +{ + /* For connection matching, we borrow string references from data + * THIS IS NOT REALLY NICE. */ + return CURL_UNCONST(CURL_EASY_STR(data, id)); +} + +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, + struct Curl_peer *origin) +{ + struct ssl_config_data *sslc = &data->set.ssl; +#if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) + CURLcode result; +#endif + + ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc); + + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { +#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) + if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) + sslc->primary.native_ca_store = TRUE; +#endif +#ifdef CURL_CA_PATH + if(!sslc->custom_capath && !CURL_EASY_STR(data, STRING_SSL_CAPATH)) { + result = Curl_setstropt(data, STRING_SSL_CAPATH, CURL_CA_PATH); + if(result) + return result; + } +#endif +#ifdef CURL_CA_BUNDLE + if(!sslc->custom_cafile && !CURL_EASY_STR(data, STRING_SSL_CAFILE)) { + result = Curl_setstropt(data, STRING_SSL_CAFILE, CURL_CA_BUNDLE); + if(result) + return result; + } +#endif + } + sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); + sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); + sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); + sslc->primary.cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); + sslc->primary.cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); + sslc->primary.signature_algorithms = + ssl_easy_steal(data, STRING_SSL_SIGNATURE_ALGORITHMS); + sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; + sslc->primary.curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); + /* Maybe these should not be used for another origin. But for + * backwards compatibility, keep them in. */ + sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); + sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + + if(Curl_peer_equal(data->state.initial_origin, origin)) { + sslc->primary.pinned_key = + ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY); + sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; + sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); + sslc->primary.key = ssl_easy_steal(data, STRING_KEY); + sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE); + sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); + sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT); + sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; + } + else { + sslc->primary.pinned_key = NULL; + sslc->primary.cert_blob = NULL; + sslc->primary.cert_type = NULL; + sslc->primary.key = NULL; + sslc->primary.key_type = NULL; + sslc->primary.key_passwd = NULL; + sslc->primary.clientcert = NULL; + sslc->primary.key_blob = NULL; + } + +#ifndef CURL_DISABLE_PROXY + sslc = &data->set.proxy_ssl; + /* no initial origin for proxy, it is not changed for redirects */ + ssl_easy_config_compl_options(NULL, NULL, sslc); + + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { +#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) + if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) + sslc->primary.native_ca_store = TRUE; +#endif +#ifdef CURL_CA_PATH + if(!sslc->custom_capath && + !CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY)) { + result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, CURL_CA_PATH); + if(result) + return result; + } +#endif +#ifdef CURL_CA_BUNDLE + if(!sslc->custom_cafile && + !CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY)) { + result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, CURL_CA_BUNDLE); + if(result) + return result; + } +#endif + } + sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); + sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); + sslc->primary.cipher_list = + ssl_easy_steal(data, STRING_SSL_CIPHER_LIST_PROXY); + sslc->primary.cipher_list13 = + ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST_PROXY); + sslc->primary.pinned_key = + ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY_PROXY); + sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; + sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); + sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); + sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); + sslc->primary.key = ssl_easy_steal(data, STRING_KEY_PROXY); + sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); + sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); + sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); + sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#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 easy handle to + * the connection. They are used for connection cache matching and + * probably outlive the easy handle */ + if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config)) + 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) +{ + Curl_ssl_config_cleanup(&conn->ssl_config); +#ifndef CURL_DISABLE_PROXY + Curl_ssl_config_cleanup(&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; + } +} diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h new file mode 100644 index 000000000000..8296604259bb --- /dev/null +++ b/lib/vtls/vtls_config.h @@ -0,0 +1,121 @@ +#ifndef HEADER_CURL_VTLS_CONFIG_H +#define HEADER_CURL_VTLS_CONFIG_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" + +struct Curl_easy; +struct connectdata; +struct Curl_peer; + +struct ssl_primary_config { + char *CApath; /* certificate directory (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 */ + char *cert_type; /* format for certificate (default: PEM) */ + char *key; /* private key filename */ + char *key_type; /* format for private key (default: PEM) */ + char *key_passwd; /* plain text private key password */ + struct curl_blob *cert_blob; + struct curl_blob *ca_info_blob; + struct curl_blob *issuercert_blob; + struct curl_blob *key_blob; + char *curves; /* list of curves to use */ + uint32_t version_max; /* max supported version the client wants to use */ + uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ + uint8_t version; /* what version the client wants to use */ + BIT(verifypeer); /* set TRUE if this is desired */ + BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ + BIT(verifystatus); /* set TRUE if certificate status must be checked */ + BIT(native_ca_store); /* use the native CA store of operating system */ + BIT(cache_session); /* cache session or not */ + BIT(deep_copy); /* members are deep copies, eg. owned here */ +}; + +struct ssl_config_data { + struct ssl_primary_config primary; + long certverifyresult; /* result from the certificate verification */ + curl_ssl_ctx_callback fsslctx; /* function to initialize SSL ctx */ + void *fsslctxp; /* parameter for call back */ + BIT(certinfo); /* gather lots of certificate info */ + BIT(earlydata); /* use TLS 1.3 early data */ + BIT(enable_beast); /* allow this flaw for interoperability's sake */ + BIT(no_revoke); /* disable SSL certificate revocation checks */ + BIT(no_partialchain); /* do not accept partial certificate chains */ + BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation + list errors */ + BIT(auto_client_cert); /* automatically locate and use a client + certificate for authentication (Schannel) */ + BIT(custom_cafile); /* application has set custom CA file */ + BIT(custom_capath); /* application has set custom CA path */ + BIT(custom_cablob); /* application has set custom CA blob */ +}; + +struct ssl_general_config { + int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ +}; + +void Curl_ssl_config_init(struct ssl_primary_config *sslc); +void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc); + +/** + * Init the `data->set.ssl` and `data->set.proxy_ssl` for + * connection matching use. + */ +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, + struct Curl_peer *origin); + +/** + * Init 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. Works for `verifypeer`, + * `verifyhost` and `verifystatus`. */ +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); + +#endif /* HEADER_CURL_VTLS_CONFIG_H */ diff --git a/lib/vtls/vtls_int.h b/lib/vtls/vtls_int.h index 6700ee74cbe1..7c1881b8d847 100644 --- a/lib/vtls/vtls_int.h +++ b/lib/vtls/vtls_int.h @@ -38,20 +38,20 @@ struct Curl_ssl_session; /* see https://www.iana.org/assignments/tls-extensiontype-values/ */ #define ALPN_HTTP_1_0_LENGTH 8 -#define ALPN_HTTP_1_0 "http/1.0" +#define ALPN_HTTP_1_0 "http/1.0" #define ALPN_HTTP_1_1_LENGTH 8 -#define ALPN_HTTP_1_1 "http/1.1" -#define ALPN_H2_LENGTH 2 -#define ALPN_H2 "h2" -#define ALPN_H3_LENGTH 2 -#define ALPN_H3 "h3" +#define ALPN_HTTP_1_1 "http/1.1" +#define ALPN_H2_LENGTH 2 +#define ALPN_H2 "h2" +#define ALPN_H3_LENGTH 2 +#define ALPN_H3 "h3" /* conservative sizes on the ALPN entries and count we are handling, * we can increase these if we ever feel the need or have to accommodate * ALPN strings from the "outside". */ -#define ALPN_NAME_MAX 10 -#define ALPN_ENTRIES_MAX 3 -#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) +#define ALPN_NAME_MAX 10 +#define ALPN_ENTRIES_MAX 3 +#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) struct alpn_spec { char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; @@ -116,6 +116,7 @@ struct ssl_connect_data { const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ void *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ + struct Curl_ssl_session *session; /* TLS session in use or NULL */ struct curltime handshake_done; /* time when handshake finished */ struct { char *alpn; /* ALPN value or NULL */ @@ -131,11 +132,9 @@ struct ssl_connect_data { BIT(peer_closed); /* peer has closed connection */ BIT(prefs_checked); /* SSL preferences have been checked */ BIT(input_pending); /* data for SSL_read() may be available */ + BIT(stats_reported); /* connect times have been reported */ }; -#undef CF_CTX_CALL_DATA -#define CF_CTX_CALL_DATA(cf) ((struct ssl_connect_data *)(cf)->ctx)->call_data - /* Definitions for SSL Implementations */ struct Curl_ssl { @@ -186,7 +185,7 @@ struct Curl_ssl { CURLcode (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t len, size_t *pnwritten); - CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex, + CURLcode (*get_channel_binding)(struct Curl_easy *data, int8_t sockindex, struct dynbuf *binding); }; @@ -206,6 +205,19 @@ CURLcode Curl_on_session_reuse(struct Curl_cfilter *cf, struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data, bool early_data_allowed); + +/* Retrieve the SSL session held at the filter of type `cft` at + * data's connection at `sockindex` or NULL if not found/available. */ +struct Curl_ssl_session *Curl_ssl_get_cf_session(struct Curl_easy *data, + const struct Curl_cftype *cft, + int8_t sockindex); + #endif /* USE_SSL */ #endif /* HEADER_CURL_VTLS_INT_H */ + +#ifdef USE_SSL +/* Restore the default SSL filter call_data accessor for unity builds. */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) ((struct ssl_connect_data *)(cf)->ctx)->call_data +#endif diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index 52c83698f9df..8140ad6c87ba 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -25,10 +25,6 @@ #ifdef USE_SSL -#ifdef HAVE_SYS_TYPES_H -#include -#endif - #include "urldata.h" #include "cfilters.h" @@ -41,17 +37,17 @@ #include "url.h" #include "llist.h" #include "curl_share.h" +#include "curl_threads.h" #include "curl_trc.h" #include "curl_sha256.h" #include "rand.h" - +#include "curlx/strdup.h" /* 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; + char *key_passwd; struct Curl_llist sessions; void *sobj; /* object instance or NULL */ Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */ @@ -77,18 +73,22 @@ static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, * when used in another process with different CWD. When a path does not * exist, this does not work. Then, we add the path as is. */ #ifdef _WIN32 - char abspath[_MAX_PATH]; - if(_fullpath(abspath, path, _MAX_PATH)) - return curlx_dyn_addf(buf, ":%s-%s", name, abspath); + char *abspath = _fullpath(NULL, path, 0); + if(abspath) { + CURLcode result = curlx_dyn_addf(buf, ":%s-%s", name, abspath); + /* !checksrc! disable BANNEDFUNC 1 */ + free(abspath); /* allocated by CRT, use system free() */ + return result; + } *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); + CURLcode result = curlx_dyn_addf(buf, ":%s-%s", name, abspath); /* !checksrc! disable BANNEDFUNC 1 */ free(abspath); /* allocated by libc, free without memdebug */ - return r; + return result; } *is_local = TRUE; } @@ -102,25 +102,67 @@ static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, const char *name, struct curl_blob *blob) { - CURLcode r = CURLE_OK; + CURLcode result = 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) + result = curlx_dyn_addf(buf, ":%s-", name); + if(result) goto out; - r = Curl_sha256it(hash, blob->data, blob->len); - if(r) + result = Curl_sha256it(hash, blob->data, blob->len); + if(result) goto out; for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) { - r = curlx_dyn_addf(buf, "%02x", hash[i]); - if(r) + result = curlx_dyn_addf(buf, "%02x", hash[i]); + if(result) goto out; } } out: - return r; + return result; +} + +static CURLcode cf_ssl_peer_key_add_mtls(struct dynbuf *buf, + struct ssl_primary_config *ssl, + bool *is_local) +{ + CURLcode result = CURLE_OK; + if(ssl->clientcert && ssl->clientcert[0]) { + result = cf_ssl_peer_key_add_path(buf, "CCERT", ssl->clientcert, is_local); + if(result) + goto out; + } + if(ssl->key && ssl->key[0]) { + result = cf_ssl_peer_key_add_path(buf, "KEY", ssl->key, is_local); + if(result) + goto out; + } + if(ssl->key_blob) { + result = cf_ssl_peer_key_add_hash(buf, "KEYBlob", ssl->key_blob); + if(result) + goto out; + } + if(ssl->cert_type && ssl->cert_type[0]) { + size_t i; + result = curlx_dyn_add(buf, ":CT-"); + for(i = 0; !result && ssl->cert_type[i]; i++) { + char c = Curl_raw_toupper(ssl->cert_type[i]); + result = curlx_dyn_addn(buf, &c, 1); + } + if(result) + goto out; + } + if(ssl->key_type && ssl->key_type[0]) { + size_t i; + result = curlx_dyn_add(buf, ":KT-"); + for(i = 0; !result && ssl->key_type[i]; i++) { + char c = Curl_raw_toupper(ssl->key_type[i]); + result = curlx_dyn_addn(buf, &c, 1); + } + } +out: + return result; } #define CURL_SSLS_LOCAL_SUFFIX ":L" @@ -134,58 +176,78 @@ static bool cf_ssl_peer_key_is_global(const char *peer_key) (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) +static CURLcode ssl_peer_key_add_transport(struct dynbuf *buf, + uint8_t transport) { - 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) { + switch(transport) { case TRNSPRT_TCP: - break; + return CURLE_OK; case TRNSPRT_UDP: - r = curlx_dyn_add(&buf, ":UDP"); - break; + return curlx_dyn_add(buf, ":UDP"); case TRNSPRT_QUIC: - r = curlx_dyn_add(&buf, ":QUIC"); - break; + return curlx_dyn_add(buf, ":QUIC"); case TRNSPRT_UNIX: - r = curlx_dyn_add(&buf, ":UNIX"); - break; + return curlx_dyn_add(buf, ":UNIX"); default: - r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); - break; + return curlx_dyn_addf(buf, ":TRNSPRT-%d", transport); } - if(r) - goto out; +} + +static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, + struct ssl_primary_config *ssl, + const struct ssl_peer *peer) +{ + CURLcode result; if(!ssl->verifypeer) { - r = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); - if(r) - goto out; + result = curlx_dyn_add(buf, ":NO-VRFY-PEER"); + if(result) + return result; } if(!ssl->verifyhost) { - r = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); - if(r) - goto out; + result = curlx_dyn_add(buf, ":NO-VRFY-HOST"); + if(result) + return result; } if(ssl->verifystatus) { - r = curlx_dyn_add(&buf, ":VRFY-STATUS"); - if(r) - goto out; + result = curlx_dyn_add(buf, ":VRFY-STATUS"); + if(result) + return result; + } + if((!ssl->verifypeer || !ssl->verifyhost) && + peer->peer && !Curl_peer_equal(peer->origin, peer->peer)) { + result = curlx_dyn_addf(buf, ":CHOST-%s:CPORT-%u", + peer->peer->hostname, + peer->peer->port); + if(result) + return result; } + return CURLE_OK; +} + +static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, + const struct ssl_peer *peer, + const char *tls_id, + char **ppeer_key) +{ + struct dynbuf buf; + size_t key_len; + bool is_local = FALSE; + CURLcode result; + + *ppeer_key = NULL; + curlx_dyn_init(&buf, 10 * 1024); + + result = curlx_dyn_addf(&buf, "%s:%d", + peer->origin->hostname, peer->origin->port); + if(result) + goto out; + result = ssl_peer_key_add_transport(&buf, peer->transport); + if(result) + goto out; + result = ssl_peer_key_add_vrfy(&buf, ssl, peer); + if(result) + goto out; #if UNITY_CERTVERIFY /* A verification callback replaces the backend's own peer verification, and on Schannel it also changes the credential flags cached under this key @@ -193,110 +255,96 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, under a callback could be picked up by a transfer that has none, which would then neither validate automatically nor call a callback. */ if(ssl->unity_certverify) { - r = curlx_dyn_addf(&buf, ":UNITY-CERTVERIFY-%p", - ssl->unity_certverify_userp); - if(r) + result = curlx_dyn_addf(&buf, ":UNITY-CERTVERIFY-%p", + ssl->unity_certverify_userp); + if(result) goto out; } #endif /* UNITY_CERTVERIFY */ - 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-%u", ssl->version, - (ssl->version_max >> 16)); - if(r) + result = curlx_dyn_addf(&buf, ":TLSVER-%d-%u", ssl->version, + (ssl->version_max >> 16)); + if(result) goto out; } if(ssl->ssl_options) { - r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); - if(r) + result = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); + if(result) goto out; } if(ssl->cipher_list) { - r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); - if(r) + result = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); + if(result) goto out; } if(ssl->cipher_list13) { - r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); - if(r) + result = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); + if(result) goto out; } if(ssl->curves) { - r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); - if(r) + result = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); + if(result) + goto out; + } + if(ssl->signature_algorithms) { + result = curlx_dyn_addf(&buf, ":SIGALGS-%s", + ssl->signature_algorithms); + if(result) goto out; } if(ssl->verifypeer) { - r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); - if(r) + result = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); + if(result) goto out; - r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); - if(r) + result = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); + if(result) goto out; - r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); - if(r) + result = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); + if(result) goto out; - r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local); - if(r) + result = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, + &is_local); + if(result) goto out; if(ssl->ca_info_blob) { - r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); - if(r) + result = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); + if(result) goto out; } if(ssl->issuercert_blob) { - r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob); - if(r) + result = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", + ssl->issuercert_blob); + if(result) goto out; } } if(ssl->cert_blob) { - r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); - if(r) + result = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); + if(result) goto out; } if(ssl->pinned_key && ssl->pinned_key[0]) { - r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); - if(r) + result = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); + if(result) 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 + result = cf_ssl_peer_key_add_mtls(&buf, ssl, &is_local); + if(result) + goto out; if(!tls_id || !tls_id[0]) { - r = CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; goto out; } - r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); - if(r) + result = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); + if(result) goto out; - r = curlx_dyn_addf(&buf, is_local ? - CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); - if(r) + result = curlx_dyn_addf(&buf, is_local ? + CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); + if(result) goto out; *ppeer_key = curlx_dyn_take(&buf, &key_len); @@ -305,7 +353,15 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, out: curlx_dyn_free(&buf); - return r; + return result; +} + +CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, + struct ssl_primary_config *sslc, + const char *tls_id, + char **ppeer_key) +{ + return ssl_peer_key_build(sslc, peer, tls_id, ppeer_key); } struct Curl_ssl_scache { @@ -314,6 +370,10 @@ struct Curl_ssl_scache { size_t peer_count; int default_lifetime_secs; long age; +#ifdef USE_MUTEX + curl_mutex_t mutex; + curl_thread_id_t locking_thread; +#endif BIT(is_locked); }; @@ -338,9 +398,9 @@ static void cf_ssl_scache_session_ldestroy(void *udata, void *obj) { struct Curl_ssl_session *s = obj; (void)udata; - curlx_free(CURL_UNCONST(s->sdata)); - curlx_free(CURL_UNCONST(s->quic_tp)); - curlx_free((void *)s->alpn); + curlx_free(s->sdata); + curlx_free(s->quic_tp); + curlx_free(s->alpn); curlx_free(s); } @@ -405,6 +465,54 @@ void Curl_ssl_session_destroy(struct Curl_ssl_session *s) } } +CURLcode Curl_ssl_session_dup(struct Curl_ssl_session *src, + struct Curl_ssl_session **pdest) +{ + struct Curl_ssl_session *dest = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(!src || !pdest) + return CURLE_BAD_FUNCTION_ARGUMENT; + *pdest = NULL; + + dest = curlx_calloc(1, sizeof(*dest)); + if(!dest) + goto out; + + dest->ietf_tls_id = src->ietf_tls_id; + dest->valid_until = src->valid_until; + dest->earlydata_max = src->earlydata_max; + dest->sectrust_verified = src->sectrust_verified; + if(src->sdata_len) { + dest->sdata = curlx_memdup(src->sdata, src->sdata_len); + if(!dest->sdata) + goto out; + dest->sdata_len = src->sdata_len; + } + if(src->quic_tp_len) { + dest->quic_tp = curlx_memdup(src->quic_tp, src->quic_tp_len); + if(!dest->quic_tp) + goto out; + dest->quic_tp_len = src->quic_tp_len; + } + if(src->alpn) { + dest->alpn = curlx_strdup(src->alpn); + if(!dest->alpn) + goto out; + } + result = CURLE_OK; + +out: + if(!result) + *pdest = dest; + else { + *pdest = NULL; + if(dest) + Curl_ssl_session_destroy(dest); + } + return result; +} + static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) { Curl_llist_destroy(&peer->sessions, NULL); @@ -416,10 +524,7 @@ static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) } peer->sobj_free = NULL; curlx_safefree(peer->clientcert); -#ifdef USE_TLS_SRP - curlx_safefree(peer->srp_username); - curlx_safefree(peer->srp_password); -#endif + curlx_safefree(peer->key_passwd); curlx_safefree(peer->ssl_peer_key); peer->age = 0; peer->hmac_set = FALSE; @@ -444,20 +549,17 @@ static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer) * - 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->exportable = (!peer->clientcert && !peer->key_passwd && (!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) +static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, + const char *ssl_peer_key, + const char *clientcert, + const char *key_passwd, + const unsigned char *salt, + const unsigned char *hmac) { CURLcode result = CURLE_OUT_OF_MEMORY; @@ -482,17 +584,11 @@ cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, if(!peer->clientcert) goto out; } - if(srp_username) { - peer->srp_username = curlx_strdup(srp_username); - if(!peer->srp_username) - goto out; - } - if(srp_password) { - peer->srp_password = curlx_strdup(srp_password); - if(!peer->srp_password) + if(key_passwd) { + peer->key_passwd = curlx_strdup(key_passwd); + if(!peer->key_passwd) goto out; } - cf_ssl_cache_peer_update(peer); result = CURLE_OK; out: @@ -567,7 +663,9 @@ CURLcode Curl_ssl_scache_create(size_t max_peers, Curl_llist_init(&scache->peers[i].sessions, cf_ssl_scache_session_ldestroy); } - +#ifdef USE_MUTEX + Curl_mutex_init(&scache->mutex); +#endif *pscache = scache; return CURLE_OK; } @@ -581,6 +679,9 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) cf_ssl_scache_clear_peer(&scache->peers[i]); } curlx_free(scache->peers); +#ifdef USE_MUTEX + Curl_mutex_destroy(&scache->mutex); +#endif curlx_free(scache); } } @@ -602,8 +703,16 @@ 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); +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + scache->locking_thread = Curl_thread_get_current_id(); + DEBUGASSERT(!scache->is_locked); + scache->is_locked = TRUE; + Curl_mutex_release(&scache->mutex); +#else DEBUGASSERT(!scache->is_locked); scache->is_locked = TRUE; +#endif } } @@ -612,32 +721,49 @@ void Curl_ssl_scache_unlock(struct Curl_easy *data) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); if(scache) { +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + scache->locking_thread = 0; + DEBUGASSERT(scache->is_locked); + scache->is_locked = FALSE; + Curl_mutex_release(&scache->mutex); +#else DEBUGASSERT(scache->is_locked); scache->is_locked = FALSE; +#endif if(CURL_SHARE_ssl_scache(data)) Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); } } +bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data) +{ + struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); + bool locked = FALSE; + if(!scache) + return FALSE; +#ifdef USE_MUTEX + Curl_mutex_acquire(&scache->mutex); + locked = scache->is_locked && Curl_thread_is_current(scache->locking_thread); + Curl_mutex_release(&scache->mutex); +#else + locked = (bool)scache->is_locked; +#endif + return locked; +} + 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) + if(peer->clientcert || peer->key_passwd) 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)) + if(Curl_timestrcmp(peer->key_passwd, conn_config->key_passwd)) return FALSE; -#endif return TRUE; } @@ -661,7 +787,7 @@ static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, /* check for entries with known peer_key */ for(i = 0; scache && i < scache->peer_count; i++) { if(scache->peers[i].ssl_peer_key && - curl_strequal(ssl_peer_key, scache->peers[i].ssl_peer_key) && + !strcmp(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]; @@ -705,8 +831,8 @@ static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, return result; } -static struct Curl_ssl_scache_peer * -cf_ssl_get_free_peer(struct Curl_ssl_scache *scache) +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; @@ -761,19 +887,15 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data, if(peer) { char buffer[64]; const char *ccert = conn_config ? conn_config->clientcert : NULL; - const char *username = NULL, *password = NULL; -#ifdef USE_TLS_SRP - username = conn_config ? conn_config->username : NULL; - password = conn_config ? conn_config->password : NULL; -#endif + const char *kpasswd = conn_config ? conn_config->key_passwd : NULL; if(!ccert && conn_config && conn_config->cert_blob) { /* when using a client cert blob, create a name for it */ curl_msnprintf(buffer, sizeof(buffer), "cert-%p", conn_config->cert_blob->data); ccert = buffer; /* data is strduped by cf_ssl_scache_peer_init */ } - result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, - username, password, NULL, NULL); + result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, kpasswd, + NULL, NULL); if(result) goto out; /* all ready */ @@ -842,7 +964,7 @@ static CURLcode cf_scache_add_session(struct Curl_cfilter *cf, 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_TRC_SSLS(data, "unable to add scache peer: %d", (int)result); Curl_ssl_session_destroy(s); goto out; } @@ -852,13 +974,13 @@ static CURLcode cf_scache_add_session(struct Curl_cfilter *cf, out: if(result) { failf(data, "[SCACHE] failed to add session for %s, error=%d", - ssl_peer_key, result); + ssl_peer_key, (int)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, + "quic_tp=%s], peer has %zu sessions now", ssl_peer_key, + (unsigned int)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; @@ -922,7 +1044,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, n = Curl_llist_head(&peer->sessions); if(n) { s = Curl_node_take_elem(n); - (scache->age)++; /* increase general age */ + scache->age++; /* increase general age */ peer->age = scache->age; /* set this as used in this age */ } } @@ -930,7 +1052,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, *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, + ssl_peer_key, (unsigned int)s->ietf_tls_id, s->alpn, s->earlydata_max, s->quic_tp ? "yes" : "no", Curl_llist_count(&peer->sessions)); } @@ -962,7 +1084,7 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, 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_TRC_SSLS(data, "unable to add scache peer: %d", (int)result); goto out; } @@ -1025,12 +1147,6 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, #define CURL_SSL_TICKET_MAX (16 * 1024) -bool Curl_ssl_scache_is_locked(struct Curl_easy *data) -{ - struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - return scache && scache->is_locked; -} - static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer) { CURLcode result; @@ -1053,11 +1169,10 @@ static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer) 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) +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; @@ -1151,8 +1266,8 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, if(!peer) { peer = cf_ssl_get_free_peer(scache); if(peer) { - result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, - NULL, NULL, salt, hmac); + result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, NULL, + salt, hmac); if(result) goto out; } @@ -1185,7 +1300,7 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, struct Curl_llist_node *n; size_t i; curl_off_t now = time(NULL); - CURLcode r = CURLE_OK; + CURLcode result = CURLE_OK; #ifdef CURLVERBOSE size_t npeers = 0, ntickets = 0; #endif @@ -1201,7 +1316,7 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, 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 */ + continue; /* skip free entry */ if(!peer->exportable) continue; @@ -1213,35 +1328,35 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, 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) + result = cf_ssl_scache_peer_set_hmac(peer); + if(result) goto out; } if(!curlx_dyn_len(&hbuf)) { - r = curlx_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt)); - if(r) + result = curlx_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt)); + if(result) goto out; - r = curlx_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac)); - if(r) + result = curlx_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac)); + if(result) goto out; } curlx_dyn_reset(&sbuf); - r = Curl_ssl_session_pack(data, s, &sbuf); - if(r) + result = Curl_ssl_session_pack(data, s, &sbuf); + if(result) 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) + result = 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(result) goto out; VERBOSE(++ntickets); n = Curl_node_next(n); } } - r = CURLE_OK; + result = CURLE_OK; CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers", ntickets, npeers); @@ -1249,7 +1364,7 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, Curl_ssl_scache_unlock(data); curlx_dyn_free(&hbuf); curlx_dyn_free(&sbuf); - return r; + return result; } #endif /* USE_SSLS_EXPORT */ diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index a6a36f16306b..c9f35e800f62 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -54,20 +54,20 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache); * connection to the peer. * If the filter is a TLS proxy filter, it uses the proxy relevant * information. - * @param cf the connection filter wanting to use it * @param peer the peer the filter wants to talk to + * @param sslc the relevant ssl configuration * @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, +CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, char **ppeer_key); /* Return if there is a session cache shall be used. - * An ssl session might not be configured or not available for + * An SSL session might not be configured or not available for * "connect-only" transfers. */ bool Curl_ssl_scache_use(struct Curl_cfilter *cf, struct Curl_easy *data); @@ -122,15 +122,16 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, /* All about an SSL session ticket */ struct Curl_ssl_session { - const void *sdata; /* session ticket data, plain bytes */ + uint8_t *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 */ + uint8_t *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 */ + BIT(sectrust_verified); /* session comes from sectrust verified TLS */ }; /* Create a `session` instance. Does NOT need locking. @@ -156,6 +157,10 @@ CURLcode Curl_ssl_session_create2(void *sdata, size_t sdata_len, unsigned char *quic_tp, size_t quic_tp_len, struct Curl_ssl_session **psession); +/* Duplicate an ssl session */ +CURLcode Curl_ssl_session_dup(struct Curl_ssl_session *src, + struct Curl_ssl_session **pdest); + /* Destroy a `session` instance. Can be called with NULL. * Does NOT need locking. */ void Curl_ssl_session_destroy(struct Curl_ssl_session *s); @@ -197,9 +202,9 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key); -#ifdef USE_SSLS_EXPORT +bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data); -bool Curl_ssl_scache_is_locked(struct Curl_easy *data); +#ifdef USE_SSLS_EXPORT CURLcode Curl_ssl_session_import(struct Curl_easy *data, const char *ssl_peer_key, diff --git a/lib/vtls/vtls_spack.c b/lib/vtls/vtls_spack.c index d96f4e41bd72..d8e51c4fd118 100644 --- a/lib/vtls/vtls_spack.c +++ b/lib/vtls/vtls_spack.c @@ -31,13 +31,6 @@ #include "vtls/vtls_spack.h" #include "curlx/strdup.h" -#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 @@ -45,6 +38,7 @@ #define CURL_SPACK_ALPN 0x05 #define CURL_SPACK_EARLYDATA 0x06 #define CURL_SPACK_QUICTP 0x07 +#define CURL_SPACK_SECTRUST 0x08 static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b) { @@ -130,26 +124,26 @@ static CURLcode spack_dec64(uint64_t *val, const uint8_t **src, static CURLcode spack_encstr16(struct dynbuf *buf, const char *s) { size_t slen = strlen(s); - CURLcode r; + CURLcode result; 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); + result = spack_enc16(buf, (uint16_t)slen); + if(!result) { + result = curlx_dyn_addn(buf, s, slen); } - return r; + return result; } static CURLcode spack_decstr16(char **val, const uint8_t **src, const uint8_t *end) { uint16_t slen; - CURLcode r; + CURLcode result; *val = NULL; - r = spack_dec16(&slen, src, end); - if(r) - return r; + result = spack_dec16(&slen, src, end); + if(result) + return result; if(end - *src < slen) return CURLE_READ_ERROR; *val = curlx_memdup0((const char *)(*src), slen); @@ -160,26 +154,26 @@ static CURLcode spack_decstr16(char **val, const uint8_t **src, static CURLcode spack_encdata16(struct dynbuf *buf, const uint8_t *data, size_t data_len) { - CURLcode r; + CURLcode result; 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); + result = spack_enc16(buf, (uint16_t)data_len); + if(!result) { + result = curlx_dyn_addn(buf, data, data_len); } - return r; + return result; } 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; + CURLcode result; *val = NULL; - r = spack_dec16(&data_len, src, end); - if(r) - return r; + result = spack_dec16(&data_len, src, end); + if(result) + return result; if(end - *src < data_len) return CURLE_READ_ERROR; *val = curlx_memdup0((const char *)(*src), data_len); @@ -192,48 +186,51 @@ CURLcode Curl_ssl_session_pack(struct Curl_easy *data, struct Curl_ssl_session *s, struct dynbuf *buf) { - CURLcode r; + CURLcode result; 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); + result = spack_enc8(buf, CURL_SPACK_VERSION); + if(!result) + result = spack_enc8(buf, CURL_SPACK_TICKET); + if(!result) + result = spack_encdata16(buf, s->sdata, s->sdata_len); + if(!result) + result = spack_enc8(buf, CURL_SPACK_IETF_ID); + if(!result) + result = spack_enc16(buf, (uint16_t)s->ietf_tls_id); + if(!result) + result = spack_enc8(buf, CURL_SPACK_VALID_UNTIL); + if(!result) + result = spack_enc64(buf, (uint64_t)s->valid_until); + if(!result && s->alpn) { + result = spack_enc8(buf, CURL_SPACK_ALPN); + if(!result) + result = spack_encstr16(buf, s->alpn); } - if(!r && s->earlydata_max) { + if(!result && 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); + result = CURLE_BAD_FUNCTION_ARGUMENT; + if(!result) + result = spack_enc8(buf, CURL_SPACK_EARLYDATA); + if(!result) + result = spack_enc32(buf, (uint32_t)s->earlydata_max); + } + if(!result && s->sectrust_verified) { + result = spack_enc8(buf, CURL_SPACK_SECTRUST); } - 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(!result && s->quic_tp && s->quic_tp_len) { + result = spack_enc8(buf, CURL_SPACK_QUICTP); + if(!result) + result = spack_encdata16(buf, s->quic_tp, s->quic_tp_len); } - if(r) - CURL_TRC_SSLS(data, "error packing data: %d", r); - return r; + if(result) + CURL_TRC_SSLS(data, "error packing data: %d", (int)result); + return result; } CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, @@ -247,83 +244,92 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, uint16_t val16; uint32_t val32; uint64_t val64; - CURLcode r; + size_t dlen; + CURLcode result; DEBUGASSERT(buf); DEBUGASSERT(buflen); *ps = NULL; - r = spack_dec8(&val8, &buf, end); - if(r) + result = spack_dec8(&val8, &buf, end); + if(result) goto out; if(val8 != CURL_SPACK_VERSION) { - r = CURLE_READ_ERROR; + result = CURLE_READ_ERROR; goto out; } s = curlx_calloc(1, sizeof(*s)); if(!s) { - r = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; goto out; } while(buf < end) { - r = spack_dec8(&val8, &buf, end); - if(r) + result = spack_dec8(&val8, &buf, end); + if(result) goto out; switch(val8) { case CURL_SPACK_ALPN: - r = spack_decstr16(&s->alpn, &buf, end); - if(r) + curlx_free(s->alpn); + result = spack_decstr16(&s->alpn, &buf, end); + if(result) goto out; break; case CURL_SPACK_EARLYDATA: - r = spack_dec32(&val32, &buf, end); - if(r) + result = spack_dec32(&val32, &buf, end); + if(result) goto out; s->earlydata_max = val32; break; case CURL_SPACK_IETF_ID: - r = spack_dec16(&val16, &buf, end); - if(r) + result = spack_dec16(&val16, &buf, end); + if(result) goto out; s->ietf_tls_id = val16; break; case CURL_SPACK_QUICTP: { - r = spack_decdata16(&pval8, &s->quic_tp_len, &buf, end); - if(r) + result = spack_decdata16(&pval8, &dlen, &buf, end); + if(result) goto out; + curlx_free(s->quic_tp); s->quic_tp = pval8; + s->quic_tp_len = dlen; break; } case CURL_SPACK_TICKET: { - r = spack_decdata16(&pval8, &s->sdata_len, &buf, end); - if(r) + result = spack_decdata16(&pval8, &dlen, &buf, end); + if(result) goto out; + curlx_free(s->sdata); s->sdata = pval8; + s->sdata_len = dlen; break; } case CURL_SPACK_VALID_UNTIL: - r = spack_dec64(&val64, &buf, end); - if(r) + result = spack_dec64(&val64, &buf, end); + if(result) goto out; s->valid_until = (curl_off_t)val64; break; + case CURL_SPACK_SECTRUST: + s->sectrust_verified = TRUE; + break; default: /* unknown tag */ - r = CURLE_READ_ERROR; + result = CURLE_READ_ERROR; goto out; } } out: - if(r) { - CURL_TRC_SSLS(data, "error unpacking data: %d", r); + if(result) { + CURL_TRC_SSLS(data, "error unpacking data: %d", (int)result); Curl_ssl_session_destroy(s); } else *ps = s; - return r; + return result; } #endif /* USE_SSL && USE_SSLS_EXPORT */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 15c81c2874bd..4b9facadf01e 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -34,10 +34,10 @@ #include #if LIBWOLFSSL_VERSION_HEX < 0x05000000 /* wolfSSL 5.0.0 (2021-11-01) */ -#error "wolfSSL version should be at least 5.0.0" +#error "wolfSSL 5.0.0 or greater required" #endif #if defined(OPENSSL_COEXIST) && LIBWOLFSSL_VERSION_HEX < 0x05007006 -#error "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL" +#error "wolfSSL 5.7.6 or greater required to coexist with OpenSSL" #endif /* To determine what functions are available we rely on one or both of: @@ -55,8 +55,8 @@ #include "urldata.h" #include "curl_trc.h" -#include "httpsrr.h" -#include "cf-dns.h" +#include "vdns/cf-dns.h" +#include "vdns/httpsrr.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" #include "vtls/vtls_scache.h" @@ -81,7 +81,7 @@ options.h. */ #ifndef KEEP_PEER_CERT #if defined(HAVE_WOLFSSL_GET_PEER_CERTIFICATE) || \ - (defined(OPENSSL_EXTRA) && !defined(NO_CERTS)) + (defined(OPENSSL_EXTRA) && !defined(NO_CERTS)) #define KEEP_PEER_CERT #endif #endif @@ -162,7 +162,8 @@ static int wssl_tls13_secret_callback(SSL *ssl, int id, return 0; } - Curl_tls_keylog_write(label, client_random, secret, secretSz); + Curl_tls_keylog_write(label, client_random, sizeof(client_random), + secret, secretSz); return 0; } #endif /* HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ @@ -203,7 +204,7 @@ static void wssl_log_tls12_secret(WOLFSSL *ssl) return; } - Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen); + Curl_tls_keylog_write("CLIENT_RANDOM", cr, crLen, ms, msLen); } #endif /* OPENSSL_EXTRA */ @@ -218,26 +219,6 @@ static int wssl_do_file_type(const char *type) return -1; } -#ifdef WOLFSSL_HAVE_KYBER -struct group_name_map { - const word16 group; - const char *name; -}; - -static const struct group_name_map gnm[] = { - { WOLFSSL_ML_KEM_512, "ML_KEM_512" }, - { WOLFSSL_ML_KEM_768, "ML_KEM_768" }, - { WOLFSSL_ML_KEM_1024, "ML_KEM_1024" }, - { WOLFSSL_SECP256R1MLKEM512, "SecP256r1MLKEM512" }, - { WOLFSSL_SECP384R1MLKEM768, "SecP384r1MLKEM768" }, - { WOLFSSL_SECP521R1MLKEM1024, "SecP521r1MLKEM1024" }, - { WOLFSSL_SECP256R1MLKEM768, "SecP256r1MLKEM768" }, - { WOLFSSL_SECP384R1MLKEM1024, "SecP384r1MLKEM1024" }, - { WOLFSSL_X25519MLKEM768, "X25519MLKEM768" }, - { 0, NULL } -}; -#endif - #ifdef USE_BIO_CHAIN static int wssl_bio_cf_create(WOLFSSL_BIO *bio) @@ -316,14 +297,14 @@ static int wssl_bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen) * 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); + skiplen = (size_t)(blen - wssl->io_send_blocked_len); blen = wssl->io_send_blocked_len; } result = Curl_conn_cf_send(cf->next, data, (const uint8_t *)buf, blen, FALSE, &nwritten); wssl->io_result = result; CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %d, %zu", - blen, result, nwritten); + blen, (int)result, nwritten); #ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); #endif @@ -360,7 +341,7 @@ static int wssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) * 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); + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", (int)result); wssl->io_result = result; return -1; } @@ -368,7 +349,8 @@ static int wssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread); wssl->io_result = result; - CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %d, %zu", blen, result, nread); + CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %d, %zu", blen, (int)result, + nread); #ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); #endif @@ -416,14 +398,17 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len) + size_t quic_tp_len, + struct Curl_ssl_session **pscs) { CURLcode result = CURLE_OK; - struct Curl_ssl_session *sc_session = NULL; + struct Curl_ssl_session *sc_session = NULL, *sc_dup = NULL; unsigned char *sdata = NULL, *sdata_ptr, *qtp_clone = NULL; unsigned int sdata_len; unsigned int earlydata_max = 0; + if(pscs) + *pscs = NULL; if(!session) goto out; @@ -465,13 +450,23 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, earlydata_max, qtp_clone, quic_tp_len, &sc_session); sdata = NULL; /* took ownership of sdata */ + if(!result && pscs && /* return a duplicate if asked for and FTP */ + (cf->conn->scheme->family == CURLPROTO_FTP)) + result = Curl_ssl_session_dup(sc_session, &sc_dup); if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); /* took ownership of `sc_session` */ + sc_session = NULL; } out: curlx_free(sdata); + if(!result && pscs) { + *pscs = sc_dup; + sc_dup = NULL; + } + Curl_ssl_session_destroy(sc_session); + Curl_ssl_session_destroy(sc_dup); return result; } @@ -480,16 +475,21 @@ 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); + DEBUGASSERT(cf); if(cf && session) { struct ssl_connect_data *connssl = cf->ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_ssl_session *scs = NULL; 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); + connssl->negotiated.alpn, NULL, 0, &scs); + if(scs) { + Curl_ssl_session_destroy(connssl->session); + connssl->session = scs; + } } } return 0; @@ -517,36 +517,35 @@ static CURLcode wssl_on_session_reuse(struct Curl_cfilter *cf, connssl->earlydata_max); } -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) +static bool wssl_apply_session( + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wss, + struct alpn_spec *alpns, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb, + struct Curl_ssl_session *scs) { struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct Curl_ssl_session *scs = NULL; - CURLcode result; + CURLcode result = CURLE_OK; + WOLFSSL_SESSION *session = NULL; + bool success = FALSE; - result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &scs); - if(!result && scs && scs->sdata && scs->sdata_len && + if(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); + goto out; } else { infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); + success = TRUE; if(ssl_config->earlydata && !cf->conn->bits.connect_only && !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { @@ -554,7 +553,6 @@ wssl_setup_session(struct Curl_cfilter *cf, if(sess_reuse_cb) { result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); if(result) { - wolfSSL_SESSION_free(session); goto out; } } @@ -573,15 +571,14 @@ wssl_setup_session(struct Curl_cfilter *cf, #endif } } - wolfSSL_SESSION_free(session); } else { failf(data, "could not decode previous session"); } } out: - Curl_ssl_scache_return(cf, data, ssl_peer_key, scs); - return result; + wolfSSL_SESSION_free(session); + return success; } static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, @@ -595,16 +592,12 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, /* 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; - #ifndef NO_FILESYSTEM /* load native CA certificates */ - if(ssl_config->native_ca_store) { + if(conn_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"); @@ -649,7 +642,7 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, ssl_cafile, ssl_capath, WOLFSSL_LOAD_FLAG_IGNORE_ERR); - if(WOLFSSL_SUCCESS != rc) { + if(rc != WOLFSSL_SUCCESS) { if(conn_config->verifypeer && !imported_native_ca && !imported_ca_info_blob) { /* Fail if we insist on successfully verifying the server. */ @@ -690,7 +683,7 @@ struct wssl_x509_share { 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(key_len == CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); (void)key; (void)key_len; @@ -734,7 +727,7 @@ static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1) : NULL; + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)) : NULL; if(share && share->store && !wssl_cached_x509_store_expired(data, share) && !wssl_cached_x509_store_different(cf, share)) { @@ -757,7 +750,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, return; share = Curl_hash_pick(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1); + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -765,7 +758,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&multi->proto_hash, CURL_UNCONST(MPROTO_WSSL_X509_KEY), - sizeof(MPROTO_WSSL_X509_KEY) - 1, + CURL_CSTRLEN(MPROTO_WSSL_X509_KEY), share, wssl_x509_share_free)) { curlx_free(share); return; @@ -804,6 +797,9 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, WOLFSSL_X509_STORE *cached_store; bool cache_criteria_met; + /* We do not want to do this again, no matter the outcome */ + wssl->x509_store_setup = TRUE; + /* 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. */ @@ -812,7 +808,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, !conn_config->CApath && !conn_config->ca_info_blob && !ssl_config->primary.CRLfile && - !ssl_config->native_ca_store; + !conn_config->native_ca_store; cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) : NULL; @@ -855,7 +851,7 @@ static CURLcode wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) for(i = 0; (str = wolfSSL_get_cipher_list(i)) != NULL; i++) { size_t n; - if((strncmp(str, "TLS13", 5) == 0) != tls13) + if((!strncmp(str, "TLS13", 5)) != tls13) continue; /* if there already is data in the string, add colon separator */ @@ -920,10 +916,10 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, #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 char *key_file = ssl_config->primary.key; const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); + const struct curl_blob *key_blob = ssl_config->primary.key_blob; + int file_type = wssl_do_file_type(ssl_config->primary.cert_type); int rc; switch(file_type) { @@ -954,7 +950,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, key_file = cert_file; } else - file_type = wssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->primary.key_type); rc = key_blob ? wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, @@ -968,8 +964,8 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, #else /* NO_FILESYSTEM */ if(ssl_config->primary.cert_blob) { const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); + const struct curl_blob *key_blob = ssl_config->primary.key_blob; + int file_type = wssl_do_file_type(ssl_config->primary.cert_type); int rc; switch(file_type) { @@ -994,7 +990,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, if(!key_blob) key_blob = cert_blob; else - file_type = wssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->primary.key_type); if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, @@ -1076,7 +1072,6 @@ static CURLcode ssl_version(struct Curl_easy *data, return CURLE_OK; } -#define QUIC_GROUPS "P-256:P-384:P-521" #ifdef WOLFSSL_TLS13 #define MAX_CIPHER_LEN 4096 #endif @@ -1137,54 +1132,43 @@ static CURLcode wssl_init_ciphers(struct Curl_easy *data, #endif } +/* wolfSSL_CTX_set1_groups_list() accepts PQC/hybrid groups (e.g. + X25519MLKEM768) that wolfSSL_CTX_set1_curves_list() rejects. It needs + OPENSSL_EXTRA, which wolfSSL's --enable-curl sets but --enable-curl=tiny + does not. */ +#ifdef OPENSSL_EXTRA +#define wssl_CTX_set1_groups_list wolfSSL_CTX_set1_groups_list +#else +#define wssl_CTX_set1_groups_list wolfSSL_CTX_set1_curves_list +#endif + static CURLcode wssl_init_curves(struct Curl_easy *data, struct wssl_ctx *wctx, - struct ssl_primary_config *conn_config, - unsigned char transport -#ifdef WOLFSSL_HAVE_KYBER - , word16 *out_pqkem -#endif - ) + struct ssl_primary_config *conn_config) { char *curves = conn_config->curves; - if(!curves && (transport == TRNSPRT_QUIC)) - curves = (char *)CURL_UNCONST(QUIC_GROUPS); - - if(curves) { -#ifdef WOLFSSL_HAVE_KYBER - size_t idx; - for(idx = 0; gnm[idx].name != NULL; idx++) { - if(!strncmp(curves, gnm[idx].name, strlen(gnm[idx].name))) { - *out_pqkem = gnm[idx].group; - break; - } - } - - if(*out_pqkem == 0) -#endif - { - if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) { - failf(data, "failed setting curves list: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } + /* Without an explicit list, leave the key share group selection to + wolfSSL's own default. */ + if(curves && !wssl_CTX_set1_groups_list(wctx->ssl_ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + return CURLE_SSL_CIPHER; } return CURLE_OK; } -static CURLcode wssl_init_ssl_handle(struct wssl_ctx *wctx, - struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_peer *peer, - struct alpn_spec *alpns, - void *ssl_user_data, - unsigned char transport, -#ifdef WOLFSSL_HAVE_KYBER - word16 pqkem, -#endif - Curl_wssl_init_session_reuse_cb - *sess_reuse_cb) +static CURLcode wssl_init_ssl_handle( + struct wssl_ctx *wctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct alpn_spec *alpns, + void *ssl_user_data, + unsigned char transport, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb) { + struct Curl_ssl_session *scs = NULL; + bool session_applied = FALSE; + /* Let's make an SSL structure */ wctx->ssl = wolfSSL_new(wctx->ssl_ctx); if(!wctx->ssl) { @@ -1204,20 +1188,30 @@ static CURLcode wssl_init_ssl_handle(struct wssl_ctx *wctx, (void)transport; #endif -#ifdef WOLFSSL_HAVE_KYBER - if(pqkem) { - if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != - WOLFSSL_SUCCESS) { - failf(data, "unable to use PQ KEM"); + if((cf->sockindex == SECONDARYSOCKET) && !(cf->cft->flags & CF_TYPE_PROXY)) { + /* FTP is a bitch. On TLS secured transfers, it is a common server + * option to require the client to use the SAME TLS session as on + * the control connection or it fails the request. See #22225. */ + scs = Curl_ssl_get_cf_session(data, cf->cft, FIRSTSOCKET); + if(scs) { + session_applied = wssl_apply_session(cf, data, wctx, alpns, + sess_reuse_cb, scs); + if(session_applied) + CURL_TRC_CF(data, cf, "applied SSL session from control connection"); + scs = NULL; } } -#endif /* Check if there is a cached ID we can/should use here! */ - if(Curl_ssl_scache_use(cf, data)) { + if(!session_applied && Curl_ssl_scache_use(cf, data)) { /* Set session from cache if there is one */ - (void)wssl_setup_session(cf, data, wctx, alpns, peer->scache_key, - sess_reuse_cb); + CURLcode result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); + if(!result && scs) { + if(wssl_apply_session(cf, data, wctx, alpns, sess_reuse_cb, scs)) + Curl_ssl_scache_return(cf, data, peer->scache_key, scs); + else + Curl_ssl_session_destroy(scs); + } } #ifdef HAVE_ALPN @@ -1260,11 +1254,12 @@ static CURLcode wssl_init_ssl_handle(struct wssl_ctx *wctx, #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG static CURLcode wssl_init_ech(struct wssl_ctx *wctx, struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct ssl_peer *peer) { int trying_ech_now = 0; - if(data->set.str[STRING_ECH_PUBLIC]) { + if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { infof(data, "ECH: outername not (yet) supported" " with wolfSSL"); return CURLE_SSL_CONNECT_ERROR; @@ -1273,15 +1268,14 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, infof(data, "ECH: GREASE is done by default by" " wolfSSL: no need to ask"); } - if(data->set.tls_ech & CURLECH_CLA_CFG && - data->set.str[STRING_ECH_CONFIG]) { - char *b64val = data->set.str[STRING_ECH_CONFIG]; + if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + const char *b64val = CURL_EASY_STR(data, STRING_ECH_CONFIG); word32 b64len = 0; b64len = (word32)strlen(b64val); - if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val, + if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, CURL_UNCONST(b64val), b64len) != WOLFSSL_SUCCESS) { - if(data->set.tls_ech & CURLECH_HARD) + if(data->set.tls_ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -1291,7 +1285,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, } else { const struct Curl_https_rrinfo *rinfo = - Curl_conn_dns_get_https(data, cf->sockindex); + Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); if(rinfo && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; @@ -1301,7 +1295,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != WOLFSSL_SUCCESS) { infof(data, "ECH: wolfSSL_SetEchConfigs failed"); - if(data->set.tls_ech & CURLECH_HARD) { + if(data->set.tls_ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1312,7 +1306,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech & CURLECH_HARD) { + if(data->set.tls_ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1341,9 +1335,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, struct ssl_primary_config *conn_config; WOLFSSL_METHOD *req_method = NULL; struct alpn_spec alpns; -#ifdef WOLFSSL_HAVE_KYBER - word16 pqkem = 0; -#endif CURLcode result = CURLE_FAILED_INIT; unsigned char transport; int tls_min, tls_max; @@ -1384,11 +1375,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, if(result) goto out; - result = wssl_init_curves(data, wctx, conn_config, transport -#ifdef WOLFSSL_HAVE_KYBER - , &pqkem -#endif - ); + result = wssl_init_curves(data, wctx, conn_config); if(result) goto out; @@ -1441,7 +1428,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, result = (*data->set.ssl.fsslctx)(data, wctx->ssl_ctx, data->set.ssl.fsslctxp); if(result) { - failf(data, "error signaled by ssl ctx callback"); + failf(data, "error signaled by SSL ctx callback"); goto out; } } @@ -1457,17 +1444,13 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, #endif result = wssl_init_ssl_handle(wctx, cf, data, peer, &alpns, ssl_user_data, - transport, -#ifdef WOLFSSL_HAVE_KYBER - pqkem, -#endif - sess_reuse_cb); + transport, sess_reuse_cb); if(result) goto out; #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG if(CURLECH_ENABLED(data)) { - result = wssl_init_ech(wctx, cf, data); + result = wssl_init_ech(wctx, cf, data, peer); if(result) goto out; } @@ -1492,9 +1475,9 @@ bool Curl_wssl_need_httpsrr(struct Curl_easy *data) #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG if(!CURLECH_ENABLED(data)) return FALSE; - if((data->set.tls_ech & CURLECH_GREASE) || - (data->set.tls_ech & CURLECH_CLA_CFG)) - return FALSE; + if((data->set.tls_ech == CURLECH_GREASE) || + CURL_EASY_STR(data, STRING_ECH_CONFIG)) + return FALSE; return TRUE; #else (void)data; @@ -1556,15 +1539,17 @@ static CURLcode wssl_connect_step1(struct Curl_cfilter *cf, wolfSSL_set_bio(wssl->ssl, bio, bio); } #else /* !USE_BIO_CHAIN */ - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - if(sockfd > INT_MAX) { - failf(data, "SSL: socket value too large"); - return CURLE_SSL_CONNECT_ERROR; - } - /* pass the raw socket into the SSL layer */ - if(!wolfSSL_set_fd(wssl->ssl, (int)sockfd)) { - failf(data, "SSL: wolfSSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; + { + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + if(sockfd > INT_MAX) { + failf(data, "SSL: socket value too large"); + return CURLE_SSL_CONNECT_ERROR; + } + /* pass the raw socket into the SSL layer */ + if(!wolfSSL_set_fd(wssl->ssl, (int)sockfd)) { + failf(data, "SSL: wolfSSL_set_fd failed"); + return CURLE_SSL_CONNECT_ERROR; + } } #endif /* USE_BIO_CHAIN */ @@ -1594,10 +1579,11 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; #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]; + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); #else - const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + const char * const pinnedpubkey = + CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY); (void)cf; #endif @@ -1722,7 +1708,7 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) * 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); + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", (int)result); return result; } } @@ -1765,9 +1751,9 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) failf(data, "unable to get peer certificate"); return CURLE_PEER_FAILED_VERIFICATION; } - ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.hostname, 0); + ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.origin->hostname, 0); CURL_TRC_CF(data, cf, "check peer certificate for IP match on %s -> %d", - connssl->peer.hostname, ret); + connssl->peer.origin->hostname, ret); if(ret != WOLFSSL_SUCCESS) detail = DOMAIN_NAME_MISMATCH; wolfSSL_X509_free(cert); @@ -1777,23 +1763,23 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } else { - if(WOLFSSL_ERROR_WANT_READ == detail) { + if(detail == WOLFSSL_ERROR_WANT_READ) { connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_AGAIN; } - else if(WOLFSSL_ERROR_WANT_WRITE == detail) { + else if(detail == WOLFSSL_ERROR_WANT_WRITE) { connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_AGAIN; } - else if(DOMAIN_NAME_MISMATCH == detail) { + else if(detail == DOMAIN_NAME_MISMATCH) { /* There is no easy way to override only the CN matching. * This enables the override of both mismatching SubjectAltNames * as also mismatching CN fields */ failf(data, " subject alt name(s) or common name do not match \"%s\"", - connssl->peer.dispname); + connssl->peer.origin->hostname); return CURLE_PEER_FAILED_VERIFICATION; } - else if(ASN_NO_SIGNER_E == detail) { + else if(detail == ASN_NO_SIGNER_E) { if(conn_config->verifypeer) { failf(data, " CA signer not available for verification"); return CURLE_SSL_CACERT_BADFILE; @@ -1804,11 +1790,11 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) "continuing anyway"); return CURLE_OK; } - else if(ASN_AFTER_DATE_E == detail) { + else if(detail == ASN_AFTER_DATE_E) { failf(data, "server verification failed: certificate has expired."); return CURLE_PEER_FAILED_VERIFICATION; } - else if(ASN_BEFORE_DATE_E == detail) { + else if(detail == ASN_BEFORE_DATE_E) { failf(data, "server verification failed: certificate not valid yet."); return CURLE_PEER_FAILED_VERIFICATION; } @@ -1915,7 +1901,7 @@ static CURLcode wssl_send(struct Curl_cfilter *cf, out: CURL_TRC_CF(data, cf, "wssl_send(len=%zu) -> %d, %zu", - blen, result, *pnwritten); + blen, (int)result, *pnwritten); return result; } @@ -1977,7 +1963,7 @@ static CURLcode wssl_shutdown(struct Curl_cfilter *cf, *done = TRUE; goto out; } - if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->ssl, nread)) { + if(wolfSSL_get_error(wctx->ssl, nread) == WOLFSSL_ERROR_WANT_WRITE) { CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; @@ -2000,7 +1986,7 @@ static CURLcode wssl_shutdown(struct Curl_cfilter *cf, break; case WOLFSSL_ERROR_NONE: /* did not get anything */ case WOLFSSL_ERROR_WANT_READ: - /* wolfSSL has send its notify and now wants to read the reply + /* wolfSSL has sent 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; @@ -2170,12 +2156,13 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, *done = FALSE; connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(ssl_connect_1 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_1) { #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG /* if we do ECH and need the HTTPS-RR information for it, * we delay the connect until it arrives or DNS resolve fails. */ if(Curl_wssl_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + !Curl_conn_dns_resolved_https(data, cf->sockindex, + connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); return CURLE_OK; } @@ -2186,7 +2173,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_2; } - if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_2) { if(connssl->earlydata_state == ssl_earlydata_await) { /* We defer the handshake until request data arrives. */ DEBUGASSERT(connssl->state == ssl_connection_deferred); @@ -2199,7 +2186,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_3; } - if(ssl_connect_3 == connssl->connecting_state) { + if(connssl->connecting_state == ssl_connect_3) { /* Once the handshake has errored, it stays in that state and * errors again on every call. */ if(wssl->hs_result) { @@ -2211,7 +2198,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, wssl->hs_result = result; goto out; } - /* handhshake was done without errors */ + /* handshake was done without errors */ #ifdef HAVE_ALPN if(connssl->alpn) { int rc; @@ -2285,8 +2272,8 @@ static CURLcode wssl_random(struct Curl_easy *data, return CURLE_OK; } -static CURLcode wssl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, +static CURLcode wssl_sha256sum(const unsigned char *input, + size_t len, unsigned char *sha256sum /* output */, size_t unused) { @@ -2294,8 +2281,15 @@ static CURLcode wssl_sha256sum(const unsigned char *tmp, /* input */ (void)unused; if(wc_InitSha256(&SHA256pw)) return CURLE_FAILED_INIT; - wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen); - wc_Sha256Final(&SHA256pw, sha256sum); + do { + word32 ilen = (word32)CURLMIN(len, UINT32_MAX); + if(wc_Sha256Update(&SHA256pw, input, ilen)) + return CURLE_BAD_FUNCTION_ARGUMENT; + len -= ilen; + input += ilen; + } while(len); + if(wc_Sha256Final(&SHA256pw, sha256sum)) + return CURLE_BAD_FUNCTION_ARGUMENT; return CURLE_OK; } diff --git a/lib/vtls/wolfssl.h b/lib/vtls/wolfssl.h index 2490bf3e9cff..2016a0069f0e 100644 --- a/lib/vtls/wolfssl.h +++ b/lib/vtls/wolfssl.h @@ -88,7 +88,8 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, int ietf_tls_id, const char *alpn, unsigned char *quic_tp, - size_t quic_tp_len); + size_t quic_tp_len, + struct Curl_ssl_session **pscs); #endif CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c index 788dfb278ade..4c5dac3e8b83 100644 --- a/lib/vtls/x509asn1.c +++ b/lib/vtls/x509asn1.c @@ -1013,7 +1013,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, return 1; /* Compute key length. */ - for(q = elem.beg; !*q && q < elem.end; q++) + for(q = elem.beg; q < elem.end && !*q; q++) ; len = ((elem.end - q) * 8); if(len) { @@ -1062,7 +1062,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, if(p) { if(do_pubkey_field(data, certnum, "dh(p)", &elem)) return 1; - if(getASN1Element(&elem, param->beg, param->end)) { + if(getASN1Element(&elem, p, param->end)) { if(do_pubkey_field(data, certnum, "dh(g)", &elem)) return 1; if(do_pubkey_field(data, certnum, "dh(pub_key)", &pk)) diff --git a/lib/ws.c b/lib/ws.c index d7840f1ffb8d..a7653df6781b 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -32,6 +32,7 @@ #include "curlx/dynbuf.h" #include "rand.h" #include "curlx/base64.h" +#include "cf-recvbuf.h" #include "connect.h" #include "sendf.h" #include "curl_trc.h" @@ -42,16 +43,15 @@ #include "curlx/strparse.h" #include "curlx/strcopy.h" -/*** - RFC 6455 Section 5.2 +/* 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| | -*/ + 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 @@ -476,7 +476,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->frame_age = 0; dec->payload_offset = 0; - ws_dec_info(dec, data, "decoded"); + ws_dec_info(dec, data, "head"); return CURLE_OK; } return CURLE_AGAIN; @@ -495,7 +495,8 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, size_t remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset, 0, SIZE_MAX); - while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) { + while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen) && + !Curl_cwriter_is_paused(data)) { if(inlen > remain) inlen = remain; result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags, @@ -632,6 +633,7 @@ static CURLcode ws_enc_add_cntrl(struct Curl_easy *data, size_t plen, unsigned int frame_type) { + (void)data; DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN); if(plen > WS_MAX_CNTRL_LEN) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -641,13 +643,6 @@ static CURLcode ws_enc_add_cntrl(struct Curl_easy *data, ws->pending.type = frame_type; ws->pending.payload_len = plen; memcpy(ws->pending.payload, payload, plen); - - if(!ws->enc.payload_remain) { /* not in the middle of another frame */ - CURLcode result = ws_enc_add_pending(data, ws); - if(!result) - (void)ws_flush(data, ws, Curl_is_in_callback(data)); - return result; - } return CURLE_OK; } @@ -700,6 +695,7 @@ static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, update_meta(ws, frame_age, frame_flags, payload_offset, payload_len, buflen); + CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen); result = Curl_cwriter_write(data, ctx->next_writer, (ctx->cw_type | CLIENTWRITE_0LEN), (const char *)buf, buflen); @@ -716,9 +712,9 @@ static CURLcode ws_cw_write(struct Curl_easy *data, { struct ws_cw_ctx *ctx = writer->ctx; struct websocket *ws; - CURLcode result; + CURLcode result = CURLE_OK; - CURL_TRC_WRITE(data, "ws_cw_write(len=%zu, type=%d)", nbytes, type); + CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type); if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); @@ -733,12 +729,16 @@ static CURLcode ws_cw_write(struct Curl_easy *data, result = Curl_bufq_write(&ctx->buf, (const uint8_t *)buf, nbytes, &nwritten); if(result) { - infof(data, "[WS] error adding data to buffer %d", result); + infof(data, "[WS] error adding data to buffer %d", (int)result); return result; } } - while(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_cwriter_flush(data, writer->next); + if(result) + goto out; + + while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { struct ws_cw_dec_ctx pass_ctx; pass_ctx.data = data; pass_ctx.ws = ws; @@ -749,29 +749,90 @@ static CURLcode ws_cw_write(struct Curl_easy *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 CURLE_OK; + result = CURLE_OK; + goto out; } else if(result) { failf(data, "[WS] decode payload error %d", (int)result); - return result; + Curl_bufq_reset(&ctx->buf); + goto out; } } if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { failf(data, "[WS] decode ending with %zu frame bytes remaining", Curl_bufq_len(&ctx->buf)); - return CURLE_RECV_ERROR; + result = CURLE_RECV_ERROR; } - return CURLE_OK; +out: + if(!result) { + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); + if(result == CURLE_AGAIN) + result = CURLE_OK; + } + return result; +} + +static CURLcode ws_cw_flush(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + CURLcode result = CURLE_OK; + + CURL_TRC_WRITE(data, "[ws] flush"); + if(!data->set.ws_raw_mode) { + struct ws_cw_ctx *ctx = writer->ctx; + struct websocket *ws; + + /* Frames should be written one by one, else the meta data does + * not fit. Flush the next writer first, so it does not aggregate + * our flushed data with anything it might have buffered. */ + result = Curl_cwriter_flush(data, writer->next); + if(result) + goto out; + + 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; + } + + while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { + 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 = CLIENTWRITE_BODY; + 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 */ + result = CURLE_OK; + goto out; + } + else if(result) { + failf(data, "[WS] decode payload error %d", (int)result); + Curl_bufq_reset(&ctx->buf); + goto out; + } + } + } + +out: + if(!result) + result = Curl_cwriter_flush(data, writer->next); + return result; } /* WebSocket payload decoding client writer. */ static const struct Curl_cwtype ws_cw_decode = { "ws-decode", NULL, + 0, ws_cw_init, ws_cw_write, + ws_cw_flush, ws_cw_close, sizeof(struct ws_cw_ctx) }; @@ -800,28 +861,27 @@ static void ws_enc_init(struct ws_encoder *enc) ws_enc_reset(enc); } -/*** - RFC 6455 Section 5.2 - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ -*/ +/* RFC 6455 Section 5.2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ static CURLcode ws_enc_add_frame(struct Curl_easy *data, struct ws_encoder *enc, @@ -995,7 +1055,7 @@ static CURLcode ws_enc_add_pending(struct Curl_easy *data, &ws->sendbuf); if(result) { CURL_TRC_WS(data, "ws_enc_cntrl(), error adding head: %d", - result); + (int)result); goto out; } result = ws_enc_write_payload(&ws->enc, data, ws->pending.payload, @@ -1003,7 +1063,7 @@ static CURLcode ws_enc_add_pending(struct Curl_easy *data, &ws->sendbuf, &n); if(result) { CURL_TRC_WS(data, "ws_enc_cntrl(), error adding payload: %d", - result); + (int)result); goto out; } if(n != ws->pending.payload_len) { @@ -1056,7 +1116,7 @@ static CURLcode ws_enc_send(struct Curl_easy *data, } } else { - result = ws_flush(data, ws, Curl_is_in_callback(data)); + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result) return result; @@ -1065,7 +1125,8 @@ static CURLcode ws_enc_send(struct Curl_easy *data, fragsize : (curl_off_t)buflen, &ws->sendbuf); if(result) { - CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d", result); + CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d", + (int)result); return result; } } @@ -1088,7 +1149,7 @@ static CURLcode ws_enc_send(struct Curl_easy *data, } /* flush, blocking when in callback */ - result = ws_flush(data, ws, Curl_is_in_callback(data)); + result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(!result && ws->sendbuf_payload > 0) { *pnsent += ws->sendbuf_payload; buffer += ws->sendbuf_payload; @@ -1219,7 +1280,7 @@ static CURLcode cr_ws_read(struct Curl_easy *data, out: CURL_TRC_READ(data, "cr_ws_read(len=%zu) -> %d, nread=%zu, eos=%d", - blen, result, *pnread, *peos); + blen, (int)result, *pnread, *peos); return result; } @@ -1360,14 +1421,13 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, The sent value is the base64 encoded version of a SHA-1 hash done on the |Sec-WebSocket-Key| header field concatenated with - the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". - */ + the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". */ /* If the response includes a |Sec-WebSocket-Extensions| header field and this header field indicates the use of an extension that was not present in the client's handshake (the server has indicated an extension not requested by the client), the client MUST Fail the WebSocket Connection. - */ + */ /* If the response includes a |Sec-WebSocket-Protocol| header field and this header field indicates the use of a subprotocol that was @@ -1390,15 +1450,17 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, k->header = FALSE; /* we will not get more response headers */ if(data->set.connect_only) { - size_t nwritten; /* In CONNECT_ONLY setup, the payloads from `mem` need to be received - * when using `curl_ws_recv` later on after this transfer is already - * marked as DONE. */ - result = Curl_bufq_write(&ws->recvbuf, (const uint8_t *)mem, - nread, &nwritten); - if(result) - goto out; - DEBUGASSERT(nread == nwritten); + * when using `curl_ws_recv/curl_easy_recv` later on, after this transfer + * is already marked as DONE. + * Since `curl_easy_recv()` is also supposed to work, we need + * to buffer the data at connection level. See #22107 */ + if(nread) { + result = Curl_cf_recvbuf_add(data, data->conn, FIRSTSOCKET, + (const uint8_t *)mem, nread); + if(result) + goto out; + } CURL_REQ_CLEAR_RECV(data); /* read no more content */ } else { /* !connect_only */ @@ -1443,7 +1505,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, if(ws_enc_reader) Curl_creader_free(data, ws_enc_reader); if(result) - CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", result); + CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", (int)result); else CURL_TRC_WS(data, "websocket established, %s mode", data->set.connect_only ? "connect-only" : "callback"); @@ -1524,111 +1586,131 @@ static CURLcode nw_in_recv(void *reader_ctx, size_t *pnread) { struct Curl_easy *data = reader_ctx; - return curl_easy_recv(data, buf, buflen, pnread); + return Curl_easy_recv(data, buf, buflen, pnread); } CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *recv, const struct curl_ws_frame **metap) { - struct Curl_easy *data = curl; - struct connectdata *conn; - struct websocket *ws; - struct ws_collect ctx; + struct Curl_eapi_guard guard; + CURLcode result = CURLE_OK; *recv = 0; *metap = NULL; - if(!GOOD_EASY_HANDLE(data) || (buflen && !buffer)) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) { + struct Curl_easy *data = curl; + struct connectdata *conn; + struct websocket *ws; + struct ws_collect ctx; - conn = data->conn; - if(!conn) { - /* Unhappy hack with lifetimes of transfers and connection */ - if(!data->set.connect_only) { - failf(data, "[WS] CONNECT_ONLY is required"); - return CURLE_UNSUPPORTED_PROTOCOL; + if(buflen && !buffer) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - Curl_getconnectinfo(data, &conn); + conn = data->conn; if(!conn) { - failf(data, "[WS] connection not found"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } - ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); - if(!ws) { - failf(data, "[WS] connection is not setup for websocket"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - memset(&ctx, 0, sizeof(ctx)); - ctx.data = data; - ctx.ws = ws; - ctx.buffer = buffer; - ctx.buflen = buflen; - - while(1) { - CURLcode result; - - /* receive more when our buffer is empty */ - if(Curl_bufq_is_empty(&ws->recvbuf)) { - size_t n; - result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n); - if(result) - return result; - else if(n == 0) { - /* connection closed */ - infof(data, "[WS] connection expectedly closed?"); - return CURLE_GOT_NOTHING; + /* Unhappy hack with lifetimes of transfers and connection */ + if(!data->set.connect_only) { + failf(data, "[WS] CONNECT_ONLY is required"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; } - 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, - ws_client_collect, &ctx); - if(result == CURLE_AGAIN) { - if(!ctx.written) { - ws_dec_info(&ws->dec, data, "need more input"); - continue; /* nothing written, try more input */ + Curl_getconnectinfo(data, &conn); + if(!conn) { + failf(data, "[WS] connection not found"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - break; } - else if(result) { - return result; + ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] connection is not setup for websocket"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - else if(ctx.written) { - /* 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. */ - break; + + memset(&ctx, 0, sizeof(ctx)); + ctx.data = data; + ctx.ws = ws; + ctx.buffer = buffer; + ctx.buflen = buflen; + + while(1) { + /* receive more when our buffer is empty */ + if(Curl_bufq_is_empty(&ws->recvbuf)) { + size_t n; + result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n); + if(result) + goto out; + else if(n == 0) { + /* connection closed */ + infof(data, "[WS] connection expectedly closed?"); + result = CURLE_GOT_NOTHING; + goto out; + } + 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, + ws_client_collect, &ctx); + if(result == CURLE_AGAIN) { + if(!ctx.written) { + ws_dec_info(&ws->dec, data, "need more input"); + continue; /* nothing written, try more input */ + } + break; + } + else if(result) { + goto out; + } + else if(ctx.written) { + /* 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. */ + break; + } } - } - /* update frame information to be passed back */ - update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset, - ctx.payload_len, ctx.bufidx); - *metap = &ws->recvframe; - *recv = ws->recvframe.len; - CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" - FMT_OFF_T ", %" FMT_OFF_T " left)", - buflen, *recv, ws->recvframe.offset, - ws->recvframe.bytesleft); - /* all's well, try to send any pending control. we do not know - * when the application will call `curl_ws_send()` again. */ - if(!data->set.ws_raw_mode && ws->pending.type) { - CURLcode r2 = ws_enc_add_pending(data, ws); - if(!r2) - (void)ws_flush(data, ws, Curl_is_in_callback(data)); + /* update frame information to be passed back */ + update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset, + ctx.payload_len, ctx.bufidx); + *metap = &ws->recvframe; + *recv = ws->recvframe.len; + CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + FMT_OFF_T ", %" FMT_OFF_T " left)", + buflen, *recv, ws->recvframe.offset, + ws->recvframe.bytesleft); + /* all's well, try to send any pending control. we do not know + * when the application will call `curl_ws_send()` again. */ + if(!data->set.ws_raw_mode && ws->pending.type) { + CURLcode r2 = ws_enc_add_pending(data, ws); + if(!r2) + (void)ws_flush(data, ws, Curl_api_is_in_callback(data)); + } + result = CURLE_OK; } - return CURLE_OK; +out: + CURL_EAPI_LEAVE(&guard); + return result; } static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, bool blocking) { + CURLcode result; + + /* If there is space, add any pending control frame */ + if(Curl_bufq_len(&ws->sendbuf) < ws->sendbuf.chunk_size) { + result = ws_enc_add_pending(data, ws); + if(result && (result != CURLE_AGAIN)) + return result; + } + if(!Curl_bufq_is_empty(&ws->sendbuf)) { - CURLcode result; const uint8_t *out; size_t outlen, n; #ifdef DEBUGBUILD @@ -1656,7 +1738,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, 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)) + else if(data->set.connect_only || Curl_api_is_in_callback(data)) result = Curl_senddata(data, out, outlen, &n); else { result = Curl_xfer_send(data, out, outlen, FALSE, &n); @@ -1670,7 +1752,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, return result; } else if(result) { - failf(data, "[WS] flush, write error %d", result); + failf(data, "[WS] flush, write error %d", (int)result); return result; } else { @@ -1740,7 +1822,7 @@ static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer, if(!buflen) return CURLE_OK; - if(Curl_is_in_callback(data)) { + if(Curl_api_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. @@ -1761,7 +1843,7 @@ static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer, } CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu", - buflen, result, *pnwritten); + buflen, (int)result, *pnwritten); return result; } @@ -1770,75 +1852,80 @@ CURLcode curl_ws_send(CURL *curl, const void *buffer_arg, curl_off_t fragsize, unsigned int flags) { - struct websocket *ws; - const uint8_t *buffer = buffer_arg; + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - struct Curl_easy *data = curl; - size_t ndummy; - size_t *pnsent = sent ? sent : &ndummy; - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - 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); + if(CURL_EAPI_ENTER(&guard, curl, ws_send, &result)) { + struct websocket *ws; + const uint8_t *buffer = buffer_arg; + struct Curl_easy *data = curl; + size_t ndummy; + size_t *pnsent = sent ? sent : &ndummy; - *pnsent = 0; + 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); - if(!buffer && buflen) { - failf(data, "[WS] buffer is NULL when buflen is not"); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } + *pnsent = 0; - if(!data->conn && data->set.connect_only) { - result = Curl_connect_only_attach(data); - if(result) - goto out; - } - if(!data->conn) { - failf(data, "[WS] No associated connection"); - result = CURLE_SEND_ERROR; - goto out; - } - 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; - } - - if(data->set.ws_raw_mode) { - /* 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) + if(!buffer && buflen) { + failf(data, "[WS] buffer is NULL when buflen is not"); + result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; + } - if(!buffer) { - failf(data, "[WS] buffer is NULL in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; + if(!data->conn && data->set.connect_only) { + result = Curl_connect_only_attach(data); + if(result) + goto out; } - if(!sent) { - failf(data, "[WS] sent is NULL in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; + if(!data->conn) { + failf(data, "[WS] No associated connection"); + result = CURLE_SEND_ERROR; + goto out; } - if(fragsize || flags) { - failf(data, "[WS] fragsize and flags must be zero in raw mode"); - return CURLE_BAD_FUNCTION_ARGUMENT; + 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; } - result = ws_send_raw(data, buffer, buflen, pnsent); - goto out; - } - /* Not RAW mode, we do the frame encoding */ - result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent); + if(data->set.ws_raw_mode) { + /* 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(!buffer) { + failf(data, "[WS] buffer is NULL in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(!sent) { + failf(data, "[WS] sent is NULL in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(fragsize || flags) { + failf(data, "[WS] fragsize and flags must be zero in raw mode"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + result = ws_send_raw(data, buffer, buflen, pnsent); + goto out; + } + + /* Not RAW mode, we do the frame encoding */ + result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent); + 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, (int)result, + *pnsent); + } 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, - *pnsent); + CURL_EAPI_LEAVE(&guard); return result; } @@ -1858,7 +1945,7 @@ const struct curl_ws_frame *curl_ws_meta(CURL *curl) /* we only return something for websocket, called from within the callback when not using raw mode */ struct Curl_easy *data = curl; - if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && + if(GOOD_EASY_HANDLE(data) && Curl_api_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); @@ -1872,46 +1959,48 @@ CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl, unsigned int flags, curl_off_t frame_len) { - struct websocket *ws; + struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - struct Curl_easy *data = curl; - - if(!GOOD_EASY_HANDLE(data)) - return CURLE_BAD_FUNCTION_ARGUMENT; - if(data->set.ws_raw_mode) { - failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled"); - return CURLE_FAILED_INIT; - } + if(CURL_EAPI_ENTER(&guard, curl, ws_start_frame, &result)) { + struct Curl_easy *data = curl; + struct websocket *ws; - CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T, - flags, frame_len); + if(data->set.ws_raw_mode) { + failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled"); + result = CURLE_FAILED_INIT; + goto out; + } - if(!data->conn) { - failf(data, "[WS] No associated connection"); - result = CURLE_SEND_ERROR; - goto out; - } - 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; - } + CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T, + flags, frame_len); - if(ws->enc.payload_remain) { - failf(data, "[WS] previous frame not finished"); - result = CURLE_SEND_ERROR; - goto out; - } + if(!data->conn) { + failf(data, "[WS] No associated connection"); + result = CURLE_SEND_ERROR; + goto out; + } + 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; + } - result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len, - &ws->sendbuf); - if(result) - CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d", - result); + if(ws->enc.payload_remain) { + failf(data, "[WS] previous frame not finished"); + result = CURLE_SEND_ERROR; + goto out; + } + result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len, + &ws->sendbuf); + if(result) + CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d", + (int)result); + } out: + CURL_EAPI_LEAVE(&guard); return result; } diff --git a/libcurl.pc.in b/libcurl.pc.in index c0ba5244a839..8f7e35dd1097 100644 --- a/libcurl.pc.in +++ b/libcurl.pc.in @@ -32,10 +32,15 @@ supported_features="@SUPPORT_FEATURES@" Name: libcurl URL: https://curl.se/ Description: Library to transfer files with HTTP, FTP, etc. +License: curl +Copyright: Copyright (C) Daniel Stenberg, , et al. +Maintainer: @CURL_PACKAGE_MAINTAINER@ Version: @CURLVERSION@ +Source: https://curl.se/download/curl-@CURLVERSION@.tar.gz Requires: @LIBCURL_PC_REQUIRES@ Requires.private: @LIBCURL_PC_REQUIRES_PRIVATE@ Libs: -L${libdir} -lcurl @LIBCURL_PC_LIBS@ Libs.private: @LIBCURL_PC_LDFLAGS_PRIVATE@ @LIBCURL_PC_LIBS_PRIVATE@ Cflags: -I${includedir} @LIBCURL_PC_CFLAGS@ Cflags.private: @LIBCURL_PC_CFLAGS_PRIVATE@ +Link.ABI: c diff --git a/m4/curl-amissl.m4 b/m4/curl-amissl.m4 index da90cc412d7a..70239a3ac695 100644 --- a/m4/curl-amissl.m4 +++ b/m4/curl-amissl.m4 @@ -23,7 +23,7 @@ #*************************************************************************** AC_DEFUN([CURL_WITH_AMISSL], [ -AC_MSG_CHECKING([whether to enable Amiga native SSL/TLS (AmiSSL v5)]) +AC_MSG_CHECKING([whether to enable Amiga native SSL/TLS (AmiSSL 5)]) if test "$HAVE_PROTO_BSDSOCKET_H" = "1"; then if test "x$OPT_AMISSL" != "xno"; then ssl_msg= @@ -36,7 +36,7 @@ if test "$HAVE_PROTO_BSDSOCKET_H" = "1"; then (OPENSSL_VERSION_NUMBER >= 0x30000000L) && defined(PROTO_AMISSL_H) return 0; #else - #error not AmiSSL v5 / OpenSSL 3 + #error not AmiSSL 5 / OpenSSL 3 #endif ]]) ],[ @@ -45,7 +45,7 @@ if test "$HAVE_PROTO_BSDSOCKET_H" = "1"; then test "amissl" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes AMISSL_ENABLED=1 OPENSSL_ENABLED=1 - dnl Use AmiSSL's built-in ca bundle + dnl Use AmiSSL's built-in CA bundle check_for_ca_bundle=1 with_ca_fallback=yes LIBS="-lamisslstubs -lamisslauto $LIBS" @@ -63,5 +63,4 @@ if test "$HAVE_PROTO_BSDSOCKET_H" = "1"; then else AC_MSG_RESULT(no) fi - ]) diff --git a/m4/curl-apple-sectrust.m4 b/m4/curl-apple-sectrust.m4 index c70b7ac8cd61..decef05c58c8 100644 --- a/m4/curl-apple-sectrust.m4 +++ b/m4/curl-apple-sectrust.m4 @@ -57,5 +57,4 @@ if test "x$OPT_APPLE_SECTRUST" = "xyes"; then else AC_MSG_RESULT(no) fi - ]) diff --git a/m4/curl-compilers.m4 b/m4/curl-compilers.m4 index 4bb7196eae13..5d838a318c41 100644 --- a/m4/curl-compilers.m4 +++ b/m4/curl-compilers.m4 @@ -64,11 +64,11 @@ AC_DEFUN([CURL_CHECK_COMPILER], [ *** compiler you are using, relative to the flags required to enable or *** disable generation of debug info, optimization options or warnings. *** -*** Whatever settings are present in CFLAGS will be used for this run. +*** Whatever settings are present in CFLAGS are used for this run. *** *** If you wish to help the curl project to better support your compiler *** you can report this and the required info on the libcurl development -*** mailing list: https://lists.haxx.selistinfo/curl-library/ +*** mailing list: https://lists.haxx.se/listinfo/curl-library/ *** _EOF fi @@ -173,8 +173,8 @@ dnl CURL_CHECK_COMPILER_GNU_C dnl ------------------------------------------------- dnl Verify if compiler being used is GNU C dnl -dnl $compiler_num will be set to MAJOR * 100 + MINOR for gcc less than version -dnl 7 and just $MAJOR * 100 for gcc version 7 and later. +dnl $compiler_num is set to MAJOR * 100 + MINOR for gcc less than version +dnl 7 and $MAJOR * 100 for gcc version 7 and later. dnl dnl Examples: dnl Version 1.2.3 => 102 @@ -188,7 +188,7 @@ AC_DEFUN([CURL_CHECK_COMPILER_GNU_C], [ AC_MSG_CHECKING([if compiler is GNU C]) CURL_CHECK_DEF([__GNUC__], [], [silent]) if test "$curl_cv_have_def___GNUC__" = "yes" && - test "$compiler_id" = "unknown"; then + test "$compiler_id" = "unknown"; then AC_MSG_RESULT([yes]) compiler_id="GNU_C" AC_MSG_CHECKING([compiler version]) @@ -305,8 +305,8 @@ AC_DEFUN([CURL_CHECK_COMPILER_SGI_MIPS_C], [ CURL_CHECK_DEF([__GNUC__], [], [silent]) CURL_CHECK_DEF([__sgi], [], [silent]) if test "$curl_cv_have_def___GNUC__" = "no" && - test "$curl_cv_have_def___sgi" = "yes" && - test "$compiler_id" = "unknown"; then + test "$curl_cv_have_def___sgi" = "yes" && + test "$compiler_id" = "unknown"; then AC_MSG_RESULT([yes]) compiler_id="SGI_MIPS_C" flags_dbg_yes="-g" @@ -330,8 +330,8 @@ AC_DEFUN([CURL_CHECK_COMPILER_SGI_MIPSPRO_C], [ CURL_CHECK_DEF([_COMPILER_VERSION], [], [silent]) CURL_CHECK_DEF([_SGI_COMPILER_VERSION], [], [silent]) if test "$curl_cv_have_def___GNUC__" = "no" && - (test "$curl_cv_have_def__SGI_COMPILER_VERSION" = "yes" || - test "$curl_cv_have_def__COMPILER_VERSION" = "yes"); then + (test "$curl_cv_have_def__SGI_COMPILER_VERSION" = "yes" || + test "$curl_cv_have_def__COMPILER_VERSION" = "yes"); then AC_MSG_RESULT([yes]) compiler_id="SGI_MIPSPRO_C" flags_dbg_yes="-g" @@ -526,8 +526,8 @@ AC_DEFUN([CURL_SET_COMPILER_BASIC_OPTS], [ CLANG|APPLECLANG) - dnl Disable warnings for unused arguments, otherwise clang will - dnl warn about compile-time arguments used during link-time, like + dnl Disable warnings for unused arguments, otherwise clang warns + dnl about compile-time arguments used during link-time, like dnl -O and -g and -pedantic. tmp_CFLAGS="$tmp_CFLAGS -Qunused-arguments" tmp_CFLAGS="$tmp_CFLAGS -Werror-implicit-function-declaration" @@ -713,7 +713,7 @@ AC_DEFUN([CURL_SET_COMPILER_OPTIMIZE_OPTS], [ dnl If optimization request setting has not been explicitly specified, dnl it has been derived from the debug setting and initially assumed. - dnl This initially assumed optimizer setting will finally be ignored + dnl This initially assumed optimizer setting are finally ignored dnl if CFLAGS or CPPFLAGS already hold optimizer flags. This implies dnl that an initially assumed optimizer setting might not be honored. @@ -862,6 +862,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [conditional-uninitialized]) CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [language-extension-token]) fi + dnl Only clang 3.1 or later if test "$compiler_num" -ge "301"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [format-non-iso]) @@ -883,6 +884,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ ;; esac fi + dnl Only clang 3.3 or later if test "$compiler_num" -ge "303"; then tmp_CFLAGS="$tmp_CFLAGS -Wno-documentation-unknown-command" @@ -914,38 +916,46 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ tmp_CFLAGS="$tmp_CFLAGS -Wno-varargs" fi fi + dnl clang 7 or later if test "$compiler_num" -ge "700"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [assign-enum]) CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [extra-semi-stmt]) fi + dnl clang 10 or later if test "$compiler_num" -ge "1000"; then tmp_CFLAGS="$tmp_CFLAGS -Wimplicit-fallthrough" # we have silencing markup for clang 10.0 and above only CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [xor-used-as-pow]) fi + dnl clang 13 or later if test "$compiler_num" -ge "1300"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [cast-function-type]) CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [reserved-identifier]) # Keep it before -Wno-reserved-macro-identifier tmp_CFLAGS="$tmp_CFLAGS -Wno-reserved-macro-identifier" # Sometimes such external macros need to be set fi + dnl clang 16 or later if test "$compiler_num" -ge "1600"; then tmp_CFLAGS="$tmp_CFLAGS -Wno-unsafe-buffer-usage" fi + dnl clang 17 or later if test "$compiler_num" -ge "1700"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [cast-function-type-strict]) # with Apple clang it requires 16.0 or above fi + dnl clang 19 or later if test "$compiler_num" -ge "1901"; then - tmp_CFLAGS="$tmp_CFLAGS -Wno-format-signedness" + CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [format-signedness]) fi + dnl clang 20 or later if test "$compiler_num" -ge "2001"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [array-compare]) fi + dnl clang 21 or later if test "$compiler_num" -ge "2101"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [c++-hidden-decl]) @@ -966,7 +976,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ tmp_CFLAGS="$tmp_CFLAGS -Wno-c99-extensions" # Avoid: warning: '_Bool' is a C99 extension fi if test "$compiler_num" -ge "309"; then - tmp_CFLAGS="$tmp_CFLAGS -Wno-comma" # Just silly + tmp_CFLAGS="$tmp_CFLAGS -Wno-comma" # Silly fi ;; esac @@ -1006,7 +1016,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [pointer-arith write-strings]) dnl If not cross-compiling with a gcc older than 3.0 if test "$cross_compiling" != "yes" || - test "$compiler_num" -ge "300"; then + test "$compiler_num" -ge "300"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [unused shadow]) fi fi @@ -1016,7 +1026,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [nested-externs]) dnl If not cross-compiling with a gcc older than 3.0 if test "$cross_compiling" != "yes" || - test "$compiler_num" -ge "300"; then + test "$compiler_num" -ge "300"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [missing-declarations]) CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [missing-prototypes]) fi @@ -1105,6 +1115,11 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ tmp_CFLAGS="$tmp_CFLAGS -ftree-vrp" fi + dnl Only gcc 4.4 or later + if test "$compiler_num" -ge "404"; then + CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [logical-op]) + fi + dnl Only gcc 4.5 or later if test "$compiler_num" -ge "405"; then CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [jump-misses-init]) @@ -1134,7 +1149,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ dnl Only gcc 5 or later if test "$compiler_num" -ge "500"; then tmp_CFLAGS="$tmp_CFLAGS -Warray-bounds=2" - tmp_CFLAGS="$tmp_CFLAGS -Wno-format-signedness" + CURL_ADD_COMPILER_WARNINGS([tmp_CFLAGS], [format-signedness]) fi dnl Only gcc 6 or later @@ -1245,7 +1260,7 @@ AC_DEFUN([CURL_SET_COMPILER_WARNING_OPTS], [ tmp_CPPFLAGS="$tmp_CPPFLAGS -Wp64" dnl Enable warnings for questionable pointer arithmetic tmp_CPPFLAGS="$tmp_CPPFLAGS -Wpointer-arith" - dnl Check for function return typw issues + dnl Check for function return type issues tmp_CPPFLAGS="$tmp_CPPFLAGS -Wreturn-type" dnl Warn on variable declarations hiding a previous one tmp_CPPFLAGS="$tmp_CPPFLAGS -Wshadow" @@ -1461,7 +1476,7 @@ AC_DEFUN([CURL_CHECK_COMPILER_STRUCT_MEMBER_SIZE], [ tst_compiler_check_two_works="yes" ]) if test "$tst_compiler_check_one_works" = "yes" && - test "$tst_compiler_check_two_works" = "yes"; then + test "$tst_compiler_check_two_works" = "yes"; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) diff --git a/m4/curl-confopts.m4 b/m4/curl-confopts.m4 index fe20155564a0..133ee219cf32 100644 --- a/m4/curl-confopts.m4 +++ b/m4/curl-confopts.m4 @@ -110,11 +110,11 @@ dnl variable want_debug value as appropriate. AC_DEFUN([CURL_CHECK_OPTION_DEBUG], [ AC_BEFORE([$0],[CURL_CHECK_OPTION_WARNINGS]) AC_BEFORE([$0],[XC_CHECK_PROG_CC]) - AC_MSG_CHECKING([whether to enable debug build options]) + AC_MSG_CHECKING([whether to enable curl debug features (for developing curl)]) OPT_DEBUG_BUILD="default" AC_ARG_ENABLE(debug, -AS_HELP_STRING([--enable-debug],[Enable debug build options]) -AS_HELP_STRING([--disable-debug],[Disable debug build options]), +AS_HELP_STRING([--enable-debug],[Enable curl debug features (for developing curl)]) +AS_HELP_STRING([--disable-debug],[Disable curl debug features (default)]), OPT_DEBUG_BUILD=$enableval) case "$OPT_DEBUG_BUILD" in no) @@ -150,25 +150,25 @@ AS_HELP_STRING([--disable-optimize],[Disable compiler optimizations]), OPT_COMPILER_OPTIMIZE=$enableval) case "$OPT_COMPILER_OPTIMIZE" in no) - dnl --disable-optimize option used. We will handle this as - dnl a request to disable compiler optimizations if possible. - dnl If the compiler is known CFLAGS and CPPFLAGS will be - dnl overridden, otherwise this can not be honored. + dnl --disable-optimize option used. We handle this as a request + dnl to disable compiler optimizations if possible. If the compiler + dnl is known CFLAGS and CPPFLAGS are overridden, otherwise this + dnl can not be honored. want_optimize="no" AC_MSG_RESULT([no]) ;; default) - dnl configure's optimize option not specified. Initially we will - dnl handle this as a request contrary to configure's setting - dnl for --enable-debug. IOW, initially, for debug-enabled builds - dnl this will be handled as a request to disable optimizations if - dnl possible, and for debug-disabled builds this will be handled - dnl initially as a request to enable optimizations if possible. - dnl Finally, if the compiler is known and CFLAGS and CPPFLAGS do - dnl not have any optimizer flag the request will be honored, in - dnl any other case the request can not be honored. + dnl configure's optimize option not specified. Initially we handle + dnl this as a request contrary to configure's setting for + dnl --enable-debug. IOW, initially, for debug-enabled builds this + dnl is handled as a request to disable optimizations if possible, + dnl and for debug-disabled builds this is handled initially as + dnl a request to enable optimizations if possible. Finally, if the + dnl compiler is known and CFLAGS and CPPFLAGS do not have any + dnl optimizer flag the request is honored, in any other case the + dnl request can not be honored. dnl IOW, existing optimizer flags defined in CFLAGS or CPPFLAGS - dnl will always take precedence over any initial assumption. + dnl always take precedence over any initial assumption. if test "$want_debug" = "yes"; then want_optimize="assume_no" AC_MSG_RESULT([(assumed) no]) @@ -178,10 +178,10 @@ AS_HELP_STRING([--disable-optimize],[Disable compiler optimizations]), fi ;; *) - dnl --enable-optimize option used. We will handle this as - dnl a request to enable compiler optimizations if possible. - dnl If the compiler is known CFLAGS and CPPFLAGS will be - dnl overridden, otherwise this can not be honored. + dnl --enable-optimize option used. We handle this as a request + dnl to enable compiler optimizations if possible. If the compiler + dnl is known CFLAGS and CPPFLAGS are overridden, otherwise this + dnl can not be honored. want_optimize="yes" AC_MSG_RESULT([yes]) ;; @@ -207,7 +207,7 @@ AS_HELP_STRING([--disable-symbol-hiding],[Disable hiding of library internal sym no) dnl --disable-symbol-hiding option used. dnl This is an indication to not attempt hiding of library internal - dnl symbols. Default symbol visibility will be used, which normally + dnl symbols. Default symbol visibility is used, which normally dnl exposes all library internal symbols. want_symbol_hiding="no" AC_MSG_RESULT([no]) @@ -361,17 +361,17 @@ dnl CURL_CONFIGURE_SYMBOL_HIDING dnl ------------------------------------------------- dnl Depending on --enable-symbol-hiding or --disable-symbol-hiding dnl configure option, and compiler capability to actually honor such -dnl option, this will modify compiler flags as appropriate and also -dnl provide needed definitions for configuration and Makefile.am files. +dnl option, this modifies compiler flags as appropriate and also +dnl provides needed definitions for configuration and Makefile.am files. dnl This macro should not be used until all compilation tests have dnl been done to prevent interferences on other tests. AC_DEFUN([CURL_CONFIGURE_SYMBOL_HIDING], [ - AC_MSG_CHECKING([whether hiding of library internal symbols will actually happen]) + AC_MSG_CHECKING([whether hiding of library internal symbols does actually happen]) CFLAG_CURL_SYMBOL_HIDING="" doing_symbol_hiding="no" if test "$want_symbol_hiding" = "yes" && - test "$supports_symbol_hiding" = "yes"; then + test "$supports_symbol_hiding" = "yes"; then doing_symbol_hiding="yes" CFLAG_CURL_SYMBOL_HIDING="$symbol_hiding_CFLAGS" AC_DEFINE_UNQUOTED(CURL_EXTERN_SYMBOL, $symbol_hiding_EXTERN, @@ -456,7 +456,7 @@ AC_DEFUN([CURL_CHECK_LIB_ARES], [ ]) if test "$want_ares" = "yes"; then - dnl finally c-ares will be used + dnl finally c-ares is used AC_DEFINE(USE_ARES, 1, [Define to enable c-ares support]) USE_ARES=1 LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE libcares" @@ -475,8 +475,8 @@ AC_DEFUN([CURL_CHECK_OPTION_HTTPSRR], [ AC_MSG_CHECKING([whether to enable HTTPSRR support]) OPT_HTTPSRR="default" AC_ARG_ENABLE(httpsrr, -AS_HELP_STRING([--enable-httpsrr],[Enable HTTPSRR support]) -AS_HELP_STRING([--disable-httpsrr],[Disable HTTPSRR support]), +AS_HELP_STRING([--enable-httpsrr],[Enable HTTPSRR support (experimental)]) +AS_HELP_STRING([--disable-httpsrr],[Disable HTTPSRR support (experimental)]), OPT_HTTPSRR=$enableval) case "$OPT_HTTPSRR" in no) @@ -510,8 +510,8 @@ AC_DEFUN([CURL_CHECK_OPTION_ECH], [ AC_MSG_CHECKING([whether to enable ECH support]) OPT_ECH="default" AC_ARG_ENABLE(ech, -AS_HELP_STRING([--enable-ech],[Enable ECH support]) -AS_HELP_STRING([--disable-ech],[Disable ECH support]), +AS_HELP_STRING([--enable-ech],[Enable ECH support (experimental)]) +AS_HELP_STRING([--disable-ech],[Disable ECH support (experimental)]), OPT_ECH=$enableval) case "$OPT_ECH" in no) @@ -547,9 +547,9 @@ AC_DEFUN([CURL_CHECK_OPTION_SSLS_EXPORT], [ OPT_SSLS_EXPORT="default" AC_ARG_ENABLE(ssls-export, AS_HELP_STRING([--enable-ssls-export], - [Enable SSL session export support]) + [Enable SSL session export support (experimental)]) AS_HELP_STRING([--disable-ssls-export], - [Disable SSL session export support]), + [Disable SSL session export support (experimental)]), OPT_SSLS_EXPORT=$enableval) case "$OPT_SSLS_EXPORT" in no) diff --git a/m4/curl-functions.m4 b/m4/curl-functions.m4 index 9d80f2f5384a..64046a834afb 100644 --- a/m4/curl-functions.m4 +++ b/m4/curl-functions.m4 @@ -25,35 +25,6 @@ dnl File version for 'aclocal' use. Keep it a single number. dnl serial 73 -dnl CURL_INCLUDES_ARPA_INET -dnl ------------------------------------------------- -dnl Set up variable with list of headers that must be -dnl included when arpa/inet.h is to be included. - -AC_DEFUN([CURL_INCLUDES_ARPA_INET], [ -curl_includes_arpa_inet="\ -/* includes start */ -#ifdef HAVE_SYS_TYPES_H -# include -#endif -#ifdef HAVE_NETINET_IN_H -# include -#endif -#ifdef HAVE_ARPA_INET_H -# include -#endif -#ifdef _WIN32 -#include -#include -#else -#include -#endif -/* includes end */" - AC_CHECK_HEADERS( - sys/types.h netinet/in.h arpa/inet.h, - [], [], [$curl_includes_arpa_inet]) -]) - dnl CURL_INCLUDES_FCNTL dnl ------------------------------------------------- @@ -467,7 +438,7 @@ dnl Verify if alarm is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_alarm, then -dnl HAVE_ALARM will be defined. +dnl HAVE_ALARM is defined. AC_DEFUN([CURL_CHECK_FUNC_ALARM], [ AC_REQUIRE([CURL_INCLUDES_UNISTD]) @@ -507,7 +478,7 @@ AC_DEFUN([CURL_CHECK_FUNC_ALARM], [ AC_LANG_PROGRAM([[ $curl_includes_unistd ]],[[ - if(alarm(0) != 0) + if(alarm(0)) return 1; ]]) ],[ @@ -552,7 +523,7 @@ dnl Verify if basename is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_basename, then -dnl HAVE_BASENAME will be defined. +dnl HAVE_BASENAME is defined. AC_DEFUN([CURL_CHECK_FUNC_BASENAME], [ AC_REQUIRE([CURL_INCLUDES_STRING]) @@ -598,7 +569,7 @@ AC_DEFUN([CURL_CHECK_FUNC_BASENAME], [ $curl_includes_libgen $curl_includes_unistd ]],[[ - if(basename(0) != 0) + if(basename(0)) return 1; ]]) ],[ @@ -643,7 +614,7 @@ dnl Verify if closesocket is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_closesocket, then -dnl HAVE_CLOSESOCKET will be defined. +dnl HAVE_CLOSESOCKET is defined. AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -658,7 +629,7 @@ AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET], [ AC_LANG_PROGRAM([[ $curl_includes_winsock2 ]],[[ - if(closesocket(0) != 0) + if(closesocket(0)) return 1; ]]) ],[ @@ -688,7 +659,7 @@ AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET], [ AC_LANG_PROGRAM([[ $curl_includes_winsock2 ]],[[ - if(closesocket(0) != 0) + if(closesocket(0)) return 1; ]]) ],[ @@ -733,7 +704,7 @@ dnl Verify if CloseSocket is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_closesocket_camel, -dnl then HAVE_CLOSESOCKET_CAMEL will be defined. +dnl then HAVE_CLOSESOCKET_CAMEL is defined. AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET_CAMEL], [ AC_REQUIRE([CURL_INCLUDES_SYS_SOCKET]) @@ -749,7 +720,7 @@ AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET_CAMEL], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(CloseSocket(0) != 0) + if(CloseSocket(0)) return 1; ]]) ],[ @@ -767,7 +738,7 @@ AC_DEFUN([CURL_CHECK_FUNC_CLOSESOCKET_CAMEL], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(CloseSocket(0) != 0) + if(CloseSocket(0)) return 1; ]]) ],[ @@ -810,7 +781,7 @@ dnl Verify if fcntl is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_fcntl, then -dnl HAVE_FCNTL will be defined. +dnl HAVE_FCNTL is defined. AC_DEFUN([CURL_CHECK_FUNC_FCNTL], [ AC_REQUIRE([CURL_INCLUDES_FCNTL]) @@ -850,7 +821,7 @@ AC_DEFUN([CURL_CHECK_FUNC_FCNTL], [ AC_LANG_PROGRAM([[ $curl_includes_fcntl ]],[[ - if(fcntl(0, 0, 0) != 0) + if(fcntl(0, 0, 0)) return 1; ]]) ],[ @@ -895,7 +866,7 @@ dnl ------------------------------------------------- dnl Verify if fcntl with status flag O_NONBLOCK is dnl available, can be compiled, and seems to work. If dnl all of these are true, then HAVE_FCNTL_O_NONBLOCK -dnl will be defined. +dnl is defined. AC_DEFUN([CURL_CHECK_FUNC_FCNTL_O_NONBLOCK], [ @@ -916,7 +887,7 @@ AC_DEFUN([CURL_CHECK_FUNC_FCNTL_O_NONBLOCK], [ $curl_includes_fcntl ]],[[ int flags = 0; - if(fcntl(0, F_SETFL, flags | O_NONBLOCK) != 0) + if(fcntl(0, F_SETFL, flags | O_NONBLOCK)) return 1; ]]) ],[ @@ -959,7 +930,7 @@ dnl Verify if freeaddrinfo is available, prototyped, dnl and can be compiled. If all of these are true, dnl and usage has not been previously disallowed with dnl shell variable curl_disallow_freeaddrinfo, then -dnl HAVE_FREEADDRINFO will be defined. +dnl HAVE_FREEADDRINFO is defined. AC_DEFUN([CURL_CHECK_FUNC_FREEADDRINFO], [ AC_REQUIRE([CURL_INCLUDES_WS2TCPIP]) @@ -1055,7 +1026,7 @@ dnl Verify if fsetxattr is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_fsetxattr, then -dnl HAVE_FSETXATTR will be defined. +dnl HAVE_FSETXATTR is defined. AC_DEFUN([CURL_CHECK_FUNC_FSETXATTR], [ AC_REQUIRE([CURL_INCLUDES_SYS_XATTR]) @@ -1097,7 +1068,7 @@ AC_DEFUN([CURL_CHECK_FUNC_FSETXATTR], [ AC_LANG_PROGRAM([[ $curl_includes_sys_xattr ]],[[ - if(fsetxattr(0, "", 0, 0, 0) != 0) + if(fsetxattr(0, "", 0, 0, 0)) return 1; ]]) ],[ @@ -1115,7 +1086,7 @@ AC_DEFUN([CURL_CHECK_FUNC_FSETXATTR], [ AC_LANG_PROGRAM([[ $curl_includes_sys_xattr ]],[[ - if(fsetxattr(0, 0, 0, 0, 0, 0) != 0) + if(fsetxattr(0, 0, 0, 0, 0, 0)) return 1; ]]) ],[ @@ -1177,8 +1148,8 @@ dnl Verify if getaddrinfo is available, prototyped, can dnl be compiled and seems to work. If all of these are dnl true, and usage has not been previously disallowed dnl with shell variable curl_disallow_getaddrinfo, then -dnl HAVE_GETADDRINFO will be defined. Additionally when -dnl HAVE_GETADDRINFO gets defined this will also attempt +dnl HAVE_GETADDRINFO is defined. Additionally when +dnl HAVE_GETADDRINFO gets defined this also attempts dnl to find out if getaddrinfo happens to be thread-safe, dnl defining HAVE_GETADDRINFO_THREADSAFE when true. @@ -1205,7 +1176,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ $curl_includes_netdb ]],[[ struct addrinfo *ai = 0; - if(getaddrinfo(0, 0, 0, &ai) != 0) + if(getaddrinfo(0, 0, 0, &ai)) return 1; ]]) ],[ @@ -1240,7 +1211,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ $curl_includes_netdb ]],[[ struct addrinfo *ai = 0; - if(getaddrinfo(0, 0, 0, &ai) != 0) + if(getaddrinfo(0, 0, 0, &ai)) return 1; ]]) ],[ @@ -1254,7 +1225,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_compi_getaddrinfo" = "yes"; then + test "$tst_compi_getaddrinfo" = "yes"; then AC_MSG_CHECKING([if getaddrinfo seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -1301,7 +1272,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ fi if test "$tst_compi_getaddrinfo" = "yes" && - test "$tst_works_getaddrinfo" != "no"; then + test "$tst_works_getaddrinfo" != "no"; then AC_MSG_CHECKING([if getaddrinfo usage allowed]) if test "x$curl_disallow_getaddrinfo" != "xyes"; then AC_MSG_RESULT([yes]) @@ -1330,7 +1301,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ if test "$curl_cv_func_getaddrinfo" = "yes"; then AC_MSG_CHECKING([if getaddrinfo is thread-safe]) if test "$curl_cv_apple" = "yes"; then - dnl Darwin 6.0 and macOS 10.2.X and newer + dnl Darwin 9+ and macOS 10.5+ tst_tsafe_getaddrinfo="yes" fi case $host_os in @@ -1342,10 +1313,18 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ dnl AIX 5.2 and newer tst_tsafe_getaddrinfo="yes" ;; - darwin[[12345]].*) - dnl Darwin 5.0 and macOS 10.1.X and older + darwin[[12345678]].*) + dnl Darwin <=8.0 and macOS <=10.4 + tst_tsafe_getaddrinfo="no" + ;; + dragonfly1.* | dragonfly2.[[01]]) + dnl DragonFly BSD <=2.1.0 tst_tsafe_getaddrinfo="no" ;; + dragonfly*) + dnl DragonFly BSD 2.2.0+ + tst_tsafe_getaddrinfo="yes" + ;; freebsd[[1234]].* | freebsd5.[[1234]]*) dnl FreeBSD 5.4 and older tst_tsafe_getaddrinfo="no" @@ -1374,6 +1353,14 @@ AC_DEFUN([CURL_CHECK_FUNC_GETADDRINFO], [ dnl NetBSD 4.X and newer tst_tsafe_getaddrinfo="yes" ;; + openbsd[[1234]].* | openbsd5.[[0123]]) + dnl OpenBSD <=5.3 + tst_tsafe_getaddrinfo="no" + ;; + openbsd*) + dnl OpenBSD 5.4+ + tst_tsafe_getaddrinfo="yes" + ;; *bsd*) dnl All other BSD's tst_tsafe_getaddrinfo="no" @@ -1408,7 +1395,7 @@ dnl Verify if gethostbyname_r is available, prototyped, dnl and can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_gethostbyname_r, then -dnl HAVE_GETHOSTBYNAME_R will be defined. +dnl HAVE_GETHOSTBYNAME_R is defined. AC_DEFUN([CURL_CHECK_FUNC_GETHOSTBYNAME_R], [ AC_REQUIRE([CURL_INCLUDES_NETDB]) @@ -1451,7 +1438,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTBYNAME_R], [ $curl_includes_netdb $curl_includes_bsdsocket ]],[[ - if(gethostbyname_r(0, 0, 0) != 0) + if(gethostbyname_r(0, 0, 0)) return 1; ]]) ],[ @@ -1470,7 +1457,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTBYNAME_R], [ $curl_includes_netdb $curl_includes_bsdsocket ]],[[ - if(gethostbyname_r(0, 0, 0, 0, 0) != 0) + if(gethostbyname_r(0, 0, 0, 0, 0)) return 1; ]]) ],[ @@ -1489,7 +1476,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTBYNAME_R], [ $curl_includes_netdb $curl_includes_bsdsocket ]],[[ - if(gethostbyname_r(0, 0, 0, 0, 0, 0) != 0) + if(gethostbyname_r(0, 0, 0, 0, 0, 0)) return 1; ]]) ],[ @@ -1553,7 +1540,7 @@ dnl Verify if gethostname is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_gethostname, then -dnl HAVE_GETHOSTNAME will be defined. +dnl HAVE_GETHOSTNAME is defined. AC_DEFUN([CURL_CHECK_FUNC_GETHOSTNAME], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -1574,7 +1561,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTNAME], [ $curl_includes_bsdsocket ]],[[ char s[1]; - if(gethostname((void *)s, 0) != 0) + if(gethostname((void *)s, 0)) return 1; ]]) ],[ @@ -1609,7 +1596,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTNAME], [ $curl_includes_bsdsocket ]],[[ char s[1]; - if(gethostname((void *)s, 0) != 0) + if(gethostname((void *)s, 0)) return 1; ]]) ],[ @@ -1641,7 +1628,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETHOSTNAME], [ int FUNCALLCONV gethostname($tst_arg1, $tst_arg2); ]],[[ char s[1]; - if(gethostname(($tst_arg1)s, 0) != 0) + if(gethostname(($tst_arg1)s, 0)) return 1; ]]) ],[ @@ -1689,7 +1676,7 @@ dnl Verify if getpeername is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_getpeername, then -dnl HAVE_GETPEERNAME will be defined. +dnl HAVE_GETPEERNAME is defined. AC_DEFUN([CURL_CHECK_FUNC_GETPEERNAME], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -1709,7 +1696,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETPEERNAME], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(getpeername(0, (void *)0, (void *)0) != 0) + if(getpeername(0, (void *)0, (void *)0)) return 1; ]]) ],[ @@ -1743,7 +1730,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETPEERNAME], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(getpeername(0, (void *)0, (void *)0) != 0) + if(getpeername(0, (void *)0, (void *)0)) return 1; ]]) ],[ @@ -1787,7 +1774,7 @@ dnl Verify if getsockname is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_getsockname, then -dnl HAVE_GETSOCKNAME will be defined. +dnl HAVE_GETSOCKNAME is defined. AC_DEFUN([CURL_CHECK_FUNC_GETSOCKNAME], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -1807,7 +1794,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETSOCKNAME], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(getsockname(0, (void *)0, (void *)0) != 0) + if(getsockname(0, (void *)0, (void *)0)) return 1; ]]) ],[ @@ -1841,7 +1828,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETSOCKNAME], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(getsockname(0, (void *)0, (void *)0) != 0) + if(getsockname(0, (void *)0, (void *)0)) return 1; ]]) ],[ @@ -1886,7 +1873,7 @@ dnl Verify if getifaddrs is available, prototyped, can dnl be compiled and seems to work. If all of these are dnl true, and usage has not been previously disallowed dnl with shell variable curl_disallow_getifaddrs, then -dnl HAVE_GETIFADDRS will be defined. +dnl HAVE_GETIFADDRS is defined. AC_DEFUN([CURL_CHECK_FUNC_GETIFADDRS], [ AC_REQUIRE([CURL_INCLUDES_STDLIB]) @@ -1928,7 +1915,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETIFADDRS], [ AC_LANG_PROGRAM([[ $curl_includes_ifaddrs ]],[[ - if(getifaddrs(0) != 0) + if(getifaddrs(0)) return 1; ]]) ],[ @@ -1942,7 +1929,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETIFADDRS], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_compi_getifaddrs" = "yes"; then + test "$tst_compi_getifaddrs" = "yes"; then AC_MSG_CHECKING([if getifaddrs seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -1970,7 +1957,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GETIFADDRS], [ fi if test "$tst_compi_getifaddrs" = "yes" && - test "$tst_works_getifaddrs" != "no"; then + test "$tst_works_getifaddrs" != "no"; then AC_MSG_CHECKING([if getifaddrs usage allowed]) if test "x$curl_disallow_getifaddrs" != "xyes"; then AC_MSG_RESULT([yes]) @@ -2004,7 +1991,7 @@ dnl Verify if gmtime_r is available, prototyped, can dnl be compiled and seems to work. If all of these are dnl true, and usage has not been previously disallowed dnl with shell variable curl_disallow_gmtime_r, then -dnl HAVE_GMTIME_R will be defined. +dnl HAVE_GMTIME_R is defined. AC_DEFUN([CURL_CHECK_FUNC_GMTIME_R], [ AC_REQUIRE([CURL_INCLUDES_STDLIB]) @@ -2063,7 +2050,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GMTIME_R], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_compi_gmtime_r" = "yes"; then + test "$tst_compi_gmtime_r" = "yes"; then AC_MSG_CHECKING([if gmtime_r seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -2090,7 +2077,7 @@ AC_DEFUN([CURL_CHECK_FUNC_GMTIME_R], [ fi if test "$tst_compi_gmtime_r" = "yes" && - test "$tst_works_gmtime_r" != "no"; then + test "$tst_works_gmtime_r" != "no"; then AC_MSG_CHECKING([if gmtime_r usage allowed]) if test "x$curl_disallow_gmtime_r" != "xyes"; then AC_MSG_RESULT([yes]) @@ -2124,7 +2111,7 @@ dnl Verify if localtime_r is available, prototyped, can dnl be compiled and seems to work. If all of these are dnl true, and usage has not been previously disallowed dnl with shell variable curl_disallow_localtime_r, then -dnl HAVE_LOCALTIME_R will be defined. +dnl HAVE_LOCALTIME_R is defined. AC_DEFUN([CURL_CHECK_FUNC_LOCALTIME_R], [ AC_REQUIRE([CURL_INCLUDES_STDLIB]) @@ -2168,7 +2155,7 @@ AC_DEFUN([CURL_CHECK_FUNC_LOCALTIME_R], [ ]],[[ time_t clock = 1170352587; struct tm result; - if(localtime_r(&clock, &result) != 0) + if(localtime_r(&clock, &result)) return 1; (void)result; ]]) @@ -2183,7 +2170,7 @@ AC_DEFUN([CURL_CHECK_FUNC_LOCALTIME_R], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_compi_localtime_r" = "yes"; then + test "$tst_compi_localtime_r" = "yes"; then AC_MSG_CHECKING([if localtime_r seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -2210,7 +2197,7 @@ AC_DEFUN([CURL_CHECK_FUNC_LOCALTIME_R], [ fi if test "$tst_compi_localtime_r" = "yes" && - test "$tst_works_localtime_r" != "no"; then + test "$tst_works_localtime_r" != "no"; then AC_MSG_CHECKING([if localtime_r usage allowed]) if test "x$curl_disallow_localtime_r" != "xyes"; then AC_MSG_RESULT([yes]) @@ -2238,321 +2225,6 @@ AC_DEFUN([CURL_CHECK_FUNC_LOCALTIME_R], [ ]) -dnl CURL_CHECK_FUNC_INET_NTOP -dnl ------------------------------------------------- -dnl Verify if inet_ntop is available, prototyped, can -dnl be compiled and seems to work. If all of these are -dnl true, and usage has not been previously disallowed -dnl with shell variable curl_disallow_inet_ntop, then -dnl HAVE_INET_NTOP will be defined. - -AC_DEFUN([CURL_CHECK_FUNC_INET_NTOP], [ - AC_REQUIRE([CURL_INCLUDES_STDLIB]) - AC_REQUIRE([CURL_INCLUDES_ARPA_INET]) - AC_REQUIRE([CURL_INCLUDES_STRING]) - - tst_links_inet_ntop="unknown" - tst_proto_inet_ntop="unknown" - tst_compi_inet_ntop="unknown" - tst_works_inet_ntop="unknown" - tst_allow_inet_ntop="unknown" - - AC_MSG_CHECKING([if inet_ntop can be linked]) - AC_LINK_IFELSE([ - AC_LANG_FUNC_LINK_TRY([inet_ntop]) - ],[ - AC_MSG_RESULT([yes]) - tst_links_inet_ntop="yes" - ],[ - AC_MSG_RESULT([no]) - tst_links_inet_ntop="no" - ]) - - if test "$tst_links_inet_ntop" = "yes"; then - AC_MSG_CHECKING([if inet_ntop is prototyped]) - AC_EGREP_CPP([inet_ntop],[ - $curl_includes_arpa_inet - ],[ - AC_MSG_RESULT([yes]) - tst_proto_inet_ntop="yes" - ],[ - AC_MSG_RESULT([no]) - tst_proto_inet_ntop="no" - ]) - fi - - if test "$tst_proto_inet_ntop" = "yes"; then - AC_MSG_CHECKING([if inet_ntop is compilable]) - AC_COMPILE_IFELSE([ - AC_LANG_PROGRAM([[ - $curl_includes_arpa_inet - ]],[[ - char ipv4res[sizeof("255.255.255.255")]; - unsigned char ipv4a[5] = ""; - if(inet_ntop(0, ipv4a, ipv4res, 0) != 0) - return 1; - ]]) - ],[ - AC_MSG_RESULT([yes]) - tst_compi_inet_ntop="yes" - ],[ - AC_MSG_RESULT([no]) - tst_compi_inet_ntop="no" - ]) - fi - - dnl only do runtime verification when not cross-compiling - if test "$cross_compiling" != "yes" && - test "$tst_compi_inet_ntop" = "yes"; then - AC_MSG_CHECKING([if inet_ntop seems to work]) - CURL_RUN_IFELSE([ - AC_LANG_PROGRAM([[ - $curl_includes_stdlib - $curl_includes_arpa_inet - $curl_includes_string - ]],[[ - char ipv6res[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; - char ipv4res[sizeof("255.255.255.255")]; - unsigned char ipv6a[26]; - unsigned char ipv4a[5]; - const char *ipv6ptr = 0; - const char *ipv4ptr = 0; - /* - */ - ipv4res[0] = '\0'; - ipv4a[0] = 0xc0; - ipv4a[1] = 0xa8; - ipv4a[2] = 0x64; - ipv4a[3] = 0x01; - ipv4a[4] = 0x01; - /* - */ - ipv4ptr = inet_ntop(AF_INET, ipv4a, ipv4res, sizeof(ipv4res)); - if(!ipv4ptr) - return 1; /* fail */ - if(ipv4ptr != ipv4res) - return 1; /* fail */ - if(!ipv4ptr[0]) - return 1; /* fail */ - if(memcmp(ipv4res, "192.168.100.1", 13) != 0) - return 1; /* fail */ - /* - */ - ipv6res[0] = '\0'; - memset(ipv6a, 0, sizeof(ipv6a)); - ipv6a[0] = 0xfe; - ipv6a[1] = 0x80; - ipv6a[8] = 0x02; - ipv6a[9] = 0x14; - ipv6a[10] = 0x4f; - ipv6a[11] = 0xff; - ipv6a[12] = 0xfe; - ipv6a[13] = 0x0b; - ipv6a[14] = 0x76; - ipv6a[15] = 0xc8; - ipv6a[25] = 0x01; - /* - */ - ipv6ptr = inet_ntop(AF_INET6, ipv6a, ipv6res, sizeof(ipv6res)); - if(!ipv6ptr) - return 1; /* fail */ - if(ipv6ptr != ipv6res) - return 1; /* fail */ - if(!ipv6ptr[0]) - return 1; /* fail */ - if(memcmp(ipv6res, "fe80::214:4fff:fe0b:76c8", 24) != 0) - return 1; /* fail */ - /* - */ - return 0; - ]]) - ],[ - AC_MSG_RESULT([yes]) - tst_works_inet_ntop="yes" - ],[ - AC_MSG_RESULT([no]) - tst_works_inet_ntop="no" - ]) - fi - - if test "$tst_compi_inet_ntop" = "yes" && - test "$tst_works_inet_ntop" != "no"; then - AC_MSG_CHECKING([if inet_ntop usage allowed]) - if test "x$curl_disallow_inet_ntop" != "xyes"; then - AC_MSG_RESULT([yes]) - tst_allow_inet_ntop="yes" - else - AC_MSG_RESULT([no]) - tst_allow_inet_ntop="no" - fi - fi - - AC_MSG_CHECKING([if inet_ntop might be used]) - if test "$tst_links_inet_ntop" = "yes" && - test "$tst_proto_inet_ntop" = "yes" && - test "$tst_compi_inet_ntop" = "yes" && - test "$tst_allow_inet_ntop" = "yes" && - test "$tst_works_inet_ntop" != "no"; then - AC_MSG_RESULT([yes]) - AC_DEFINE_UNQUOTED(HAVE_INET_NTOP, 1, - [Define to 1 if you have an IPv6 capable working inet_ntop function.]) - curl_cv_func_inet_ntop="yes" - else - AC_MSG_RESULT([no]) - curl_cv_func_inet_ntop="no" - fi -]) - - -dnl CURL_CHECK_FUNC_INET_PTON -dnl ------------------------------------------------- -dnl Verify if inet_pton is available, prototyped, can -dnl be compiled and seems to work. If all of these are -dnl true, and usage has not been previously disallowed -dnl with shell variable curl_disallow_inet_pton, then -dnl HAVE_INET_PTON will be defined. - -AC_DEFUN([CURL_CHECK_FUNC_INET_PTON], [ - AC_REQUIRE([CURL_INCLUDES_STDLIB]) - AC_REQUIRE([CURL_INCLUDES_ARPA_INET]) - AC_REQUIRE([CURL_INCLUDES_STRING]) - - tst_links_inet_pton="unknown" - tst_proto_inet_pton="unknown" - tst_compi_inet_pton="unknown" - tst_works_inet_pton="unknown" - tst_allow_inet_pton="unknown" - - AC_MSG_CHECKING([if inet_pton can be linked]) - AC_LINK_IFELSE([ - AC_LANG_FUNC_LINK_TRY([inet_pton]) - ],[ - AC_MSG_RESULT([yes]) - tst_links_inet_pton="yes" - ],[ - AC_MSG_RESULT([no]) - tst_links_inet_pton="no" - ]) - - if test "$tst_links_inet_pton" = "yes"; then - AC_MSG_CHECKING([if inet_pton is prototyped]) - AC_EGREP_CPP([inet_pton],[ - $curl_includes_arpa_inet - ],[ - AC_MSG_RESULT([yes]) - tst_proto_inet_pton="yes" - ],[ - AC_MSG_RESULT([no]) - tst_proto_inet_pton="no" - ]) - fi - - if test "$tst_proto_inet_pton" = "yes"; then - AC_MSG_CHECKING([if inet_pton is compilable]) - AC_COMPILE_IFELSE([ - AC_LANG_PROGRAM([[ - $curl_includes_arpa_inet - ]],[[ - unsigned char ipv4a[4 + 1] = ""; - const char *ipv4src = "192.168.100.1"; - if(inet_pton(0, ipv4src, ipv4a) != 0) - return 1; - ]]) - ],[ - AC_MSG_RESULT([yes]) - tst_compi_inet_pton="yes" - ],[ - AC_MSG_RESULT([no]) - tst_compi_inet_pton="no" - ]) - fi - - dnl only do runtime verification when not cross-compiling - if test "$cross_compiling" != "yes" && - test "$tst_compi_inet_pton" = "yes"; then - AC_MSG_CHECKING([if inet_pton seems to work]) - CURL_RUN_IFELSE([ - AC_LANG_PROGRAM([[ - $curl_includes_stdlib - $curl_includes_arpa_inet - $curl_includes_string - ]],[[ - unsigned char ipv6a[16 + 1]; - unsigned char ipv4a[4 + 1]; - const char *ipv6src = "fe80::214:4fff:fe0b:76c8"; - const char *ipv4src = "192.168.100.1"; - /* - */ - memset(ipv4a, 1, sizeof(ipv4a)); - if(inet_pton(AF_INET, ipv4src, ipv4a) != 1) - return 1; /* fail */ - /* - */ - if((ipv4a[0] != 0xc0) || - (ipv4a[1] != 0xa8) || - (ipv4a[2] != 0x64) || - (ipv4a[3] != 0x01) || - (ipv4a[4] != 0x01)) - return 1; /* fail */ - /* - */ - memset(ipv6a, 1, sizeof(ipv6a)); - if(inet_pton(AF_INET6, ipv6src, ipv6a) != 1) - return 1; /* fail */ - /* - */ - if((ipv6a[0] != 0xfe) || - (ipv6a[1] != 0x80) || - (ipv6a[8] != 0x02) || - (ipv6a[9] != 0x14) || - (ipv6a[10] != 0x4f) || - (ipv6a[11] != 0xff) || - (ipv6a[12] != 0xfe) || - (ipv6a[13] != 0x0b) || - (ipv6a[14] != 0x76) || - (ipv6a[15] != 0xc8) || - (ipv6a[16] != 0x01)) - return 1; /* fail */ - /* - */ - if((ipv6a[2] != 0x0) || - (ipv6a[3] != 0x0) || - (ipv6a[4] != 0x0) || - (ipv6a[5] != 0x0) || - (ipv6a[6] != 0x0) || - (ipv6a[7] != 0x0)) - return 1; /* fail */ - /* - */ - return 0; - ]]) - ],[ - AC_MSG_RESULT([yes]) - tst_works_inet_pton="yes" - ],[ - AC_MSG_RESULT([no]) - tst_works_inet_pton="no" - ]) - fi - - if test "$tst_compi_inet_pton" = "yes" && - test "$tst_works_inet_pton" != "no"; then - AC_MSG_CHECKING([if inet_pton usage allowed]) - if test "x$curl_disallow_inet_pton" != "xyes"; then - AC_MSG_RESULT([yes]) - tst_allow_inet_pton="yes" - else - AC_MSG_RESULT([no]) - tst_allow_inet_pton="no" - fi - fi - - AC_MSG_CHECKING([if inet_pton might be used]) - if test "$tst_links_inet_pton" = "yes" && - test "$tst_proto_inet_pton" = "yes" && - test "$tst_compi_inet_pton" = "yes" && - test "$tst_allow_inet_pton" = "yes" && - test "$tst_works_inet_pton" != "no"; then - AC_MSG_RESULT([yes]) - AC_DEFINE_UNQUOTED(HAVE_INET_PTON, 1, - [Define to 1 if you have an IPv6 capable working inet_pton function.]) - curl_cv_func_inet_pton="yes" - else - AC_MSG_RESULT([no]) - curl_cv_func_inet_pton="no" - fi -]) - - dnl CURL_CHECK_FUNC_IOCTL dnl ------------------------------------------------- dnl Verify if ioctl is available, prototyped, and @@ -2599,7 +2271,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTL], [ AC_LANG_PROGRAM([[ $curl_includes_stropts ]],[[ - if(ioctl(0, 0, 0) != 0) + if(ioctl(0, 0, 0)) return 1; ]]) ],[ @@ -2643,7 +2315,7 @@ dnl ------------------------------------------------- dnl Verify if ioctl with the FIONBIO command is dnl available, can be compiled, and seems to work. If dnl all of these are true, then HAVE_IOCTL_FIONBIO -dnl will be defined. +dnl is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTL_FIONBIO], [ @@ -2657,7 +2329,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTL_FIONBIO], [ $curl_includes_stropts ]],[[ int flags = 0; - if(ioctl(0, FIONBIO, &flags) != 0) + if(ioctl(0, FIONBIO, &flags)) return 1; ]]) ],[ @@ -2699,7 +2371,7 @@ dnl ------------------------------------------------- dnl Verify if ioctl with the SIOCGIFADDR command is available, dnl struct ifreq is defined, they can be compiled, and seem to dnl work. If all of these are true, then HAVE_IOCTL_SIOCGIFADDR -dnl will be defined. +dnl is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTL_SIOCGIFADDR], [ @@ -2714,7 +2386,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTL_SIOCGIFADDR], [ #include ]],[[ struct ifreq ifr; - if(ioctl(0, SIOCGIFADDR, &ifr) != 0) + if(ioctl(0, SIOCGIFADDR, &ifr)) return 1; ]]) ],[ @@ -2757,7 +2429,7 @@ dnl Verify if ioctlsocket is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_ioctlsocket, then -dnl HAVE_IOCTLSOCKET will be defined. +dnl HAVE_IOCTLSOCKET is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -2772,7 +2444,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET], [ AC_LANG_PROGRAM([[ $curl_includes_winsock2 ]],[[ - if(ioctlsocket(0, 0, 0) != 0) + if(ioctlsocket(0, 0, 0)) return 1; ]]) ],[ @@ -2802,7 +2474,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET], [ AC_LANG_PROGRAM([[ $curl_includes_winsock2 ]],[[ - if(ioctlsocket(0, 0, 0) != 0) + if(ioctlsocket(0, 0, 0)) return 1; ]]) ],[ @@ -2847,7 +2519,7 @@ dnl ------------------------------------------------- dnl Verify if ioctlsocket with the FIONBIO command is dnl available, can be compiled, and seems to work. If dnl all of these are true, then HAVE_IOCTLSOCKET_FIONBIO -dnl will be defined. +dnl is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_FIONBIO], [ @@ -2861,7 +2533,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_FIONBIO], [ $curl_includes_winsock2 ]],[[ unsigned long flags = 0; - if(ioctlsocket(0, FIONBIO, &flags) != 0) + if(ioctlsocket(0, FIONBIO, &flags)) return 1; ]]) ],[ @@ -2904,7 +2576,7 @@ dnl Verify if IoctlSocket is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_ioctlsocket_camel, -dnl then HAVE_IOCTLSOCKET_CAMEL will be defined. +dnl then HAVE_IOCTLSOCKET_CAMEL is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL], [ AC_REQUIRE([CURL_INCLUDES_BSDSOCKET]) @@ -2918,7 +2590,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL], [ AC_LANG_PROGRAM([[ $curl_includes_bsdsocket ]],[[ - if(IoctlSocket(0, 0, 0) != 0) + if(IoctlSocket(0, 0, 0)) return 1; ]]) ],[ @@ -2935,7 +2607,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL], [ AC_LANG_PROGRAM([[ $curl_includes_bsdsocket ]],[[ - if(IoctlSocket(0, 0, 0) != 0) + if(IoctlSocket(0, 0, 0)) return 1; ]]) ],[ @@ -2978,7 +2650,7 @@ dnl CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL_FIONBIO dnl ------------------------------------------------- dnl Verify if IoctlSocket with FIONBIO command is available, dnl can be compiled, and seems to work. If all of these are -dnl true, then HAVE_IOCTLSOCKET_CAMEL_FIONBIO will be defined. +dnl true, then HAVE_IOCTLSOCKET_CAMEL_FIONBIO is defined. AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL_FIONBIO], [ AC_REQUIRE([CURL_INCLUDES_BSDSOCKET]) @@ -2993,7 +2665,7 @@ AC_DEFUN([CURL_CHECK_FUNC_IOCTLSOCKET_CAMEL_FIONBIO], [ $curl_includes_bsdsocket ]],[[ long flags = 0; - if(IoctlSocket(0, FIONBIO, &flags) != 0) + if(IoctlSocket(0, FIONBIO, &flags)) return 1; ]]) ],[ @@ -3036,7 +2708,7 @@ dnl Verify if memrchr is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_memrchr, then -dnl HAVE_MEMRCHR will be defined. +dnl HAVE_MEMRCHR is defined. AC_DEFUN([CURL_CHECK_FUNC_MEMRCHR], [ AC_REQUIRE([CURL_INCLUDES_STRING]) @@ -3064,7 +2736,7 @@ AC_DEFUN([CURL_CHECK_FUNC_MEMRCHR], [ AC_LANG_PROGRAM([[ $curl_includes_string ]],[[ - if(memrchr("", 0, 0) != 0) + if(memrchr("", 0, 0)) return 1; ]]) ],[ @@ -3096,7 +2768,7 @@ AC_DEFUN([CURL_CHECK_FUNC_MEMRCHR], [ AC_LANG_PROGRAM([[ $curl_includes_string ]],[[ - if(memrchr("", 0, 0) != 0) + if(memrchr("", 0, 0)) return 1; ]]) ],[ @@ -3141,7 +2813,7 @@ dnl Verify if sigaction is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_sigaction, then -dnl HAVE_SIGACTION will be defined. +dnl HAVE_SIGACTION is defined. AC_DEFUN([CURL_CHECK_FUNC_SIGACTION], [ AC_REQUIRE([CURL_INCLUDES_SIGNAL]) @@ -3181,7 +2853,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SIGACTION], [ AC_LANG_PROGRAM([[ $curl_includes_signal ]],[[ - if(sigaction(0, 0, 0) != 0) + if(sigaction(0, 0, 0)) return 1; ]]) ],[ @@ -3226,7 +2898,7 @@ dnl Verify if siginterrupt is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_siginterrupt, then -dnl HAVE_SIGINTERRUPT will be defined. +dnl HAVE_SIGINTERRUPT is defined. AC_DEFUN([CURL_CHECK_FUNC_SIGINTERRUPT], [ AC_REQUIRE([CURL_INCLUDES_SIGNAL]) @@ -3266,7 +2938,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SIGINTERRUPT], [ AC_LANG_PROGRAM([[ $curl_includes_signal ]],[[ - if(siginterrupt(0, 0) != 0) + if(siginterrupt(0, 0)) return 1; ]]) ],[ @@ -3311,7 +2983,7 @@ dnl Verify if signal is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_signal, then -dnl HAVE_SIGNAL will be defined. +dnl HAVE_SIGNAL is defined. AC_DEFUN([CURL_CHECK_FUNC_SIGNAL], [ AC_REQUIRE([CURL_INCLUDES_SIGNAL]) @@ -3351,7 +3023,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SIGNAL], [ AC_LANG_PROGRAM([[ $curl_includes_signal ]],[[ - if(signal(0, 0) != 0) + if(signal(0, 0)) return 1; ]]) ],[ @@ -3396,7 +3068,7 @@ dnl Verify if sigsetjmp is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_sigsetjmp, then -dnl HAVE_SIGSETJMP will be defined. +dnl HAVE_SIGSETJMP is defined. AC_DEFUN([CURL_CHECK_FUNC_SIGSETJMP], [ AC_REQUIRE([CURL_INCLUDES_SETJMP]) @@ -3425,7 +3097,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SIGSETJMP], [ $curl_includes_setjmp ]],[[ sigjmp_buf env; - if(sigsetjmp(env, 0) != 0) + if(sigsetjmp(env, 0)) return 1; ]]) ],[ @@ -3458,7 +3130,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SIGSETJMP], [ $curl_includes_setjmp ]],[[ sigjmp_buf env; - if(sigsetjmp(env, 0) != 0) + if(sigsetjmp(env, 0)) return 1; ]]) ],[ @@ -3503,7 +3175,7 @@ dnl Verify if socket is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_socket, then -dnl HAVE_SOCKET will be defined. +dnl HAVE_SOCKET is defined. AC_DEFUN([CURL_CHECK_FUNC_SOCKET], [ AC_REQUIRE([CURL_INCLUDES_WINSOCK2]) @@ -3521,7 +3193,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SOCKET], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(socket(0, 0, 0) != 0) + if(socket(0, 0, 0)) return 1; ]]) ],[ @@ -3555,7 +3227,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SOCKET], [ $curl_includes_bsdsocket $curl_includes_sys_socket ]],[[ - if(socket(0, 0, 0) != 0) + if(socket(0, 0, 0)) return 1; ]]) ],[ @@ -3600,7 +3272,7 @@ dnl Verify if socketpair is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_socketpair, then -dnl HAVE_SOCKETPAIR will be defined. +dnl HAVE_SOCKETPAIR is defined. AC_DEFUN([CURL_CHECK_FUNC_SOCKETPAIR], [ AC_REQUIRE([CURL_INCLUDES_SYS_SOCKET]) @@ -3641,7 +3313,7 @@ AC_DEFUN([CURL_CHECK_FUNC_SOCKETPAIR], [ $curl_includes_sys_socket ]],[[ int sv[2]; - if(socketpair(0, 0, 0, sv) != 0) + if(socketpair(0, 0, 0, sv)) return 1; ]]) ],[ @@ -3686,7 +3358,7 @@ dnl Verify if strcasecmp is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_strcasecmp, then -dnl HAVE_STRCASECMP will be defined. +dnl HAVE_STRCASECMP is defined. AC_DEFUN([CURL_CHECK_FUNC_STRCASECMP], [ AC_REQUIRE([CURL_INCLUDES_STRING]) @@ -3726,7 +3398,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRCASECMP], [ AC_LANG_PROGRAM([[ $curl_includes_string ]],[[ - if(strcasecmp("", "") != 0) + if(strcasecmp("", "")) return 1; ]]) ],[ @@ -3770,7 +3442,7 @@ dnl Verify if strcmpi is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_strcmpi, then -dnl HAVE_STRCMPI will be defined. +dnl HAVE_STRCMPI is defined. AC_DEFUN([CURL_CHECK_FUNC_STRCMPI], [ AC_REQUIRE([CURL_INCLUDES_STRING]) @@ -3810,7 +3482,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRCMPI], [ AC_LANG_PROGRAM([[ $curl_includes_string ]],[[ - if(strcmpi(0, 0) != 0) + if(strcmpi(0, 0)) return 1; ]]) ],[ @@ -3854,7 +3526,7 @@ dnl ------------------------------------------------- dnl Verify if strerror_r is available, prototyped, can be compiled and dnl seems to work. If all of these are true, and usage has not been dnl previously disallowed with shell variable curl_disallow_strerror_r, -dnl then HAVE_STRERROR_R will be defined, as well as one of +dnl then HAVE_STRERROR_R is defined, as well as one of dnl HAVE_GLIBC_STRERROR_R or HAVE_POSIX_STRERROR_R. dnl dnl glibc-style strerror_r: @@ -3919,7 +3591,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ $curl_includes_string ]],[[ char s[1]; - if(strerror_r(0, s, 0) != 0) + if(strerror_r(0, s, 0)) return 1; ]]) ],[ @@ -3942,7 +3614,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ char *strerror_r(int errnum, char *workbuf, $arg3 bufsize); ]],[[ char s[1]; - if(strerror_r(0, s, 0) != 0) + if(strerror_r(0, s, 0)) return 1; (void)s; ]]) @@ -3965,7 +3637,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_glibc_strerror_r" = "yes"; then + test "$tst_glibc_strerror_r" = "yes"; then AC_MSG_CHECKING([if strerror_r seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -3994,7 +3666,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ fi if test "$tst_compi_strerror_r" = "yes" && - test "$tst_works_glibc_strerror_r" != "yes"; then + test "$tst_works_glibc_strerror_r" != "yes"; then AC_MSG_CHECKING([if strerror_r is POSIX like]) tst_posix_strerror_r_type_arg3="unknown" for arg3 in 'size_t' 'int' 'unsigned int'; do @@ -4005,7 +3677,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ int strerror_r(int errnum, char *resultbuf, $arg3 bufsize); ]],[[ char s[1]; - if(strerror_r(0, s, 0) != 0) + if(strerror_r(0, s, 0)) return 1; (void)s; ]]) @@ -4028,7 +3700,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ dnl only do runtime verification when not cross-compiling if test "$cross_compiling" != "yes" && - test "$tst_posix_strerror_r" = "yes"; then + test "$tst_posix_strerror_r" = "yes"; then AC_MSG_CHECKING([if strerror_r seems to work]) CURL_RUN_IFELSE([ AC_LANG_PROGRAM([[ @@ -4063,13 +3735,13 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ tst_glibc_strerror_r="no" fi if test "$tst_glibc_strerror_r" = "yes" && - test "$tst_works_glibc_strerror_r" != "no" && - test "$tst_posix_strerror_r" != "yes"; then + test "$tst_works_glibc_strerror_r" != "no" && + test "$tst_posix_strerror_r" != "yes"; then tst_allow_strerror_r="check" fi if test "$tst_posix_strerror_r" = "yes" && - test "$tst_works_posix_strerror_r" != "no" && - test "$tst_glibc_strerror_r" != "yes"; then + test "$tst_works_posix_strerror_r" != "no" && + test "$tst_glibc_strerror_r" != "yes"; then tst_allow_strerror_r="check" fi if test "$tst_allow_strerror_r" = "check"; then @@ -4111,7 +3783,6 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [ test "$tst_allow_strerror_r" = "unknown"; then AC_MSG_WARN([cannot determine strerror_r() style: edit lib/curl_config.h manually.]) fi - ]) @@ -4121,7 +3792,7 @@ dnl Verify if stricmp is available, prototyped, and dnl can be compiled. If all of these are true, and dnl usage has not been previously disallowed with dnl shell variable curl_disallow_stricmp, then -dnl HAVE_STRICMP will be defined. +dnl HAVE_STRICMP is defined. AC_DEFUN([CURL_CHECK_FUNC_STRICMP], [ AC_REQUIRE([CURL_INCLUDES_STRING]) @@ -4161,7 +3832,7 @@ AC_DEFUN([CURL_CHECK_FUNC_STRICMP], [ AC_LANG_PROGRAM([[ $curl_includes_string ]],[[ - if(stricmp(0, 0) != 0) + if(stricmp(0, 0)) return 1; ]]) ],[ @@ -4199,6 +3870,92 @@ AC_DEFUN([CURL_CHECK_FUNC_STRICMP], [ fi ]) + +dnl CURL_CHECK_FUNC_MEMSET_S +dnl ------------------------------------------------- +dnl Verify if memset_s is available, prototyped, and +dnl can be compiled. If all of these are true, and +dnl usage has not been previously disallowed with +dnl shell variable curl_disallow_memset_s, then +dnl HAVE_MEMSET_S is defined. + +AC_DEFUN([CURL_CHECK_FUNC_MEMSET_S], [ + AC_REQUIRE([CURL_INCLUDES_STRING]) + + tst_links_memset_s="unknown" + tst_proto_memset_s="unknown" + tst_compi_memset_s="unknown" + tst_allow_memset_s="unknown" + + AC_MSG_CHECKING([if memset_s can be linked]) + AC_LINK_IFELSE([ + AC_LANG_FUNC_LINK_TRY([memset_s]) + ],[ + AC_MSG_RESULT([yes]) + tst_links_memset_s="yes" + ],[ + AC_MSG_RESULT([no]) + tst_links_memset_s="no" + ]) + + if test "$tst_links_memset_s" = "yes"; then + AC_MSG_CHECKING([if memset_s is prototyped]) + AC_EGREP_CPP([memset_s],[ + $curl_includes_string + ],[ + AC_MSG_RESULT([yes]) + tst_proto_memset_s="yes" + ],[ + AC_MSG_RESULT([no]) + tst_proto_memset_s="no" + ]) + fi + + if test "$tst_proto_memset_s" = "yes"; then + AC_MSG_CHECKING([if memset_s is compilable]) + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ + $curl_includes_string + ]],[[ + char buf[2]; + if(memset_s(buf, sizeof(buf), 0, sizeof(buf))) + return 1; + ]]) + ],[ + AC_MSG_RESULT([yes]) + tst_compi_memset_s="yes" + ],[ + AC_MSG_RESULT([no]) + tst_compi_memset_s="no" + ]) + fi + + if test "$tst_compi_memset_s" = "yes"; then + AC_MSG_CHECKING([if memset_s usage allowed]) + if test "x$curl_disallow_memset_s" != "xyes"; then + AC_MSG_RESULT([yes]) + tst_allow_memset_s="yes" + else + AC_MSG_RESULT([no]) + tst_allow_memset_s="no" + fi + fi + + AC_MSG_CHECKING([if memset_s might be used]) + if test "$tst_links_memset_s" = "yes" && + test "$tst_proto_memset_s" = "yes" && + test "$tst_compi_memset_s" = "yes" && + test "$tst_allow_memset_s" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE_UNQUOTED(HAVE_MEMSET_S, 1, + [Define to 1 if you have the memset_s function.]) + curl_cv_func_memset_s="yes" + else + AC_MSG_RESULT([no]) + curl_cv_func_memset_s="no" + fi +]) + dnl CURL_RUN_IFELSE dnl ------------------------------------------------- dnl Wrapper macro to use instead of AC_RUN_IFELSE. It @@ -4271,6 +4028,7 @@ dnl CURL_ATOMIC dnl ------------------------------------------------------------- dnl Check if _Atomic works. But only check if stdatomic.h exists. dnl + AC_DEFUN([CURL_ATOMIC],[ AC_CHECK_HEADERS(stdatomic.h, [ AC_MSG_CHECKING([if _Atomic is available]) @@ -4352,5 +4110,4 @@ AC_DEFUN([CURL_SIZEOF], [ eval "$tname=$r" AC_DEFINE_UNQUOTED(TYPE, [$r], [Size of $1 in number of bytes]) - ]) diff --git a/m4/curl-gnutls.m4 b/m4/curl-gnutls.m4 index b8ee3f5780c9..59012a1ab5d9 100644 --- a/m4/curl-gnutls.m4 +++ b/m4/curl-gnutls.m4 @@ -30,114 +30,110 @@ AC_DEFUN([CURL_WITH_GNUTLS], [ if test "x$OPT_GNUTLS" != "xno"; then ssl_msg= - if test "x$OPT_GNUTLS" != "xno"; then - - addld="" - addlib="" - gtlslib="" - version="" - addcflags="" - - if test "x$OPT_GNUTLS" = "xyes"; then - dnl this is with no particular path given - CURL_CHECK_PKGCONFIG(gnutls) - - if test "$PKGCONFIG" != "no"; then - addlib=`$PKGCONFIG --libs-only-l gnutls` - addld=`$PKGCONFIG --libs-only-L gnutls` - addcflags=`$PKGCONFIG --cflags-only-I gnutls` - version=`$PKGCONFIG --modversion gnutls` - gtlslib=`echo $addld | $SED -e 's/^-L//'` - else - dnl without pkg-config, we try libgnutls-config as that was how it - dnl used to be done - check=`libgnutls-config --version 2>/dev/null` - if test -n "$check"; then - addlib=`libgnutls-config --libs` - addcflags=`libgnutls-config --cflags` - version=`libgnutls-config --version` - gtlslib=`libgnutls-config --prefix`/lib$libsuff - fi - fi + addld="" + addlib="" + gtlslib="" + version="" + addcflags="" + + if test "x$OPT_GNUTLS" = "xyes"; then + dnl this is with no particular path given + CURL_CHECK_PKGCONFIG(gnutls) + + if test "$PKGCONFIG" != "no"; then + addlib=`$PKGCONFIG --libs-only-l gnutls` + addld=`$PKGCONFIG --libs-only-L gnutls` + addcflags=`$PKGCONFIG --cflags-only-I gnutls` + version=`$PKGCONFIG --modversion gnutls` + gtlslib=`echo $addld | $SED -e 's/^-L//'` else - dnl this is with a given path, first check if there is a libgnutls-config - dnl there and if not, make an educated guess - cfg=$OPT_GNUTLS/bin/libgnutls-config - check=`$cfg --version 2>/dev/null` + dnl without pkg-config, we try libgnutls-config as that was how it + dnl used to be done + check=`libgnutls-config --version 2>/dev/null` if test -n "$check"; then - addlib=`$cfg --libs` - addcflags=`$cfg --cflags` - version=`$cfg --version` - gtlslib=`$cfg --prefix`/lib$libsuff - else - dnl without pkg-config and libgnutls-config, we guess a lot! - addlib=-lgnutls - addld=-L$OPT_GNUTLS/lib$libsuff - addcflags=-I$OPT_GNUTLS/include - dnl we just do not know - version="" - gtlslib=$OPT_GNUTLS/lib$libsuff + addlib=`libgnutls-config --libs` + addcflags=`libgnutls-config --cflags` + version=`libgnutls-config --version` + gtlslib=`libgnutls-config --prefix`/lib$libsuff fi fi - - if test -z "$version"; then - dnl lots of efforts, still no go - version="unknown" + else + dnl this is with a given path, first check if there is a libgnutls-config + dnl there and if not, make an educated guess + cfg=$OPT_GNUTLS/bin/libgnutls-config + check=`$cfg --version 2>/dev/null` + if test -n "$check"; then + addlib=`$cfg --libs` + addcflags=`$cfg --cflags` + version=`$cfg --version` + gtlslib=`$cfg --prefix`/lib$libsuff + else + dnl without pkg-config and libgnutls-config, we guess a lot! + addlib=-lgnutls + addld=-L$OPT_GNUTLS/lib$libsuff + addcflags=-I$OPT_GNUTLS/include + dnl we do not know + version="" + gtlslib=$OPT_GNUTLS/lib$libsuff fi + fi - if test -n "$addlib"; then + if test -z "$version"; then + dnl lots of efforts, still no go + version="unknown" + fi - CLEANLIBS="$LIBS" - CLEANCPPFLAGS="$CPPFLAGS" - CLEANLDFLAGS="$LDFLAGS" - CLEANLDFLAGSPC="$LDFLAGSPC" + if test -n "$addlib"; then - LIBS="$addlib $LIBS" - LDFLAGS="$LDFLAGS $addld" - LDFLAGSPC="$LDFLAGSPC $addld" - if test "$addcflags" != "-I/usr/include"; then - CPPFLAGS="$CPPFLAGS $addcflags" - fi + CLEANLIBS="$LIBS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLDFLAGS="$LDFLAGS" + CLEANLDFLAGSPC="$LDFLAGSPC" - dnl this function is selected since it was introduced in 3.1.10 - AC_CHECK_LIB(gnutls, gnutls_x509_crt_get_dn2, - [ - AC_DEFINE(USE_GNUTLS, 1, [if GnuTLS is enabled]) - GNUTLS_ENABLED=1 - USE_GNUTLS="yes" - ssl_msg="GnuTLS" - QUIC_ENABLED=yes - test "gnutls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes - ], - [ - LIBS="$CLEANLIBS" - CPPFLAGS="$CLEANCPPFLAGS" - LDFLAGS="$CLEANLDFLAGS" - LDFLAGSPC="$CLEANLDFLAGSPC" - ]) - - if test "$USE_GNUTLS" = "yes"; then - AC_MSG_NOTICE([detected GnuTLS version $version]) - check_for_ca_bundle=1 - if test -n "$gtlslib"; then - dnl when shared libs were found in a path that the runtime - dnl linker does not search through, we need to add it to - dnl CURL_LIBRARY_PATH to prevent further configure tests to fail - dnl due to this - if test "$cross_compiling" != "yes"; then - CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$gtlslib" - export CURL_LIBRARY_PATH - AC_MSG_NOTICE([Added $gtlslib to CURL_LIBRARY_PATH]) - fi + LIBS="$addlib $LIBS" + LDFLAGS="$LDFLAGS $addld" + LDFLAGSPC="$LDFLAGSPC $addld" + if test "$addcflags" != "-I/usr/include"; then + CPPFLAGS="$CPPFLAGS $addcflags" + fi + + dnl this function is selected since it was introduced in 3.1.10 + AC_CHECK_LIB(gnutls, gnutls_x509_crt_get_dn2, + [ + AC_DEFINE(USE_GNUTLS, 1, [if GnuTLS is enabled]) + GNUTLS_ENABLED=1 + USE_GNUTLS="yes" + ssl_msg="GnuTLS" + QUIC_ENABLED=yes + test "gnutls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes + ], + [ + LIBS="$CLEANLIBS" + CPPFLAGS="$CLEANCPPFLAGS" + LDFLAGS="$CLEANLDFLAGS" + LDFLAGSPC="$CLEANLDFLAGSPC" + ]) + + if test "$USE_GNUTLS" = "yes"; then + AC_MSG_NOTICE([detected GnuTLS version $version]) + check_for_ca_bundle=1 + if test -n "$gtlslib"; then + dnl when shared libs were found in a path that the runtime + dnl linker does not search through, we need to add it to + dnl CURL_LIBRARY_PATH to prevent further configure tests to fail + dnl due to this + if test "$cross_compiling" != "yes"; then + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$gtlslib" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $gtlslib to CURL_LIBRARY_PATH]) fi - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE gnutls" fi + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE gnutls" fi - - fi dnl GNUTLS not disabled + fi test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" -fi +fi dnl GnuTLS not disabled dnl dnl Check which crypto backend GnuTLS uses @@ -145,7 +141,7 @@ dnl if test "$GNUTLS_ENABLED" = "1"; then USE_GNUTLS_NETTLE= dnl First check if we can detect either crypto library via transitive linking - AC_CHECK_LIB(gnutls, nettle_MD5Init, [ USE_GNUTLS_NETTLE=1 ]) + AC_CHECK_LIB(gnutls, nettle_md5_init, [ USE_GNUTLS_NETTLE=1 ]) dnl If not, try linking directly to both of them to see if they are available if test -z "$USE_GNUTLS_NETTLE"; then @@ -174,7 +170,7 @@ if test "$GNUTLS_ENABLED" = "1"; then CPPFLAGS="$CPPFLAGS $addcflags" fi - AC_CHECK_LIB(nettle, nettle_MD5Init, + AC_CHECK_LIB(nettle, nettle_md5_init, [ USE_GNUTLS_NETTLE=1 ], @@ -204,16 +200,65 @@ if test "$GNUTLS_ENABLED" = "1"; then LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE nettle" fi - dnl --- - dnl We require GnuTLS with SRP support. - dnl - dnl In GnuTLS 3.8.0 (2023-02-10) and upper, this check always succeeds. - dnl Detecting actual TLS-SRP support needs poking the API at runtime. - dnl --- - AC_CHECK_LIB(gnutls, gnutls_srp_verifier, - [ - AC_DEFINE(HAVE_GNUTLS_SRP, 1, [if you have the function gnutls_srp_verifier]) - HAVE_GNUTLS_SRP=1 - ]) + USE_GNUTLS_HOGWEED= + dnl First check if we can detect either crypto library via transitive linking + AC_CHECK_LIB(gnutls, nettle_ed25519_sha512_sign, [ USE_GNUTLS_HOGWEED=1 ]) + + dnl If not, try linking directly to both of them to see if they are available + if test -z "$USE_GNUTLS_HOGWEED"; then + + dnl this is with no particular path given + CURL_CHECK_PKGCONFIG(hogweed) + + if test "$PKGCONFIG" != "no"; then + addlib=`$PKGCONFIG --libs-only-l hogweed` + addld=`$PKGCONFIG --libs-only-L hogweed` + addcflags=`$PKGCONFIG --cflags-only-I hogweed` + version=`$PKGCONFIG --modversion hogweed` + gtlslib=`echo $addld | $SED -e 's/^-L//'` + + if test -n "$addlib"; then + + CLEANLIBS="$LIBS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLDFLAGS="$LDFLAGS" + CLEANLDFLAGSPC="$LDFLAGSPC" + + LIBS="$addlib $LIBS" + LDFLAGS="$LDFLAGS $addld" + LDFLAGSPC="$LDFLAGSPC $addld" + if test "$addcflags" != "-I/usr/include"; then + CPPFLAGS="$CPPFLAGS $addcflags" + fi + + AC_CHECK_LIB(hogweed, nettle_ed25519_sha512_sign, + [ + USE_GNUTLS_HOGWEED=1 + ], + [ + LIBS="$CLEANLIBS" + CPPFLAGS="$CLEANCPPFLAGS" + LDFLAGS="$CLEANLDFLAGS" + LDFLAGSPC="$CLEANLDFLAGSPC" + ]) + + if test "$USE_GNUTLS_HOGWEED" = "1"; then + if test -z "$version"; then + version="unknown" + fi + AC_MSG_NOTICE([detected hogweed version $version]) + fi + fi + fi + if test -z "$USE_GNUTLS_HOGWEED"; then + AC_MSG_ERROR([GnuTLS found, but hogweed was not found]) + fi + else + LIBS="-lhogweed $LIBS" + fi + + if test "$USE_GNUTLS_HOGWEED" = "1"; then + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE hogweed" + fi fi ]) diff --git a/m4/curl-mbedtls.m4 b/m4/curl-mbedtls.m4 index 7c5bccd22983..0c19f0723717 100644 --- a/m4/curl-mbedtls.m4 +++ b/m4/curl-mbedtls.m4 @@ -33,90 +33,85 @@ if test "x$OPT_MBEDTLS" != "xno"; then _ldflagspc=$LDFLAGSPC ssl_msg= - if test "x$OPT_MBEDTLS" != "xno"; then - - if test "x$OPT_MBEDTLS" = "xyes"; then - OPT_MBEDTLS="" + if test "x$OPT_MBEDTLS" = "xyes"; then + OPT_MBEDTLS="" + fi + + if test -z "$OPT_MBEDTLS"; then + dnl check for lib first without setting any new path + + AC_CHECK_LIB(mbedtls, mbedtls_ssl_init, + dnl libmbedtls found, set the variable + [ + AC_DEFINE(USE_MBEDTLS, 1, [if mbedTLS is enabled]) + MBEDTLS_ENABLED=1 + USE_MBEDTLS="yes" + ssl_msg="mbedTLS" + test "mbedtls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes + ], [], -lmbedx509 -lmbedcrypto) + fi + + addld="" + addlib="" + addcflags="" + mbedtlslib="" + + if test "$USE_MBEDTLS" != "yes" && test -n "$OPT_MBEDTLS"; then + dnl add the path and test again + addld=-L$OPT_MBEDTLS/lib$libsuff + addcflags=-I$OPT_MBEDTLS/include + mbedtlslib=$OPT_MBEDTLS/lib$libsuff + + LDFLAGS="$LDFLAGS $addld" + LDFLAGSPC="$LDFLAGSPC $addld" + if test "$addcflags" != "-I/usr/include"; then + CPPFLAGS="$CPPFLAGS $addcflags" fi - if test -z "$OPT_MBEDTLS"; then - dnl check for lib first without setting any new path - - AC_CHECK_LIB(mbedtls, mbedtls_havege_init, - dnl libmbedtls found, set the variable + AC_CHECK_LIB(mbedtls, mbedtls_ssl_init, [ - AC_DEFINE(USE_MBEDTLS, 1, [if mbedTLS is enabled]) - MBEDTLS_ENABLED=1 - USE_MBEDTLS="yes" - ssl_msg="mbedTLS" - test "mbedtls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes - ], [], -lmbedx509 -lmbedcrypto) - fi - - addld="" - addlib="" - addcflags="" - mbedtlslib="" - - if test "$USE_MBEDTLS" != "yes"; then - dnl add the path and test again - addld=-L$OPT_MBEDTLS/lib$libsuff - addcflags=-I$OPT_MBEDTLS/include - mbedtlslib=$OPT_MBEDTLS/lib$libsuff - - LDFLAGS="$LDFLAGS $addld" - LDFLAGSPC="$LDFLAGSPC $addld" - if test "$addcflags" != "-I/usr/include"; then - CPPFLAGS="$CPPFLAGS $addcflags" + AC_DEFINE(USE_MBEDTLS, 1, [if mbedTLS is enabled]) + MBEDTLS_ENABLED=1 + USE_MBEDTLS="yes" + ssl_msg="mbedTLS" + test "mbedtls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes + ], + [ + CPPFLAGS=$_cppflags + LDFLAGS=$_ldflags + LDFLAGSPC=$_ldflagspc + ], -lmbedx509 -lmbedcrypto) + fi + + if test "$USE_MBEDTLS" = "yes"; then + AC_MSG_NOTICE([detected mbedTLS]) + check_for_ca_bundle=1 + + LIBS="-lmbedtls -lmbedx509 -lmbedcrypto $LIBS" + + if test -n "$mbedtlslib"; then + dnl when shared libs were found in a path that the runtime + dnl linker does not search through, we need to add it to + dnl CURL_LIBRARY_PATH to prevent further configure tests to fail + dnl due to this + if test "$cross_compiling" != "yes"; then + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$mbedtlslib" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $mbedtlslib to CURL_LIBRARY_PATH]) fi - - AC_CHECK_LIB(mbedtls, mbedtls_ssl_init, - [ - AC_DEFINE(USE_MBEDTLS, 1, [if mbedTLS is enabled]) - MBEDTLS_ENABLED=1 - USE_MBEDTLS="yes" - ssl_msg="mbedTLS" - test "mbedtls" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes - ], - [ - CPPFLAGS=$_cppflags - LDFLAGS=$_ldflags - LDFLAGSPC=$_ldflagspc - ], -lmbedx509 -lmbedcrypto) fi - - if test "$USE_MBEDTLS" = "yes"; then - AC_MSG_NOTICE([detected mbedTLS]) - check_for_ca_bundle=1 - - LIBS="-lmbedtls -lmbedx509 -lmbedcrypto $LIBS" - - if test -n "$mbedtlslib"; then - dnl when shared libs were found in a path that the runtime - dnl linker does not search through, we need to add it to - dnl CURL_LIBRARY_PATH to prevent further configure tests to fail - dnl due to this - if test "$cross_compiling" != "yes"; then - CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$mbedtlslib" - export CURL_LIBRARY_PATH - AC_MSG_NOTICE([Added $mbedtlslib to CURL_LIBRARY_PATH]) - fi - fi - dnl FIXME: Enable when mbedTLS was detected via pkg-config - if false; then - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE mbedtls mbedx509 mbedcrypto" - fi - - dnl Check DES support in mbedTLS <4. - AC_CHECK_FUNCS(mbedtls_des_crypt_ecb) - if test "$ac_cv_func_mbedtls_des_crypt_ecb" = 'yes'; then - HAVE_MBEDTLS_DES_CRYPT_ECB=1 - fi + dnl FIXME: Enable when mbedTLS was detected via pkg-config + if false; then + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE mbedtls mbedx509 mbedcrypto" fi - fi dnl mbedTLS not disabled + dnl Check DES support in mbedTLS <4. + AC_CHECK_FUNCS(mbedtls_des_crypt_ecb) + if test "$ac_cv_func_mbedtls_des_crypt_ecb" = 'yes'; then + HAVE_MBEDTLS_DES_CRYPT_ECB=1 + fi + fi test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" -fi - +fi dnl mbedTLS not disabled ]) diff --git a/m4/curl-openssl.m4 b/m4/curl-openssl.m4 index 256037b19a76..8a1d07b02a4d 100644 --- a/m4/curl-openssl.m4 +++ b/m4/curl-openssl.m4 @@ -33,7 +33,7 @@ AC_DEFUN([CURL_WITH_OPENSSL], [ if test "x$OPT_OPENSSL" != "xno"; then ssl_msg= - dnl backup the pre-ssl variables + dnl backup the pre-detection variables CLEANLDFLAGS="$LDFLAGS" CLEANLDFLAGSPC="$LDFLAGSPC" CLEANCPPFLAGS="$CPPFLAGS" @@ -80,7 +80,7 @@ if test "x$OPT_OPENSSL" != "xno"; then dnl the user told us to look OPENSSL_PCDIR="$OPT_OPENSSL/lib/pkgconfig" if test -f "$OPENSSL_PCDIR/openssl.pc"; then - AC_MSG_NOTICE([PKG_CONFIG_LIBDIR will be set to "$OPENSSL_PCDIR"]) + AC_MSG_NOTICE([PKG_CONFIG_LIBDIR is set to "$OPENSSL_PCDIR"]) PKGTEST="yes" fi @@ -88,15 +88,14 @@ if test "x$OPT_OPENSSL" != "xno"; then dnl try lib64 instead OPENSSL_PCDIR="$OPT_OPENSSL/lib64/pkgconfig" if test -f "$OPENSSL_PCDIR/openssl.pc"; then - AC_MSG_NOTICE([PKG_CONFIG_LIBDIR will be set to "$OPENSSL_PCDIR"]) + AC_MSG_NOTICE([PKG_CONFIG_LIBDIR is set to "$OPENSSL_PCDIR"]) PKGTEST="yes" fi fi - if test "$PKGTEST" != "yes"; then - if test ! -f "$PREFIX_OPENSSL/include/openssl/ssl.h"; then - AC_MSG_ERROR([$PREFIX_OPENSSL is a bad --with-openssl prefix!]) - fi + if test "$PKGTEST" != "yes" && + test ! -f "$PREFIX_OPENSSL/include/openssl/ssl.h"; then + AC_MSG_ERROR([$PREFIX_OPENSSL is a bad --with-openssl prefix!]) fi dnl in case pkg-config comes up empty, use what we got @@ -220,47 +219,43 @@ if test "x$OPT_OPENSSL" != "xno"; then if test "$OPENSSL_ENABLED" != "1"; then LIBS="$CLEANLIBS" - fi - - if test "x$OPT_OPENSSL" != "xoff" && - test "$OPENSSL_ENABLED" != "1"; then - AC_MSG_ERROR([OpenSSL libs and/or directories were not found where specified!]) + AC_MSG_ERROR([OpenSSL libs and/or directories were not found!]) fi fi if test "$OPENSSL_ENABLED" = "1"; then dnl These can only exist if OpenSSL exists - AC_MSG_CHECKING([for BoringSSL]) + AC_MSG_CHECKING([for AWS-LC]) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include ]],[[ - #ifndef OPENSSL_IS_BORINGSSL - #error not boringssl + #ifndef OPENSSL_IS_AWSLC + #error not AWS-LC #endif ]]) ],[ AC_MSG_RESULT([yes]) - ssl_msg="BoringSSL" - OPENSSL_IS_BORINGSSL=1 + ssl_msg="AWS-LC" + OPENSSL_IS_AWSLC=1 ],[ AC_MSG_RESULT([no]) ]) - AC_MSG_CHECKING([for AWS-LC]) + AC_MSG_CHECKING([for BoringSSL]) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include ]],[[ - #ifndef OPENSSL_IS_AWSLC - #error not AWS-LC + #ifndef OPENSSL_IS_BORINGSSL + #error not BoringSSL #endif ]]) ],[ AC_MSG_RESULT([yes]) - ssl_msg="AWS-LC" - OPENSSL_IS_AWSLC=1 + ssl_msg="BoringSSL" + OPENSSL_IS_BORINGSSL=1 ],[ AC_MSG_RESULT([no]) ]) @@ -282,7 +277,7 @@ if test "x$OPT_OPENSSL" != "xno"; then ]) if test "$ssl_msg" = 'OpenSSL'; then - AC_MSG_CHECKING([for OpenSSL >= v3]) + AC_MSG_CHECKING([for OpenSSL >= 3]) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include @@ -294,7 +289,7 @@ if test "x$OPT_OPENSSL" != "xno"; then #endif ]]) ],[],[ - AC_MSG_ERROR([OpenSSL 3.0.0 or upper required.]) + AC_MSG_ERROR([OpenSSL 3.0.0 or greater required.]) ]) fi fi @@ -315,7 +310,7 @@ if test "x$OPT_OPENSSL" != "xno"; then if test "$OPENSSL_ENABLED" = "1"; then if test -n "$LIB_OPENSSL"; then - dnl when the ssl shared libs were found in a path that the runtime + dnl when the SSL shared libs were found in a path that the runtime dnl linker does not search through, we need to add it to CURL_LIBRARY_PATH dnl to prevent further configure tests to fail due to this if test "$cross_compiling" != "yes"; then @@ -330,15 +325,14 @@ if test "x$OPT_OPENSSL" != "xno"; then fi fi - test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" -fi + if test "$OPENSSL_ENABLED" != "1"; then + AC_MSG_NOTICE([OPT_OPENSSL: $OPT_OPENSSL]) + AC_MSG_NOTICE([OPENSSL_ENABLED: $OPENSSL_ENABLED]) + AC_MSG_ERROR([--with-openssl was given but OpenSSL could not be detected]) + fi -if test "x$OPT_OPENSSL" != "xno" && - test "$OPENSSL_ENABLED" != "1"; then - AC_MSG_NOTICE([OPT_OPENSSL: $OPT_OPENSSL]) - AC_MSG_NOTICE([OPENSSL_ENABLED: $OPENSSL_ENABLED]) - AC_MSG_ERROR([--with-openssl was given but OpenSSL could not be detected]) -fi + test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" +fi dnl OpenSSL not disabled if test "$OPENSSL_ENABLED" = "1"; then dnl --- @@ -363,38 +357,15 @@ if test "$OPENSSL_ENABLED" = "1"; then ]) dnl --- - dnl We require OpenSSL with SRP support. - dnl --- - AC_MSG_CHECKING([for SRP support in OpenSSL]) - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([[ - #ifndef OPENSSL_SUPPRESS_DEPRECATED - #define OPENSSL_SUPPRESS_DEPRECATED - #endif - #include - ]],[[ - SSL_CTX_set_srp_username(NULL, NULL); - SSL_CTX_set_srp_password(NULL, NULL); - ]]) - ],[ - AC_MSG_RESULT([yes]) - AC_DEFINE(HAVE_OPENSSL_SRP, 1, [if you have the functions SSL_CTX_set_srp_username and SSL_CTX_set_srp_password]) - HAVE_OPENSSL_SRP=1 - ],[ - AC_MSG_RESULT([no]) - ]) - - dnl --- - dnl Whether the OpenSSL configuration will be loaded automatically + dnl Whether the OpenSSL configuration is loaded automatically dnl --- AC_ARG_ENABLE(openssl-auto-load-config, AS_HELP_STRING([--enable-openssl-auto-load-config],[Enable automatic loading of OpenSSL configuration]) AS_HELP_STRING([--disable-openssl-auto-load-config],[Disable automatic loading of OpenSSL configuration]), [ if test "x$enableval" = "xno"; then AC_MSG_NOTICE([automatic loading of OpenSSL configuration disabled]) - AC_DEFINE(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG, 1, [if the OpenSSL configuration will not be loaded automatically]) + AC_DEFINE(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG, 1, [if the OpenSSL configuration is not loaded automatically]) fi ]) - fi ]) diff --git a/m4/curl-override.m4 b/m4/curl-override.m4 index 79b24567ac98..59e6548dbd35 100644 --- a/m4/curl-override.m4 +++ b/m4/curl-override.m4 @@ -28,7 +28,7 @@ dnl serial 7 dnl CURL_OVERRIDE_AUTOCONF dnl ------------------------------------------------- dnl Placing a call to this macro in configure.ac after -dnl the one to AC_INIT will make macros in this file +dnl the one to AC_INIT makes macros in this file dnl visible to the rest of the compilation overriding dnl those from Autoconf. diff --git a/m4/curl-reentrant.m4 b/m4/curl-reentrant.m4 index 6ac731bed9f1..bf073e2ff6e3 100644 --- a/m4/curl-reentrant.m4 +++ b/m4/curl-reentrant.m4 @@ -320,10 +320,10 @@ AC_DEFUN([CURL_CHECK_NEED_THREAD_SAFE_SYSTEM], [ dnl CURL_CONFIGURE_FROM_NOW_ON_WITH_REENTRANT dnl ------------------------------------------------- dnl This macro ensures that configuration tests done -dnl after this will execute with preprocessor symbol -dnl _REENTRANT defined. This macro also ensures that -dnl the generated config file defines NEED_REENTRANT -dnl and that in turn curl_setup.h will define _REENTRANT. +dnl after this execute with preprocessor symbol _REENTRANT +dnl defined. This macro also ensures that the generated +dnl config file defines NEED_REENTRANT and that in turn +dnl curl_setup.h defines _REENTRANT. dnl Internal macro for CURL_CONFIGURE_REENTRANT. AC_DEFUN([CURL_CONFIGURE_FROM_NOW_ON_WITH_REENTRANT], [ @@ -340,10 +340,10 @@ _EOF dnl CURL_CONFIGURE_FROM_NOW_ON_WITH_THREAD_SAFE dnl ------------------------------------------------- dnl This macro ensures that configuration tests done -dnl after this will execute with preprocessor symbol -dnl _THREAD_SAFE defined. This macro also ensures that -dnl the generated config file defines NEED_THREAD_SAFE -dnl and that in turn curl_setup.h will define _THREAD_SAFE. +dnl after this execute with preprocessor symbol_THREAD_SAFE +dnl defined. This macro also ensures that the generated +dnl config file defines NEED_THREAD_SAFE and that in turn +dnl curl_setup.h defines _THREAD_SAFE. dnl Internal macro for CURL_CONFIGURE_THREAD_SAFE. AC_DEFUN([CURL_CONFIGURE_FROM_NOW_ON_WITH_THREAD_SAFE], [ @@ -409,7 +409,7 @@ AC_DEFUN([CURL_CONFIGURE_REENTRANT], [ AC_MSG_CHECKING([if _REENTRANT is onwards defined]) if test "$tmp_reentrant_initially_defined" = "yes" || - test "$tmp_need_reentrant" = "yes"; then + test "$tmp_need_reentrant" = "yes"; then CURL_CONFIGURE_FROM_NOW_ON_WITH_REENTRANT AC_MSG_RESULT([yes]) else @@ -463,7 +463,7 @@ AC_DEFUN([CURL_CONFIGURE_THREAD_SAFE], [ AC_MSG_CHECKING([if _THREAD_SAFE is onwards defined]) if test "$tmp_thread_safe_initially_defined" = "yes" || - test "$tmp_need_thread_safe" = "yes"; then + test "$tmp_need_thread_safe" = "yes"; then CURL_CONFIGURE_FROM_NOW_ON_WITH_THREAD_SAFE AC_MSG_RESULT([yes]) else diff --git a/m4/curl-rustls.m4 b/m4/curl-rustls.m4 index 2a035680ffcf..86bed3c32315 100644 --- a/m4/curl-rustls.m4 +++ b/m4/curl-rustls.m4 @@ -30,7 +30,7 @@ dnl ---------------------------------------------------- if test "x$OPT_RUSTLS" != "xno"; then ssl_msg= - dnl backup the pre-ssl variables + dnl backup the pre-detection variables CLEANLDFLAGS="$LDFLAGS" CLEANLDFLAGSPC="$LDFLAGSPC" CLEANCPPFLAGS="$CPPFLAGS" @@ -57,7 +57,7 @@ if test "x$OPT_RUSTLS" != "xno"; then RUSTLS_PCDIR="$PREFIX_RUSTLS/lib/pkgconfig" if test -f "$RUSTLS_PCDIR/rustls.pc"; then - AC_MSG_NOTICE([PKG_CONFIG_LIBDIR will be set to "$RUSTLS_PCDIR"]) + AC_MSG_NOTICE([PKG_CONFIG_LIBDIR is set to "$RUSTLS_PCDIR"]) PKGTEST="yes" fi @@ -65,7 +65,7 @@ if test "x$OPT_RUSTLS" != "xno"; then dnl try lib64 instead RUSTLS_PCDIR="$PREFIX_RUSTLS/lib64/pkgconfig" if test -f "$RUSTLS_PCDIR/rustls.pc"; then - AC_MSG_NOTICE([PKG_CONFIG_LIBDIR will be set to "$RUSTLS_PCDIR"]) + AC_MSG_NOTICE([PKG_CONFIG_LIBDIR is set to "$RUSTLS_PCDIR"]) PKGTEST="yes" fi fi @@ -95,7 +95,7 @@ if test "x$OPT_RUSTLS" != "xno"; then SSL_CPPFLAGS="-I$PREFIX_RUSTLS/include" fi - dnl we will verify AC_CHECK_LIB later on + dnl we verify AC_CHECK_LIB later on AC_DEFINE(USE_RUSTLS, 1, [if Rustls is enabled]) USE_RUSTLS="yes" fi @@ -185,13 +185,10 @@ if test "x$OPT_RUSTLS" != "xno"; then test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" - if test "x$OPT_RUSTLS" != "xno" && - test "$RUSTLS_ENABLED" != "1"; then + if test "$RUSTLS_ENABLED" != "1"; then AC_MSG_NOTICE([OPT_RUSTLS: $OPT_RUSTLS]) AC_MSG_NOTICE([RUSTLS_ENABLED: $RUSTLS_ENABLED]) AC_MSG_ERROR([--with-rustls was given but Rustls could not be detected]) fi fi ]) - -RUSTLS_ENABLED diff --git a/m4/curl-schannel.m4 b/m4/curl-schannel.m4 index 3d0385347c10..e7358fbf62f2 100644 --- a/m4/curl-schannel.m4 +++ b/m4/curl-schannel.m4 @@ -26,8 +26,7 @@ AC_DEFUN([CURL_WITH_SCHANNEL], [ AC_MSG_CHECKING([whether to enable Windows native SSL/TLS]) if test "x$OPT_SCHANNEL" != "xno"; then ssl_msg= - if test "x$OPT_SCHANNEL" != "xno" && - test "$curl_cv_native_windows" = "yes"; then + if test "$curl_cv_native_windows" = "yes"; then if test "$curl_cv_winuwp" = "yes"; then AC_MSG_ERROR([UWP does not support Schannel.]) fi diff --git a/m4/curl-wolfssl.m4 b/m4/curl-wolfssl.m4 index 1d7b46721b32..29ae6b0c6cfd 100644 --- a/m4/curl-wolfssl.m4 +++ b/m4/curl-wolfssl.m4 @@ -43,134 +43,131 @@ if test "$OPT_WOLFSSL" != "no"; then ssl_msg= - if test "$OPT_WOLFSSL" != "no"; then - - if test "$OPT_WOLFSSL" = "yes"; then - OPT_WOLFSSL="" + if test "$OPT_WOLFSSL" = "yes"; then + OPT_WOLFSSL="" + fi + + dnl try pkg-config magic + CURL_CHECK_PKGCONFIG(wolfssl, [$wolfpkg]) + AC_MSG_NOTICE([Check directory $wolfpkg]) + + addld="" + addlib="" + addcflags="" + if test "$PKGCONFIG" != "no"; then + addlib=`CURL_EXPORT_PCDIR([$wolfpkg]) + $PKGCONFIG --libs-only-l wolfssl` + addld=`CURL_EXPORT_PCDIR([$wolfpkg]) + $PKGCONFIG --libs-only-L wolfssl` + addcflags=`CURL_EXPORT_PCDIR([$wolfpkg]) + $PKGCONFIG --cflags-only-I wolfssl` + version=`CURL_EXPORT_PCDIR([$wolfpkg]) + $PKGCONFIG --modversion wolfssl` + wolfssllibpath=`echo $addld | $SED -e 's/^-L//'` + else + addlib=-lwolfssl + dnl use system defaults if user does not supply a path + if test -n "$OPT_WOLFSSL"; then + addld=-L$OPT_WOLFSSL/lib$libsuff + addcflags=-I$OPT_WOLFSSL/include + wolfssllibpath=$OPT_WOLFSSL/lib$libsuff fi - - dnl try pkg-config magic - CURL_CHECK_PKGCONFIG(wolfssl, [$wolfpkg]) - AC_MSG_NOTICE([Check directory $wolfpkg]) - - addld="" - addlib="" - addcflags="" - if test "$PKGCONFIG" != "no"; then - addlib=`CURL_EXPORT_PCDIR([$wolfpkg]) - $PKGCONFIG --libs-only-l wolfssl` - addld=`CURL_EXPORT_PCDIR([$wolfpkg]) - $PKGCONFIG --libs-only-L wolfssl` - addcflags=`CURL_EXPORT_PCDIR([$wolfpkg]) - $PKGCONFIG --cflags-only-I wolfssl` - version=`CURL_EXPORT_PCDIR([$wolfpkg]) - $PKGCONFIG --modversion wolfssl` - wolfssllibpath=`echo $addld | $SED -e 's/^-L//'` - else - addlib=-lwolfssl - dnl use system defaults if user does not supply a path - if test -n "$OPT_WOLFSSL"; then - addld=-L$OPT_WOLFSSL/lib$libsuff - addcflags=-I$OPT_WOLFSSL/include - wolfssllibpath=$OPT_WOLFSSL/lib$libsuff - fi + fi + + if test "$curl_cv_apple" = "yes"; then + addlib="$addlib -framework Security -framework CoreFoundation" + elif test "$curl_cv_native_windows" = "yes"; then + addlib="$addlib -lcrypt32" + else + addlib="$addlib -lm" + fi + + if test "$USE_WOLFSSL" != "yes"; then + CPPFLAGS="$CPPFLAGS -DWOLFSSL_OPTIONS_IGNORE_SYS" + + LDFLAGS="$LDFLAGS $addld" + LDFLAGSPC="$LDFLAGSPC $addld" + AC_MSG_NOTICE([Add $addld to LDFLAGS]) + if test "$addcflags" != "-I/usr/include"; then + CPPFLAGS="$CPPFLAGS $addcflags" + AC_MSG_NOTICE([Add $addcflags to CPPFLAGS]) fi - if test "$curl_cv_apple" = "yes"; then - addlib="$addlib -framework Security -framework CoreFoundation" - else - addlib="$addlib -lm" + my_ac_save_LIBS="$LIBS" + LIBS="$addlib $LIBS" + AC_MSG_NOTICE([Add $addlib to LIBS]) + + AC_MSG_CHECKING([for wolfSSL_Init in -lwolfssl]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[ + #include + #include + ]],[[ + return wolfSSL_Init(); + ]]) + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(USE_WOLFSSL, 1, [if wolfSSL is enabled]) + WOLFSSL_ENABLED=1 + USE_WOLFSSL="yes" + ssl_msg="wolfSSL" + test "wolfssl" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes + ], + [ + AC_MSG_RESULT(no) + CPPFLAGS=$_cppflags + LDFLAGS=$_ldflags + LDFLAGSPC=$_ldflagspc + wolfssllibpath="" + ]) + LIBS="$my_ac_save_LIBS" + fi + + if test "$USE_WOLFSSL" = "yes"; then + AC_MSG_NOTICE([detected wolfSSL]) + check_for_ca_bundle=1 + + LIBS="$addlib $LIBS" + + dnl is this wolfSSL providing the original QUIC API? + AC_CHECK_FUNCS([wolfSSL_set_quic_use_legacy_codepoint], [QUIC_ENABLED=yes]) + + dnl wolfSSL needs configure --enable-opensslextra to have *get_peer* + dnl wc_Des_EcbEncrypt is needed for NTLM support. + dnl if wolfSSL_BIO_set_shutdown is present, we have the full BIO feature set + AC_CHECK_FUNCS(wolfSSL_get_peer_certificate \ + wolfSSL_UseALPN \ + wolfSSL_BIO_new \ + wolfSSL_BIO_set_shutdown \ + wc_Des_EcbEncrypt) + + dnl if this symbol is present, we want the include path to include the + dnl OpenSSL API root as well + if test "$ac_cv_func_wc_Des_EcbEncrypt" = "yes"; then + HAVE_WC_DES_ECBENCRYPT=1 fi - if test "$USE_WOLFSSL" != "yes"; then - CPPFLAGS="$CPPFLAGS -DWOLFSSL_OPTIONS_IGNORE_SYS" - - LDFLAGS="$LDFLAGS $addld" - LDFLAGSPC="$LDFLAGSPC $addld" - AC_MSG_NOTICE([Add $addld to LDFLAGS]) - if test "$addcflags" != "-I/usr/include"; then - CPPFLAGS="$CPPFLAGS $addcflags" - AC_MSG_NOTICE([Add $addcflags to CPPFLAGS]) - fi - - my_ac_save_LIBS="$LIBS" - LIBS="$addlib $LIBS" - AC_MSG_NOTICE([Add $addlib to LIBS]) - - AC_MSG_CHECKING([for wolfSSL_Init in -lwolfssl]) - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([[ - #include - #include - ]],[[ - return wolfSSL_Init(); - ]]) - ],[ - AC_MSG_RESULT(yes) - AC_DEFINE(USE_WOLFSSL, 1, [if wolfSSL is enabled]) - WOLFSSL_ENABLED=1 - USE_WOLFSSL="yes" - ssl_msg="wolfSSL" - test "wolfssl" != "$DEFAULT_SSL_BACKEND" || VALID_DEFAULT_SSL_BACKEND=yes - ], - [ - AC_MSG_RESULT(no) - CPPFLAGS=$_cppflags - LDFLAGS=$_ldflags - LDFLAGSPC=$_ldflagspc - wolfssllibpath="" - ]) - LIBS="$my_ac_save_LIBS" + dnl if this symbol is present, we can make use of BIO filter chains + if test "$ac_cv_func_wolfSSL_BIO_new" = "yes"; then + HAVE_WOLFSSL_BIO_NEW=1 fi - if test "$USE_WOLFSSL" = "yes"; then - AC_MSG_NOTICE([detected wolfSSL]) - check_for_ca_bundle=1 - - LIBS="$addlib $LIBS" - - dnl is this wolfSSL providing the original QUIC API? - AC_CHECK_FUNCS([wolfSSL_set_quic_use_legacy_codepoint], [QUIC_ENABLED=yes]) - - dnl wolfSSL needs configure --enable-opensslextra to have *get_peer* - dnl wc_Des_EcbEncrypt is needed for NTLM support. - dnl if wolfSSL_BIO_set_shutdown is present, we have the full BIO feature set - AC_CHECK_FUNCS(wolfSSL_get_peer_certificate \ - wolfSSL_UseALPN \ - wolfSSL_BIO_new \ - wolfSSL_BIO_set_shutdown \ - wc_Des_EcbEncrypt) - - dnl if this symbol is present, we want the include path to include the - dnl OpenSSL API root as well - if test "$ac_cv_func_wc_Des_EcbEncrypt" = "yes"; then - HAVE_WC_DES_ECBENCRYPT=1 - fi - - dnl if this symbol is present, we can make use of BIO filter chains - if test "$ac_cv_func_wolfSSL_BIO_new" = "yes"; then - HAVE_WOLFSSL_BIO_NEW=1 + if test -n "$wolfssllibpath"; then + dnl when shared libs were found in a path that the runtime + dnl linker does not search through, we need to add it to + dnl CURL_LIBRARY_PATH to prevent further configure tests to fail + dnl due to this + if test "$cross_compiling" != "yes"; then + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$wolfssllibpath" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $wolfssllibpath to CURL_LIBRARY_PATH]) fi - - if test -n "$wolfssllibpath"; then - dnl when shared libs were found in a path that the runtime - dnl linker does not search through, we need to add it to - dnl CURL_LIBRARY_PATH to prevent further configure tests to fail - dnl due to this - if test "$cross_compiling" != "yes"; then - CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$wolfssllibpath" - export CURL_LIBRARY_PATH - AC_MSG_NOTICE([Added $wolfssllibpath to CURL_LIBRARY_PATH]) - fi - fi - LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE wolfssl" - else - AC_MSG_ERROR([--with-wolfssl but wolfSSL was not found or does not work]) fi - - fi dnl wolfSSL not disabled + LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE wolfssl" + else + AC_MSG_ERROR([--with-wolfssl but wolfSSL was not found or does not work]) + fi test -z "$ssl_msg" || ssl_backends="${ssl_backends:+$ssl_backends, }$ssl_msg" -fi - +fi dnl wolfSSL not disabled ]) diff --git a/m4/xc-lt-iface.m4 b/m4/xc-lt-iface.m4 index 0d8b0ef31fc4..9f79fdb95210 100644 --- a/m4/xc-lt-iface.m4 +++ b/m4/xc-lt-iface.m4 @@ -111,7 +111,7 @@ esac dnl dnl Default behavior on some systems where building a shared library out -dnl of non-PIC compiled objects will fail with following linker error +dnl of non-PIC compiled objects fails with following linker error dnl "relocation R_X86_64_32 can not be used when making a shared object" dnl is to build PIC objects even for static libraries. This behavior may dnl be overridden using 'configure --disable-shared --without-pic'. @@ -183,10 +183,10 @@ dnl xc_lt_build_static m4_define([_XC_CHECK_LT_BUILD_LIBRARIES], [ + # -# Verify if finally libtool shared libraries will be built +# Verify if finally libtool shared libraries are built # - case "x$enable_shared" in @%:@ (( xyes | xno) xc_lt_build_shared=$enable_shared @@ -197,9 +197,8 @@ case "x$enable_shared" in @%:@ (( esac # -# Verify if finally libtool static libraries will be built +# Verify if finally libtool static libraries are built # - case "x$enable_static" in @%:@ (( xyes | xno) xc_lt_build_static=$enable_static @@ -362,10 +361,10 @@ dnl xc_lt_build_static_only m4_define([_XC_CHECK_LT_BUILD_SINGLE_VERSION], [ + # -# Verify if libtool shared libraries will be built while static not built +# Verify if libtool shared libraries are built while static not built # - AC_MSG_CHECKING([whether to build shared libraries only]) if test "$xc_lt_build_shared" = "yes" && test "$xc_lt_build_static" = "no"; then @@ -376,9 +375,8 @@ fi AC_MSG_RESULT([$xc_lt_build_shared_only]) # -# Verify if libtool static libraries will be built while shared not built +# Verify if libtool static libraries are built while shared not built # - AC_MSG_CHECKING([whether to build static libraries only]) if test "$xc_lt_build_static" = "yes" && test "$xc_lt_build_shared" = "no"; then diff --git a/m4/zz40-xc-ovr.m4 b/m4/zz40-xc-ovr.m4 index 5d2b2d0b727d..2713dcc8cb52 100644 --- a/m4/zz40-xc-ovr.m4 +++ b/m4/zz40-xc-ovr.m4 @@ -606,7 +606,7 @@ dnl XC_CONFIGURE_PREAMBLE macro and happens early in dnl generated configure script. The second one shows and dnl logs the result of the check into config.log at a later dnl configure stage. Placement of this second stage in -dnl generated configure script will be done where first +dnl generated configure script is done where first dnl direct or indirect usage of this macro happens. AC_DEFUN([XC_CHECK_PATH_SEPARATOR], diff --git a/m4/zz50-xc-ovr.m4 b/m4/zz50-xc-ovr.m4 index 155eb7f9b494..563c5ab25a19 100644 --- a/m4/zz50-xc-ovr.m4 +++ b/m4/zz50-xc-ovr.m4 @@ -52,8 +52,8 @@ m4_define([AC_LIBTOOL_LANG_GCJ_CONFIG],[:]) dnl XC_OVR_ZZ50 dnl ------------------------------------------------- -dnl Placing a call to this macro in configure.ac will -dnl make macros in this file visible to other macros +dnl Placing a call to this macro in configure.ac +dnl makes macros in this file visible to other macros dnl used for same configure script, overriding those dnl provided elsewhere. diff --git a/projects/OS400/.checksrc b/projects/OS400/.checksrc index 3bd88ff1e7db..e27d5729de0b 100644 --- a/projects/OS400/.checksrc +++ b/projects/OS400/.checksrc @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: curl -# Possibly not what we want, but cannot test, just silence the warnings +# Possibly not what we want, but cannot test, thus silence the warnings allowfunc calloc allowfunc free allowfunc malloc diff --git a/projects/OS400/README.OS400 b/projects/OS400/README.OS400 index 5d4b643a5e31..71a78990f378 100644 --- a/projects/OS400/README.OS400 +++ b/projects/OS400/README.OS400 @@ -71,6 +71,9 @@ options: CURLOPT_FTP_ALTERNATIVE_TO_USER CURLOPT_HAPROXY_CLIENT_IP CURLOPT_HSTS + CURLOPT_HTTPSIG_HEADERS + CURLOPT_HTTPSIG_KEY + CURLOPT_HTTPSIG_KEYID CURLOPT_INTERFACE CURLOPT_ISSUERCERT CURLOPT_KEYPASSWD diff --git a/projects/OS400/ccsidcurl.c b/projects/OS400/ccsidcurl.c index 0982eed639f7..8b3b2fe6c6a0 100644 --- a/projects/OS400/ccsidcurl.c +++ b/projects/OS400/ccsidcurl.c @@ -34,6 +34,7 @@ #pragma enum(int) +#include "curl_setup.h" #include "curl.h" #include "mprintf.h" #include "slist.h" @@ -41,12 +42,13 @@ #include "url.h" #include "setopt.h" #include "getinfo.h" +#include "curlx/dynbuf.h" #include "ccsidcurl.h" #include "os400sys.h" #ifndef SIZE_MAX -#define SIZE_MAX ((size_t)~0) /* Is unsigned on OS/400. */ +#define SIZE_MAX ((size_t) ~0) #endif #define ASCII_CCSID 819 /* Use ISO-8859-1 as ASCII. */ @@ -56,15 +58,23 @@ #define ALLOC_GRANULE 8 /* Alloc. granule for curl_formadd_ccsid(). */ + +/* A string terminator that works for all CCSIDs. */ +static const char universal_terminator[] = {0, 0, 0, 0}; + +/* Freeing const pointers more easily. */ +#define untyped_free(p) curlx_free(CURL_UNCONST(p)) + + static void makeOS400IconvCode(char buf[ICONV_ID_SIZE], unsigned int ccsid) { - /** - *** Convert a CCSID to the corresponding IBM iconv_open() character - *** code identifier. - *** This code is specific to the OS400 implementation of the iconv library. - *** CCSID 65535 (no conversion) is replaced by the ASCII CCSID. - *** CCSID 0 is interpreted by the OS400 as the job's CCSID. - **/ + /* + * Convert a CCSID to the corresponding IBM iconv_open() character + * code identifier. + * This code is specific to the OS400 implementation of the iconv library. + * CCSID 65535 (no conversion) is replaced by the ASCII CCSID. + * CCSID 0 is interpreted by the OS400 as the job's CCSID. + */ ccsid &= 0xFFFF; @@ -81,12 +91,12 @@ static iconv_t iconv_open_CCSID(unsigned int ccsidout, unsigned int ccsidin, char fromcode[ICONV_ID_SIZE]; char tocode[ICONV_ID_SIZE]; - /** - *** Like iconv_open(), but character codes are given as CCSIDs. - *** If `cstr' is non-zero, conversion is set up to stop whenever a - *** null character is encountered. - *** See iconv_open() IBM description in "National Language Support API". - **/ + /* + * Like iconv_open(), but character codes are given as CCSIDs. + * If `cstr' is non-zero, conversion is set up to stop whenever a + * null character is encountered. + * See iconv_open() IBM description in "National Language Support API". + */ makeOS400IconvCode(fromcode, ccsidin); makeOS400IconvCode(tocode, ccsidout); @@ -98,44 +108,35 @@ static iconv_t iconv_open_CCSID(unsigned int ccsidout, unsigned int ccsidin, return iconv_open(tocode, fromcode); } -static int convert(char *d, size_t dlen, int dccsid, const char *s, int slen, - int sccsid) +static int convert(char *d, size_t dlen, const char *s, size_t slen, + unsigned int ccsidin, unsigned int ccsidout) { int i; iconv_t cd; size_t lslen; - /** - *** Convert `sccsid'-coded `slen'-data bytes at `s' into `dccsid'-coded - *** data stored in the `dlen'-byte buffer at `d'. - *** If `slen' < 0, source string is null-terminated. - *** CCSID 65535 (no conversion) is replaced by the ASCII CCSID. - *** Return the converted destination byte count, or -1 if error. - **/ + /* + * Convert `ccsidin'-coded `slen'-data bytes at `s' into `ccsidout'-coded + * data stored in the `dlen'-byte buffer at `d'. + * If `slen' is CURL_ZERO_TERMINATED, let iconv() detect the end of + * input string. + * CCSID 65535 (no conversion) is replaced by the ASCII CCSID. + * Return the converted destination byte count, or -1 if error. + */ - if(sccsid == 65535) - sccsid = ASCII_CCSID; + if(ccsidin == 65535) + ccsidin = ASCII_CCSID; - if(dccsid == 65535) - dccsid = ASCII_CCSID; - - if(sccsid == dccsid) { - lslen = slen >= 0 ? slen : strlen(s) + 1; - i = lslen < dlen ? lslen : dlen; - - if(s != d && i > 0) - memcpy(d, s, i); - - return i; - } + if(ccsidout == 65535) + ccsidout = ASCII_CCSID; - if(slen < 0) { + if(slen == CURL_ZERO_TERMINATED) { lslen = 0; - cd = iconv_open_CCSID(dccsid, sccsid, 1); + cd = iconv_open_CCSID(ccsidout, ccsidin, 1); } else { lslen = (size_t)slen; - cd = iconv_open_CCSID(dccsid, sccsid, 0); + cd = iconv_open_CCSID(ccsidout, ccsidin, 0); } if(ICONV_OPEN_ERROR(cd)) @@ -143,7 +144,7 @@ static int convert(char *d, size_t dlen, int dccsid, const char *s, int slen, i = dlen; - if((int)iconv(cd, (char **)&s, &lslen, &d, &dlen) < 0) + if((int)iconv(cd, (char **) &s, &lslen, &d, &dlen) < 0) i = -1; else i -= dlen; @@ -152,75 +153,116 @@ static int convert(char *d, size_t dlen, int dccsid, const char *s, int slen, return i; } -static char *dynconvert(int dccsid, const char *s, int slen, int sccsid, - int *olen) +static CURLcode dyn_addn_CCSID(struct dynbuf *db, + const void *mem, size_t len, + unsigned int ccsidin, unsigned int ccsidout) { - char *d; - char *cp; + iconv_t cd; size_t dlen; - int l; - static const char nullbyte = 0; + CURLcode result = CURLE_OK; + char buffer[128]; - /* Like convert, but the destination is allocated and returned. */ + if(ccsidin == 65535) + ccsidin = ASCII_CCSID; - dlen = (size_t)(slen < 0 ? strlen(s) : slen) + 1; - dlen *= MAX_CONV_EXPANSION; /* Allow some expansion. */ - d = malloc(dlen); + if(ccsidout == 65535) + ccsidout = ASCII_CCSID; - if(!d) - return (char *)NULL; + cd = iconv_open_CCSID(ccsidout, ccsidin, len == CURL_ZERO_TERMINATED); - l = convert(d, dlen, dccsid, s, slen, sccsid); - - if(l < 0) { - free(d); - return (char *)NULL; + if(ICONV_OPEN_ERROR(cd)) { + curlx_dyn_free(db); + return CURLE_NOT_BUILT_IN; } - if(slen < 0) { - /* Need to null-terminate even when source length is given. - Since destination code size is unknown, use a conversion to generate - terminator. */ - - int l2 = convert(d + l, dlen - l, dccsid, &nullbyte, -1, ASCII_CCSID); + while(len) { + size_t dummylen = 0; + char *dptr = buffer; + int err = 0; + + dlen = sizeof(buffer); + if((int)iconv(cd, (char **) &mem, + len == CURL_ZERO_TERMINATED ? &dummylen : &len, + &dptr, &dlen) < 0) { + /* !checksrc! disable ERRNOVAR 1 */ + err = errno; + } + result = CURLE_BAD_FUNCTION_ARGUMENT; + switch(err) { + case 0: + case E2BIG: + break; + case ENOMEM: + result = CURLE_OUT_OF_MEMORY; + FALLTHROUGH(); + default: + curlx_dyn_free(db); + iconv_close(cd); + return result; + } - if(l2 < 0) { - free(d); - return (char *)NULL; + result = curlx_dyn_addn(db, (const void *)buffer, dptr - buffer); + if(result) { + iconv_close(cd); + return result; } - l += l2; + if(!err) + break; } - if((size_t)l < dlen) { - cp = realloc(d, l); /* Shorten to minimum needed. */ + iconv_close(cd); + dlen = curlx_dyn_len(db); + + if(len == CURL_ZERO_TERMINATED) { + /* The null terminator has been converted AND counted as a character. + * Measure it by an additional conversion and drop it. */ + int tlen = convert(buffer, sizeof(buffer), universal_terminator, 1, + ASCII_CCSID, ccsidout); - if(cp) - d = cp; + if(tlen < 0 || tlen > dlen) { + curlx_dyn_free(db); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + dlen -= tlen; + } + else { + /* Make sure the string is followed by an universal terminator. */ + result = curlx_dyn_addn(db, universal_terminator, + sizeof(universal_terminator)); } - if(olen) - *olen = l; - return d; + /* Restore the real string length. */ + if(!result) + result = curlx_dyn_setlen(db, dlen); + + return result; } -static struct curl_slist *slist_convert(int dccsid, struct curl_slist *from, - int sccsid) +static struct curl_slist *slist_convert(struct curl_slist *from, + unsigned int ccsidin, + unsigned int ccsidout) { struct curl_slist *to = (struct curl_slist *)NULL; + struct dynbuf db; + size_t plen; + + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_HTTP_HEADER); for(; from; from = from->next) { struct curl_slist *nl; - char *cp = dynconvert(dccsid, from->data, -1, sccsid, NULL); + char *s; - if(!cp) { + if(dyn_addn_CCSID(&db, from->data, CURL_ZERO_TERMINATED, + ccsidin, ccsidout)) { curl_slist_free_all(to); - return (struct curl_slist *)NULL; + return NULL; } - nl = Curl_slist_append_nodup(to, cp); + s = curlx_dyn_take(&db, &plen); + nl = Curl_slist_append_nodup(to, s); if(!nl) { curl_slist_free_all(to); - free(cp); + free(s); return NULL; } to = nl; @@ -231,35 +273,56 @@ static struct curl_slist *slist_convert(int dccsid, struct curl_slist *from, static char *keyed_string(localkey_t key, const char *ascii, unsigned int ccsid) { - int i; + size_t len; char *ebcdic; + struct dynbuf db; if(!ascii) - return (char *)NULL; - - i = MAX_CONV_EXPANSION * (strlen(ascii) + 1); + return NULL; - ebcdic = Curl_thread_buffer(key, i); - if(!ebcdic) - return ebcdic; - - if(convert(ebcdic, i, ccsid, ascii, -1, ASCII_CCSID) < 0) - return (char *)NULL; + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); + if(dyn_addn_CCSID(&db, ascii, CURL_ZERO_TERMINATED, ASCII_CCSID, ccsid)) + return NULL; + len = curlx_dyn_len(&db); + ebcdic = Curl_thread_buffer(key, len + sizeof(universal_terminator)); + if(ebcdic) { + memcpy(ebcdic, curlx_dyn_ptr(&db), len); + memcpy(ebcdic + len, universal_terminator, sizeof(universal_terminator)); + } + curlx_dyn_free(&db); return ebcdic; } -const char *curl_to_ccsid(const char *s, unsigned int ccsid) +const char *curl_to_ccsid(const char *string, unsigned int ccsid) { - if(s) - s = dynconvert(ccsid, s, -1, ASCII_CCSID, NULL); + char *s = NULL; + + if(string) { + struct dynbuf db; + size_t len; + + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); + dyn_addn_CCSID(&db, string, CURL_ZERO_TERMINATED, ASCII_CCSID, ccsid); + s = curlx_dyn_take(&db, &len); + } + return s; } -const char *curl_from_ccsid(const char *s, unsigned int ccsid) +const char *curl_from_ccsid(const char *string, unsigned int ccsid) { - if(s) - s = dynconvert(ASCII_CCSID, s, -1, ccsid, NULL); + char *s = NULL; + + if(string) { + struct dynbuf db; + size_t len; + + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); + dyn_addn_CCSID(&db, string, CURL_ZERO_TERMINATED, ccsid, ASCII_CCSID); + s = curlx_dyn_take(&db, &len); + } + return s; } @@ -269,103 +332,99 @@ char *curl_version_ccsid(unsigned int ccsid) } char *curl_easy_escape_ccsid(CURL *handle, const char *string, int length, - unsigned int sccsid, unsigned int dccsid) + unsigned int ccsidin, unsigned int ccsidout) { - char *s; + struct dynbuf db; char *d; + size_t len; - if(!string) { - /* !checksrc! disable ERRNOVAR 1 */ - errno = EINVAL; - return (char *)NULL; - } + if(!string) + return NULL; - s = dynconvert(ASCII_CCSID, string, length ? length : -1, sccsid, NULL); + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); - if(!s) - return (char *)NULL; + if(dyn_addn_CCSID(&db, string, length ? length : CURL_ZERO_TERMINATED, + ccsidin, ASCII_CCSID)) + return NULL; - d = curl_easy_escape(handle, s, 0); - free(s); + d = curl_easy_escape(handle, curlx_dyn_ptr(&db), curlx_dyn_len(&db)); + curlx_dyn_free(&db); if(!d) - return (char *)NULL; + return NULL; - s = dynconvert(dccsid, d, -1, ASCII_CCSID, NULL); - free(d); - return s; + dyn_addn_CCSID(&db, d, CURL_ZERO_TERMINATED, ASCII_CCSID, ccsidout); + untyped_free(d); + return curlx_dyn_take(&db, &len); } char *curl_easy_unescape_ccsid(CURL *handle, const char *string, int length, - int *outlength, unsigned int sccsid, - unsigned int dccsid) + int *outlength, + unsigned int ccsidin, unsigned int ccsidout) { - char *s; + struct dynbuf db; char *d; + size_t len; - if(!string) { - /* !checksrc! disable ERRNOVAR 1 */ - errno = EINVAL; - return (char *)NULL; - } + if(!string) + return NULL; - s = dynconvert(ASCII_CCSID, string, length ? length : -1, sccsid, NULL); + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); - if(!s) - return (char *)NULL; + if(dyn_addn_CCSID(&db, string, length ? length : CURL_ZERO_TERMINATED, + ccsidin, ASCII_CCSID)) + return NULL; - d = curl_easy_unescape(handle, s, 0, outlength); - free(s); + d = curl_easy_unescape(handle, + curlx_dyn_ptr(&db), curlx_dyn_len(&db), outlength); + curlx_dyn_free(&db); if(!d) - return (char *)NULL; - - s = dynconvert(dccsid, d, -1, ASCII_CCSID, NULL); - free(d); + return NULL; - if(s && outlength) - *outlength = strlen(s); - - return s; + if(!dyn_addn_CCSID(&db, d, CURL_ZERO_TERMINATED, ASCII_CCSID, ccsidout)) + if(outlength) + *outlength = curlx_dyn_len(&db); + untyped_free(d); + return curlx_dyn_take(&db, &len); } struct curl_slist *curl_slist_append_ccsid(struct curl_slist *list, const char *data, unsigned int ccsid) { - char *s; - - s = (char *)NULL; + const char *s; if(!data) return curl_slist_append(list, data); - s = dynconvert(ASCII_CCSID, data, -1, ccsid, NULL); + s = curl_from_ccsid(data, ccsid); if(!s) - return (struct curl_slist *)NULL; + return NULL; - list = curl_slist_append(list, s); - free(s); + list = Curl_slist_append_nodup(list, s); + if(!list) + untyped_free(s); return list; } time_t curl_getdate_ccsid(const char *p, const time_t *unused, unsigned int ccsid) { - char *s; + const char *s; time_t t; if(!p) return curl_getdate(p, unused); - s = dynconvert(ASCII_CCSID, p, -1, ccsid, NULL); + s = curl_from_ccsid(p, ccsid); if(!s) - return (time_t)-1; + return (time_t) -1; t = curl_getdate(s, unused); - free(s); + untyped_free(s); return t; } @@ -378,7 +437,8 @@ static int convert_version_info_string(const char **stringp, char **bufp, Return 0 if ok, else -1. */ if(*stringp) { - int l = convert(*bufp, *left, ccsid, *stringp, -1, ASCII_CCSID); + int l = convert(*bufp, *left, *stringp, CURL_ZERO_TERMINATED, + ASCII_CCSID, ccsid); if(l <= 0) return -1; @@ -432,7 +492,7 @@ curl_version_info_data *curl_version_info_ccsid(CURLversion stamp, /* If caller has been compiled with a newer version, error. */ if(stamp > CURLVERSION_NOW) - return (curl_version_info_data *)NULL; + return NULL; p = curl_version_info(stamp); @@ -469,11 +529,11 @@ curl_version_info_data *curl_version_info_ccsid(CURLversion stamp, sizeof(*id)); if(!id || !cp) - return (curl_version_info_data *)NULL; + return NULL; /* Copy data and convert strings. */ - memcpy((char *)id, (char *)p, sizeof(*p)); + memcpy(id, p, sizeof(*p)); if(id->protocols) { i = nproto * sizeof(id->protocols[0]); @@ -485,14 +545,14 @@ curl_version_info_data *curl_version_info_ccsid(CURLversion stamp, for(i = 0; id->protocols[i]; i++) if(convert_version_info_string(((const char **)id->protocols) + i, - &cp, &n, ccsid)) - return (curl_version_info_data *)NULL; + &cp, &n, ccsid)) + return NULL; } for(i = 0; i < sizeof(charfields) / sizeof(charfields[0]); i++) { cpp = (const char **)((char *)p + charfields[i]); if(*cpp && convert_version_info_string(cpp, &cp, &n, ccsid)) - return (curl_version_info_data *)NULL; + return NULL; } return id; @@ -527,9 +587,9 @@ void curl_certinfo_free_all(struct curl_certinfo *info) for(i = 0; i < info->num_of_certs; i++) curl_slist_free_all(info->certinfo[i]); - free((char *)info->certinfo); + untyped_free(info->certinfo); } - free((char *)info); + untyped_free(info); } } @@ -537,7 +597,7 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) { va_list arg; void *paramp; - CURLcode ret; + CURLcode result; struct Curl_easy *data; /* WARNING: unlike curl_easy_getinfo(), the strings returned by this @@ -546,11 +606,11 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) data = (struct Curl_easy *)curl; va_start(arg, info); paramp = va_arg(arg, void *); - ret = Curl_getinfo(data, info, paramp); + result = Curl_getinfo(data, info, paramp); - if(ret == CURLE_OK) { + if(result == CURLE_OK) { unsigned int ccsid; - char **cpp; + const char **cpp; struct curl_slist **slp; struct curl_certinfo *cipf; struct curl_certinfo *cipt; @@ -559,13 +619,15 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) case CURLINFO_STRING: ccsid = va_arg(arg, unsigned int); - cpp = (char **)paramp; + cpp = (const char **)paramp; if(*cpp) { - *cpp = dynconvert(ccsid, *cpp, -1, ASCII_CCSID, NULL); + const char *s = curl_to_ccsid(*cpp, ccsid); - if(!*cpp) - ret = CURLE_OUT_OF_MEMORY; + if(!s) + result = CURLE_OUT_OF_MEMORY; + else + *cpp = s; } break; @@ -576,33 +638,33 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) case CURLINFO_CERTINFO: cipf = *(struct curl_certinfo **)paramp; if(cipf) { - cipt = (struct curl_certinfo *)malloc(sizeof(*cipt)); + cipt = curlx_malloc(sizeof(*cipt)); if(!cipt) - ret = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; else { - cipt->certinfo = - (struct curl_slist **)calloc(cipf->num_of_certs + 1, - sizeof(struct curl_slist *)); + cipt->certinfo = curlx_calloc(cipf->num_of_certs + 1, + sizeof(struct curl_slist *)); if(!cipt->certinfo) - ret = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; else { int i; cipt->num_of_certs = cipf->num_of_certs; for(i = 0; i < cipf->num_of_certs; i++) - if(cipf->certinfo[i]) - if(!(cipt->certinfo[i] = slist_convert(ccsid, - cipf->certinfo[i], - ASCII_CCSID))) { - ret = CURLE_OUT_OF_MEMORY; + if(cipf->certinfo[i]) { + cipt->certinfo[i] = slist_convert(cipf->certinfo[i], + ASCII_CCSID, ccsid); + if(!cipt->certinfo[i]) { + result = CURLE_OUT_OF_MEMORY; break; } + } } } - if(ret != CURLE_OK) { + if(result != CURLE_OK) { curl_certinfo_free_all(cipt); - cipt = (struct curl_certinfo *)NULL; + cipt = NULL; } *(struct curl_certinfo **)paramp = cipt; @@ -618,9 +680,9 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) default: slp = (struct curl_slist **)paramp; if(*slp) { - *slp = slist_convert(ccsid, *slp, ASCII_CCSID); + *slp = slist_convert(*slp, ASCII_CCSID, ccsid); if(!*slp) - ret = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; } break; } @@ -628,7 +690,7 @@ CURLcode curl_easy_getinfo_ccsid(CURL *curl, CURLINFO info, ...) } va_end(arg); - return ret; + return result; } static int Curl_is_formadd_string(CURLformoption option) @@ -655,50 +717,36 @@ static void Curl_formadd_release_local(struct curl_forms *forms, int nargs, if(nargs != skip) if(Curl_is_formadd_string(forms[nargs].option)) if(forms[nargs].value) - free((char *)forms[nargs].value); + untyped_free(forms[nargs].value); - free((char *)forms); + untyped_free(forms); } static int Curl_formadd_convert(struct curl_forms *forms, int formx, int lengthx, unsigned int ccsid) { - int l; + size_t len = CURL_ZERO_TERMINATED; char *cp; - char *cp2; + struct dynbuf db; if(formx < 0 || !forms[formx].value) return 0; - if(lengthx >= 0) - l = (int)forms[lengthx].value; - else - l = strlen(forms[formx].value) + 1; - - cp = malloc(MAX_CONV_EXPANSION * l); - - if(!cp) - return -1; + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); - l = convert(cp, MAX_CONV_EXPANSION * l, ASCII_CCSID, forms[formx].value, l, - ccsid); + if(lengthx >= 0) + len = (size_t)forms[lengthx].value; - if(l < 0) { - free(cp); + if(dyn_addn_CCSID(&db, forms[formx].value, len, ccsid, ASCII_CCSID)) return -1; - } - - cp2 = realloc(cp, l); /* Shorten buffer to the string size. */ - - if(cp2) - cp = cp2; + cp = curlx_dyn_take(&db, &len); forms[formx].value = cp; if(lengthx >= 0) - forms[lengthx].value = (char *)l; /* Update length after conversion. */ + forms[lengthx].value = (char *)len; /* Update length after conversion. */ - return l; + return len; } CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, @@ -739,7 +787,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, /* Allocate the local curl_forms array. */ lformlen = ALLOC_GRANULE; - lforms = malloc(lformlen * sizeof(*lforms)); + lforms = curlx_malloc(lformlen * sizeof(*lforms)); if(!lforms) return CURL_FORMADD_MEMORY; @@ -753,7 +801,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, lengthx = -1; namex = -1; namelengthx = -1; - forms = (struct curl_forms *)NULL; + forms = NULL; va_start(arg, last_post); for(;;) { @@ -761,7 +809,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, if(nargs >= lformlen) { lformlen += ALLOC_GRANULE; - tforms = realloc(lforms, lformlen * sizeof(*lforms)); + tforms = curlx_realloc(lforms, lformlen * sizeof(*lforms)); if(!tforms) { result = CURL_FORMADD_MEMORY; @@ -794,7 +842,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, switch(option) { case CURLFORM_END: - forms = (struct curl_forms *)NULL; /* Leave array mode. */ + forms = NULL; /* Leave array mode. */ continue; case CURLFORM_ARRAY: @@ -978,34 +1026,27 @@ struct cfcdata { unsigned int ccsid; }; -static size_t Curl_formget_callback_ccsid(void *arg, const char *buf, - size_t len) +static size_t formget_callback_ccsid(void *arg, const char *buf, size_t len) { struct cfcdata *p; - char *b; - int l; + size_t olen; size_t ret; + struct dynbuf db; p = (struct cfcdata *)arg; if((long)len <= 0) - return (*p->append)(p->arg, buf, len); - - b = malloc(MAX_CONV_EXPANSION * len); + return p->append(p->arg, buf, len); - if(!b) - return (size_t)-1; + curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); - l = convert(b, MAX_CONV_EXPANSION * len, p->ccsid, buf, len, ASCII_CCSID); - - if(l < 0) { - free(b); - return (size_t)-1; - } + if(dyn_addn_CCSID(&db, buf, len, ASCII_CCSID, p->ccsid)) + return (size_t) -1; - ret = (*p->append)(p->arg, b, l); - free(b); - return ret == l ? len : -1; + olen = curlx_dyn_len(&db); + ret = p->append(p->arg, curlx_dyn_ptr(&db), olen); + curlx_dyn_free(&db); + return ret == olen ? len : -1; } int curl_formget_ccsid(struct curl_httppost *form, void *arg, @@ -1016,19 +1057,21 @@ int curl_formget_ccsid(struct curl_httppost *form, void *arg, lcfc.append = append; lcfc.arg = arg; lcfc.ccsid = ccsid; - return curl_formget(form, (void *)&lcfc, Curl_formget_callback_ccsid); + return curl_formget(form, (void *) &lcfc, formget_callback_ccsid); } -CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) +CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) { CURLcode result; va_list arg; - char *s; - char *cp = NULL; + const char *s; unsigned int ccsid; + struct dynbuf db; curl_off_t pfsize; - struct Curl_easy *data = easy; + size_t len; + struct Curl_easy *data = curl; + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); va_start(arg, tag); switch(tag) { @@ -1061,6 +1104,9 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) case CURLOPT_FTP_ALTERNATIVE_TO_USER: case CURLOPT_HAPROXY_CLIENT_IP: case CURLOPT_HSTS: + case CURLOPT_HTTPSIG_HEADERS: + case CURLOPT_HTTPSIG_KEY: + case CURLOPT_HTTPSIG_KEYID: case CURLOPT_INTERFACE: case CURLOPT_ISSUERCERT: case CURLOPT_KEYPASSWD: @@ -1129,11 +1175,11 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) case CURLOPT_USERPWD: case CURLOPT_XOAUTH2_BEARER: /* END TRANSLATABLE STRING OPTIONS */ - s = va_arg(arg, char *); + s = va_arg(arg, const char *); ccsid = va_arg(arg, unsigned int); if(s) { - s = dynconvert(ASCII_CCSID, s, -1, ccsid, NULL); + s = curl_from_ccsid(s, ccsid); if(!s) { result = CURLE_OUT_OF_MEMORY; @@ -1141,69 +1187,51 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) } } - result = curl_easy_setopt(easy, tag, s); - free(s); + result = curl_easy_setopt(curl, tag, s); + untyped_free(s); break; case CURLOPT_COPYPOSTFIELDS: /* Special case: byte count may have been given by CURLOPT_POSTFIELDSIZE prior to this call. In this case, convert the given byte count and replace the length according to the conversion result. */ - s = va_arg(arg, char *); + s = va_arg(arg, const char *); ccsid = va_arg(arg, unsigned int); pfsize = data->set.postfieldsize; if(!s || !pfsize || ccsid == NOCONV_CCSID || ccsid == ASCII_CCSID) { - result = curl_easy_setopt(easy, CURLOPT_COPYPOSTFIELDS, s); + result = curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, s); break; } - if(pfsize == -1) { - /* Data is null-terminated. */ - s = dynconvert(ASCII_CCSID, s, -1, ccsid, NULL); - - if(!s) { - result = CURLE_OUT_OF_MEMORY; - break; - } - } + if(pfsize == -1) + result = dyn_addn_CCSID(&db, s, CURL_ZERO_TERMINATED, + ccsid, ASCII_CCSID); else { /* Data length specified. */ - size_t len; - - if(pfsize < 0 || pfsize > SIZE_MAX) { - result = CURLE_OUT_OF_MEMORY; - break; - } - - len = pfsize; - pfsize = len * MAX_CONV_EXPANSION; - - if(pfsize > SIZE_MAX) - pfsize = SIZE_MAX; - - cp = malloc(pfsize); - - if(!cp) { + if(pfsize < 0 || pfsize > SIZE_MAX) result = CURLE_OUT_OF_MEMORY; - break; - } + else + result = dyn_addn_CCSID(&db, s, (size_t)pfsize, ccsid, ASCII_CCSID); + } - pfsize = convert(cp, pfsize, ASCII_CCSID, s, len, ccsid); + if(result) + break; - if(pfsize < 0) { - result = CURLE_OUT_OF_MEMORY; - break; - } + s = curlx_dyn_take(&db, &len); - data->set.postfieldsize = pfsize; /* Replace data size. */ - s = cp; - cp = NULL; + /* The following lines give data ownership to the library without + copying them. */ + result = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, s); + if(!result) { + data->set.str_copypostfields = CURL_UNCONST(s); /* Adopt. */ + if(pfsize != -1) + data->set.postfieldsize = len; } + else + untyped_free(s); - result = curl_easy_setopt(easy, CURLOPT_POSTFIELDS, s); - data->set.str[STRING_COPYPOSTFIELDS] = s; /* Give to library. */ break; default: @@ -1215,41 +1243,26 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) if(bp && bp->data && bp->len && ccsid != NOCONV_CCSID && ccsid != ASCII_CCSID) { - pfsize = (curl_off_t)bp->len * MAX_CONV_EXPANSION; - - if(pfsize > SIZE_MAX) - pfsize = SIZE_MAX; - - cp = malloc(pfsize); - - if(!cp) { - result = CURLE_OUT_OF_MEMORY; - break; - } - - pfsize = convert(cp, pfsize, ASCII_CCSID, bp->data, bp->len, ccsid); - - if(pfsize < 0) { - result = CURLE_OUT_OF_MEMORY; - break; - } + result = dyn_addn_CCSID(&db, bp->data, bp->len, ccsid, ASCII_CCSID); + if(result) + break; - blob.data = cp; - blob.len = pfsize; + blob.data = curlx_dyn_ptr(&db); + blob.len = curlx_dyn_len(&db); blob.flags = bp->flags | CURL_BLOB_COPY; bp = &blob; } - result = curl_easy_setopt(easy, tag, bp); + result = curl_easy_setopt(curl, tag, bp); break; } FALLTHROUGH(); case CURLOPT_ERRORBUFFER: /* This is an output buffer. */ - result = Curl_vsetopt(easy, tag, arg); + result = Curl_vsetopt(curl, tag, arg); break; } va_end(arg); - free(cp); + curlx_dyn_free(&db); return result; } @@ -1257,7 +1270,10 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...) char *curl_form_long_value(long value) { - /* ILE/RPG cannot cast an integer to a pointer. This procedure does it. */ + /* ILE/RPG cannot cast an integer to a pointer. This procedure does it. + As OS/400 is unable to dereference a pointer built from an integer only, + the goal here is only to keep the integer value as a (invalid) pointer + for a later reverse conversion. */ return (char *)value; } @@ -1285,51 +1301,46 @@ CURLcode curl_multi_setopt_RPGnum_(CURLM *multi, CURLMoption tag, char *curl_pushheader_bynum_cssid(struct curl_pushheaders *h, size_t num, unsigned int ccsid) { - char *d = (char *)NULL; - char *s = curl_pushheader_bynum(h, num); - - if(s) - d = dynconvert(ccsid, s, -1, ASCII_CCSID, NULL); - - return d; + return CURL_UNCONST(curl_to_ccsid(curl_pushheader_bynum(h, num), ccsid)); } char *curl_pushheader_byname_ccsid(struct curl_pushheaders *h, const char *header, unsigned int ccsidin, unsigned int ccsidout) { - char *d = (char *)NULL; + const char *d = NULL; if(header) { - header = dynconvert(ASCII_CCSID, header, -1, ccsidin, NULL); + const char *hdr = curl_from_ccsid(header, ccsidin); + + if(hdr) { + char *s = curl_pushheader_byname(h, hdr); - if(header) { - char *s = curl_pushheader_byname(h, header); - free((char *)header); + untyped_free(hdr); if(s) - d = dynconvert(ccsidout, s, -1, ASCII_CCSID, NULL); + d = curl_to_ccsid(s, ccsidout); } } - return d; + return CURL_UNCONST(d); } static CURLcode mime_string_call(curl_mimepart *part, const char *string, unsigned int ccsid, CURLcode (*mimefunc)(curl_mimepart *part, const char *string)) { - char *s = (char *)NULL; + const char *s; CURLcode result; if(!string) return mimefunc(part, string); - s = dynconvert(ASCII_CCSID, string, -1, ccsid, NULL); + s = curl_from_ccsid(string, ccsid); if(!s) return CURLE_OUT_OF_MEMORY; result = mimefunc(part, s); - free(s); + untyped_free(s); return result; } @@ -1366,58 +1377,68 @@ CURLcode curl_mime_filedata_ccsid(curl_mimepart *part, const char *filename, CURLcode curl_mime_data_ccsid(curl_mimepart *part, const char *data, size_t datasize, unsigned int ccsid) { - char *s = (char *)NULL; + struct dynbuf db; CURLcode result; - int osize; if(!data) return curl_mime_data(part, data, datasize); - s = dynconvert(ASCII_CCSID, data, datasize, ccsid, &osize); - if(!s) - return CURLE_OUT_OF_MEMORY; - result = curl_mime_data(part, s, osize); - free(s); + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); + + result = dyn_addn_CCSID(&db, data, datasize, ccsid, ASCII_CCSID); + + if(!result) { + size_t osize; + char *newdata = curlx_dyn_take(&db, &osize); + + result = curl_mime_data(part, newdata, datasize == CURL_ZERO_TERMINATED ? + datasize : osize); + untyped_free(newdata); + } + return result; } CURLUcode curl_url_get_ccsid(CURLU *handle, CURLUPart what, char **part, unsigned int flags, unsigned int ccsid) { - char *s = (char *)NULL; + char *s = NULL; CURLUcode result; if(!part) return CURLUE_BAD_PARTPOINTER; - *part = (char *)NULL; + *part = NULL; result = curl_url_get(handle, what, &s, flags); if(result == CURLUE_OK) { if(s) { - *part = dynconvert(ccsid, s, -1, ASCII_CCSID, NULL); - if(!*part) + const char *d = curl_to_ccsid(s, ccsid); + + if(d) + *part = CURL_UNCONST(d); + else result = CURLUE_OUT_OF_MEMORY; } } if(s) - free(s); + untyped_free(s); return result; } CURLUcode curl_url_set_ccsid(CURLU *handle, CURLUPart what, const char *part, unsigned int flags, unsigned int ccsid) { - char *s = (char *)NULL; + const char *s = NULL; CURLUcode result; if(part) { - s = dynconvert(ASCII_CCSID, part, -1, ccsid, NULL); + s = curl_from_ccsid(part, ccsid); if(!s) return CURLUE_OUT_OF_MEMORY; } result = curl_url_set(handle, what, s, flags); if(s) - free(s); + untyped_free(s); return result; } @@ -1427,11 +1448,11 @@ curl_easy_option_by_name_ccsid(const char *name, unsigned int ccsid) const struct curl_easyoption *option = NULL; if(name) { - char *s = dynconvert(ASCII_CCSID, name, -1, ccsid, NULL); + const char *s = curl_from_ccsid(name, ccsid); if(s) { option = curl_easy_option_by_name(s); - free(s); + untyped_free(s); } } @@ -1443,12 +1464,12 @@ const char * curl_easy_option_get_name_ccsid(const struct curl_easyoption *option, unsigned int ccsid) { - char *name = NULL; + const char *name = NULL; if(option && option->name) - name = dynconvert(ccsid, option->name, -1, ASCII_CCSID, NULL); + name = curl_to_ccsid(option->name, ccsid); - return (const char *)name; + return name; } /* Header API CCSID support. */ @@ -1459,12 +1480,12 @@ CURLHcode curl_easy_header_ccsid(CURL *easy, const char *name, size_t index, CURLHcode result = CURLHE_BAD_ARGUMENT; if(name) { - char *s = dynconvert(ASCII_CCSID, name, -1, ccsid, NULL); + const char *s = curl_from_ccsid(name, ccsid); result = CURLHE_OUT_OF_MEMORY; if(s) { result = curl_easy_header(easy, s, index, origin, request, hout); - free(s); + untyped_free(s); } } diff --git a/projects/OS400/ccsidcurl.h b/projects/OS400/ccsidcurl.h index f9e667a9e41c..f38e57967c2c 100644 --- a/projects/OS400/ccsidcurl.h +++ b/projects/OS400/ccsidcurl.h @@ -37,13 +37,14 @@ CURL_EXTERN char *curl_easy_unescape_ccsid(CURL *handle, const char *string, int length, int *outlength, unsigned int sccsid, unsigned int dccsid); -CURL_EXTERN struct curl_slist *curl_slist_append_ccsid(struct curl_slist *l, +CURL_EXTERN struct curl_slist *curl_slist_append_ccsid(struct curl_slist *list, const char *data, unsigned int ccsid); CURL_EXTERN time_t curl_getdate_ccsid(const char *p, const time_t *unused, unsigned int ccsid); -CURL_EXTERN curl_version_info_data *curl_version_info_ccsid(CURLversion stamp, - unsigned int cid); +CURL_EXTERN curl_version_info_data *curl_version_info_ccsid( + CURLversion stamp, + unsigned int ccsid); CURL_EXTERN const char *curl_easy_strerror_ccsid(CURLcode error, unsigned int ccsid); CURL_EXTERN const char *curl_share_strerror_ccsid(CURLSHcode error, diff --git a/projects/OS400/curl.inc.in b/projects/OS400/curl.inc.in index f97f26e01282..5b21429fed91 100644 --- a/projects/OS400/curl.inc.in +++ b/projects/OS400/curl.inc.in @@ -214,11 +214,20 @@ d c X'00000040' d CURLAUTH_AWS_SIGV4... d c X'00000080' + d CURLAUTH_HTTPSIG... + d c X'00000100' d CURLAUTH_ONLY... d c X'80000000' - d CURLAUTH_ANY c X'7FFFFFEF' + d CURLAUTH_ANY c X'7FFFFEEF' d CURLAUTH_ANYSAFE... - d c X'7FFFFFEE' + d c X'7FFFFEEE' + * + d CURLHTTPSIG_NONE... + d c 0 + d CURLHTTPSIG_ED25519... + d c 1 + d CURLHTTPSIG_HMAC_SHA256... + d c 2 * d CURLSSH_AUTH_ANY... d c X'7FFFFFFF' @@ -894,6 +903,8 @@ d c 6 d CURLPROXY_SOCKS5_HOSTNAME... d c 7 + d CURLPROXY_HTTPS3... + d c 8 * d curl_khstat s 10i 0 based(######ptr######) Enum d CURLKHSTAT_FINE_ADD_TO_FILE... @@ -1092,8 +1103,6 @@ d c X'10000000' d CURLPROTO_GOPHERS... d c X'20000000' - d CURLPROTO_MQTTS... - d c X'40000000' d CURLPROTO_ALL c X'FFFFFFFF' * d CURLoption s 10i 0 based(######ptr######) Enum @@ -1705,6 +1714,14 @@ d c 00327 d CURLOPT_SSL_SIGNATURE_ALGORITHMS... d c 10328 + d CURLOPT_HTTPSIG_ALGORITHM... + d c 00329 + d CURLOPT_HTTPSIG_KEY... + d c 10330 + d CURLOPT_HTTPSIG_KEYID... + d c 10331 + d CURLOPT_HTTPSIG_HEADERS... + d c 10332 * /if not defined(CURL_NO_OLDIES) d CURLOPT_FILE c 10001 @@ -1948,6 +1965,16 @@ d c X'00600041' d CURLINFO_USED_PROXY... CURLINFO_LONG + 66 d c X'00200042' + d CURLINFO_POSTTRANSFER_TIME_T... CURLINFO_OFF_T + 67 + d c X'00600043' + d CURLINFO_EARLYDATA_SENT_T... CURLINFO_OFF_T + 68 + d c X'00600044' + d CURLINFO_HTTPAUTH_USED... CURLINFO_LONG + 69 + d c X'00200045' + d CURLINFO_PROXYAUTH_USED... CURLINFO_LONG + 70 + d c X'00200046' + d CURLINFO_SIZE_DELIVERED... CURLINFO_OFF_T + 71 + d c X'00600047' * d CURLINFO_HTTP_CODE... Old ...RESPONSE_CODE d c X'00200002' @@ -2217,6 +2244,10 @@ d c 20018 d CURLMOPT_NOTIFYDATA... d c 10019 + d CURLMOPT_RESOLVE_THREADS_MAX... + d c 00020 + d CURLMOPT_QUICK_EXIT... + d c 00021 * d CURLMinfo_offt s 10i 0 based(######ptr######) Enum d CURLMINFO_NONE... @@ -2234,10 +2265,12 @@ * * Definition of bits for the CURLMOPT_NETWORK_CHANGED argument. * - d CURLMNWC_CLEAR_CONNS... + d CURLMNWC_CLEAR_ALL... d c x'00000001' - d CURLMNWC_CLEAR_DNS... + d CURLMNWC_CLEAR_CONNS... d c x'00000002' + d CURLMNWC_CLEAR_DNS... + d c x'00000004' * * Bitmask bits for CURLMOPT_PIPELINING. * @@ -2340,6 +2373,8 @@ d c 30 d CURLUE_TOO_LARGE... d c 31 + d CURLUE_BACKSLASH... + d c 32 * d CURLUPart s 10i 0 based(######ptr######) Enum d CURLUPART_URL c 0 @@ -3246,7 +3281,8 @@ d curl_ws_meta pr * extproc('curl_ws_meta') curl_ws_frame * d curl * value CURL * * - d curl_ws_start pr extproc('curl_ws_start') + d curl_ws_start_frame... + d pr extproc('curl_ws_start_frame') d like(CURLcode) d curl * value CURL * d flags 10u 0 value diff --git a/projects/OS400/curlcl.c b/projects/OS400/curlcl.c index 7a7f3c6459d6..8085307f31ca 100644 --- a/projects/OS400/curlcl.c +++ b/projects/OS400/curlcl.c @@ -149,7 +149,7 @@ int main(int argsc, struct arguments *args) if(!exitcode) { /* Allocate space for parsed arguments. */ - argv = (char **)malloc((argc + 1) * sizeof(*argv) + argsize); + argv = malloc((argc + 1) * sizeof(*argv) + argsize); if(!argv) { fputs("Memory allocation error\n", stderr); exitcode = -2; diff --git a/projects/OS400/curlmain.c b/projects/OS400/curlmain.c index 54ca865264dd..fa2e93d35f47 100644 --- a/projects/OS400/curlmain.c +++ b/projects/OS400/curlmain.c @@ -42,13 +42,13 @@ extern int QadrtFreeEnviron(void); extern char * setlocale_a(int, const char *); /* The ASCII main program. */ -extern int main_a(int argc, char **argv); +extern int main_a(int argc, char *argv[]); /* Global values of original EBCDIC arguments. */ int ebcdic_argc; char ** ebcdic_argv; -int main(int argc, char **argv) +int main(int argc, char *argv[]) { int i; int j; @@ -59,7 +59,7 @@ int main(int argc, char **argv) size_t inbytesleft; size_t outbytesleft; char dummybuf[128]; - /* To/From codes are 32 byte long strings with + /* To/From codes are 32-byte strings with reserved fields initialized to ZEROs */ const char tocode[32] = { "IBMCCSID01208" }; /* Use UTF-8. */ const char fromcode[32] = { "IBMCCSID000000000010" }; @@ -86,7 +86,7 @@ int main(int argc, char **argv) } /* Allocate memory for the ASCII arguments and vector. */ - argv = (char **)malloc((argc + 1) * sizeof(*argv) + bytecount); + argv = malloc((argc + 1) * sizeof(*argv) + bytecount); /* Build the vector and convert argument encoding. */ outbuf = (char *)(argv + argc + 1); diff --git a/projects/OS400/os400sys.c b/projects/OS400/os400sys.c index 36ed2480ead6..bea84500629d 100644 --- a/projects/OS400/os400sys.c +++ b/projects/OS400/os400sys.c @@ -28,8 +28,6 @@ #include #include "config-os400.h" /* Not curl_setup.h: we only need some defines. */ -#include -#include #include #include @@ -57,11 +55,11 @@ #include "os400sys.h" -/** -*** QADRT OS/400 ASCII runtime defines only the most used procedures, but a -*** lot of them are not supported. This module implements ASCII wrappers for -*** those that are used by libcurl, but not defined by QADRT. -**/ +/* + * QADRT OS/400 ASCII runtime defines only the most used procedures, but a + * lot of them are not supported. This module implements ASCII wrappers for + * those that are used by libcurl, but not defined by QADRT. + */ #pragma convert(0) /* Restore EBCDIC. */ @@ -370,6 +368,10 @@ static int Curl_gss_convert_in_place(OM_uint32 *minor_status, gss_buffer_t buf) return 0; } +/* Max string input length is a precaution against abuse and to detect junk + input easier and better. */ +#define CURL_MAX_INPUT_LENGTH 8000000 + OM_uint32 Curl_gss_import_name_a(OM_uint32 *minor_status, gss_buffer_t in_name, gss_OID in_name_type, gss_name_t *out_name) { @@ -381,7 +383,14 @@ OM_uint32 Curl_gss_import_name_a(OM_uint32 *minor_status, gss_buffer_t in_name, return gss_import_name(minor_status, in_name, in_name_type, out_name); memcpy((char *)&in, (char *)in_name, sizeof(in)); - i = in.length; + if(in.length > CURL_MAX_INPUT_LENGTH) { + if(minor_status) + /* !checksrc! disable ERRNOVAR 1 */ + *minor_status = ENOMEM; + + return GSS_S_FAILURE; + } + i = (unsigned int)in.length; in.value = malloc(i + 1); if(!in.value) { @@ -424,17 +433,17 @@ OM_uint32 Curl_gss_display_status_a(OM_uint32 *minor_status, return rc; } -OM_uint32 -Curl_gss_init_sec_context_a(OM_uint32 *minor_status, - gss_cred_id_t cred_handle, - gss_ctx_id_t *context_handle, - gss_name_t target_name, gss_OID mech_type, - gss_flags_t req_flags, OM_uint32 time_req, - gss_channel_bindings_t input_chan_bindings, - gss_buffer_t input_token, - gss_OID *actual_mech_type, - gss_buffer_t output_token, gss_flags_t *ret_flags, - OM_uint32 *time_rec) +OM_uint32 Curl_gss_init_sec_context_a( + OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_ctx_id_t *context_handle, + gss_name_t target_name, gss_OID mech_type, + gss_flags_t req_flags, OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_OID *actual_mech_type, + gss_buffer_t output_token, gss_flags_t *ret_flags, + OM_uint32 *time_rec) { int rc; gss_buffer_desc in; @@ -445,8 +454,15 @@ Curl_gss_init_sec_context_a(OM_uint32 *minor_status, if(inp) { if(inp->length && inp->value) { - unsigned int i = inp->length; + unsigned int i; + if(inp->length > CURL_MAX_INPUT_LENGTH) { + if(minor_status) + /* !checksrc! disable ERRNOVAR 1 */ + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + i = (unsigned int)inp->length; in.value = malloc(i + 1); if(!in.value) { if(minor_status) @@ -1017,4 +1033,4 @@ int Curl_os400_inflateEnd(z_streamp strm) return ret; } -#endif +#endif /* HAVE_LIBZ */ diff --git a/projects/Windows/generate.bat b/projects/Windows/generate.bat index 8441f7f3f3bf..7bfb98efd3a4 100644 --- a/projects/Windows/generate.bat +++ b/projects/Windows/generate.bat @@ -214,6 +214,10 @@ rem for /f "delims=" %%c in ('dir /b ..\..\lib\vauth\*.c') do call :element lib\vauth "%%c" %3 ) else if "!var!" == "CURL_LIB_VAUTH_H_FILES" ( for /f "delims=" %%h in ('dir /b ..\..\lib\vauth\*.h') do call :element lib\vauth "%%h" %3 + ) else if "!var!" == "CURL_LIB_VDNS_C_FILES" ( + for /f "delims=" %%c in ('dir /b ..\..\lib\vdns\*.c') do call :element lib\vdns "%%c" %3 + ) else if "!var!" == "CURL_LIB_VDNS_H_FILES" ( + for /f "delims=" %%h in ('dir /b ..\..\lib\vdns\*.h') do call :element lib\vdns "%%h" %3 ) else if "!var!" == "CURL_LIB_VQUIC_C_FILES" ( for /f "delims=" %%c in ('dir /b ..\..\lib\vquic\*.c') do call :element lib\vquic "%%c" %3 ) else if "!var!" == "CURL_LIB_VQUIC_H_FILES" ( @@ -337,8 +341,8 @@ rem :seterr rem Set the caller's errorlevel. rem %1[opt]: Errorlevel as integer. - rem If %1 is empty the errorlevel will be set to 0. - rem If %1 is not empty and not an integer the errorlevel will be set to 1. + rem If %1 is empty the errorlevel is set to 0. + rem If %1 is not empty and not an integer the errorlevel is set to 1. setlocal set EXITCODE=%~1 if not defined EXITCODE set EXITCODE=0 diff --git a/projects/Windows/tmpl/curl.vcxproj b/projects/Windows/tmpl/curl.vcxproj index 6d63601b6bba..a6a74e7aad70 100644 --- a/projects/Windows/tmpl/curl.vcxproj +++ b/projects/Windows/tmpl/curl.vcxproj @@ -852,7 +852,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -882,7 +882,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -911,7 +911,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -941,7 +941,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -970,7 +970,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1002,7 +1002,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1033,7 +1033,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1065,7 +1065,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1096,7 +1096,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1128,7 +1128,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1159,7 +1159,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1190,7 +1190,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1222,7 +1222,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1254,7 +1254,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1285,7 +1285,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1315,7 +1315,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1344,7 +1344,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1373,7 +1373,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1403,7 +1403,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1433,7 +1433,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1462,7 +1462,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1494,7 +1494,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurld.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) true @@ -1525,7 +1525,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win32\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console @@ -1555,7 +1555,7 @@ ..\..\..\..\include;%(AdditionalIncludeDirectories) - ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) + bcrypt.lib;ws2_32.lib;iphlpapi.lib;wldap32.lib;libcurl.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\build\Win64\$SUBDIR\$(Configuration);%(AdditionalLibraryDirectories) Console diff --git a/projects/Windows/tmpl/libcurl.vcxproj b/projects/Windows/tmpl/libcurl.vcxproj index af141d0bd11f..767256f6a52b 100644 --- a/projects/Windows/tmpl/libcurl.vcxproj +++ b/projects/Windows/tmpl/libcurl.vcxproj @@ -2034,6 +2034,7 @@ CURL_LIB_C_FILES CURL_LIB_CURLX_C_FILES CURL_LIB_VAUTH_C_FILES +CURL_LIB_VDNS_C_FILES CURL_LIB_VQUIC_C_FILES CURL_LIB_VSSH_C_FILES CURL_LIB_VTLS_C_FILES @@ -2042,6 +2043,7 @@ CURL_LIB_VTLS_C_FILES CURL_LIB_H_FILES CURL_LIB_CURLX_H_FILES CURL_LIB_VAUTH_H_FILES +CURL_LIB_VDNS_H_FILES CURL_LIB_VQUIC_H_FILES CURL_LIB_VSSH_H_FILES CURL_LIB_VTLS_H_FILES diff --git a/projects/vms/build_gnv_curl_pcsi_desc.com b/projects/vms/build_gnv_curl_pcsi_desc.com index 566384de0257..7726b4aa9f43 100644 --- a/projects/vms/build_gnv_curl_pcsi_desc.com +++ b/projects/vms/build_gnv_curl_pcsi_desc.com @@ -134,7 +134,7 @@ $ write pdsc " end if;" $! $write pdsc " software VMSPORTS ''base' ZLIB ;" $write pdsc - - " if (not ) ;" + " if (not ) ;" $write pdsc " error NEED_ZLIB;" $write pdsc " end if;" $! @@ -425,7 +425,7 @@ $ destname = "[gnv.beta" + destname - "[gnv.usr" $ endif $ endif $! -$! It should be just a directory then. +$! It should be a directory then. $!------------------------------------- $ filedir = f$edit(f$parse(filename,,,"DIRECTORY"), "lowercase") $! If this is not a directory then start processing files. diff --git a/projects/vms/build_vms.com b/projects/vms/build_vms.com index 253fccbc8074..d79fdb594079 100644 --- a/projects/vms/build_vms.com +++ b/projects/vms/build_vms.com @@ -38,7 +38,7 @@ $! Always link a debug image. $! NOIEEE Do not use IEEE floating point. (Alpha/I64) $! VAX must always use DFLOAT $! NOLARGE Disable large-file support if large file support available. -$! (Non-VAX, VMS >= V7.2.) +$! (Non-VAX, VMS >= v7.2.) $! NOLDAP Disable LDAP support if LDAP is available. $! NOKERBEROS Disable Kerberos support if Kerberos is available. $! LIST Create C compiler listings and linker maps. @@ -723,7 +723,7 @@ $ endif $ 'vo_c' " SSLLIB = ''ssllib'" $! $! TODO: Why are we translating the logical name? -$! The logical aname used to find the shared image should just be used +$! The logical aname used to find the shared image should be used $! as translating it could result in the wrong location at run time. $ if (openssl .eq. 1) $ then diff --git a/projects/vms/config_h.com b/projects/vms/config_h.com index e42ecd9d78db..49f6a3a51612 100644 --- a/projects/vms/config_h.com +++ b/projects/vms/config_h.com @@ -10,7 +10,7 @@ $! The CONFIGURE shell script will be examined for hints and a few symbols $! but most of the tests will not produce valid results on OpenVMS. Some $! will produce false positives and some will produce false negatives. $! -$! It is easier to just read the config.h_in file and make up tests based +$! It is easier to read the config.h_in file and make up tests based $! on what is in it! $! $! This file will create an empty config_vms.h file if one does not exist. @@ -20,7 +20,7 @@ $! $! The config_vms.h will be invoked by the resulting config.h file. $! $! This procedure knows about the DEC C RTL on the system it is on. -$! Future versions may be handle the GNV, the OpenVMS porting library, +$! Future versions may handle the GNV, the OpenVMS porting library, $! and others. $! $! This procedure may not guess the options correctly for all architectures, @@ -381,7 +381,7 @@ $! Manual check for LL on $!----------------------------------------------- $ if key2 .eqs. "LL" $ then -$ write tf "#ifndef __VAX +$ write tf "#ifndef __VAX" $ write tf "#define HAVE_''key2' 1" $ write tf "#endif" $ goto cfgh_in_loop1 @@ -1143,7 +1143,7 @@ $!----------------------------------------------------------------------- $ if keysym .eqs. "STRINGIZE" $ then $ write tf "#ifndef HAVE_STRINGIZE" -$ write tf "#define HAVE_STRINGSIZE 1" +$ write tf "#define HAVE_STRINGIZE 1" $ write tf "#endif" $ goto cfgh_in_loop1 $ endif @@ -1241,7 +1241,7 @@ $ then $ write tf "#ifndef HAVE_''keysym'" $ write tf "#define HAVE_''keysym' 1" $if p2 .nes. "" then write sys$output "''decc_shr' #define ''keysym' 1" -$ write tf "#endif +$ write tf "#endif" $ goto cfgh_in_loop1 $ endif $! @@ -1374,11 +1374,11 @@ $ then $ if key2b .eqs. "INO" .and. key2_h .eqs. "_T" $ then $ write tf "#ifndef SIZEOF_INO_T" -$ write tf "#if !__USING_STD_STAT +$ write tf "#if !__USING_STD_STAT" $ write tf "#define SIZEOF_INO_T 6" -$ write tf "#else +$ write tf "#else" $ write tf "#define SIZEOF_INO_T 8" -$ write tf "#endif +$ write tf "#endif" $ write tf "#endif" $ goto cfgh_in_loop1 $ endif @@ -1648,16 +1648,6 @@ $ write tf "#endif" $ goto cfgh_in_loop1 $ endif $! -$! Process STDC_HEADERS (SAMBA!) -$!--------------------------- -$ if key2 .eqs. "STDC_HEADERS" -$ then -$ write tf "#ifndef STDC_HEADERS" -$ write tf "#define STDC_HEADERS 1" -$ write tf "#endif" -$ goto cfgh_in_loop1 -$ endif -$! $! Process PROTOTYPES directive $!------------------------------------- $ if key2 .eqs. "PROTOTYPES" diff --git a/projects/vms/curl_crtl_init.c b/projects/vms/curl_crtl_init.c index a044ee72e042..09c46dd03c38 100644 --- a/projects/vms/curl_crtl_init.c +++ b/projects/vms/curl_crtl_init.c @@ -126,7 +126,7 @@ static int sys_trnlnm(const char *logname, char *value, int value_len) if($VMS_STATUS_SUCCESS(status)) { - /* Null-terminate and return the string */ + /* null-terminate and return the string */ /*--------------------------------------*/ value[result] = '\0'; } @@ -168,9 +168,7 @@ static int sys_crelnm(const char *logname, const char *value) /* Start of DECC RTL Feature handling */ -/* -** Sets default value for a feature -*/ +/* Sets default value for a feature */ #ifdef __VAX static void set_feature_default(const char *name, const char *value) { @@ -309,9 +307,9 @@ void (* const iniarray[])(void) = { set_features }; #endif /* -** Force a reference to LIB$INITIALIZE to ensure it -** exists in the image. -*/ + * Force a reference to LIB$INITIALIZE to ensure it + * exists in the image. + */ int LIB$INITIALIZE(void); #ifdef __DECC #pragma extern_model strict_refdef diff --git a/projects/vms/curl_gnv_build_steps.txt b/projects/vms/curl_gnv_build_steps.txt index c02c898dad7c..88af34f5802e 100644 --- a/projects/vms/curl_gnv_build_steps.txt +++ b/projects/vms/curl_gnv_build_steps.txt @@ -20,7 +20,7 @@ Currently building curl using GNV takes longer than building curl via DCL. The GNV procedure actually uses the same configure and makefiles that Unix builds use. -Building curl on OpenVMS using GNV requires GNV V2.1-2 or the updated +Building curl on OpenVMS using GNV requires GNV v2.1-2 or the updated images that are available via anonymous FTP at encompasserve.org in the gnv directory. It also requires the GNV Bash 4.2.45 kit as an update from the same location or from the sourceforge.net GNV project. @@ -268,7 +268,7 @@ branding the PCSI kit based on who is making the kit. This compares the VMS specific source with the backup staging directory for it and updates with any changes. - Leave off "UPDATE" to just check without doing any changes. + Leave off "UPDATE" to check without doing any changes. If you are not using NFS mounted disks and do not want to have a separate directory for staging the sources for backup make sure diff --git a/projects/vms/curl_release_note_start.txt b/projects/vms/curl_release_note_start.txt index 1a67b36020b0..184b458dd669 100644 --- a/projects/vms/curl_release_note_start.txt +++ b/projects/vms/curl_release_note_start.txt @@ -36,7 +36,7 @@ the GNV$LIBCURL shared image and create logical names GNV$LIBCURL to reference it. It will create the GNV$CURL_INCLUDE logical name for build procedures to access the header files. -Normally to use curl from DCL, just create a foreign command as: +Normally to use curl from DCL, create a foreign command as: curl :== $gnv$gnu:[usr.bin]gnv$curl.exe If you need to work around having the older HP SSL kit installed, then diff --git a/projects/vms/curlmsg.msg b/projects/vms/curlmsg.msg index 8d428e88b6b2..b02fe5649c85 100644 --- a/projects/vms/curlmsg.msg +++ b/projects/vms/curlmsg.msg @@ -111,7 +111,7 @@ LDAP_INVALID_URL FILESIZE_EXCEEDED USE_SSL_FAILED SEND_FAIL_REWIND -SSL_ENGINE_INITFAILED +SSL_ENGINE_INITFAILED LOGIN_DENIED TFTP_NOTFOUND TFTP_PERM diff --git a/projects/vms/generate_config_vms_h_curl.com b/projects/vms/generate_config_vms_h_curl.com index e4d97fd566c9..6e78b801f243 100644 --- a/projects/vms/generate_config_vms_h_curl.com +++ b/projects/vms/generate_config_vms_h_curl.com @@ -67,7 +67,7 @@ $if f$locate(",nossl,", args_lower) .lt. args_len then nossl = 1 $if .not. nossl $then $! -$! ssl$* logicals means HP ssl is present +$! ssl$* logicals means HP SSL is present $!---------------------------------------- $ if f$trnlnm("ssl$root") .nes. "" $ then @@ -96,7 +96,7 @@ $ nohpssl = 1 $ hpssl = 0 $ endif $! -$! Finally check to see if hp ssl has been specifically included. +$! Finally check to see if HP SSL has been specifically included. $!---------------------------------------------------------------- $ if f$locate(",nohpssl,", args_lower) .lt. args_len $ then @@ -215,7 +215,7 @@ $write cvh "" $! $! We are now setting this on the GNV build, so also do this $! for compatibility. -$write cvh "/* Location of default ca path */" +$write cvh "/* Location of default CA path */" $write cvh "#define curl_ca_path ""gnv$curl_ca_path""" $! $! The config_h.com finds a bunch of default disable commands in diff --git a/projects/vms/make_gnv_curl_install.sh b/projects/vms/make_gnv_curl_install.sh index 4723070387d0..623fcc3a6be0 100755 --- a/projects/vms/make_gnv_curl_install.sh +++ b/projects/vms/make_gnv_curl_install.sh @@ -37,7 +37,7 @@ export GNV_CC_MAIN_POSIX_EXIT=1 make cd ../.. # adjust the libcurl.pc file, GNV currently ignores the Lib: line. -# but is noisy about it, so we just remove it. +# but is noisy about it, so we remove it. sed -e 's/^Libs:/#Libs:/g' libcurl.pc > libcurl.pc_new rm libcurl.pc mv libcurl.pc_new libcurl.pc diff --git a/projects/vms/make_pcsi_curl_kit_name.com b/projects/vms/make_pcsi_curl_kit_name.com index 956f7c167798..c7c2b26ada7e 100644 --- a/projects/vms/make_pcsi_curl_kit_name.com +++ b/projects/vms/make_pcsi_curl_kit_name.com @@ -67,7 +67,7 @@ $ write sys$output "*****" $! $! $! Base is one of 'VMS', 'AXPVMS', 'I64VMS', 'VAXVMS' and indicates what -$! binaries are in the kit. A kit with just 'VMS' can be installed on all +$! binaries are in the kit. A kit with only 'VMS' can be installed on all $! architectures. $! $ base = "VMS" diff --git a/projects/vms/readme b/projects/vms/readme index 661dc9b471d3..9db0ee38735b 100644 --- a/projects/vms/readme +++ b/projects/vms/readme @@ -19,9 +19,9 @@ curl_gnv_build_steps.txt and other useful information. Prerequisites: -OpenVMS V7.0 or later (any platform) -DECC V6.5 or later -OpenSSL or hp SSL, if you want SSL support +OpenVMS v7.0 or later (any platform) +DECC v6.5 or later +OpenSSL or HP SSL, if you want SSL support What is Here: diff --git a/projects/vms/report_openssl_version.c b/projects/vms/report_openssl_version.c index d2b0d367bc49..9a8538e17da4 100644 --- a/projects/vms/report_openssl_version.c +++ b/projects/vms/report_openssl_version.c @@ -39,7 +39,7 @@ unsigned long LIB$SET_SYMBOL(const struct dsc$descriptor_s *symbol, const struct dsc$descriptor_s *value, const unsigned long *table_type); -int main(int argc, char **argv) +int main(int argc, char *argv[]) { void *libptr; const char *(*ssl_version)(int t); diff --git a/projects/vms/stage_curl_install.com b/projects/vms/stage_curl_install.com index 10ae17adcb3e..48f6514e5f4e 100644 --- a/projects/vms/stage_curl_install.com +++ b/projects/vms/stage_curl_install.com @@ -96,7 +96,7 @@ $ this_dir = f$element(i, ",", dest_dirs) $ i = i + 1 $ if this_dir .eqs. "" then goto curl_dir_loop $ if this_dir .eqs. "," then goto curl_dir_loop_end -$! Just create the directories, do not delete them. +$! Create the directories, do not delete them. $! -------------------------------------------------- $ if remove_files .eq. 0 $ then diff --git a/scripts/Makefile.am b/scripts/Makefile.am index e0f433422a8d..dc3353f03953 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -23,11 +23,12 @@ ########################################################################### EXTRA_DIST = coverage.sh completion.pl firefox-db2pem.sh checksrc.pl \ - checksrc-all.pl mk-ca-bundle.pl mk-unity.pl schemetable.c cd2nroff nroff2cd \ + checksrc-all.pl mk-ca-bundle.pl mk-unity.pl cd2nroff nroff2cd \ cdall cd2cd managen dmaketgz maketgz release-tools.sh verify-release \ - cmakelint.sh mdlinkcheck CMakeLists.txt perlcheck.sh pythonlint.sh \ - spacecheck.pl randdisable wcurl top-complexity extract-unit-protos \ - .checksrc badwords badwords-all badwords.txt + cmakelint.sh cmakeopts.sh mdlinkcheck CMakeLists.txt perlcheck.sh \ + pythonlint.sh spacecheck.pl randdisable wcurl top-complexity \ + extract-unit-protos .checksrc badwords badwords-all badwords.txt top-length \ + testnum dist_bin_SCRIPTS = wcurl diff --git a/scripts/badwords b/scripts/badwords index 5ed7a3ece14e..53c93f40318a 100755 --- a/scripts/badwords +++ b/scripts/badwords @@ -5,7 +5,7 @@ # # bad[:=]correct # -# If separator is '=', the string will be compared case sensitively. +# If separator is '=', the string is compared case sensitively. # If separator is ':', the check is done case insensitively. # # To add white listed uses of bad words that are removed before checking for @@ -149,7 +149,7 @@ sub sourcecode { my $flags = 0; my @lines; my $line; - open(F, "<$f"); + open(F, "<", $f); while() { my $l = $_; ($state, $flags, $line) = srcline($state, $flags, $l); @@ -175,7 +175,7 @@ my %wl; my @w; my @exact; my $file = shift @ARGV; -open(CONFIG, "<$file") or die "Cannot open '$file': $!"; +open(CONFIG, "<", $file) or die "Cannot open '$file': $!"; while() { chomp; if($_ =~ /^#/) { @@ -184,14 +184,14 @@ while() { if(/^---:([^:]*):(.*)/) { # whitelist file + word my $word = lc($2); - $wl{"$1:$word"}=1; + $wl{"$1:$word"} = 1; } elsif($_ =~ /^---(.+)/) { # whitelist word push @whitelist, $1; } elsif($_ =~ /^(.*)([:=])(.*)/) { - my ($bad, $sep, $better)=($1, $2, $3); + my ($bad, $sep, $better) = ($1, $2, $3); if($sep eq "=") { $alt{$bad} = $better; push @exact, $bad; @@ -261,7 +261,7 @@ sub highlight { sub document { my ($f) = @_; my @lines; - open(F, "<$f"); + open(F, "<", $f); while() { push @lines, $_; } @@ -318,7 +318,7 @@ sub file { } my @filemasks = @ARGV; -open(my $git_ls_files, '-|', 'git', 'ls-files', '--', @filemasks) or die "Failed running git ls-files: $!"; +open(my $git_ls_files, '-|', 'git', 'ls-files', '--end-of-options', ":!:$file", @filemasks) or die "Failed running git ls-files: $!"; my @files; while(my $each = <$git_ls_files>) { chomp $each; diff --git a/scripts/badwords.txt b/scripts/badwords.txt index 2de6f5bbe8f6..69bc87a08c85 100644 --- a/scripts/badwords.txt +++ b/scripts/badwords.txt @@ -14,13 +14,27 @@ run-time:runtime set-up:setup tool chain:toolchain tool-chain:toolchain +well known:well-known wild-card:wildcard wild card:wildcard +threadsafe:thread-safe thread safe:thread-safe +thread safety:thread-safety thread unsafe:thread-unsafe multi thread:multi-thread +nul terminate:null-terminate +null terminate:null-terminate +zero terminate:null-terminate +nul terminated:null-terminated null terminated:null-terminated +NULL-terminated=null-terminated zero terminated:null-terminated +zero-terminated:null-terminated +nul terminator:null-terminator +null terminator:null-terminator +zero terminator:null-terminator +work-around:workaround or work around +work-arounds:workarounds or works around it's:it is aren't:are not can't:cannot @@ -31,12 +45,12 @@ doesn't:does not don't:do not haven't:have not i'd:I would -i'll:I will +i'll:avoid contraction and rewrite to present tense i'm:I am i've:I have isn't:is not it'd:it would -it'll:it will +it'll:avoid contraction and rewrite to present tense might've:might have needn't:need not should've:should have @@ -44,21 +58,21 @@ shouldn't:should not that's:that is there's:there is they'd:They would -they'll:They will +they'll:avoid contraction and rewrite to present tense they're:They are they've:They have -this'll:this will +this'll:avoid contraction and rewrite to present tense wasn't:was not we'd:we would -we'll:we will +we'll:avoid contraction and rewrite to present tense we're:we are we've:we have weren't:were not -won't:will not +won't:avoid contraction and rewrite to present tense would've:would have wouldn't:would not you'd:you would -you'll:you will +you'll:avoid contraction and rewrite to present tense you're:you are you've:you have a html:an html @@ -92,10 +106,13 @@ will:rewrite to present tense 63 bit:63-bit 64 bit:64-bit 128 bit:128-bit +256 bit:256-bit 8-bits:8 bits 16-bits:16 bits 32-bits:32 bits 64-bits:64 bits +initialise:initialize +initialising:initializing very:rephrase using an alternative word just:rephrase using an alternative word simply:rephrase using an alternative word @@ -115,3 +132,4 @@ with with:with ---Curl Corporation ---:lib/:will ---:src/:will +---:tests/data/:file name diff --git a/scripts/cd2cd b/scripts/cd2cd index 182cb62c8a34..6c9cd0c278c8 100755 --- a/scripts/cd2cd +++ b/scripts/cd2cd @@ -79,7 +79,7 @@ sub outseealso { sub single { my @head; my @seealso; - my ($f)=@_; + my ($f) = @_; my $title; my $section; my $source; @@ -89,7 +89,7 @@ sub single { my $salist = 0; my $copyright; my $spdx; - open(F, "<:crlf", $f) || + open(F, "<:crlf", $f) or return 1; while() { $line++; @@ -103,13 +103,13 @@ sub single { next; } if(/^Title: *(.*)/i) { - $title=$1; + $title = $1; } elsif(/^Section: *(.*)/i) { - $section=$1; + $section = $1; } elsif(/^Source: *(.*)/i) { - $source=$1; + $source = $1; } elsif(/^See-also: +(.*)/i) { $salist = 0; @@ -130,10 +130,10 @@ sub single { } # REUSE-IgnoreStart elsif(/^C: (.*)/i) { - $copyright=$1; + $copyright = $1; } elsif(/^SPDX-License-Identifier: (.*)/i) { - $spdx=$1; + $spdx = $1; } # REUSE-IgnoreEnd elsif(/^---/) { @@ -212,7 +212,7 @@ HEAD close(F); if($inplace) { - open(O, ">$f") || return 1; + open(O, ">", $f) or return 1; print O @desc; close(O); } diff --git a/scripts/cd2nroff b/scripts/cd2nroff index 62c9df025a08..02f97bc41efc 100755 --- a/scripts/cd2nroff +++ b/scripts/cd2nroff @@ -59,7 +59,7 @@ Usage: cd2nroff [options] [file.md] specified directory, instead of writing to stdout -e If -d is used, this option can provide an added "extension", arbitrary text really, to append to the filename. --h This help text, +-h This help text -v Show version then exit HELP ; @@ -100,27 +100,33 @@ sub outprotocols { my (@p) = @_; my $comma = 0; my @o; + my $tls = 0; push @o, ".SH PROTOCOLS\n"; - if($p[0] eq "TLS") { - push @o, "This functionality affects all TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc."; - } - else { - my @s = sort @p; - push @o, "This functionality affects "; - for my $e (sort @s) { + my @s = sort @p; + push @o, "This functionality affects "; + for my $e (sort @s) { + if($e eq "TLS") { + $tls = 1; + } + else { push @o, sprintf "%s%s", $comma ? (($e eq $s[-1]) ? " and " : ", "): "", lc($e); $comma = 1; } - if($#s == 0) { - if($s[0] eq "All") { - push @o, " supported protocols"; - } - else { - push @o, " only"; - } + } + if($tls) { + push @o, sprintf + "%sall TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc.", + $comma ? " and ": " "; + } + if($#s == 0) { + if($s[0] eq "All") { + push @o, " supported protocols"; + } + else { + push @o, " only"; } } push @o, "\n"; @@ -194,12 +200,23 @@ my %knowntls = ( 'none' => 1, ); +sub quoted { + my ($d) = @_; + + # in verbatim/quoted blocks, make backslashes literal + $d =~ s/\\/\\\\/g; + + # lines starting with a period or apostrophe need it escaped + $d =~ s/^([.'])/\\&$1/; + return $d; +} + sub single { my @seealso; my @proto; my @tls; my $d; - my ($f)=@_; + my ($f) = @_; my $copyright; my $errors = 0; my $fh; @@ -234,13 +251,13 @@ sub single { next; } if(/^Title: *(.*)/i) { - $title=$1; + $title = $1; } elsif(/^Section: *(.*)/i) { - $section=$1; + $section = $1; } elsif(/^Source: *(.*)/i) { - $source=$1; + $source = $1; } elsif(/^See-also: +(.*)/i) { $list = 1; # 1 for see-also @@ -260,7 +277,7 @@ sub single { $list = 3; # 3 for TLS backend } elsif(/^Added-in: *(.*)/i) { - $addedin=$1; + $addedin = $1; if(($addedin !~ /^[0-9.]+[0-9]\z/) && ($addedin ne "n/a")) { print STDERR "$f:$line:1:ERROR: invalid version number in Added-in line: $addedin\n"; @@ -285,10 +302,10 @@ sub single { } # REUSE-IgnoreStart elsif(/^C: (.*)/i) { - $copyright=$1; + $copyright = $1; } elsif(/^SPDX-License-Identifier: (.*)/i) { - $spdx=$1; + $spdx = $1; } # REUSE-IgnoreEnd elsif(/^---/) { @@ -385,28 +402,27 @@ sub single { if($quote == 4) { # remove the indentation if($d =~ /^ (.*)/) { - push @desc, "$1\n"; + $d = quoted($1); + push @desc, "$d\n"; next; } - else { + # end of quote + $quote = 0; + push @desc, ".fi\n"; + + # fall-through + } + else { + if(/^~~~/) { # end of quote $quote = 0; push @desc, ".fi\n"; next; } - } - if(/^~~~/) { - # end of quote - $quote = 0; - push @desc, ".fi\n"; + $d = quoted($d); + push @desc, $d; next; } - # convert single backslashes to doubles - $d =~ s/\\/\\\\/g; - # lines starting with a period needs it escaped - $d =~ s/^\./\\&./; - push @desc, $d; - next; } # remove single line HTML comments @@ -415,7 +431,7 @@ sub single { # **bold** $d =~ s/\*\*(\S.*?)\*\*/\\fB$1\\fP/g; # *italics* - $d =~ s/\*(\S.*?)\*/\\fI$1\\fP/g; + $d =~ s/\*(\w.*?[\w)])\*/\\fI$1\\fP/g; my $back = $d; @@ -426,7 +442,7 @@ sub single { print STDERR "$f:$line:1:ERROR: un-escaped < or > used\n"; $errors++; } - # convert backslash-'<' or '> to just the second character + # convert backslash-'<' or '> to the second character $d =~ s/\\([<>])/$1/g; # mentions of curl symbols with man pages use italics by default @@ -506,7 +522,8 @@ sub single { $quote = 4; push @desc, "\n" if($blankline && !$header); $header = 0; - push @desc, ".nf\n$1\n"; + my $d = quoted($1); + push @desc, ".nf\n$d\n"; } elsif(/^[ \t]*\n/) { # count and ignore blank lines @@ -536,7 +553,7 @@ sub single { } if($d =~ /^[ \t]*\n/) { # replaced away all contents - $blankline= 1; + $blankline = 1; } else { push @desc, $d; diff --git a/scripts/cdall b/scripts/cdall index 1ea0f42f255f..53e6e3692984 100755 --- a/scripts/cdall +++ b/scripts/cdall @@ -29,8 +29,8 @@ use strict; use warnings; sub convert { - my ($dir)=@_; - opendir(my $dh, $dir) || die "could not open $dir"; + my ($dir) = @_; + opendir(my $dh, $dir) or die "could not open $dir"; my @cd = grep { /\.md\z/ && -f "$dir/$_" } readdir($dh); closedir $dh; diff --git a/scripts/checksrc-all.pl b/scripts/checksrc-all.pl index 5b1cba7af624..fd80c1b4277a 100755 --- a/scripts/checksrc-all.pl +++ b/scripts/checksrc-all.pl @@ -13,7 +13,7 @@ my @files; my $is_git = 0; if(system('git rev-parse --is-inside-work-tree >/dev/null 2>&1') == 0) { - open(O, '-|', 'git', 'ls-files', '*.[ch]') || die; push @files, ; close(O); + open(O, '-|', 'git', 'ls-files', '*.[ch]') or die; push @files, ; close(O); $is_git = 1; } else { @@ -34,7 +34,7 @@ for my $dir (@dirs) { if($is_git) { @files = (); - open(O, '-|', 'git', 'ls-files', "$dir/*.[ch]") || die; push @files, ; close(O); + open(O, '-|', 'git', 'ls-files', ":(glob)$dir/*.[ch]") or die; push @files, ; close(O); chomp(@files); } else { diff --git a/scripts/checksrc.pl b/scripts/checksrc.pl index d796bc5da0ce..5d3230c1e497 100755 --- a/scripts/checksrc.pl +++ b/scripts/checksrc.pl @@ -35,8 +35,8 @@ my $serrors = 0; my $suppressed; # skipped problems my $file; -my $dir="."; -my $wlist=""; +my $dir = "."; +my $wlist = ""; my @alist; my $windows_os = $^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys'; my $verbose = 0; @@ -65,10 +65,12 @@ "_wfopen" => 1, "_wfreopen" => 1, "_wopen" => 1, + "abort" => 1, "accept" => 1, "accept4" => 1, "access" => 1, "aprintf" => 1, + "assert" => 1, "atoi" => 1, "atol" => 1, "calloc" => 1, @@ -87,6 +89,8 @@ "getaddrinfo" => 1, "gets" => 1, "gmtime" => 1, + "inet_ntop" => 1, + "inet_pton" => 1, "llseek" => 1, "LoadLibrary" => 1, "LoadLibraryA" => 1, @@ -201,14 +205,16 @@ 'TRAILINGSPACE' => 'Trailing whitespace on the line', 'TYPEDEFSTRUCT' => 'typedefed struct', 'UNUSEDIGNORE' => 'a warning ignore was not used', + 'USESAFEFREE' => 'replace curlx_free() + NULL assignment with curlx_safefree()', + 'VOIDEXCL' => '(void)! is not something we like', ); sub readskiplist { open(my $W, '<', "$dir/checksrc.skip") or return; - my @all=<$W>; + my @all = <$W>; for(@all) { $windows_os ? $_ =~ s/\r?\n$// : chomp; - $skiplist{$_}=1; + $skiplist{$_} = 1; } close($W); } @@ -342,7 +348,7 @@ sub checkwarn { } elsif($file =~ /^-b(.*)/) { $banfunc{$1} = $1; - print STDERR "ban use of \"$1\"\n"; + # print STDERR "ban use of \"$1\"\n"; $file = shift @ARGV; next; } @@ -430,8 +436,8 @@ sub accept_violations { print "'$r' is not a warning to accept!\n"; exit; } - $ignore{$r}=999999; - $ignore_used{$r}=0; + $ignore{$r} = 999999; + $ignore_used{$r} = 0; } } @@ -462,9 +468,9 @@ sub enable_warn { $line, length($what) + 11, $file, $l, "No warning was inhibited!"); } - $ignore_set{$what}=0; - $ignore_used{$what}=0; - $ignore{$what}=0; + $ignore_set{$what} = 0; + $ignore_used{$what} = 0; + $ignore{$what} = 0; } sub checksrc { my ($cmd, $line, $file, $l) = @_; @@ -473,9 +479,9 @@ sub checksrc { $what =~ s: *\*/$::; # cut off end of C comment # print "ENABLE $enable WHAT $what\n"; if($enable eq "disable") { - my ($warn, $scope)=($1, $2); + my ($warn, $scope) = ($1, $2); if($what =~ /([^ ]*) +(.*)/) { - ($warn, $scope)=($1, $2); + ($warn, $scope) = ($1, $2); } else { $warn = $what; @@ -483,7 +489,7 @@ sub checksrc { } # print "IGNORE $warn for SCOPE $scope\n"; if($scope eq "all") { - $scope=999999; + $scope = 999999; } # Comparing for a literal zero rather than the scalar value zero @@ -501,9 +507,9 @@ sub checksrc { "$warn already disabled from line $ignore_set{$warn}"); } else { - $ignore{$warn}=$scope; - $ignore_set{$warn}=$line; - $ignore_line[$line]=$l; + $ignore{$warn} = $scope; + $ignore_set{$warn} = $line; + $ignore_line[$line] = $l; } } elsif($enable eq "enable") { @@ -527,20 +533,22 @@ sub scanfile { my ($file) = @_; my $line = 1; - my $prevl=""; - my $prevpl=""; + my $prevl = ""; + my $prevpl = ""; my $l = ""; my $prep = 0; my $prevp = 0; + my $prevfreeindent = ""; + my $prevfreevar = ""; if($verbose) { printf "Checking file: $file\n"; } - open(my $R, '<', $file) || die "failed to open $file"; + open(my $R, '<', $file) or die "failed to open $file"; - my $incomment=0; - my @copyright=(); + my $incomment = 0; + my @copyright = (); my %includes; checksrc_clear(); # for file based ignores accept_violations(); @@ -646,7 +654,7 @@ sub scanfile { } else { # still within a comment - $l=""; + $l = ""; } } @@ -699,7 +707,7 @@ sub scanfile { my $nostr = nostrings($l); # check spaces after for/if/while/function call if($nostr =~ /^(.*)(for|if|while|switch| ([a-zA-Z0-9_]+)) \((.)/) { - my ($leading, $word, $extra, $first)=($1,$2,$3,$4); + my ($leading, $word, $extra, $first) = ($1, $2, $3, $4); if($1 =~ / *\#/) { # this is a #if, treat it differently } @@ -751,7 +759,7 @@ sub scanfile { my $cond = $4; if($cond =~ / = /) { checkwarn("ASSIGNWITHINCONDITION", - $line, $pos+1, $file, $l, + $line, $pos + 1, $file, $l, "assignment within conditional expression"); } my $temp = $cond; @@ -876,7 +884,7 @@ sub scanfile { } } - # check for "return" with parentheses around just a value/name + # check for "return" with parentheses around a value/name if($l =~ /^(.*\W)return \(\w*\);/) { checkwarn("RETURNPAREN", $line, length($1)+7, $file, $l, "return with paren"); @@ -895,15 +903,15 @@ sub scanfile { # check for comma without space if($l =~ /^(.*),[^ \n]/) { - my $pref=$1; - my $ign=0; + my $pref = $1; + my $ign = 0; if($pref =~ / *\#/) { # this is a #if, treat it differently - $ign=1; + $ign = 1; } elsif($pref =~ /\/\*/) { # this is a comment - $ign=1; + $ign = 1; } elsif($pref =~ /[\"\']/) { $ign = 1; @@ -973,6 +981,24 @@ sub scanfile { $line, length($1), $file, $ol, "no space before label"); } + if($prevfreevar ne "") { + if(rindex($l, "$prevfreeindent$prevfreevar = NULL;", 0) == 0) { + checkwarn("USESAFEFREE", + $line, length($prevfreeindent), $file, $ol, + "replace curlx_free() + NULL assignment with curlx_safefree()"); + } + } + if($l) { + if($l =~ /^( *)curlx_free\(([^)]+)\);/) { + $prevfreeindent = $1; + $prevfreevar = $2; + } + else { + $prevfreeindent = ""; + $prevfreevar = ""; + } + } + # scan for use of banned functions my $bl = $l; again: @@ -1144,6 +1170,12 @@ sub scanfile { "space after exclamation mark"); } + if($nostr =~ /(.*)\(void\)\!/) { + checkwarn("VOIDEXCL", + $line, length($1)+1, $file, $ol, + "exclamation after (void) is weird"); + } + if($nostr =~ /(.*)\b(EACCES|EADDRINUSE|EADDRNOTAVAIL|EAFNOSUPPORT|EBADF|ECONNREFUSED|ECONNRESET|EINPROGRESS|EINTR|EINVAL|EISCONN|EMSGSIZE|ENOMEM|ETIMEDOUT|EWOULDBLOCK)\b/) { checkwarn("ERRNOVAR", $line, length($1), $file, $ol, @@ -1199,18 +1231,18 @@ sub scanfile { # A rather more interesting, and correct, check would be to not test # only locally committed files but inspect all files wrt the year of # their last commit. Removing the `git rev-list origin/master..HEAD` - # condition below will enforce copyright year checks against the year + # condition below enforces copyright year checks against the year # the file was last committed (and thus edited to some degree). my $commityear = undef; @copyright = sort {$$b{year} cmp $$a{year}} @copyright; # if the file is modified, assume commit year this year - if(`git status -s -- "$file"` =~ /^ [MARCU]/) { + if(qx(git status -s --end-of-options "$file") =~ /^ [MARCU]/) { $commityear = (localtime(time))[5] + 1900; } else { # min-parents=1 to ignore wrong initial commit in truncated repos - my $grl = `git rev-list --max-count=1 --min-parents=1 --timestamp HEAD -- "$file"`; + my $grl = qx(git rev-list --max-count=1 --min-parents=1 --timestamp --end-of-options HEAD -- "$file"); if($grl) { chomp $grl; $commityear = (localtime((split(/ /, $grl))[0]))[5] + 1900; diff --git a/scripts/cmakelint.sh b/scripts/cmakelint.sh index 30a735f87edc..150c21f2914d 100755 --- a/scripts/cmakelint.sh +++ b/scripts/cmakelint.sh @@ -27,7 +27,7 @@ # https://cmake-format.readthedocs.io/en/latest/lint-usage.html # https://github.com/cheshirekow/cmake_format/blob/master/cmakelang/configuration.py -# Run cmakelint on the curl source code. It will check all files given on the +# Run cmakelint on the curl source code. It checks all files given on the # command-line, or else all relevant files in git, or if not in a git # repository, all files starting in the tree rooted in the current directory. # @@ -36,12 +36,12 @@ # # The xargs invocation is portable, but does not preserve spaces in filenames. # If such a file is ever added, then this can be portably fixed by switching to -# "xargs -I{}" and appending {} to the end of the xargs arguments (which will -# call cmakelint once per file) or by using the GNU extension "xargs -d'\n'". +# "xargs -I{}" and appending {} to the end of the xargs arguments (which calls +# cmakelint once per file) or by using the GNU extension "xargs -d'\n'". set -eu -cd "$(dirname "$0")"/.. +cd -- "$(dirname "$0")"/.. { if [ -n "${1:-}" ]; then diff --git a/scripts/cmakeopts.sh b/scripts/cmakeopts.sh new file mode 100755 index 000000000000..a58e086b4c73 --- /dev/null +++ b/scripts/cmakeopts.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright (C) Viktor Szakats +# +# SPDX-License-Identifier: curl + +# Verify if CMake options are documented + +set -eu + +cd -- "$(dirname "$0")"/.. + +anyerr=0 +while read -r opt; do + if ! grep -q -F -- "- \`$opt\`: " docs/INSTALL-CMAKE.md; then + echo "CMake option missing from documentation: '$opt'" + anyerr=1 + fi +done < <( + { + git grep -o -E 'option\([A-Z][A-Z0-9_]+' ':!tests/cmake' | + grep -i cmake | + sed -E -e 's/^.+://g' -e 's/option\(//g' + + git grep -h -o -E 'curl_dependency_option\([A-Z][A-Z0-9_]+' | + sed -e 's/curl_dependency_option(//g' + + git grep -h -o -E "^# - \`[A-Z0-9_]+\`: " ':CMake/Find**' | + grep -o -E '[A-Z0-9_]+' | + grep -v -E '(_FOUND|_VERSION|NGTCP2_CRYPTO_BACKEND)' + } | sort -u +) + +exit "${anyerr}" diff --git a/scripts/completion.pl b/scripts/completion.pl index 27e2f062e7a4..203f2acfb6f4 100755 --- a/scripts/completion.pl +++ b/scripts/completion.pl @@ -80,21 +80,21 @@ sub parse_main_opts { my (@files, @list); my ($dir_handle, $file_content); - opendir($dir_handle, $opts_dir) || die "Unable to open dir: $opts_dir due to error: $!"; + opendir($dir_handle, $opts_dir) or die "Unable to open dir: $opts_dir due to error: $!"; @files = readdir($dir_handle); - closedir($dir_handle) || die "Unable to close handle on dir: $opts_dir due to error: $!"; + closedir($dir_handle) or die "Unable to close handle on dir: $opts_dir due to error: $!"; # We want regular files that end with .md and do not start with an underscore # Edge case: MANPAGE.md does not start with an underscore but also is not documentation for an option @files = grep { $_ =~ /\.md$/i && !/^_/ && -f "$opts_dir/$_" && $_ ne "MANPAGE.md" } @files; for my $file (@files) { - open(my $doc_handle, '<', "$opts_dir/$file") || die "Unable to open file: $file due to error: $!"; + open(my $doc_handle, '<', "$opts_dir/$file") or die "Unable to open file: $file due to error: $!"; $file_content = join('', <$doc_handle>); - close($doc_handle) || die "Unable to close file: $file due to error: $!"; + close($doc_handle) or die "Unable to close file: $file due to error: $!"; # Extract the curldown header section demarcated by --- - $file_content =~ /^---\s*\n(.*?)\n---\s*\n/s || die "Unable to parse file $file"; + $file_content =~ /^---\s*\n(.*?)\n---\s*\n/s or die "Unable to parse file $file"; $file_content = $1; my ($short, $long, $arg, $desc); diff --git a/scripts/contributors.sh b/scripts/contributors.sh index 37f1e5835bb8..f43f91d44615 100755 --- a/scripts/contributors.sh +++ b/scripts/contributors.sh @@ -62,7 +62,7 @@ CURLWWW="${CURLWWW:-../curl-www}" git -C "$CURLWWW" log --pretty=full --use-mailmap "$start..HEAD" fi } | \ - grep -Eai '(^Author|^Commit|^ +[a-z-]+-by):' | \ + grep -Eai '(^Author|^Commit|^ +[a-z-]+-(by|to)):' | \ cut -d: -f2- | \ cut '-d(' -f1 | \ cut '-d<' -f1 | \ @@ -82,7 +82,7 @@ awk ' { if(length($0)) { num++; - n = sprintf("%s%s%s,", n, length(n)?" ":"", $0); + n = sprintf("%s%s%s,", n, length(n) ? " " : "", $0); #print n; if(length(n) > 77) { printf(" %s\n", p); @@ -93,7 +93,7 @@ awk ' } END { - pp=substr(p,1,length(p)-1); + pp = substr(p, 1, length(p) - 1); printf(" %s\n", pp); printf(" (%d contributors)\n", num); } diff --git a/scripts/contrithanks.sh b/scripts/contrithanks.sh index 47438701cdd4..cea98976f540 100755 --- a/scripts/contrithanks.sh +++ b/scripts/contrithanks.sh @@ -36,11 +36,11 @@ if test "$start" = "-h"; then exit fi if test "$start" = "stdout"; then - # output the names on stdout - use_stdout="yes" - start="" + # output the names on stdout + use_stdout="yes" + start="" else - use_stdout="no"; + use_stdout="no" fi if test -z "$start"; then start=$(git tag --sort=taggerdate | grep "^curl-" | tail -1) @@ -62,7 +62,7 @@ tail -n +7 ./docs/THANKS | sed 's/ github/ github/i' > $rand git -C "$CURLWWW" log --use-mailmap "$start..HEAD" fi } | \ - grep -Eai '(^Author|^Commit|^ +[a-z-]+-by):' | \ + grep -Eai '(^Author|^Commit|^ +[a-z-]+-(by|to)):' | \ cut -d: -f2- | \ cut '-d(' -f1 | \ cut '-d<' -f1 | \ diff --git a/scripts/delta b/scripts/delta index 02b0232c47c2..d5b174044f51 100755 --- a/scripts/delta +++ b/scripts/delta @@ -34,8 +34,20 @@ use strict; use warnings; use POSIX; +use IPC::Open3; use Time::Piece; +sub cmd { + my @out; + my $as_string = shift if($_[0] eq '$'); # return as string (vs. list of lines) + my $hideerr = shift if($_[0] eq '2>'); # 2>/dev/null + my $pid = open3(my $in, my $out, $hideerr ? '>/dev/null' : '>&STDERR', @_); + close $in; + push @out, <$out>; + waitpid($pid, 0); + return $as_string ? join('', @out) : @out; +} + my $start = $ARGV[0] || ''; if($start eq "-h") { @@ -43,76 +55,92 @@ if($start eq "-h") { exit; } elsif($start eq "") { - $start = `git tag --sort=taggerdate | grep "^curl-" | tail -1`; + my @tags = cmd('git', 'tag', '--sort=taggerdate', '--no-column', '--list', 'curl-*'); + $start = $tags[-1]; # latest tag chomp $start; } -my $commits = `git log --oneline $start.. | wc -l`; -my $committers = `git shortlog -s $start.. | wc -l`; -my $bcommitters = `git shortlog -s $start | wc -l`; +my $commits = cmd('$', 'git', 'rev-list', '--count', "$start.."); +my $committers = cmd('git', 'shortlog', '-s', "$start.."); +my $bcommitters = cmd('git', 'shortlog', '-s', $start); -my $acommits = `git log --oneline | wc -l`; -my $acommitters = `git shortlog -s | wc -l`; +my $acommits = cmd('$', 'git', 'rev-list', '--count', 'HEAD'); +my $acommitters = cmd('git', 'shortlog', '-s', 'HEAD'); # delta from now compared to before my $ncommitters = $acommitters - $bcommitters; # number of contributors right now -my $acontribs = `./scripts/contrithanks.sh stdout | wc -l`; +my $acontribs = cmd('./scripts/contrithanks.sh', 'stdout'); # number when the tag was set -my $bcontribs = `git show $start:docs/THANKS | grep -c '^[^ ]'`; +my $bcontribs = cmd('$', 'git', 'grep', '-h', '-c', '^[^ ]', "$start:docs/THANKS"); # delta my $contribs = $acontribs - $bcontribs; # number of setops: sub setopts { - my ($f)=@_; - open(H, $f); - my $opts; - while() { - if(/^ CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) { - $opts++; + my $mode = shift; + my $opts = 0; + if(open(H, $mode, @_)) { + while() { + if(/^ CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) { + $opts++; + } } + close(H); + } + else { + die join(' ', @_) . ": $!\n"; } - close(H); return $opts; } -my $asetopts = setopts("/dev/null | grep -c '{ *"....--'`; -my $noptions=$aoptions - $boptions; +my $aoptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', 'src/tool_listhelp.c'); +my $boptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', "$start:src/tool_listhelp.c"); +my $noptions = $aoptions - $boptions; # current local branch -my $branch=`git rev-parse --abbrev-ref HEAD 2>/dev/null`; +my $branch = cmd('$', '2>', 'git', 'rev-parse', '--abbrev-ref', 'HEAD'); chomp $branch; # Number of files in git -my $afiles=`git ls-files | wc -l`; -my $deletes=`git diff-tree --diff-filter=A -r --summary origin/$branch $start 2>/dev/null | wc -l`; -my $creates=`git diff-tree --diff-filter=D -r --summary origin/$branch $start 2>/dev/null | wc -l`; +my $afiles = cmd('git', 'ls-files'); +my $deletes = cmd('2>', 'git', 'diff-tree', '--diff-filter=A', '-r', '--summary', "origin/$branch", $start); +my $creates = cmd('2>', 'git', 'diff-tree', '--diff-filter=D', '-r', '--summary', "origin/$branch", $start); # Time since that tag -my $tagged=`git for-each-ref --format="%(refname:short) | %(taggerdate:unix)" refs/tags/* | grep ^$start | cut '-d|' -f2`; # Unix timestamp -my $taggednice=`git for-each-ref --format="%(refname:short) | %(creatordate)" refs/tags/* | grep ^$start | cut '-d|' -f2`; # human readable time +sub tagstamp { + my $col = shift; + foreach my $line (@_) { + if(index($line, "$start ") == 0) { + my @cols = split(/\|/, $line); + return $cols[$col - 1]; + } + } +} +my @tagged = cmd('git', 'for-each-ref', '--format=%(refname:short) | %(taggerdate:unix) | %(creatordate)', 'refs/tags/*'); +my $tagged = tagstamp(2, @tagged); # Unix timestamp +my $taggednice = tagstamp(3, @tagged); # human readable time chomp $taggednice; -my $now=POSIX::strftime("%s", localtime()); -my $elapsed=$now - $tagged; # number of seconds since tag -my $total=$now - Time::Piece->strptime('19980320', '%Y%m%d')->epoch; -my $totalhttpget=$now - Time::Piece->strptime('19961111', '%Y%m%d')->epoch; +my $now = POSIX::strftime("%s", localtime()); +my $elapsed = $now - $tagged; # number of seconds since tag +my $total = $now - Time::Piece->strptime('19980320', '%Y%m%d')->epoch; +my $totalhttpget = $now - Time::Piece->strptime('19961111', '%Y%m%d')->epoch; # Number of public functions in libcurl -my $apublic=`git grep ^CURL_EXTERN -- include/curl | wc -l`; -my $bpublic=`git grep ^CURL_EXTERN $start -- include/curl | wc -l`; +my $apublic = cmd('git', 'grep', '--end-of-options', '^CURL_EXTERN', '--', 'include/curl'); +my $bpublic = cmd('git', 'grep', '--end-of-options', '^CURL_EXTERN', $start, '--', 'include/curl'); my $public = $apublic - $bpublic; # diffstat my ($fileschanged, $insertions, $deletions); -my $diffstat=`git diff --stat $start.. | tail -1`; +my @diffstat = cmd('2>', 'git', 'diff', '--stat', "$start.."); +my $diffstat = $diffstat[-1]; if($diffstat =~ /^ *(\d+) files changed, (\d+) insertions\(\+\), (\d+)/) { - ($fileschanged, $insertions, $deletions)=($1, $2, $3); + ($fileschanged, $insertions, $deletions) = ($1, $2, $3); } # Changes/bug-fixes currently logged @@ -123,16 +151,16 @@ open(F, ") { if($_ =~ /following changes:/) { - $mode=1; + $mode = 1; } elsif($_ =~ /following bugfixes:/) { - $mode=2; + $mode = 2; } elsif($_ =~ /known bugs:/) { - $mode=3; + $mode = 3; } elsif($_ =~ /like these:/) { - $mode=4; + $mode = 4; } if($_ =~ /^ o /) { if($mode == 1) { diff --git a/scripts/extract-unit-protos b/scripts/extract-unit-protos index b9154012f4ff..47efe6fd03bb 100755 --- a/scripts/extract-unit-protos +++ b/scripts/extract-unit-protos @@ -35,7 +35,7 @@ my $error; sub scanfile { my ($file) = @_; - open(F, "<$file") || die "$file failed"; + open(F, "<", $file) or die "$file failed"; my $unit = 0; my $line = 0; my $unitref = 0; diff --git a/scripts/firefox-db2pem.sh b/scripts/firefox-db2pem.sh index 7d31b1288693..b763ef10a26a 100755 --- a/scripts/firefox-db2pem.sh +++ b/scripts/firefox-db2pem.sh @@ -23,7 +23,7 @@ # * # *************************************************************************** # This shell script creates a fresh ca-bundle.crt file for use with libcurl. -# It extracts all ca certs it finds in the local Firefox database and converts +# It extracts all CA certs it finds in the local Firefox database and converts # them all into PEM format. # # It uses the "certutil" command line tool from the NSS project to perform the diff --git a/scripts/maketgz b/scripts/maketgz index e6bc53dcdc1b..5ee69983fdae 100755 --- a/scripts/maketgz +++ b/scripts/maketgz @@ -180,7 +180,7 @@ fi retar() { tempdir=$1 - rm -rf "$tempdir" + rm -rf "${tempdir:?}" mkdir "$tempdir" cd "$tempdir" gzip -dc "../$targz" | tar -xf - @@ -188,7 +188,7 @@ retar() { tar --create --format=ustar --owner=0 --group=0 --numeric-owner --sort=name curl-* | gzip --best --no-name > out.tar.gz mv out.tar.gz ../ cd .. - rm -rf "$tempdir" + rm -rf "${tempdir:?}" } retar ".tarbuild" @@ -218,14 +218,14 @@ gzip -dc "$targz" | xz -6e - > "$xz" # Now make a zip archive from the tar.gz original # makezip() { - rm -rf "$tempdir" + rm -rf "${tempdir:?}" mkdir "$tempdir" cd "$tempdir" gzip -dc "../$targz" | tar -xf - find . | sort | zip -9 -X "$zip" -@ >/dev/null mv "$zip" ../ cd .. - rm -rf "$tempdir" + rm -rf "${tempdir:?}" } zip="curl-$version.zip" diff --git a/scripts/managen b/scripts/managen index d70df2fbfd0c..9eececee9aa2 100755 --- a/scripts/managen +++ b/scripts/managen @@ -66,7 +66,7 @@ my $indent = 4; # get the long name version, return the man page string sub manpageify { - my ($k, $manpage)=@_; + my ($k, $manpage) = @_; my $trail = ''; # the matching pattern might include a trailing dot that cannot be part of # the option name @@ -85,12 +85,12 @@ sub manpageify { return "--$k$trail"; } -my $colwidth=79; # max number of columns +my $colwidth = 79; # max number of columns sub prefixline { my ($num) = @_; - print "\t" x ($num/8); - print ' ' x ($num%8); + print "\t" x ($num / 8); + print ' ' x ($num % 8); } sub justline { @@ -220,7 +220,7 @@ sub printdesc { } sub seealso { - my($standalone, $data)=@_; + my($standalone, $data) = @_; if($standalone) { return sprintf ".SH \"SEE ALSO\"\n$data\n"; @@ -231,7 +231,7 @@ sub seealso { } sub overrides { - my ($standalone, $data)=@_; + my ($standalone, $data) = @_; if($standalone) { return ".SH \"OVERRIDES\"\n$data\n"; } @@ -263,7 +263,7 @@ my %protexists = ( ); sub protocols { - my ($f, $line, $manpage, $standalone, $data)=@_; + my ($f, $line, $manpage, $standalone, $data) = @_; my @e = split(/ +/, $data); for my $pr (@e) { if(!$protexists{$pr}) { @@ -282,7 +282,7 @@ sub protocols { } sub too_old { - my ($version)=@_; + my ($version) = @_; my $a = 999999; if($version =~ /^(\d+)\.(\d+)\.(\d+)/) { $a = $1 * 1000 + $2 * 10 + $3; @@ -299,7 +299,7 @@ sub too_old { } sub added { - my ($standalone, $data)=@_; + my ($standalone, $data) = @_; if(too_old($data)) { # do not mention ancient additions return ""; @@ -494,7 +494,7 @@ sub render { } } - # convert backslash-'<' or '> to just the second character + # convert backslash-'<' or '> to the second character $d =~ s/\\([><])/$1/g; # convert single backslash to double-backslash $d =~ s/\\/\\\\/g if($manpage); @@ -560,9 +560,9 @@ sub maybespace { } sub single { - my ($dir, $manpage, $f, $standalone)=@_; + my ($dir, $manpage, $f, $standalone) = @_; my $fh; - open($fh, "<:crlf", "$dir/$f") || + open($fh, "<:crlf", "$dir/$f") or die "could not find $dir/$f"; my $short; my $long; @@ -597,28 +597,28 @@ sub single { next; } if(/^Short: *(.)/i) { - $short=$1; + $short = $1; } elsif(/^Long: *(.*)/i) { - $long=$1; + $long = $1; } elsif(/^Added: *(.*)/i) { - $added=$1; + $added = $1; } elsif(/^Tags: *(.*)/i) { - $tags=$1; + $tags = $1; } elsif(/^Arg: *(.*)/i) { - $arg=$1; + $arg = $1; } elsif(/^Magic: *(.*)/i) { - $magic=$1; + $magic = $1; } elsif(/^Mutexed: *(.*)/i) { - $mutexed=$1; + $mutexed = $1; } elsif(/^Protocols: *(.*)/i) { - $protocols=$1; + $protocols = $1; } elsif(/^See-also: +(.+)/i) { if(@seealso) { @@ -628,16 +628,16 @@ sub single { push @seealso, $1; } elsif(/^See-also:/i) { - $list=2; + $list = 2; } elsif(/^ *- (.*)/i && ($list == 2)) { push @seealso, $1; } elsif(/^Requires: *(.*)/i) { - $requires=$1; + $requires = $1; } elsif(/^Category: *(.*)/i) { - $category=$1; + $category = $1; } elsif(/^Example: +(.+)/i) { push @examples, $1; @@ -650,20 +650,20 @@ sub single { push @examples, $1; } elsif(/^Multi: *(.*)/i) { - $multi=$1; + $multi = $1; } elsif(/^Scope: *(.*)/i) { - $scope=$1; + $scope = $1; } elsif(/^Experimental: yes/i) { - $experimental=1; + $experimental = 1; } # REUSE-IgnoreStart elsif(/^C: (.*)/i) { - $copyright=$1; + $copyright = $1; } elsif(/^SPDX-License-Identifier: (.*)/i) { - $spdx=$1; + $spdx = $1; } # REUSE-IgnoreEnd elsif(/^Help: *(.*)/i) { @@ -705,7 +705,7 @@ sub single { } else { chomp; - print STDERR "$f:$line:1:WARN: unrecognized line in $f, ignoring:\n:'$_';" + print STDERR "$f:$line:1:WARN: unrecognized line in $f, ignoring: '$_';\n"; } } @@ -787,7 +787,7 @@ sub single { } elsif($multi eq "append") { push @extra, - sprintf("${pre}%s can be used several times in a command line\n", + sprintf("${pre}%s can be used several times in a command line.\n", manpageify($long, $manpage)); } elsif($multi eq "boolean") { @@ -855,7 +855,7 @@ sub single { " is built to support $requires.\n"; } if($mutexed) { - my @m=split(/ /, $mutexed); + my @m = split(/ /, $mutexed); my $mstr; my $num = scalar(@m); my $count = 0; @@ -868,15 +868,15 @@ sub single { if($count == ($num -1)) { $sep = " and "; } - $mstr .= sprintf "%s$l", $mstr?$sep:""; + $mstr .= sprintf "%s$l", $mstr ? $sep : ""; $count++; } push @foot, overrides($standalone, "This option is mutually exclusive with $mstr.\n"); } if($examples[0]) { - my $s =""; - $s="s" if($examples[1]); + my $s = ""; + $s = "s" if($examples[1]); foreach my $e (@examples) { my $check = $e; # verify the used options @@ -916,7 +916,7 @@ sub single { push @ex, "[0q]Example$s:\n"; # # long ASCII examples are wrapped. Preferably at the last space - # before the margin. Or at a colon. Otherwise it just cuts at the + # before the margin. Or at a colon. Otherwise it cuts at the # exact boundary. # foreach my $e (@examples) { @@ -933,7 +933,7 @@ sub single { if(length($e) > $maxwidth) { $r = maybespace($r); } - my $slash =""; + my $slash = ""; $e = substr($e, length($r)); if(length($e) > 0) { $slash = "\\"; @@ -967,9 +967,9 @@ sub single { } sub getshortlong { - my ($dir, $f)=@_; + my ($dir, $f) = @_; $f =~ s/^.*\///; - open(F, "<:crlf", "$dir/$f") || + open(F, "<:crlf", "$dir/$f") or die "could not find $dir/$f"; my $short; my $long; @@ -988,13 +988,13 @@ sub getshortlong { next; } if(/^Short: (.)/i) { - $short=$1; + $short = $1; } elsif(/^Long: (.*)/i) { - $long=$1; + $long = $1; } elsif(/^Help: (.*)/i) { - $help=$1; + $help = $1; my $len = length($help); if($len >= 49) { printf STDERR "$f:$line:1:WARN: oversized help text: %d characters\n", @@ -1002,13 +1002,13 @@ sub getshortlong { } } elsif(/^Arg: (.*)/i) { - $arg=$1; + $arg = $1; } elsif(/^Protocols: (.*)/i) { - $protocols=$1; + $protocols = $1; } elsif(/^Category: (.*)/i) { - $category=$1; + $category = $1; } elsif(/^---/) { last; @@ -1016,14 +1016,14 @@ sub getshortlong { } close(F); if($short) { - $optshort{$short}=$long; + $optshort{$short} = $long; } if($long) { - $optlong{$long}=$short; - $helplong{$long}=$help; - $arglong{$long}=$arg; - $protolong{$long}=$protocols; - $catlong{$long}=$category; + $optlong{$long} = $short; + $helplong{$long} = $help; + $arglong{$long} = $arg; + $protolong{$long} = $protocols; + $catlong{$long} = $category; } } @@ -1035,9 +1035,9 @@ sub indexoptions { } sub header { - my ($dir, $manpage, $f)=@_; + my ($dir, $manpage, $f) = @_; my $fh; - open($fh, "<:crlf", "$dir/$f") || + open($fh, "<:crlf", "$dir/$f") or die "could not find $dir/$f"; my @d = render($manpage, $fh, $f, 1); close($fh); @@ -1047,7 +1047,7 @@ sub header { sub sourcecategories { my ($dir) = @_; my %cats; - open(H, "<$dir/../../src/tool_help.h") || + open(H, "<", "$dir/../../src/tool_help.h") or die "cannot find the header file"; while() { if(/^\#define CURLHELP_([A-Z0-9]*)/) { @@ -1127,7 +1127,7 @@ HEAD $bitmask .= ' | '; } } - $bitmask =~ s/(?=.{76}).{1,76}\|/$&\n /g; + $bitmask =~ s/(?=.{75}).{1,75}\|/$&\n /g; my $arg = $arglong{$long}; if($arg) { $opt .= " $arg"; @@ -1143,7 +1143,14 @@ HEAD elsif(length($desc) > 78) { print STDERR "WARN: the --$long description is too long\n"; } - print $line; + my $ifdef = ""; + my $endif = ""; + + if($long =~ /^(ipfs|httpsig)/) { + $ifdef = sprintf "#ifndef CURL_DISABLE_%s\n", uc($1); + $endif = "#endif\n"; + } + print $ifdef.$line.$endif; } print <) { if($_ =~ /^#define LIBCURL_VERSION \"([0-9.]*)/) { $version = $1; diff --git a/scripts/mdlinkcheck b/scripts/mdlinkcheck index 4569b764d789..4d30e90d392e 100755 --- a/scripts/mdlinkcheck +++ b/scripts/mdlinkcheck @@ -90,7 +90,7 @@ if(defined $ARGV[0] && $ARGV[0] eq "--dry-run") { } # list all files to scan for links -my @files=`git ls-files docs include lib scripts src`; +my @files = qx(git ls-files .circleci appveyor.yml docs include lib scripts src); sub storelink { my ($f, $line, $link) = @_; @@ -108,7 +108,7 @@ sub storelink { #print "-- whitelisted: $link\n"; $whitelist{$link}++; } - # example.com is just example + # example.com is used as example elsif($link =~ /^https:\/\/(.*)example.(com|org|net)/) { #print "-- example: $link\n"; } @@ -154,7 +154,7 @@ sub storelink { sub findlinks { my ($f) = @_; my $line = 1; - open(F, "<:crlf", $f) || + open(F, "<:crlf", $f) or return; # is it a markdown extension? diff --git a/scripts/mk-ca-bundle.pl b/scripts/mk-ca-bundle.pl index 4eb759863435..e2e9acf629a7 100755 --- a/scripts/mk-ca-bundle.pl +++ b/scripts/mk-ca-bundle.pl @@ -153,7 +153,7 @@ () print " 3. certdata.txt file format may change, lag time to update this script\n"; print " 4. Generally unwise to blindly trust CAs without manual review & verification\n"; print " 5. Mozilla apps use additional security checks are not represented in certdata\n"; - print " 6. Use of this script will make a security engineer grind his teeth and\n"; + print " 6. Use of this script makes a security engineer grind his teeth and\n"; print " swear at you. ;)\n"; exit; } else { # Short Form Warning @@ -224,16 +224,16 @@ ($$@) } split(',', $param_string); # Find all values which are not in the list of valid values or "ALL" - my @invalid = grep { !is_in_list($_,"ALL",@valid_values) } @values; + my @invalid = grep { !is_in_list($_, "ALL", @valid_values) } @values; if(scalar(@invalid) > 0) { # Tell the user which parameters were invalid and print the standard help - # message which will exit + # message which also exits print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join(", ", map { "\"$_\"" } @invalid), "\n"; HELP_MESSAGE(); } - @values = @valid_values if(is_in_list("ALL",@values)); + @values = @valid_values if(is_in_list("ALL", @values)); return @values; } @@ -257,7 +257,7 @@ sub sha256 { sub oldhash { my $hash = ""; - open(C, "<$_[0]") || return 0; + open(C, "<", $_[0]) or return 0; while() { chomp; if($_ =~ /^\#\# SHA256: (.*)/) { @@ -309,7 +309,7 @@ (%) # If we have an HTTPS URL then use curl if($url =~ /^https:\/\//i) { - my $curl = `curl -V`; + my $curl = qx(curl -V); if($curl) { if($curl =~ /^Protocols:.* https( |$)/m) { report "Get certdata with curl!"; @@ -403,7 +403,7 @@ (%) if($stdout) { open(CRT, '> -') or die "Could not open STDOUT: $!\n"; } else { - open(CRT,">$crt.~") or die "Could not open $crt.~: $!\n"; + open(CRT, ">", "$crt.~") or die "Could not open $crt.~: $!\n"; } print CRT <>$crt.~") or die "Could not open $crt.~: $!"; + open(CRT, ">>", "$crt.~") or die "Could not open $crt.~: $!"; } } $pipe = "|$openssl x509 -text -inform PEM"; @@ -647,7 +647,7 @@ (%) print TMP $pem; close(TMP) or die "Could not close openssl pipe: $!"; if(!$stdout) { - open(CRT, ">>$crt.~") or die "Could not open $crt.~: $!"; + open(CRT, ">>", "$crt.~") or die "Could not open $crt.~: $!"; } } report "Processed: $caname" if($opt_v); diff --git a/scripts/mk-unity.pl b/scripts/mk-unity.pl index 014cfe8eceb9..41354c15d120 100755 --- a/scripts/mk-unity.pl +++ b/scripts/mk-unity.pl @@ -65,7 +65,7 @@ sub include($@) { my $filename = shift; - if($concat) { + if($concat && $filename =~ /([a-z0-9_]+)\.c$/) { if(! -f $filename) { foreach my $path (@incpath) { my $fullfn = $path . "/" . $filename; diff --git a/scripts/nroff2cd b/scripts/nroff2cd index 51b6974b8dfb..374b0cbdab58 100755 --- a/scripts/nroff2cd +++ b/scripts/nroff2cd @@ -30,7 +30,7 @@ This script converts an nroff file to curldown Example: cd2nroff [options] > Note: when converting .nf sections, this tool does not know if the -section is code or just regular quotes. It then assumes and uses ~~~c +section is code or regular quotes. It then assumes and uses ~~~c for code. =end comment @@ -42,8 +42,8 @@ use warnings; my $nroff2cd = "0.1"; # to keep check sub single { - my ($f)=@_; - open(F, "<:crlf", $f) || + my ($f) = @_; + open(F, "<:crlf", $f) or return 1; my $line; my $title; @@ -67,7 +67,7 @@ sub single { # remove leading directory $f =~ s/(.*?\/)//; close(F); - open(F, "<:crlf", $f) || return 1; + open(F, "<:crlf", $f) or return 1; } if($d =~ /^\.TH ([^ ]*) (\d) \"(.*?)\" ([^ \n]*)/) { # header, this needs to be the first thing after leading comments @@ -116,7 +116,7 @@ HEAD # if there are enclosing quotes, remove them first $word =~ s/[\"\'](.*)[\"\']\z/$1/; if($word eq "SEE ALSO") { - # we just slurp up this section + # we slurp up this section next; } push @desc, "\n# $word\n\n"; @@ -131,7 +131,7 @@ HEAD push @desc, "\n## $word\n\n"; } elsif($d =~ /^\.IP/) { - # .IP with no text we just skip + # .IP with no text we skip } elsif($d =~ /^\.BR (.*)/) { # only used for SEE ALSO diff --git a/scripts/perlcheck.sh b/scripts/perlcheck.sh index c243f50271c5..92970e401553 100755 --- a/scripts/perlcheck.sh +++ b/scripts/perlcheck.sh @@ -25,12 +25,12 @@ # The xargs invocation is portable, but does not preserve spaces in filenames. # If such a file is ever added, then this can be portably fixed by switching to -# "xargs -I{}" and appending {} to the end of the xargs arguments (which will -# call cmakelint once per file) or by using the GNU extension "xargs -d'\n'". +# "xargs -I{}" and appending {} to the end of the xargs arguments (which calls +# cmakelint once per file) or by using the GNU extension "xargs -d'\n'". set -eu -cd "$(dirname "$0")"/.. +cd -- "$(dirname "$0")"/.. procs=6 command -v nproc >/dev/null && procs="$(nproc)" diff --git a/scripts/pythonlint.sh b/scripts/pythonlint.sh index 5c1a3cdcc3fd..2cd3a6c44f57 100755 --- a/scripts/pythonlint.sh +++ b/scripts/pythonlint.sh @@ -27,9 +27,17 @@ # locations, or all Python files found in the current directory tree by # default. -ruff check --extend-select=B007,B016,C405,C416,COM818,D200,D213,D204,D401,\ +set -eu + +ruff check --target-version=py38 \ + --extend-select=B007,B016,C405,C416,COM818,D200,D213,D204,D401,\ D415,FURB129,N818,PERF401,PERF403,PIE790,PIE808,PLW0127,Q004,RUF010,SIM101,\ -SIM117,SIM118,TRY400,TRY401,RET503,RET504,UP004,B018,B904,RSE102,RET505,I001 \ -"$@" +SIM110,SIM117,SIM118,TRY400,TRY401,RET503,RET504,UP004,B018,B904,RSE102,RET505,\ +I001,PERF102,C419 \ + --ignore=FA100 \ + "$@" # Checks that are in preview only, since --preview otherwise turns them all on -ruff check --preview --select=E301,E302,E303,E304,E305,E306,E502 "$@" +ruff check --target-version=py38 --preview \ + --select=E301,E302,E303,E304,E305,E306,E502,FURB110,FURB113,FURB156,FURB171 \ + --ignore=RUF027 \ + "$@" diff --git a/scripts/randdisable b/scripts/randdisable index a59c6c812563..f91a2ac3b28c 100755 --- a/scripts/randdisable +++ b/scripts/randdisable @@ -28,7 +28,7 @@ use List::Util qw/shuffle/; my @disable; sub getoptions { - my @all = `./configure --help`; + my @all = qx(./configure --help); for my $o (@all) { chomp $o; if($o =~ /(--disable-[^ ]*)/) { diff --git a/scripts/release-notes.pl b/scripts/release-notes.pl index a4b4e2550fa0..706f34435919 100755 --- a/scripts/release-notes.pl +++ b/scripts/release-notes.pl @@ -32,8 +32,8 @@ # $ ./scripts/release-notes.pl # # 2. Edit RELEASE-NOTES and remove all entries that do not belong. Unused -# references below will be cleaned up in the next step. Make sure to move -# "changes" up to the changes section. All entries will by default be listed +# references below are cleaned up in the next step. Make sure to move +# "changes" up to the changes section. All entries are by default listed # under bug-fixes as this script cannot know where to put them. # # 3. Run the cleanup script and let it sort the entries and remove unused @@ -58,8 +58,8 @@ use warnings; my $cleanup = (@ARGV && $ARGV[0] eq "cleanup"); -my @gitlog=`git log @^{/RELEASE-NOTES:.synced}..` if(!$cleanup); -my @releasenotes=`cat RELEASE-NOTES`; +my @gitlog = qx(git log @^{/RELEASE-NOTES:.synced}..) if(!$cleanup); +my @releasenotes = qx(cat RELEASE-NOTES); my @o; # the entire new RELEASE-NOTES my @refused; # [num] = [2 bits of use info] @@ -68,7 +68,7 @@ for my $l (@releasenotes) { if($l =~ /^ o .*\[(\d+)\]/) { # referenced, set bit 0 - $refused[$1]=1; + $refused[$1] = 1; my $m = $l; chomp $m; $m =~ s/^ o //; @@ -107,7 +107,7 @@ sub getref { # 'https://elsewhere.example.com/discussion' sub extract { - my ($ref)=@_; + my ($ref) = @_; if($ref =~ /^(\#|)(\d+)/) { # return the plain number return $2; @@ -175,7 +175,7 @@ sub extract { # call at the end of a parsed commit sub onecommit { - my ($short)=@_; + my ($short) = @_; my $ref = ''; if($dupe{$short}) { @@ -199,7 +199,7 @@ sub onecommit { if($ref) { my $r = getref(); $refs[$r] = $ref; - $moreinfo{$short}=$r; + $moreinfo{$short} = $r; $refused[$r] |= 1; } } @@ -220,7 +220,7 @@ sub onecommit { push @o, sprintf " o %s%s\n", $f, $moreinfo{$f}? sprintf(" [%d]", $moreinfo{$f}): ""; if($moreinfo{$f}) { - $refused[$moreinfo{$f}]=3; + $refused[$moreinfo{$f}] = 3; } } push @o, " --- new entries are listed above this ---"; @@ -261,7 +261,7 @@ sub onecommit { } } -open(O, ">RELEASE-NOTES"); +open(O, ">", 'RELEASE-NOTES'); for my $l (@o) { print O $l; } diff --git a/scripts/schemetable.c b/scripts/schemetable.c deleted file mode 100644 index afa54c39aff7..000000000000 --- a/scripts/schemetable.c +++ /dev/null @@ -1,181 +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 - -/* - * Use this tool to generate an updated table for the Curl_getn_scheme_handler - * function in url.c. - */ - -static const char *scheme[] = { - "dict", - "file", - "ftp", - "ftps", - "gopher", - "gophers", - "http", - "https", - "imap", - "imaps", - "ldap", - "ldaps", - "mqtt", - "mqtts", - "pop3", - "pop3s", - "rtsp", - "scp", - "sftp", - "smb", - "smbs", - "smtp", - "smtps", - "telnet", - "tftp", - "ws", - "wss", - NULL, -}; - -unsigned int calc(const char *s, int add, int shift) -{ - const char *so = s; - unsigned int c = add; - while(*s) { - c <<= shift; - c += *s; - s++; - } - return c; -} - -unsigned int num[100]; -unsigned int ix[100]; - -static void showtable(int try, int init, int shift) -{ - int nulls = 0; - int i; - for(i = 0; scheme[i]; ++i) - num[i] = calc(scheme[i], init, shift); - for(i = 0; scheme[i]; ++i) - ix[i] = num[i] % try; - printf("/*\n" - " unsigned int c = %d\n" - " while(l) {\n" - " c <<= %d;\n" - " c += Curl_raw_tolower(*s);\n" - " s++;\n" - " l--;\n" - " }\n" - "*/\n", - init, shift); - - printf(" static const struct Curl_scheme * const all_schemes[%d] = {", try); - - /* generate table */ - for(i = 0; i < try; i++) { - int match = 0; - int j; - for(j = 0; scheme[j]; j++) { - if(ix[j] == i) { - printf("\n &Curl_scheme_%s,", scheme[j]); - match = 1; - nulls = 0; - break; - } - } - if(!match) - printf(" NULL,"); - } - printf("\n };\n"); -} - -int main(void) -{ - int i; - int try; - int besttry = 9999; - int bestadd = 0; - int bestshift = 0; - int add; - int shift; - for(shift = 0; shift < 8; shift++) { - for(add = 0; add < 999; add++) { - for(i = 0; scheme[i]; ++i) { - unsigned int v = calc(scheme[i], add, shift); - int j; - int badcombo = 0; - for(j = 0; j < i; j++) { - - if(num[j] == v) { -#if 0 - printf("NOPE: %u is a dupe (%s and %s)\n", - v, scheme[i], scheme[j]); -#endif - badcombo = 1; - break; - } - } - if(badcombo) - break; - num[i] = v; - } -#if 0 - for(i = 0; scheme[i].n; ++i) { - printf("%u - %s\n", num[i], scheme[i].n); - } -#endif - /* try different remainders to find smallest possible table */ - for(try = 28; try < 199; try++) { - int good = 1; - for(i = 0; scheme[i]; ++i) { - ix[i] = num[i] % try; - } - /* check for dupes */ - for(i = 0; scheme[i] && good; ++i) { - int j; - for(j = 0; j < i; j++) { - if(ix[j] == ix[i]) { - good = 0; - break; - } - } - } - if(good) { - if(try < besttry) { - besttry = try; - bestadd = add; - bestshift = shift; - } - break; - } - } - } - } - - showtable(besttry, bestadd, bestshift); -} diff --git a/scripts/singleuse.pl b/scripts/singleuse.pl index 957334215078..6332a9bfec8b 100755 --- a/scripts/singleuse.pl +++ b/scripts/singleuse.pl @@ -167,7 +167,7 @@ sub doublecheck { my ($f, $used) = @_; - open(F, '-|', 'git', 'grep', '-Fwle', $f, '--', 'lib', @unittests, 'projects'); + open(F, '-|', 'git', 'grep', '-Fwl', '--end-of-options', $f, '--', 'lib', @unittests, 'projects'); my @also; while() { my $e = $_; @@ -182,7 +182,7 @@ sub doublecheck { return @also; } -open(N, '-|', 'nm', $file) || die; +open(N, '-|', 'nm', $file) or die; my %exist; my %uses; @@ -198,13 +198,13 @@ sub doublecheck { $file = $1; } if($l =~ /^([0-9a-f]+) T _?(.*)/) { - my ($name)=($2); + my ($name) = ($2); #print "Define $name in $file\n"; $file =~ s/^libcurl_la-//; $exist{$name} = $file; } elsif($l =~ /^ U _?(.*)/) { - my ($name)=($1); + my ($name) = ($1); #print "Uses $name in $file\n"; $uses{$name} .= "$file, "; } diff --git a/scripts/spacecheck.pl b/scripts/spacecheck.pl index 416408e56ebe..44274d0a21b4 100755 --- a/scripts/spacecheck.pl +++ b/scripts/spacecheck.pl @@ -54,7 +54,6 @@ '^renovate\.json$', '^docs/DISTROS\.md$', '^projects/Windows/tmpl/.+\.vcxproj$', - '^tests/certs/srp-verifier-', '^tests/data/test', ); @@ -85,10 +84,7 @@ sub fn_match { } sub eol_detect { - my ($content) = @_; - - my $cr = () = $content =~ /\r/g; - my $lf = () = $content =~ /\n/g; + my ($cr, $lf) = @_; if($cr > 0 && $lf == 0) { return 'cr'; @@ -108,6 +104,7 @@ sub eol_detect { my $max_repeat_space = 79; my $max_line_len = 192; +my $max_lines = 10000; my $max_path_len = 64; my $max_filename_len = 48; @@ -141,7 +138,10 @@ sub eol_detect { push @err, 'content: has tab'; } - my $eol = eol_detect($content); + my $cnt_cr = () = $content =~ /\r/g; + my $cnt_lf = () = $content =~ /\n/g; + + my $eol = eol_detect($cnt_cr, $cnt_lf); if($eol eq '') { push @err, 'content: has mixed EOL types'; @@ -157,6 +157,13 @@ sub eol_detect { push @err, 'content: must use LF EOL for this file type'; } + if($cnt_cr > $max_lines) { + push @err, sprintf('content: too many lines (%d > %d)', $cnt_cr, $max_lines); + } + elsif($cnt_lf > $max_lines) { + push @err, sprintf('content: too many lines (%d > %d)', $cnt_lf, $max_lines); + } + if($content =~ /[ \t]\n/) { my $line; for my $l (split(/\n/, $content)) { diff --git a/scripts/testnum b/scripts/testnum new file mode 100755 index 000000000000..3050381b114a --- /dev/null +++ b/scripts/testnum @@ -0,0 +1,68 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +use strict; +use warnings; + +my $num = 10; # provide (at least) this many proposals + +my @all; +my $expect = 0; + +open(F, ") { + if(/^TESTCASES/) { + $expect = 1; + } + elsif($expect) { + if(/^ test(\d+)/) { + my $f = $1; + if($f == $expect) { + $expect = $f + 1; + } + else { + if(($f - 1) != $expect) { + # a range + for my $t ($expect .. ($f - 1)) { + push @all, $t; + } + } + else { + # single value + push @all, $expect; + } + } + $expect = $f + 1; + } + } +} + +use List::Util qw(shuffle); + +for my $t (shuffle @all) { + print "test$t\n"; + if(!--$num) { + last; + } +} diff --git a/scripts/top-complexity b/scripts/top-complexity index f2d869f607a2..7ad84f29dd9b 100755 --- a/scripts/top-complexity +++ b/scripts/top-complexity @@ -30,15 +30,15 @@ use warnings; # Check for a command in the PATH of the test server. # sub checkcmd { - my ($cmd)=@_; + my ($cmd) = @_; my @paths; if($^O eq 'MSWin32' || $^O eq 'dos' || $^O eq 'os2') { # PATH separator is different - @paths=(split(';', $ENV{'PATH'})); + @paths = (split(';', $ENV{'PATH'})); } else { - @paths=(split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin", - "/sbin", "/usr/bin", "/usr/local/bin"); + @paths = (split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin", + "/sbin", "/usr/bin", "/usr/local/bin"); } for(@paths) { if(-x "$_/$cmd" && ! -d "$_/$cmd") { @@ -82,7 +82,7 @@ my %whitelist = ( # complexity above this level is treated as an error and contributes to the # script's exit code -my $cutoff = 60; +my $cutoff = 50; # show this many from the top my $top = $ARGV[0] ? $ARGV[0] : 25; @@ -96,16 +96,16 @@ my $alllines = 0; # 142 417 809 1677 1305 src/tool_getparam.c(1677): getparameter for my $l (@output) { chomp $l; - if($l =~/^(\d+)\t\d+\t\d+\t\d+\t(\d+)\t([^\(]+).*: ([^ ]*)/) { - my ($score, $len, $path, $func)=($1, $2, $3, $4); + if($l =~ /^(\d+)\t\d+\t\d+\t\d+\t(\d+)\t([^\(]+).*: ([^ ]*)/) { + my ($score, $len, $path, $func) = ($1, $2, $3, $4); my $allow = 0; if($whitelist{$func} && ($score <= $whitelist{$func})) { $allow = 1; } - $where{"$path:$func"}=$score; - $perm{"$path:$func"}=$allow; + $where{"$path:$func"} = $score; + $perm{"$path:$func"} = $allow; if(($score > $cutoff) && !$allow) { $error++; } diff --git a/scripts/top-length b/scripts/top-length new file mode 100755 index 000000000000..7bed39b8db24 --- /dev/null +++ b/scripts/top-length @@ -0,0 +1,133 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +use strict; +use warnings; + +####################################################################### +# Check for a command in the PATH of the test server. +# +sub checkcmd { + my ($cmd) = @_; + my @paths; + if($^O eq 'MSWin32' || $^O eq 'dos' || $^O eq 'os2') { + # PATH separator is different + @paths = (split(';', $ENV{'PATH'})); + } + else { + @paths = (split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin", + "/sbin", "/usr/bin", "/usr/local/bin"); + } + for(@paths) { + if(-x "$_/$cmd" && ! -d "$_/$cmd") { + # executable bit but not a directory! + return "$_/$cmd"; + } + } + return ""; +} + +my $pmccabe = checkcmd("pmccabe"); +if(!$pmccabe) { + print "Make sure 'pmccabe' exists in your PATH\n"; + exit 1; +} +if(! -r "lib/url.c" || ! -r "lib/urldata.h") { + print "Invoke this script in the curl source tree root\n"; + exit 1; +} + +my @files; +open(my $git, "-|", "git", "ls-files", "*.c") or die "git ls-files failed: $!"; +while(<$git>) { + chomp $_; + my $file = $_; + # we cannot filter these with git so do it here + if($file =~ /^(lib|src)/) { + push @files, $file; + } +} +close($git); + +open(my $pmc, "-|", $pmccabe, @files) or die "pmccabe failed: $!"; +my @output = <$pmc>; +close($pmc); + +# these functions can be this long, but not longer +my %whitelist = ( + ); + +# function length above this level is treated as an error and contributes to +# the script's exit code +my $cutoff = 500; + +# show this many from the top +my $top = $ARGV[0] ? $ARGV[0] : 25; + +my $error = 0; +my %where; +my %perm; +my $funcs = 0; +my $alllines = 0; +# each line starts with the complexity score +# 142 417 809 1677 1305 src/tool_getparam.c(1677): getparameter +for my $l (@output) { + chomp $l; + if($l =~ /^(\d+)\t\d+\t\d+\t\d+\t(\d+)\t([^\(]+).*: ([^ ]*)/) { + my ($score, $length, $path, $func) = ($1, $2, $3, $4); + + my $allow = 0; + if($whitelist{$func} && + ($length <= $whitelist{$func})) { + $allow = 1; + } + $where{"$path:$func"} = $length; + $perm{"$path:$func"} = $allow; + if(($length > $cutoff) && !$allow) { + $error++; + } + + $alllines += $length; + $funcs++; + } +} + +my $showncutoff; +for my $e (sort {$where{$b} <=> $where{$a}} keys %where) { + if(!$showncutoff && + ($where{$e} <= $cutoff)) { + print "\n---- threshold: $cutoff ----\n\n"; + $showncutoff = 1; + } + printf "%-5d %s%s\n", $where{$e}, $e, + $perm{$e} ? " [ALLOWED]" : ""; + if(!--$top) { + last; + } +} + +printf "\nAverage function length: %.2f lines\n", $alllines/$funcs; + +exit $error; diff --git a/scripts/verify-release b/scripts/verify-release index b24d9b370d5a..f0339d306ff6 100755 --- a/scripts/verify-release +++ b/scripts/verify-release @@ -27,8 +27,10 @@ # This script remakes a provided curl release and verifies that the newly # built version is identical to the original file. # -# It is designed to be invoked in a clean directory with the path to the -# release tarball as an argument. +# Invoke in a clean directory with the release tarball file (stored in the +# same directory) as an argument for basic verification. +# +# For full verification: run the script in an up-to-date curl git repository. # set -eu @@ -36,40 +38,84 @@ set -eu tarball="${1:-}" if [ -z "$tarball" ]; then - echo "Provide a curl release tarball name as argument" - exit + echo "Provide a curl release tarball name as argument" + exit 1 fi i="$(find . -maxdepth 1 -type d -name 'curl-*' | wc -l)" if test "$i" -gt 1; then - echo "multiple curl-* entries found, disambiguate please" - exit + echo "multiple curl-* entries found, disambiguate please" + exit 1 +fi + +# check if this is in a git clone directory + +if git log -1 include/curl/curl.h 2>/dev/null >/dev/null; then + echo "*** Detected a git checkout, do full verification" + withgit=1 +else + echo "*** Lacking a full git checkout, do the lesser verification" + withgit=0 fi mkdir -p _tarballs rm -rf _tarballs/* # checksum the original tarball to compare with later -sha256sum "$tarball" >_tarballs/checksum +sha256sum -- "$tarball" >_tarballs/checksum + +# extract version number from filename +tarver=$(echo "$tarball" | sed 's/curl-\([0-9.]*\)\..*/\1/') + +# extract the version from the official header file +curlver=$(tar xOf "$tarball" "curl-$tarver/include/curl/curlver.h" | grep '#define LIBCURL_VERSION ' | sed 's/[^0-9.]//g') + +if test "$tarver" != "$curlver"; then + echo "Tarball file version ($tarver) mismatches contents of tarball ($curlver)" + exit 1 +fi + +timestamp=$(tar xOf "$tarball" "curl-$tarver/docs/RELEASE-TOOLS.md" | grep 'SOURCE_DATE_EPOCH=' | sed 's/[^0-9.]//g') + +if test "$withgit" = 0; then + # without git + + # extract the release contents + tar xf "$tarball" -# extract the release contents -tar xf "$tarball" + # move away the original tarball + mv -- "$tarball" "_tarballs/orig-$tarball" -curlver=$(grep '#define LIBCURL_VERSION ' curl-*/include/curl/curlver.h | sed 's/[^0-9.]//g') + pwd=$(pwd) + cd "curl-$curlver" + ./configure --without-ssl --without-libpsl + ./scripts/dmaketgz "$curlver" "$timestamp" -echo "version $curlver" + for f in "curl-$curlver.tar.gz" "curl-$curlver.tar.bz2" "curl-$curlver.tar.xz" "curl-$curlver.zip"; do + mv "$f" ../_tarballs/ + done + cd "$pwd" +else + tag=$(tar xOf "$tarball" "curl-$tarver/docs/RELEASE-TOOLS.md" | grep 'tag/commit: curl-' | head -n 1 | sed 's/.*\(curl-[0-9_]*\).*/\1/') + echo "*** Use git tag $tag" -timestamp=$(grep -Eo 'SOURCE_DATE_EPOCH=[0-9]*' curl-"$curlver"/docs/RELEASE-TOOLS.md | cut -d= -f2) + # move away the original tarball + mv -- "$tarball" "_tarballs/orig-$tarball" -pwd=$(pwd) -cd "curl-$curlver" -./configure --without-ssl --without-libpsl -./scripts/dmaketgz "$curlver" "$timestamp" + prevtag=$(git symbolic-ref -q --short HEAD || git rev-parse HEAD) + git checkout -f "$tag" -mv curl-"$curlver"* ../_tarballs/ -cd "$pwd" -cd "_tarballs" + ./scripts/dmaketgz "$curlver" "$timestamp" + + # switch back to where it was + git checkout -f "$prevtag" + + for f in "curl-$curlver.tar.gz" "curl-$curlver.tar.bz2" "curl-$curlver.tar.xz" "curl-$curlver.zip"; do + mv "$f" _tarballs/ + done +fi +cd _tarballs # compare the new tarball against the original sha256sum -c checksum diff --git a/scripts/wcurl b/scripts/wcurl index c39806cf946d..5fb68275efb1 100755 --- a/scripts/wcurl +++ b/scripts/wcurl @@ -26,10 +26,10 @@ # # SPDX-License-Identifier: curl -# Stop on errors and on usage of unset variables. -set -eu +# Stop on errors, usage of unset variables and disable glob expansion. +set -euf -VERSION="2026.01.05" +VERSION="2026.08.30" PROGRAM_NAME="$(basename "$0")" readonly PROGRAM_NAME @@ -67,7 +67,7 @@ Options: --no-decode-filename: Do not percent-decode the output filename, even if the percent-encoding in the URL was done by wcurl, e.g.: The URL contained whitespace. - --dry-run: Do not actually execute curl, just print what would be invoked. + --dry-run: Do not actually execute curl, print what would be invoked. -V, --version: Print version information. @@ -119,7 +119,8 @@ readonly PER_URL_PARAMETERS="\ # 2F = / # 5C = \ # 3A = : -readonly UNSAFE_PERCENT_ENCODE="%2F %5C %3A" +# 7F = DEL +readonly UNSAFE_PERCENT_ENCODE="%2F %5C %3A %7F" # Whether to invoke curl or not. DRY_RUN="false" @@ -131,6 +132,18 @@ sanitize() error "You must provide at least one URL to download." fi + # Split the extra curl options while IFS still has its default value, then + # join them back with spaces, so that restricting IFS below does not change + # the way the user's options are split. + # shellcheck disable=SC2086 + set -- ${CURL_OPTIONS} + + # Split the list of URLs on spaces only. Spaces are percent-encoded before a + # URL is added to the list, so every URL is a single field, and a tab or a + # newline inside one URL cannot turn it into more than one curl transfer. + IFS=" " + CURL_OPTIONS="$*" + readonly CURL_OPTIONS URLS DRY_RUN HAS_USER_SET_OUTPUT } @@ -190,14 +203,14 @@ percent_decode() get_url_filename() { # Remove protocol and query string if present. - hostname_and_path="$(printf %s "${1}" | sed -e 's,^[^/]*//,,' -e 's,?.*$,,')" + hostname_and_path="$(printf %s "${1}" | sed -e 's,^[^/]*//,,' -e 's,[?#].*$,,')" # If what remains contains a slash, there is a path; return it percent-decoded. case "${hostname_and_path}" in # sed to remove everything preceding the last '/', e.g.: "example/something" becomes "something" - # sed to also replace ':' with the percent_encoded %3A - */*) percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,:,%3A,g')" ;; + # sed to also replace '\' with the percent_encoded %5C and ':' with %3A + */*) percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,\\,%5C,g' -e 's,:,%3A,g')" ;; esac - # No slash means there was just a hostname and no path; return empty string. + # No slash means there was only a hostname and no path; return empty string. } # Execute curl with the list of URLs provided by the user. @@ -240,6 +253,8 @@ exec_curl() # If there are less than two URLs, do not set the parallel flag. if [ "$#" -lt 2 ]; then CURL_PARALLEL="" + elif [ "${HAS_USER_SET_OUTPUT}" = "true" ] && [ -z "${CURL_NO_CLOBBER}" ]; then + error "Using '--output' with multiple URLs requires curl >= 7.83.0 ('--no-clobber')." fi # Start assembling the command. @@ -260,7 +275,7 @@ exec_curl() [ -z "${OUTPUT_PATH}" ] && OUTPUT_PATH=index.html fi # shellcheck disable=SC2086 - set -- "$@" ${NEXT_PARAMETER} ${PER_URL_PARAMETERS} ${CURL_NO_CLOBBER} --output "${OUTPUT_PATH}" ${CURL_OPTIONS} "${url}" + set -- "$@" ${NEXT_PARAMETER} ${PER_URL_PARAMETERS} ${CURL_NO_CLOBBER} --output "${OUTPUT_PATH}" ${CURL_OPTIONS} --url "${url}" NEXT_PARAMETER="--next" done @@ -283,6 +298,9 @@ while [ -n "${1-}" ]; do ;; --curl-options) + if [ -z "${2-}" ]; then + error "Option '${1}' requires an argument." + fi shift CURL_OPTIONS="${CURL_OPTIONS} ${1}" ;; @@ -298,6 +316,9 @@ while [ -n "${1-}" ]; do ;; -o | -O | --output) + if [ -z "${2-}" ]; then + error "Option '${1}' requires an argument." + fi shift HAS_USER_SET_OUTPUT="true" OUTPUT_PATH="${1}" diff --git a/src/Makefile.inc b/src/Makefile.inc index fe2bae5c32e3..84c0a3a02de7 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -24,7 +24,7 @@ # Shared between CMakeLists.txt and Makefile.am # Using the backslash as line continuation character might be problematic with -# some make flavours. If we ever want to change this in a portable manner then +# some make flavors. If we ever want to change this in a portable manner then # we should consider this idea : # CSRC1 = file1.c file2.c file3.c # CSRC2 = file4.c file5.c file6.c diff --git a/src/config2setopts.c b/src/config2setopts.c index 9138b3b14742..b6c242dad6cb 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -38,6 +38,7 @@ #include "tool_cb_see.h" #include "tool_cb_dbg.h" #include "tool_helpers.h" +#include "tool_paramhlp.h" #include "tool_version.h" #ifdef HAVE_NETINET_IN_H @@ -46,6 +47,14 @@ #define BUFFER_SIZE 102400L +/* return TRUE if the error code is "lethal" */ +static bool setopt_bad(CURLcode result) +{ + return result && + (result != CURLE_NOT_BUILT_IN) && + (result != CURLE_UNKNOWN_OPTION); +} + #ifdef IP_TOS static int get_address_family(curl_socket_t sockfd) { @@ -147,29 +156,40 @@ static CURLcode url_proto_and_rewrite(char **url, curl_url_set(uh, CURLUPART_URL, *url, CURLU_GUESS_SCHEME | CURLU_NON_SUPPORT_SCHEME); if(!uc) { - uc = curl_url_get(uh, CURLUPART_SCHEME, &schemep, CURLU_DEFAULT_SCHEME); - if(!uc) { -#ifdef CURL_DISABLE_IPFS - (void)config; -#else - if(curl_strequal(schemep, proto_ipfs) || - curl_strequal(schemep, proto_ipns)) { - result = ipfs_url_rewrite(uh, schemep, url, config); - /* short-circuit proto_token, we know it is ipfs or ipns */ - if(curl_strequal(schemep, proto_ipfs)) - proto = proto_ipfs; - else if(curl_strequal(schemep, proto_ipns)) - proto = proto_ipns; - if(result) - config->synthetic_error = TRUE; + if(config->proto_default) { + /* when a default proto is requested, do not guess */ + uc = curl_url_get(uh, CURLUPART_SCHEME, &schemep, + CURLU_NO_GUESS_SCHEME); + if(uc == CURLUE_NO_SCHEME) { + /* use the default */ + proto = proto_token(config->proto_default); + if(proto) + uc = CURLUE_OK; } - else -#endif /* !CURL_DISABLE_IPFS */ - proto = proto_token(schemep); - curl_free(schemep); } - else if(uc == CURLUE_OUT_OF_MEMORY) + else { + uc = curl_url_get(uh, CURLUPART_SCHEME, &schemep, + CURLU_DEFAULT_SCHEME); + } + if(schemep) + proto = proto_token(schemep); +#ifndef CURL_DISABLE_IPFS + if(!uc && + (curl_strequal(schemep, proto_ipfs) || + curl_strequal(schemep, proto_ipns))) { + result = ipfs_url_rewrite(uh, schemep, url, config); + /* short-circuit proto_token, we know it is ipfs or ipns */ + if(curl_strequal(schemep, proto_ipfs)) + proto = proto_ipfs; + else if(curl_strequal(schemep, proto_ipns)) + proto = proto_ipns; + if(result) + config->synthetic_error = TRUE; + } +#endif /* !CURL_DISABLE_IPFS */ + if(uc == CURLUE_OUT_OF_MEMORY) result = CURLE_OUT_OF_MEMORY; + curl_free(schemep); } else if(uc == CURLUE_OUT_OF_MEMORY) result = CURLE_OUT_OF_MEMORY; @@ -194,11 +214,11 @@ static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, MY_SETOPT_STR(curl, CURLOPT_SSH_PRIVATE_KEYFILE, config->key); MY_SETOPT_STR(curl, CURLOPT_SSH_PUBLIC_KEYFILE, config->pubkey); - /* SSH host key md5 checking allows us to fail if we are not talking to who + /* SSH host key MD5 checking allows us to fail if we are not talking to who we think we should */ MY_SETOPT_STR(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, config->hostpubmd5); - /* SSH host key sha256 checking allows us to fail if we are not talking to + /* SSH host key SHA256 checking allows us to fail if we are not talking to who we think we should */ MY_SETOPT_STR(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, config->hostpubsha256); @@ -545,6 +565,89 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl) return result; } +/* only --httpsig-* options */ +#ifndef CURL_DISABLE_HTTPSIG +static CURLcode httpsig_setopts(struct OperationConfig *config, CURL *curl) +{ + CURLcode result = CURLE_OK; + /* HTTP Message Signatures are enabled when any of the --httpsig-* options + is given. --httpsig-key and --httpsig-keyid are then required, while + --httpsig-algo is optional and defaults to ed25519. */ + if(config->httpsig_algorithm || config->httpsig_key || + config->httpsig_keyid || config->httpsig_headers) { + long httpsig_alg = CURLHTTPSIG_NONE; + + if(!config->httpsig_key) { + errorf("--httpsig-key is required"); + return CURLE_FAILED_INIT; + } + if(!config->httpsig_keyid) { + errorf("--httpsig-keyid is required"); + return CURLE_FAILED_INIT; + } + + if(!config->httpsig_algorithm || + curl_strequal(config->httpsig_algorithm, "ed25519")) + httpsig_alg = CURLHTTPSIG_ED25519; + else if(curl_strequal(config->httpsig_algorithm, "hmac-sha256")) + httpsig_alg = CURLHTTPSIG_HMAC_SHA256; + else { + errorf("--httpsig-algo: unsupported algorithm '%s'", + config->httpsig_algorithm); + return CURLE_FAILED_INIT; + } + my_setopt_long(curl, CURLOPT_HTTPSIG_ALGORITHM, httpsig_alg); + MY_SETOPT_STR(curl, CURLOPT_HTTPSIG_HEADERS, config->httpsig_headers); + MY_SETOPT_STR(curl, CURLOPT_HTTPSIG_KEYID, config->httpsig_keyid); + + if(config->httpsig_key[0] == '@') { + FILE *keyf = curlx_fopen(&config->httpsig_key[1], FOPEN_READTEXT); + if(keyf) { + char *hexdata = NULL; + ParameterError pe = file2string(&hexdata, keyf); + + curlx_fclose(keyf); + if(pe == PARAM_NO_MEM) { + curlx_safefree(hexdata); + return CURLE_OUT_OF_MEMORY; + } + if(pe == PARAM_READ_ERROR) { + curlx_safefree(hexdata); + errorf("httpsig: cannot read key file '%s'", + &config->httpsig_key[1]); + return CURLE_READ_ERROR; + } + if(!hexdata || !*hexdata) { + curlx_safefree(hexdata); + errorf("httpsig: key file '%s' is empty", &config->httpsig_key[1]); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + /* can't use the MY_SETOPT_STR() macro here since it returns on error + and we must free the hexdata */ + result = my_setopt_str(curl, CURLOPT_HTTPSIG_KEY, hexdata); + curlx_safefree(hexdata); + if(setopt_bad(result)) + return result; + } + else { + errorf("httpsig: cannot open key file '%s'", &config->httpsig_key[1]); + return CURLE_READ_ERROR; + } + } + else { + if(!config->httpsig_key[0]) { + errorf("httpsig: key is empty"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + MY_SETOPT_STR(curl, CURLOPT_HTTPSIG_KEY, config->httpsig_key); + } + } + return CURLE_OK; +} +#else +#define httpsig_setopts(x,y) CURLE_OK +#endif + /* only for HTTP transfers */ static CURLcode http_setopts(struct OperationConfig *config, CURL *curl, const char *use_proto) @@ -560,6 +663,9 @@ static CURLcode http_setopts(struct OperationConfig *config, CURL *curl, #ifndef CURL_DISABLE_AWS MY_SETOPT_STR(curl, CURLOPT_AWS_SIGV4, config->aws_sigv4); #endif + result = httpsig_setopts(config, curl); + if(result) + return result; my_setopt_long(curl, CURLOPT_AUTOREFERER, config->autoreferer); if(config->proxyheaders) { @@ -762,27 +868,8 @@ static CURLcode proxy_setopts(struct OperationConfig *config, CURL *curl) if(config->haproxy_clientip) MY_SETOPT_STR(curl, CURLOPT_HAPROXY_CLIENT_IP, config->haproxy_clientip); - return result; -} + MY_SETOPT_STR(curl, CURLOPT_PROXY_KEYPASSWD, config->proxy_key_passwd); -static CURLcode tls_srp_setopts(struct OperationConfig *config, CURL *curl) -{ - CURLcode result = CURLE_OK; - if(config->tls_username) - MY_SETOPT_STR(curl, CURLOPT_TLSAUTH_USERNAME, config->tls_username); - if(config->tls_password) - MY_SETOPT_STR(curl, CURLOPT_TLSAUTH_PASSWORD, config->tls_password); - if(config->tls_authtype) - MY_SETOPT_STR(curl, CURLOPT_TLSAUTH_TYPE, config->tls_authtype); - if(config->proxy_tls_username) - MY_SETOPT_STR(curl, CURLOPT_PROXY_TLSAUTH_USERNAME, - config->proxy_tls_username); - if(config->proxy_tls_password) - MY_SETOPT_STR(curl, CURLOPT_PROXY_TLSAUTH_PASSWORD, - config->proxy_tls_password); - if(config->proxy_tls_authtype) - MY_SETOPT_STR(curl, CURLOPT_PROXY_TLSAUTH_TYPE, - config->proxy_tls_authtype); return result; } @@ -841,54 +928,12 @@ static void buffersize(struct OperationConfig *config, CURL *curl) my_setopt_long(curl, CURLOPT_BUFFERSIZE, BUFFER_SIZE); } -CURLcode config2setopts(struct OperationConfig *config, - struct per_transfer *per, - CURL *curl, - CURLSH *share) +static CURLcode credentials_and_headers_setopts(struct OperationConfig *config, + CURL *curl) { - const char *use_proto; - CURLcode result = url_proto_and_rewrite(&per->url, config, &use_proto); - - /* Avoid having this setopt added to the --libcurl source output. */ - if(!result) - result = curl_easy_setopt(curl, CURLOPT_SHARE, share); - if(result) - return result; - - if(TRUE -#ifdef DEBUGBUILD - && getenv("CURL_QUICK_EXIT") -#endif - ) { - /* QUICK_EXIT allows for running threads to be detached and not - * joined. Preferably in non-debug runs. */ - result = curl_easy_setopt(curl, CURLOPT_QUICK_EXIT, 1L); - if(result) - return result; - } - - gen_trace_setopts(config, curl); - - buffersize(config, curl); - - MY_SETOPT_STR(curl, CURLOPT_URL, per->url); - my_setopt_long(curl, CURLOPT_NOPROGRESS, - global->noprogress || global->silent); - /* call after the line above. It may override CURLOPT_NOPROGRESS */ - gen_cb_setopts(config, per, curl); + CURLcode result = CURLE_OK; - my_setopt_long(curl, CURLOPT_NOBODY, config->no_body); MY_SETOPT_STR(curl, CURLOPT_XOAUTH2_BEARER, config->oauth_bearer); - result = proxy_setopts(config, curl); - if(setopt_bad(result) || config->synthetic_error) - return result; - - my_setopt_long(curl, CURLOPT_FAILONERROR, config->fail == FAIL_WO_BODY); - MY_SETOPT_STR(curl, CURLOPT_REQUEST_TARGET, config->request_target); - my_setopt_long(curl, CURLOPT_UPLOAD, !!per->uploadfile); - my_setopt_long(curl, CURLOPT_DIRLISTONLY, config->dirlistonly); - my_setopt_long(curl, CURLOPT_APPEND, config->ftp_append); - if(config->netrc_opt) my_setopt_enum(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); else if(config->netrc || config->netrc_file) @@ -897,19 +942,8 @@ CURLcode config2setopts(struct OperationConfig *config, my_setopt_enum(curl, CURLOPT_NETRC, CURL_NETRC_IGNORED); MY_SETOPT_STR(curl, CURLOPT_NETRC_FILE, config->netrc_file); - my_setopt_long(curl, CURLOPT_TRANSFERTEXT, config->use_ascii); MY_SETOPT_STR(curl, CURLOPT_LOGIN_OPTIONS, config->login_options); MY_SETOPT_STR(curl, CURLOPT_USERPWD, config->userpwd); - MY_SETOPT_STR(curl, CURLOPT_RANGE, config->range); - my_setopt_ptr(curl, CURLOPT_ERRORBUFFER, per->errorbuffer); - my_setopt_long(curl, CURLOPT_TIMEOUT_MS, config->timeout_ms); - - result = setopt_post(config, curl); - if(result) - return result; - - if(config->mime_options) - my_setopt_long(curl, CURLOPT_MIME_OPTIONS, config->mime_options); if(config->authtype) my_setopt_bitmask(curl, CURLOPT_HTTPAUTH, config->authtype); @@ -922,12 +956,35 @@ CURLcode config2setopts(struct OperationConfig *config, config->useragent : CURL_NAME "/" CURL_VERSION); } - result = http_setopts(config, curl, use_proto); - if(!result) - result = ftp_setopts(config, curl, use_proto); + MY_SETOPT_STR(curl, CURLOPT_KEYPASSWD, config->key_passwd); + return result; +} + +static CURLcode transfer_setopts(struct OperationConfig *config, + struct per_transfer *per, + CURL *curl) +{ + CURLcode result = CURLE_OK; + + my_setopt_long(curl, CURLOPT_NOBODY, config->no_body); + my_setopt_long(curl, CURLOPT_FAILONERROR, config->fail == FAIL_WO_BODY); + MY_SETOPT_STR(curl, CURLOPT_REQUEST_TARGET, config->request_target); + my_setopt_long(curl, CURLOPT_UPLOAD, !!per->uploadfile); + my_setopt_long(curl, CURLOPT_DIRLISTONLY, config->dirlistonly); + my_setopt_long(curl, CURLOPT_APPEND, config->ftp_append); + + my_setopt_long(curl, CURLOPT_TRANSFERTEXT, config->use_ascii); + MY_SETOPT_STR(curl, CURLOPT_RANGE, config->range); + my_setopt_ptr(curl, CURLOPT_ERRORBUFFER, per->errorbuffer); + my_setopt_long(curl, CURLOPT_TIMEOUT_MS, config->timeout_ms); + + result = setopt_post(config, curl); if(result) return result; + if(config->mime_options) + my_setopt_long(curl, CURLOPT_MIME_OPTIONS, config->mime_options); + my_setopt_long(curl, CURLOPT_LOW_SPEED_LIMIT, config->low_speed_limit); my_setopt_long(curl, CURLOPT_LOW_SPEED_TIME, config->low_speed_time); my_setopt_offt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, config->sendpersecond); @@ -938,26 +995,10 @@ CURLcode config2setopts(struct OperationConfig *config, else my_setopt_offt(curl, CURLOPT_RESUME_FROM_LARGE, 0); - MY_SETOPT_STR(curl, CURLOPT_KEYPASSWD, config->key_passwd); - MY_SETOPT_STR(curl, CURLOPT_PROXY_KEYPASSWD, config->proxy_key_passwd); - - result = ssh_setopts(config, curl, use_proto); - if(setopt_bad(result)) - return result; - - if(feature_ssl) { - result = ssl_ca_setopts(config, curl); - if(!result) - result = ssl_setopts(config, curl); - if(setopt_bad(result)) - return result; - } - if(config->path_as_is) my_setopt_long(curl, CURLOPT_PATH_AS_IS, 1); if(config->no_body || config->remote_time) - /* no body or use remote time */ my_setopt_long(curl, CURLOPT_FILETIME, 1); my_setopt_long(curl, CURLOPT_CRLF, config->crlf); @@ -969,7 +1010,53 @@ CURLcode config2setopts(struct OperationConfig *config, my_setopt_offt(curl, CURLOPT_TIMEVALUE_LARGE, config->condtime); MY_SETOPT_STR(curl, CURLOPT_CUSTOMREQUEST, config->customrequest); customrequest_helper(config->httpreq, config->customrequest); - my_setopt_ptr(curl, CURLOPT_STDERR, tool_stderr); + + return result; +} + +static CURLcode protocol_setopts(struct OperationConfig *config, + struct per_transfer *per, + CURL *curl, + const char *use_proto) +{ + CURLcode result = CURLE_OK; +#ifndef DEBUGBUILD + (void)per; +#endif + result = http_setopts(config, curl, use_proto); + if(!result) + result = ftp_setopts(config, curl, use_proto); + if(result) + return result; + + result = ssh_setopts(config, curl, use_proto); + if(setopt_bad(result)) + return result; + + if(feature_ssl) { + result = ssl_ca_setopts(config, curl); + if(!result) + result = ssl_setopts(config, curl); + if(setopt_bad(result)) + return result; +#ifdef DEBUGBUILD + if(!per->urlnum) { + char *env = getenv("CURL_DBG_NO_USE_SSL_ON_FIRST"); + if(env) + my_setopt_enum(curl, CURLOPT_USE_SSL, CURLUSESSL_NONE); + } +#endif + } + + return result; +} + +static CURLcode dns_and_network_setopts(struct OperationConfig *config, + struct per_transfer *per, + CURL *curl) +{ + CURLcode result = CURLE_OK; + MY_SETOPT_STR(curl, CURLOPT_INTERFACE, config->iface); progressbarinit(&per->progressbar, config); MY_SETOPT_STR(curl, CURLOPT_DNS_SERVERS, config->dns_servers); @@ -1006,27 +1093,46 @@ CURLcode config2setopts(struct OperationConfig *config, if(config->tftp_blksize && proto_tftp) my_setopt_long(curl, CURLOPT_TFTP_BLKSIZE, config->tftp_blksize); + return result; +} + +static CURLcode mail_and_sasl_setopts(struct OperationConfig *config, + CURL *curl) +{ + CURLcode result = CURLE_OK; + MY_SETOPT_STR(curl, CURLOPT_MAIL_FROM, config->mail_from); my_setopt_slist(curl, CURLOPT_MAIL_RCPT, config->mail_rcpt); my_setopt_long(curl, CURLOPT_MAIL_RCPT_ALLOWFAILS, config->mail_rcpt_allowfails); + MY_SETOPT_STR(curl, CURLOPT_MAIL_AUTH, config->mail_auth); + MY_SETOPT_STR(curl, CURLOPT_SASL_AUTHZID, config->sasl_authzid); + my_setopt_long(curl, CURLOPT_SASL_IR, config->sasl_ir); + if(config->create_file_mode) my_setopt_long(curl, CURLOPT_NEW_FILE_PERMS, config->create_file_mode); + return result; +} + +static CURLcode misc_setopts(struct OperationConfig *config, CURL *curl) +{ + CURLcode result = CURLE_OK; + + my_setopt_ptr(curl, CURLOPT_STDERR, tool_stderr); + if(config->resolve) + my_setopt_slist(curl, CURLOPT_RESOLVE, config->resolve); + if(config->connect_to) + my_setopt_slist(curl, CURLOPT_CONNECT_TO, config->connect_to); + + if(config->gssapi_delegation) + my_setopt_long(curl, CURLOPT_GSSAPI_DELEGATION, config->gssapi_delegation); + if(config->proto_present) MY_SETOPT_STR(curl, CURLOPT_PROTOCOLS_STR, config->proto_str); if(config->proto_redir_present) MY_SETOPT_STR(curl, CURLOPT_REDIR_PROTOCOLS_STR, config->proto_redir_str); - my_setopt_slist(curl, CURLOPT_RESOLVE, config->resolve); - my_setopt_slist(curl, CURLOPT_CONNECT_TO, config->connect_to); - - if(feature_tls_srp) { - result = tls_srp_setopts(config, curl); - if(setopt_bad(result)) - return result; - } - if(config->gssapi_delegation) my_setopt_long(curl, CURLOPT_GSSAPI_DELEGATION, config->gssapi_delegation); @@ -1069,6 +1175,61 @@ CURLcode config2setopts(struct OperationConfig *config, } #endif } - my_setopt_long(curl, CURLOPT_UPLOAD_FLAGS, config->upload_flags); + if(!result) + my_setopt_long(curl, CURLOPT_UPLOAD_FLAGS, config->upload_flags); + + return result; +} + +CURLcode config2setopts(struct OperationConfig *config, + struct per_transfer *per, + CURL *curl, + CURLSH *share) +{ + const char *use_proto; + CURLcode result = url_proto_and_rewrite(&per->url, config, &use_proto); + + /* Avoid having this setopt added to the --libcurl source output. */ + if(!result) + result = curl_easy_setopt(curl, CURLOPT_SHARE, share); + if(result) + return result; + +#ifdef DEBUGBUILD + if(getenv("CURL_QUICK_EXIT")) +#endif + { + /* QUICK_EXIT allows for running threads to be detached and not + * joined. Preferably in non-debug runs. */ + result = curl_easy_setopt(curl, CURLOPT_QUICK_EXIT, 1L); + if(result) + return result; + } + + gen_trace_setopts(config, curl); + + buffersize(config, curl); + + MY_SETOPT_STR(curl, CURLOPT_URL, per->url); + my_setopt_long(curl, CURLOPT_NOPROGRESS, + global->noprogress || global->silent); + /* call after the line above. It may override CURLOPT_NOPROGRESS */ + gen_cb_setopts(config, per, curl); + + result = proxy_setopts(config, curl); + if(setopt_bad(result) || config->synthetic_error) + return result; + + result = credentials_and_headers_setopts(config, curl); + if(!setopt_bad(result)) + result = transfer_setopts(config, per, curl); + if(!setopt_bad(result)) + result = protocol_setopts(config, per, curl, use_proto); + if(!setopt_bad(result)) + result = dns_and_network_setopts(config, per, curl); + if(!setopt_bad(result)) + result = mail_and_sasl_setopts(config, curl); + if(!setopt_bad(result)) + result = misc_setopts(config, curl); return result; } diff --git a/src/curlinfo.c b/src/curlinfo.c index 13b9d62ce068..87f00032b139 100644 --- a/src/curlinfo.c +++ b/src/curlinfo.c @@ -34,7 +34,7 @@ #include "multihandle.h" /* for ENABLE_WAKEUP */ #include "tool_xattr.h" /* for USE_XATTR */ #include "curl_sha512_256.h" /* for CURL_HAVE_SHA512_256 */ -#include "asyn.h" /* for USE_RESOLV_ARES, USE_RESOLV_THREADED */ +#include "vdns/asyn.h" /* for USE_RESOLV_ARES, USE_RESOLV_THREADED */ #include "fake_addrinfo.h" /* for USE_FAKE_GETADDRINFO */ #include @@ -43,7 +43,7 @@ #include /* for OPENSSL_NO_OCSP */ #endif -static const char *disabled[] = { +static const char * const disabled[] = { "bindlocal: " #ifdef CURL_DISABLE_BINDLOCAL "OFF" @@ -93,6 +93,13 @@ static const char *disabled[] = { "OFF" #else "ON" +#endif + , + "httpsig: " +#ifdef CURL_DISABLE_HTTPSIG + "OFF" +#else + "ON" #endif , "DoH: " @@ -195,14 +202,14 @@ static const char *disabled[] = { #endif , "large-time: " -#if (SIZEOF_TIME_T < 5) +#if SIZEOF_TIME_T < 5 "OFF" #else "ON" #endif , "large-size: " -#if (SIZEOF_SIZE_T < 5) +#if SIZEOF_SIZE_T < 5 "OFF" #else "ON" @@ -263,13 +270,10 @@ static const char *disabled[] = { #endif }; -int main(int argc, const char **argv) +int main(void) { size_t i; - (void)argc; - (void)argv; - for(i = 0; i < CURL_ARRAYSIZE(disabled); i++) puts(disabled[i]); diff --git a/src/mkhelp.pl b/src/mkhelp.pl index 053f74177619..013ddc51c80b 100755 --- a/src/mkhelp.pl +++ b/src/mkhelp.pl @@ -86,13 +86,13 @@ HEAD ; - my $c=0; + my $c = 0; for(split(//, $gzippedContent)) { - my $num=ord($_); + my $num = ord($_); if(!($c % 12)) { print " "; } - printf(" 0x%02x,", 0+$num); + printf(" 0x%02x,", 0 + $num); if(!(++$c % 12)) { print "\n"; } @@ -105,7 +105,7 @@ { (void)opaque; /* not a typo, keep it curlx_calloc() */ - return (voidpf)curlx_calloc(items, size); + return curlx_calloc(items, size); } static void zfree_func(voidpf opaque, voidpf ptr) { diff --git a/src/terminal.c b/src/terminal.c index b25c0e6e8006..2e28d9232309 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -62,6 +62,10 @@ unsigned int get_terminal_columns(void) struct winsize ts; if(!ioctl(STDIN_FILENO, TIOCGWINSZ, &ts)) cols = (int)ts.ws_col; + else if(!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ts)) + cols = (int)ts.ws_col; + else if(!ioctl(STDERR_FILENO, TIOCGWINSZ, &ts)) + cols = (int)ts.ws_col; #elif defined(_WIN32) && !defined(CURL_WINDOWS_UWP) { HANDLE stderr_hnd = GetStdHandle(STD_ERROR_HANDLE); diff --git a/src/tool_cb_dbg.c b/src/tool_cb_dbg.c index c9c14e6d1311..2a69ce0f1179 100644 --- a/src/tool_cb_dbg.c +++ b/src/tool_cb_dbg.c @@ -123,8 +123,8 @@ static void dump(const char *timebuf, const char *idsbuf, const char *text, #define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \ CURL_FORMAT_CURL_OFF_T "] " /* -** callback for CURLOPT_DEBUGFUNCTION -*/ + * callback for CURLOPT_DEBUGFUNCTION + */ int tool_debug_cb(CURL *handle, curl_infotype type, char *data, size_t size, void *userdata) @@ -134,7 +134,7 @@ int tool_debug_cb(CURL *handle, curl_infotype type, struct timeval tv; char timebuf[20]; /* largest signed 64-bit is: 9,223,372,036,854,775,807 - * max length in decimal: 1 + (6*3) = 19 + * max length in decimal: 1 + (6 * 3) = 19 * formatted via TRC_IDS_FORMAT_IDS_2 this becomes 2 + 19 + 1 + 19 + 2 = 43 * negative xfer-id are not printed, negative conn-ids use TRC_IDS_FORMAT_1 */ diff --git a/src/tool_cb_dbg.h b/src/tool_cb_dbg.h index d78afb36eaa1..8df54a240f54 100644 --- a/src/tool_cb_dbg.h +++ b/src/tool_cb_dbg.h @@ -26,9 +26,8 @@ #include "tool_setup.h" /* -** callback for CURLOPT_DEBUGFUNCTION -*/ - + * callback for CURLOPT_DEBUGFUNCTION + */ int tool_debug_cb(CURL *handle, curl_infotype type, char *data, size_t size, void *userdata); diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index 9b3a33aefbc2..014e4b832ed1 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -268,6 +268,7 @@ static size_t save_etag(const char *etag_h, const char *endp, /* Truncate regular files to avoid stale etag content */ if((fd != -1) && + etag_save->regular_file && !curlx_fstat(fd, &file) && (S_ISREG(file.st_mode) && toolx_ftruncate(fd, 0))) @@ -282,6 +283,32 @@ static size_t save_etag(const char *etag_h, const char *endp, return 0; /* ok */ } +static bool set_filename(struct OutStruct *outs, + struct per_transfer *per, char *filename) +{ + if(outs->stream) { + /* indication of problem, get out! */ + curlx_free(filename); + return FALSE; + } + if(outs->alloc_filename) + curlx_safefree(outs->filename); + + if(per->config->output_dir) { + char *f = curl_maprintf("%s/%s", per->config->output_dir, filename); + curlx_free(filename); + if(!f) + return FALSE; + outs->filename = curlx_strdup(f); + curl_free(f); + if(!outs->filename) + return FALSE; + } + else + outs->filename = filename; + return TRUE; +} + /* * This function sets the filename where output shall be written when curl * options --remote-name (-O) and --remote-header-name (-J) have been @@ -303,27 +330,8 @@ static size_t content_disposition(const char *str, const char *end, if(p < end) { /* as a precaution */ char *filename = parse_filename(p, cb - (p - str), 0); if(filename) { - if(outs->stream) { - /* indication of problem, get out! */ - curlx_free(filename); + if(!set_filename(outs, per, filename)) return CURL_WRITEFUNC_ERROR; - } - if(outs->alloc_filename) - curlx_safefree(outs->filename); - - if(per->config->output_dir) { - char *f = curl_maprintf("%s/%s", per->config->output_dir, - filename); - curlx_free(filename); - if(!f) - return CURL_WRITEFUNC_ERROR; - outs->filename = curlx_strdup(f); - curl_free(f); - if(!outs->filename) - return CURL_WRITEFUNC_ERROR; - } - else - outs->filename = filename; outs->alloc_filename = TRUE; outs->is_cd_filename = TRUE; /* set to avoid clobbering existing files by default */ @@ -358,28 +366,8 @@ static size_t content_disposition(const char *str, const char *end, len = cb - (size_t)(p - str); filename = parse_filename(p, len, ';'); if(filename) { - if(outs->stream) { - /* indication of problem, get out! */ - curlx_free(filename); + if(!set_filename(outs, per, filename)) return CURL_WRITEFUNC_ERROR; - } - if(outs->alloc_filename) - curlx_safefree(outs->filename); - - if(per->config->output_dir) { - char *f = curl_maprintf("%s/%s", per->config->output_dir, - filename); - curlx_free(filename); - if(!f) - return CURL_WRITEFUNC_ERROR; - outs->filename = curlx_strdup(f); - curl_free(f); - if(!outs->filename) - return CURL_WRITEFUNC_ERROR; - } - else - outs->filename = filename; - outs->is_cd_filename = TRUE; outs->regular_file = TRUE; outs->fopened = FALSE; @@ -424,10 +412,10 @@ static size_t content_disposition(const char *str, const char *end, } /* -** callback for CURLOPT_HEADERFUNCTION -* -* 'size' is always 1 -*/ + * callback for CURLOPT_HEADERFUNCTION + * + * 'size' is always 1 + */ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) { struct per_transfer *per = userdata; diff --git a/src/tool_cb_hdr.h b/src/tool_cb_hdr.h index 37be591e70ee..07b50eb0ddae 100644 --- a/src/tool_cb_hdr.h +++ b/src/tool_cb_hdr.h @@ -52,9 +52,8 @@ struct HdrCbData { int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream); /* -** callback for CURLOPT_HEADERFUNCTION -*/ - + * callback for CURLOPT_HEADERFUNCTION + */ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata); #endif /* HEADER_CURL_TOOL_CB_HDR_H */ diff --git a/src/tool_cb_prg.c b/src/tool_cb_prg.c index 1af621dbaaa0..60709426e42c 100644 --- a/src/tool_cb_prg.c +++ b/src/tool_cb_prg.c @@ -36,8 +36,7 @@ my $pi = 3.1415; foreach my $i (1 .. 200) { printf "%d, ", sin($i / 200 * 2 * $pi) * 500000 + 500000; - } -*/ + } */ static const int sinus[] = { 515704, 531394, 547052, 562664, 578214, 593687, 609068, 624341, 639491, 654504, 669364, 684057, 698568, 712883, 726989, 740870, 754513, 767906, @@ -104,9 +103,8 @@ static void fly(struct ProgressData *bar, bool moved) } /* -** callback for CURLOPT_XFERINFOFUNCTION -*/ - + * callback for CURLOPT_XFERINFOFUNCTION + */ static void update_width(struct ProgressData *bar) { int cols = get_terminal_columns(); @@ -128,32 +126,43 @@ int tool_progress_cb(void *clientp, struct ProgressData *bar = &per->progressbar; curl_off_t total; curl_off_t point; + curl_off_t totalall; + curl_off_t nowall; + + totalall = ((CURL_OFF_T_MAX - dltotal) < ultotal) ? + CURL_OFF_T_MAX : dltotal + ultotal; + + nowall = ((CURL_OFF_T_MAX - dlnow) < ulnow) ? + CURL_OFF_T_MAX : dlnow + ulnow; + + if(!bar->calls) + update_width(bar); /* Calculate expected transfer size. initial_size can be less than zero when indicating that we are expecting to get the filesize from the remote */ if(bar->initial_size < 0) { if(dltotal || ultotal) - total = dltotal + ultotal; + total = totalall; else total = CURL_OFF_T_MAX; } - else if((CURL_OFF_T_MAX - bar->initial_size) < (dltotal + ultotal)) + else if((CURL_OFF_T_MAX - bar->initial_size) < totalall) total = CURL_OFF_T_MAX; else - total = dltotal + ultotal + bar->initial_size; + total = totalall + bar->initial_size; /* Calculate the current progress. initial_size can be less than zero when indicating that we are expecting to get the filesize from the remote */ if(bar->initial_size < 0) { if(dltotal || ultotal) - point = dlnow + ulnow; + point = nowall; else point = CURL_OFF_T_MAX; } - else if((CURL_OFF_T_MAX - bar->initial_size) < (dlnow + ulnow)) + else if((CURL_OFF_T_MAX - bar->initial_size) < nowall) point = CURL_OFF_T_MAX; else - point = dlnow + ulnow + bar->initial_size; + point = nowall + bar->initial_size; if(bar->calls) { /* after first call... */ @@ -230,8 +239,6 @@ void progressbarinit(struct ProgressData *bar, struct OperationConfig *config) if(config->use_resume) bar->initial_size = config->resume_from; - update_width(bar); - bar->out = tool_stderr; bar->tick = 150; bar->barmove = 1; diff --git a/src/tool_cb_prg.h b/src/tool_cb_prg.h index 9c2dda2d7e35..5939d158da87 100644 --- a/src/tool_cb_prg.h +++ b/src/tool_cb_prg.h @@ -45,9 +45,8 @@ struct OperationConfig; void progressbarinit(struct ProgressData *bar, struct OperationConfig *config); /* -** callback for CURLOPT_PROGRESSFUNCTION -*/ - + * callback for CURLOPT_PROGRESSFUNCTION + */ int tool_progress_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); diff --git a/src/tool_cb_rea.c b/src/tool_cb_rea.c index a818d0804d64..bf9bd15ad640 100644 --- a/src/tool_cb_rea.c +++ b/src/tool_cb_rea.c @@ -68,9 +68,8 @@ static void waitfd(int waitms, int fd) #endif /* -** callback for CURLOPT_READFUNCTION -*/ - + * callback for CURLOPT_READFUNCTION + */ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) { ssize_t rc = 0; @@ -109,7 +108,7 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) #ifndef CURL_WINDOWS_UWP rc = sread(per->infd, buffer, curlx_uztosi(sz * nmemb)); if(rc < 0) { - if(SOCKERRNO == SOCKEWOULDBLOCK) { + if(SOCK_EAGAIN(SOCKERRNO)) { errno = 0; config->readbusy = TRUE; return CURL_READFUNC_PAUSE; @@ -153,9 +152,8 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) } /* -** callback for CURLOPT_XFERINFOFUNCTION used to unpause busy reads -*/ - + * callback for CURLOPT_XFERINFOFUNCTION used to unpause busy reads + */ int tool_readbusy_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) diff --git a/src/tool_cb_rea.h b/src/tool_cb_rea.h index 06899d3ef342..c44ceaaa0d02 100644 --- a/src/tool_cb_rea.h +++ b/src/tool_cb_rea.h @@ -26,15 +26,13 @@ #include "tool_setup.h" /* -** callback for CURLOPT_READFUNCTION -*/ - + * callback for CURLOPT_READFUNCTION + */ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata); /* -** callback for CURLOPT_XFERINFOFUNCTION used to unpause busy reads -*/ - + * callback for CURLOPT_XFERINFOFUNCTION used to unpause busy reads + */ int tool_readbusy_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); diff --git a/src/tool_cb_see.c b/src/tool_cb_see.c index bb33bd22890d..50e630d60d79 100644 --- a/src/tool_cb_see.c +++ b/src/tool_cb_see.c @@ -28,12 +28,11 @@ #include "tool_cb_see.h" /* -** callback for CURLOPT_SEEKFUNCTION -** -** Notice that this is not supposed to return the resulting offset. This -** shall only return CURL_SEEKFUNC_* return codes. -*/ - + * callback for CURLOPT_SEEKFUNCTION + * + * Notice that this is not supposed to return the resulting offset. This + * shall only return CURL_SEEKFUNC_* return codes. + */ int tool_seek_cb(void *userdata, curl_off_t offset, int whence) { struct per_transfer *per = userdata; diff --git a/src/tool_cb_see.h b/src/tool_cb_see.h index e473223526ae..1cac0ace5cfb 100644 --- a/src/tool_cb_see.h +++ b/src/tool_cb_see.h @@ -26,8 +26,8 @@ #include "tool_setup.h" /* -** callback for CURLOPT_SEEKFUNCTION -*/ + * callback for CURLOPT_SEEKFUNCTION + */ int tool_seek_cb(void *userdata, curl_off_t offset, int whence); #endif /* HEADER_CURL_TOOL_CB_SEE_H */ diff --git a/src/tool_cb_soc.c b/src/tool_cb_soc.c index 0df0a1e6fb24..ddc11ac86857 100644 --- a/src/tool_cb_soc.c +++ b/src/tool_cb_soc.c @@ -30,11 +30,10 @@ #include "tool_cb_soc.h" /* -** callback for CURLOPT_OPENSOCKETFUNCTION -** -** Notice that only Linux is supported for the moment. -*/ - + * callback for CURLOPT_OPENSOCKETFUNCTION + * + * Notice that only Linux is supported for the moment. + */ curl_socket_t tool_socket_open_mptcp_cb(void *clientp, curlsocktype purpose, struct curl_sockaddr *addr) diff --git a/src/tool_cb_soc.h b/src/tool_cb_soc.h index f02150aa8284..d45d72a361e1 100644 --- a/src/tool_cb_soc.h +++ b/src/tool_cb_soc.h @@ -26,9 +26,8 @@ #include "tool_setup.h" /* -** callback for CURLOPT_OPENSOCKETFUNCTION -*/ - + * callback for CURLOPT_OPENSOCKETFUNCTION + */ curl_socket_t tool_socket_open_mptcp_cb(void *clientp, curlsocktype purpose, struct curl_sockaddr *addr); diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index d412a1ea88d7..c67e3975d2d1 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -125,12 +125,12 @@ static size_t win_console(intptr_t fhnd, struct OutStruct *outs, /* attempt to complete an incomplete UTF-8 sequence from previous call. the sequence does not have to be well-formed. */ if(outs->utf8seq[0] && rlen) { - bool complete = false; + bool complete = FALSE; /* two byte sequence (lead byte 110yyyyy) */ if(0xC0 <= outs->utf8seq[0] && outs->utf8seq[0] < 0xE0) { outs->utf8seq[1] = *rbuf++; --rlen; - complete = true; + complete = TRUE; } /* three byte sequence (lead byte 1110zzzz) */ else if(0xE0 <= outs->utf8seq[0] && outs->utf8seq[0] < 0xF0) { @@ -141,7 +141,7 @@ static size_t win_console(intptr_t fhnd, struct OutStruct *outs, if(rlen && !outs->utf8seq[2]) { outs->utf8seq[2] = *rbuf++; --rlen; - complete = true; + complete = TRUE; } } /* four byte sequence (lead byte 11110uuu) */ @@ -157,7 +157,7 @@ static size_t win_console(intptr_t fhnd, struct OutStruct *outs, if(rlen && !outs->utf8seq[3]) { outs->utf8seq[3] = *rbuf++; --rlen; - complete = true; + complete = TRUE; } } @@ -213,8 +213,7 @@ static size_t win_console(intptr_t fhnd, struct OutStruct *outs, /* grow the buffer if needed */ if(len > global->term.len) { - wchar_t *buf = (wchar_t *)curlx_realloc(global->term.buf, - len * sizeof(wchar_t)); + wchar_t *buf = curlx_realloc(global->term.buf, len * sizeof(wchar_t)); if(!buf) return CURL_WRITEFUNC_ERROR; global->term.len = len; @@ -238,9 +237,8 @@ static size_t win_console(intptr_t fhnd, struct OutStruct *outs, #endif /* _WIN32 */ /* -** callback for CURLOPT_WRITEFUNCTION -*/ - + * callback for CURLOPT_WRITEFUNCTION + */ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) { size_t rc; diff --git a/src/tool_cb_wrt.h b/src/tool_cb_wrt.h index 55502f440dcf..c99bd7c5e789 100644 --- a/src/tool_cb_wrt.h +++ b/src/tool_cb_wrt.h @@ -26,9 +26,8 @@ #include "tool_setup.h" /* -** callback for CURLOPT_WRITEFUNCTION -*/ - + * callback for CURLOPT_WRITEFUNCTION + */ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata); /* create a local file for writing, return TRUE on success */ diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index 515df19073c9..26b44f8438cd 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -81,12 +81,6 @@ static void free_config_fields(struct OperationConfig *config) curlx_safefree(config->range); curlx_safefree(config->userpwd); - curlx_safefree(config->tls_username); - curlx_safefree(config->tls_password); - curlx_safefree(config->tls_authtype); - curlx_safefree(config->proxy_tls_username); - curlx_safefree(config->proxy_tls_password); - curlx_safefree(config->proxy_tls_authtype); curlx_safefree(config->proxyuserpwd); curlx_safefree(config->proxy); @@ -145,7 +139,9 @@ static void free_config_fields(struct OperationConfig *config) curlx_safefree(config->proxy_key); curlx_safefree(config->key_type); curlx_safefree(config->proxy_key_type); + curlx_strzero(config->key_passwd); curlx_safefree(config->key_passwd); + curlx_strzero(config->proxy_key_passwd); curlx_safefree(config->proxy_key_passwd); curlx_safefree(config->pubkey); curlx_safefree(config->hostpubmd5); @@ -158,6 +154,7 @@ static void free_config_fields(struct OperationConfig *config) curlx_safefree(config->request_target); curlx_safefree(config->customrequest); curlx_safefree(config->krblevel); + curlx_strzero(config->oauth_bearer); curlx_safefree(config->oauth_bearer); curlx_safefree(config->sasl_authzid); curlx_safefree(config->unix_socket_path); @@ -187,6 +184,10 @@ static void free_config_fields(struct OperationConfig *config) curlx_safefree(config->ftp_account); curlx_safefree(config->ftp_alternative_to_user); curlx_safefree(config->aws_sigv4); + curlx_safefree(config->httpsig_algorithm); + curlx_safefree(config->httpsig_headers); + curlx_safefree(config->httpsig_key); + curlx_safefree(config->httpsig_keyid); curlx_safefree(config->ech); curlx_safefree(config->ech_config); curlx_safefree(config->ech_public); @@ -219,7 +220,7 @@ void config_free(struct OperationConfig *config) * round to verify them. * * The main point is to make sure that what is returned is different than what - * the regular memory functions return so that mixup will trigger problems. + * the regular memory functions return so that mixup does trigger problems. * * This test setup currently only works when building with a *shared* libcurl * and not static, as in the latter case the tool and the library share some of @@ -316,7 +317,7 @@ CURLcode globalconf_init(void) _djstat_flags |= _STAT_INODE | _STAT_EXEC_MAGIC | _STAT_DIRSIZE; #endif - /* Initialise the global config */ + /* Initialize the global config */ global->showerror = FALSE; /* show errors when silent */ global->styled_output = TRUE; /* enable detection */ global->parallel_max = PARALLEL_DEFAULT; @@ -326,7 +327,8 @@ CURLcode globalconf_init(void) if(global->first) { /* Perform the libcurl initialization */ #ifdef CURL_DEBUG_GLOBAL_MEM - result = curl_global_init_mem(CURL_GLOBAL_ALL, custom_malloc, custom_free, + result = curl_global_init_mem(CURL_GLOBAL_DEFAULT, + custom_malloc, custom_free, custom_realloc, custom_strdup, custom_calloc); #else diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 0f8bc6fe0824..7a587f6675c8 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -77,12 +77,6 @@ struct OperationConfig { char *dns_ipv6_addr; /* dot notation */ char *userpwd; char *login_options; - char *tls_username; - char *tls_password; - char *tls_authtype; - char *proxy_tls_username; - char *proxy_tls_password; - char *proxy_tls_authtype; char *proxyuserpwd; char *proxy; char *noproxy; @@ -159,6 +153,10 @@ struct OperationConfig { char *unix_socket_path; /* path to Unix domain socket */ char *haproxy_clientip; /* client IP for HAProxy protocol */ char *aws_sigv4; + char *httpsig_algorithm; + char *httpsig_headers; + char *httpsig_key; + char *httpsig_keyid; char *ech; /* Config set by --ech keywords */ char *ech_config; /* Config set by "--ech esl:" option */ char *ech_public; /* Config set by "--ech pn:" option */ diff --git a/src/tool_doswin.c b/src/tool_doswin.c index 4b2a2a34b333..be6cb510d075 100644 --- a/src/tool_doswin.c +++ b/src/tool_doswin.c @@ -29,6 +29,8 @@ #include "curlx/version_win32.h" /* for curlx_verify_windows_version() */ #ifdef _WIN32 +# include "curlx/winapi.h" /* for curlx_win32_random() */ +# include "curlx/nonblock.h" /* for curlx_nonblock() */ # include #elif !defined(__DJGPP__) || (__DJGPP__ < 2) /* DJGPP 2.0 has _use_lfn() */ # define CURL_USE_LFN(f) 0 /* long filenames never available */ @@ -144,13 +146,13 @@ static SANITIZEcode msdosify(char ** const sanitized, const char *file_name, static const char illegal_chars_dos[] = ".+, ;=[]" /* illegal in DOS */ "|<>/\\\":?*"; /* illegal in DOS & W95 */ - static const char *illegal_chars_w95 = &illegal_chars_dos[8]; + static const char * const illegal_chars_w95 = &illegal_chars_dos[8]; int idx, dot_idx; const char *s = file_name; char *d = dos_name; - const char * const dlimit = dos_name + sizeof(dos_name) - 1; + const char * const dlimit = dos_name + CURL_CSTRLEN(dos_name); const char *illegal_aliens = illegal_chars_dos; - size_t len = sizeof(illegal_chars_dos) - 1; + size_t len = CURL_CSTRLEN(illegal_chars_dos); if(!sanitized) return SANITIZE_ERR_BAD_ARGUMENT; @@ -336,7 +338,7 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized, curl_strnequal(p, "PRN", 3) || curl_strnequal(p, "AUX", 3) || curl_strnequal(p, "NUL", 3)) ? 3 : - (curl_strnequal(p, "CLOCK$", 6)) ? 6 : + curl_strnequal(p, "CLOCK$", 6) ? 6 : (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ? (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0; @@ -549,7 +551,7 @@ SANITIZEcode sanitize_file_name(char ** const sanitized, const char *file_name, * 4. Windows Directory (e.g. C:\Windows) * 5. all directories along %PATH% * - * For WinXP and later search order actually depends on registry value: + * For Windows XP and later search order actually depends on registry value: * HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode */ CURLcode FindWin32CACert(struct OperationConfig *config, @@ -639,7 +641,7 @@ static struct TerminalSettings { LONG valid; } TerminalSettings; -/* Offered by mingw-w64 v7+. MS SDK ~10.16299/~VS2017+. */ +/* Offered by mingw-w64 v7+, MS SDK 10.0.10586.0/VS2015 Update 1+ */ #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif @@ -678,7 +680,7 @@ static void init_terminal(void) return; if((TerminalSettings.dwOutputMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) - tool_term_has_bold = true; + tool_term_has_bold = TRUE; else { /* The signal handler is set before attempting to change the console mode because otherwise a signal would not be caught after the change but @@ -688,7 +690,7 @@ static void init_terminal(void) if(SetConsoleMode(TerminalSettings.hStdOut, (TerminalSettings.dwOutputMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))) { - tool_term_has_bold = true; + tool_term_has_bold = TRUE; atexit(restore_terminal); } else { @@ -702,62 +704,142 @@ static void init_terminal(void) #ifdef USE_WINSOCK /* The following STDIN non - blocking read techniques are heavily inspired by nmap and ncat (https://nmap.org/ncat/) */ -struct win_thread_data { +static struct win_thread_data { /* This is a copy of the true stdin file handle before any redirection. It is read by the thread. */ HANDLE stdin_handle; - /* This is the listen socket for the thread. It is closed after the first - connection. */ - curl_socket_t socket_l; -}; - -static DWORD WINAPI win_stdin_thread_func(void *thread_data) + /* This is the socket the thread will forward stdin to. It is connected to + the socket which replaces the stdin handle. */ + curl_socket_t socket_w; + /* This is a mutex-like object which we use to synchronize + * cleanup_tdata_sync() */ + CRITICAL_SECTION crit_sect; +} tdata = { NULL, CURL_SOCKET_BAD, {0}}; + +static void cleanup_tdata_sync(void) { - struct win_thread_data *tdata = (struct win_thread_data *)thread_data; - struct sockaddr_in clientAddr; - int clientAddrLen = sizeof(clientAddr); - - curl_socket_t socket_w = CURL_ACCEPT(tdata->socket_l, - (struct sockaddr *)&clientAddr, - &clientAddrLen); - - if(socket_w == CURL_SOCKET_BAD) { - errorf("accept error: %d", SOCKERRNO); - goto ThreadCleanup; + EnterCriticalSection(&tdata.crit_sect); + if(tdata.stdin_handle) { + CloseHandle(tdata.stdin_handle); + tdata.stdin_handle = NULL; } - sclose(tdata->socket_l); - tdata->socket_l = CURL_SOCKET_BAD; - if(shutdown(socket_w, SHUT_RD)) { - errorf("shutdown error: %d", SOCKERRNO); - goto ThreadCleanup; + if(tdata.socket_w != CURL_SOCKET_BAD) { + sclose(tdata.socket_w); + tdata.socket_w = CURL_SOCKET_BAD; } + LeaveCriticalSection(&tdata.crit_sect); +} + +static DWORD WINAPI win_stdin_thread_func(void *thread_data) +{ + (void)thread_data; + for(;;) { DWORD n; ssize_t nwritten; char buffer[BUFSIZ]; - if(!ReadFile(tdata->stdin_handle, buffer, sizeof(buffer), &n, NULL)) + /* If stdin is a pipe then end-of-data signaling may differ depending on + how curl was built, the shell and the input. Two ways have been + observed: + - ReadFile fails with GetLastError ERROR_BROKEN_PIPE + - ReadFile succeeds with 0 bytes read (seen on mingw) */ + + if(!ReadFile(tdata.stdin_handle, buffer, sizeof(buffer), &n, NULL)) break; if(n == 0) break; - nwritten = swrite(socket_w, buffer, n); + nwritten = swrite(tdata.socket_w, buffer, n); if(nwritten == -1) break; if((DWORD)nwritten != n) break; } -ThreadCleanup: - CloseHandle(tdata->stdin_handle); - tdata->stdin_handle = NULL; - if(tdata->socket_l != CURL_SOCKET_BAD) { - sclose(tdata->socket_l); - tdata->socket_l = CURL_SOCKET_BAD; + + /* wait for all data to be received by the main thread: + shut down the write side of our socket so that a FIN is sent "after all + data is sent and acknowledged by the receiver". recv is called to wait for + this to happen. the wait time includes time of up to 2 min (OS typical) + since it's possible the receiver will not reply to the FIN. + */ + if(shutdown(tdata.socket_w, SHUT_WR) == 0) { + char buf[1024]; + /* read until close or error while ignoring all incoming */ + while(sread(tdata.socket_w, buf, sizeof(buf)) > 0) + ; } - if(socket_w != CURL_SOCKET_BAD) - sclose(socket_w); - curlx_free(tdata); + cleanup_tdata_sync(); + + return 0; +} + +static int swrite_blocking_on_nonblock(curl_socket_t nonblock_sock, + const unsigned char *data, + size_t nbytes) +{ + fd_set fdwrite; + fd_set fdexcep; + size_t nwritten = 0; + + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + FD_SET(nonblock_sock, &fdwrite); + + do { + ssize_t ret; + + FD_SET(nonblock_sock, &fdexcep); + + if(select(0, NULL, &fdwrite, &fdexcep, NULL) <= 0) { + errorf("select error: %d", SOCKERRNO); + return -1; + } + + if(FD_ISSET(nonblock_sock, &fdexcep)) { + int sock_err = 0; + int sock_err_size = sizeof(sock_err); + getsockopt(nonblock_sock, SOL_SOCKET, SO_ERROR, + (char *)&sock_err, &sock_err_size); + errorf("connect failure: %d", sock_err); + return -1; + } + + ret = swrite(nonblock_sock, data + nwritten, nbytes - nwritten); + if(ret <= 0) { + if(SOCK_EAGAIN(SOCKERRNO)) + continue; + + errorf("socket write error: %d", SOCKERRNO); + return -1; + } + + nwritten += ret; + } while(nwritten < nbytes); + + return 0; +} + +static int read_auth_val(curl_socket_t sock, uint64_t* auth_val_ptr) +{ + size_t nread = 0; + + do { + ssize_t ret = sread(sock, (unsigned char *)auth_val_ptr + nread, + sizeof(*auth_val_ptr) - nread); + if(ret <= 0) { + if(!ret) + errorf("stdin relay peer disconnected"); + else + errorf("read error: %d", SOCKERRNO); + + return -1; + } + nread += ret; + } while(nread < sizeof(*auth_val_ptr)); + return 0; } @@ -765,31 +847,26 @@ static DWORD WINAPI win_stdin_thread_func(void *thread_data) curl_socket_t win32_stdin_read_thread(void) { int rc = 0; - struct win_thread_data *tdata = NULL; - static HANDLE stdin_thread = NULL; + HANDLE stdin_thread = NULL; static curl_socket_t socket_r = CURL_SOCKET_BAD; + curl_socket_t socket_l = CURL_SOCKET_BAD; + uint64_t auth_rnd = 0; + uint64_t recvd_val = 1; if(socket_r != CURL_SOCKET_BAD) { - assert(stdin_thread != NULL); return socket_r; } - assert(stdin_thread == NULL); do { curl_socklen_t socksize = 0; struct sockaddr_in selfaddr; - /* Prepare handles for thread */ - tdata = (struct win_thread_data *) - curlx_calloc(1, sizeof(struct win_thread_data)); - if(!tdata) { - errorf("curlx_calloc() error"); - break; - } - /* Create the listening socket for the thread. When it starts, it accepts - * our connection and begin writing STDIN data to the connection. */ - tdata->socket_l = CURL_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if(tdata->socket_l == CURL_SOCKET_BAD) { + InitializeCriticalSection(&tdata.crit_sect); + + /* Create the listening socket. It is used to create the writing socket by + * accepting a connection from the reading socket. */ + socket_l = CURL_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(socket_l == CURL_SOCKET_BAD) { errorf("socket() error: %d", SOCKERRNO); break; } @@ -799,59 +876,81 @@ curl_socket_t win32_stdin_read_thread(void) selfaddr.sin_family = AF_INET; selfaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* Bind to any available loopback port */ - if(bind(tdata->socket_l, (const struct sockaddr *)&selfaddr, socksize)) { + if(bind(socket_l, (const struct sockaddr *)&selfaddr, socksize)) { errorf("bind error: %d", SOCKERRNO); break; } - /* Bind to any available loopback port */ - if(getsockname(tdata->socket_l, (struct sockaddr *)&selfaddr, &socksize)) { + /* Retrieve the assigned loopback port/address */ + if(getsockname(socket_l, (struct sockaddr *)&selfaddr, &socksize)) { errorf("getsockname error: %d", SOCKERRNO); break; } - if(listen(tdata->socket_l, 1)) { + if(listen(socket_l, 1)) { errorf("listen error: %d", SOCKERRNO); break; } - /* Make a copy of the stdin handle to be used by win_stdin_thread_func */ - if(!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), - GetCurrentProcess(), &tdata->stdin_handle, - 0, FALSE, DUPLICATE_SAME_ACCESS)) { - errorf("DuplicateHandle error: 0x%08lx", GetLastError()); + /* Create the reading socket */ + socket_r = CURL_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(socket_r == CURL_SOCKET_BAD) { + errorf("socket error: %d", SOCKERRNO); break; } - /* Start up the thread. We do not bother keeping a reference to it - because it runs until program termination. From here on out all reads - from the stdin handle or file descriptor 0 is reading from the - socket that is fed by the thread. */ - stdin_thread = CreateThread(NULL, 0, win_stdin_thread_func, - tdata, 0, NULL); - if(!stdin_thread) { - errorf("CreateThread error: 0x%08lx", GetLastError()); + /* Make the reading socket nonblocking */ + if(curlx_nonblock(socket_r, TRUE)) { + errorf("curlx_nonblock() error"); break; } - tdata = NULL; /* win_stdin_thread_func owns it now */ - /* Connect to the thread and rearrange our own STDIN handles */ - socket_r = CURL_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if(socket_r == CURL_SOCKET_BAD) { - errorf("socket error: %d", SOCKERRNO); + /* Connect to the listening socket */ + if(connect(socket_r, (const struct sockaddr *)&selfaddr, socksize)) { + int sockerr = SOCKERRNO; + if(!SOCK_EAGAIN(sockerr)) { + errorf("connect error: %d", sockerr); + break; + } + } + + /* Accept the connection on the other end, creating the writing socket + * which will be given to the background thread */ + tdata.socket_w = CURL_ACCEPT(socket_l, NULL, NULL); + + if(tdata.socket_w == CURL_SOCKET_BAD) { + errorf("accept error: %d", SOCKERRNO); break; } - /* Hard close the socket on closesocket() */ - setsockopt(socket_r, SOL_SOCKET, SO_DONTLINGER, 0, 0); + /* We don't need the listening socket anymore */ + sclose(socket_l); + socket_l = CURL_SOCKET_BAD; - if(connect(socket_r, (const struct sockaddr *)&selfaddr, socksize)) { - errorf("connect error: %d", SOCKERRNO); + /* Authenticate the reading socket to the writing socket to make sure + * we don't leak information.*/ + if(curlx_win32_random((unsigned char *)&auth_rnd, sizeof(auth_rnd))) { + errorf("curlx_win32_random() error"); + break; + } + + if(swrite_blocking_on_nonblock(socket_r, (unsigned char *)&auth_rnd, + sizeof(auth_rnd))) + break; + + if(read_auth_val(tdata.socket_w, &recvd_val)) + break; + + if(recvd_val != auth_rnd) { + errorf("relay peer auth failed"); break; } - if(shutdown(socket_r, SHUT_WR)) { - errorf("shutdown error: %d", SOCKERRNO); + /* Make a copy of the stdin handle to be used by win_stdin_thread_func */ + if(!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), + GetCurrentProcess(), &tdata.stdin_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + errorf("DuplicateHandle error: 0x%08lx", GetLastError()); break; } @@ -861,41 +960,52 @@ curl_socket_t win32_stdin_read_thread(void) break; } + /* Start up the thread. We do not bother keeping a reference to it + because it runs until program termination. From here on out all reads + from the stdin handle or file descriptor 0 is reading from the + socket that is fed by the thread. */ + stdin_thread = CreateThread(NULL, 0, win_stdin_thread_func, + NULL, 0, NULL); + if(!stdin_thread) { + errorf("CreateThread error: 0x%08lx", GetLastError()); + break; + } + CloseHandle(stdin_thread); + + /* Starting the thread is the last thing we do, since there aren't any + * reliable ways to close it in case of subsequent errors. */ + rc = 1; } while(0); if(rc != 1) { - if(socket_r != CURL_SOCKET_BAD && tdata) { + /* we rely on the background thread not running at this point, as there + * could be TOCTOU bugs otherwise */ + if(socket_r != CURL_SOCKET_BAD) { if(GetStdHandle(STD_INPUT_HANDLE) == (HANDLE)socket_r && - tdata->stdin_handle) { + tdata.stdin_handle) { /* restore STDIN */ - SetStdHandle(STD_INPUT_HANDLE, tdata->stdin_handle); - tdata->stdin_handle = NULL; + SetStdHandle(STD_INPUT_HANDLE, tdata.stdin_handle); + tdata.stdin_handle = NULL; } sclose(socket_r); socket_r = CURL_SOCKET_BAD; } - if(stdin_thread) { - TerminateThread(stdin_thread, 1); - CloseHandle(stdin_thread); - stdin_thread = NULL; - } - - if(tdata) { - if(tdata->stdin_handle) - CloseHandle(tdata->stdin_handle); - if(tdata->socket_l != CURL_SOCKET_BAD) - sclose(tdata->socket_l); + if(socket_l != CURL_SOCKET_BAD) + sclose(socket_l); - curlx_free(tdata); - } + cleanup_tdata_sync(); + DeleteCriticalSection(&tdata.crit_sect); return CURL_SOCKET_BAD; } - assert(socket_r != CURL_SOCKET_BAD); + /* prevent mem leak warnings */ + atexit(&cleanup_tdata_sync); + + DEBUGASSERT(socket_r != CURL_SOCKET_BAD); return socket_r; } #endif /* USE_WINSOCK */ diff --git a/src/tool_easysrc.c b/src/tool_easysrc.c index 5b3c6cb45b6f..c2a34c3b2b1e 100644 --- a/src/tool_easysrc.c +++ b/src/tool_easysrc.c @@ -64,7 +64,7 @@ static const char * const srchard[] = { "", NULL }; -static const char *const srcend[] = { +static const char * const srcend[] = { "", " return (int)result;", "}", diff --git a/src/tool_formparse.c b/src/tool_formparse.c index e129452d4362..450a2b3a994a 100644 --- a/src/tool_formparse.c +++ b/src/tool_formparse.c @@ -33,7 +33,7 @@ static struct tool_mime *tool_mime_new(struct tool_mime *parent, toolmimekind kind) { - struct tool_mime *m = (struct tool_mime *)curlx_calloc(1, sizeof(*m)); + struct tool_mime *m = curlx_calloc(1, sizeof(*m)); if(m) { m->kind = kind; @@ -69,9 +69,8 @@ static struct tool_mime *tool_mime_new_data(struct tool_mime *parent, } /* -** unsigned size_t to signed curl_off_t -*/ - + * unsigned size_t to signed curl_off_t + */ #define CURL_MASK_UCOFFT ((unsigned CURL_TYPEOF_CURL_OFF_T)~0) #define CURL_MASK_SCOFFT (CURL_MASK_UCOFFT >> 1) @@ -192,8 +191,8 @@ void tool_mime_free(struct tool_mime *mime) } /* Mime part callbacks for stdin. */ -size_t tool_mime_stdin_read(char *buffer, - size_t size, size_t nitems, void *arg) +static size_t tool_mime_stdin_read(char *buffer, + size_t size, size_t nitems, void *arg) { struct tool_mime *sip = (struct tool_mime *)arg; curl_off_t bytesleft; @@ -217,7 +216,8 @@ size_t tool_mime_stdin_read(char *buffer, if(ferror(stdin)) { char errbuf[STRERROR_LEN]; /* Show error only once. */ - warnf("stdin: %s", curlx_strerror(errno, errbuf, sizeof(errbuf))); + warnf("Failed to read from stdin: %s", + curlx_strerror(errno, errbuf, sizeof(errbuf))); return CURL_READFUNC_ABORT; } } @@ -226,7 +226,7 @@ size_t tool_mime_stdin_read(char *buffer, return nitems; } -int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) +static int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) { struct tool_mime *sip = (struct tool_mime *)instream; @@ -250,71 +250,103 @@ int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) /* Translate an internal mime tree into a libcurl mime tree. */ +#define MAX_FORMPARTS 100000 /* arbitrarily picked */ + static CURLcode tool2curlparts(CURL *curl, struct tool_mime *m, curl_mime *mime) { CURLcode result = CURLE_OK; - curl_mimepart *part = NULL; - curl_mime *submime = NULL; - const char *filename = NULL; + struct tool_mime *curr; + struct tool_mime **nodes = NULL; + int count; + int i; + + if(!m) + return CURLE_OK; + + for(curr = m, count = 0; curr; curr = curr->prev) { + if(count > MAX_FORMPARTS) + return CURLE_BAD_FUNCTION_ARGUMENT; + count++; + } - if(m) { - result = tool2curlparts(curl, m->prev, mime); - if(!result) { - part = curl_mime_addpart(mime); - if(!part) - result = CURLE_OUT_OF_MEMORY; - } - if(!result) { - filename = m->filename; - switch(m->kind) { - case TOOLMIME_PARTS: - result = tool2curlmime(curl, m, &submime); - if(!result) { - result = curl_mime_subparts(part, submime); - if(result) - curl_mime_free(submime); - } - break; + nodes = curlx_malloc(sizeof(struct tool_mime *) * count); + if(!nodes) + return CURLE_OUT_OF_MEMORY; - case TOOLMIME_DATA: - result = curl_mime_data(part, m->data, CURL_ZERO_TERMINATED); - break; + /* populate array from the end to the beginning */ + curr = m; + for(i = count - 1; i >= 0; i--) { + nodes[i] = curr; + curr = curr->prev; + } - case TOOLMIME_FILE: - case TOOLMIME_FILEDATA: - result = curl_mime_filedata(part, m->data); - if(!result && m->kind == TOOLMIME_FILEDATA && !filename) - result = curl_mime_filename(part, NULL); - break; + for(i = 0; i < count; i++) { + struct tool_mime *node = nodes[i]; + curl_mimepart *part = NULL; + curl_mime *submime = NULL; + const char *filename = node->filename; - case TOOLMIME_STDIN: - if(!filename) - filename = "-"; - FALLTHROUGH(); - case TOOLMIME_STDINDATA: - result = curl_mime_data_cb(part, m->size, - (curl_read_callback)tool_mime_stdin_read, - (curl_seek_callback)tool_mime_stdin_seek, - NULL, m); - break; + part = curl_mime_addpart(mime); + if(!part) { + result = CURLE_OUT_OF_MEMORY; + break; + } - default: - /* Other cases not possible in this context. */ - break; + switch(node->kind) { + case TOOLMIME_PARTS: + result = tool2curlmime(curl, node, &submime); + if(!result) { + result = curl_mime_subparts(part, submime); + if(result) + curl_mime_free(submime); } + break; + + case TOOLMIME_DATA: + result = curl_mime_data(part, node->data, CURL_ZERO_TERMINATED); + break; + + case TOOLMIME_FILE: + case TOOLMIME_FILEDATA: + result = curl_mime_filedata(part, node->data); + if(!result && node->kind == TOOLMIME_FILEDATA && !filename) + result = curl_mime_filename(part, NULL); + break; + + case TOOLMIME_STDIN: + if(!filename) + filename = "-"; + FALLTHROUGH(); + case TOOLMIME_STDINDATA: + result = curl_mime_data_cb(part, node->size, + tool_mime_stdin_read, + tool_mime_stdin_seek, + NULL, node); + break; + + default: + /* Other cases not possible in this context. */ + break; } + + /* Common part configuration */ if(!result && filename) result = curl_mime_filename(part, filename); if(!result) - result = curl_mime_type(part, m->type); + result = curl_mime_type(part, node->type); if(!result) - result = curl_mime_headers(part, m->headers, 0); + result = curl_mime_headers(part, node->headers, 0); if(!result) - result = curl_mime_encoder(part, m->encoder); + result = curl_mime_encoder(part, node->encoder); if(!result) - result = curl_mime_name(part, m->name); + result = curl_mime_name(part, node->name); + + if(result) + break; } + + curlx_free(nodes); return result; } @@ -408,14 +440,17 @@ static int slist_append(struct curl_slist **plist, const char *data) return 0; } -/* Read headers from a file and append to list. */ +#define HEADER_LINE_BUFFER_SIZE 8192 + +/* Read headers from a file and append to list. + Return zero on success, non-zero on error. */ static int read_field_headers(FILE *fp, struct curl_slist **pheaders) { struct dynbuf line; bool error = FALSE; int err = 0; - curlx_dyn_init(&line, 8092); + curlx_dyn_init(&line, HEADER_LINE_BUFFER_SIZE); while(my_get_line(fp, &line, &error)) { const char *ptr = curlx_dyn_ptr(&line); size_t len = curlx_dyn_len(&line); @@ -436,7 +471,7 @@ static int read_field_headers(FILE *fp, struct curl_slist **pheaders) /* append this new line onto the previous line */ struct dynbuf amend; struct curl_slist *l = *pheaders; - curlx_dyn_init(&amend, 8092); + curlx_dyn_init(&amend, HEADER_LINE_BUFFER_SIZE); /* find the last node */ while(l && l->next) l = l->next; @@ -450,14 +485,12 @@ static int read_field_headers(FILE *fp, struct curl_slist **pheaders) curl_slist_append */ l->data = curl_maprintf("%s", curlx_dyn_ptr(&amend)); curlx_dyn_free(&amend); - if(!l->data) { - errorf("Out of memory for field headers"); - err = 1; - } + if(!l->data) + err = -1; } - else { + else err = slist_append(pheaders, ptr); - } + if(err) { errorf("Out of memory for field headers"); err = -1; @@ -472,6 +505,163 @@ static int read_field_headers(FILE *fp, struct curl_slist **pheaders) return err; } +static void param_type(char **ptr, char **ptype, char **endct, char *sep) +{ + char *p = *ptr; + size_t tlen; + for(p += CURL_CSTRLEN("type="); ISBLANK(*p); p++) + ; + /* set type pointer */ + *ptype = p; + + /* find end of content-type */ + tlen = strcspn(p, "()<>@,;:\\\"[]?=\r\n "); + p += tlen; + *endct = p; + *sep = *p; + *ptr = p; +} + +static void param_filename(char **ptr, char **endct, char **pfilename, + char endchar, char *sep) +{ + char *p = *ptr; + char *endpos; + char *tp; + + if(*endct) { + **endct = '\0'; + *endct = NULL; + } + for(p += CURL_CSTRLEN("filename="); ISBLANK(*p); p++) + ; + tp = p; + *pfilename = get_param_word(&p, &endpos, endchar); + /* If not quoted, strip trailing spaces. */ + if(*pfilename == tp) + while(endpos > *pfilename && ISBLANK(endpos[-1])) + endpos--; + *sep = *p; + *endpos = '\0'; + *ptr = p; +} + +static int param_headers(char **ptr, char **endct, + struct curl_slist **pheaders, char endchar, char *sep) +{ + char *p = *ptr; + char *endpos; + char *tp; + + if(*endct) { + **endct = '\0'; + *endct = NULL; + } + p += CURL_CSTRLEN("headers="); + if(*p == '@' || *p == '<') { + char *hdrfile; + FILE *fp; + /* Read headers from a file. */ + do { + p++; + } while(ISBLANK(*p)); + tp = p; + hdrfile = get_param_word(&p, &endpos, endchar); + /* If not quoted, strip trailing spaces. */ + if(hdrfile == tp) + while(endpos > hdrfile && ISBLANK(endpos[-1])) + endpos--; + *sep = *p; + *endpos = '\0'; + fp = curlx_fopen(hdrfile, FOPEN_READTEXT); + if(!fp) { + char errbuf[STRERROR_LEN]; + warnf("Cannot read from %s: %s", hdrfile, + curlx_strerror(errno, errbuf, sizeof(errbuf))); + } + else { + int i = read_field_headers(fp, pheaders); + + curlx_fclose(fp); + if(i) { + curl_slist_free_all(*pheaders); + return -1; + } + } + } + else { + char *hdr; + + while(ISBLANK(*p)) + p++; + tp = p; + hdr = get_param_word(&p, &endpos, endchar); + /* If not quoted, strip trailing spaces. */ + if(hdr == tp) + while(endpos > hdr && ISBLANK(endpos[-1])) + endpos--; + *sep = *p; + *endpos = '\0'; + if(slist_append(pheaders, hdr)) { + errorf("Out of memory for field header"); + curl_slist_free_all(*pheaders); + return -1; + } + } + *ptr = p; + return 0; +} + +static void param_encoder(char **ptr, char **endct, char **pencoder, + char endchar, char *sep) +{ + char *p = *ptr; + char *endpos; + char *tp; + + if(*endct) { + **endct = '\0'; + *endct = NULL; + } + for(p += CURL_CSTRLEN("encoder="); ISBLANK(*p); p++) + ; + tp = p; + *pencoder = get_param_word(&p, &endpos, endchar); + /* If not quoted, strip trailing spaces. */ + if(*pencoder == tp) + while(endpos > *pencoder && ISBLANK(endpos[-1])) + endpos--; + *sep = *p; + *endpos = '\0'; + *ptr = p; +} + +/** + * Parses a single parameter part and its associated metadata from a string. + * + * This function extracts a primary data word and scans for optional + * semicolon-separated attributes including 'type=', 'filename=', 'headers=', + * and 'encoder='. + * + * Used for parsing command-line form arguments or multipart/form-data + * attributes. + * + * @param endchar The character that signifies the end of the entire + * parameter block (e.g., ',' or '\0'). + * @param str Pointer to the current position in the input string. + * Updated to point at the delimiter or terminator that + * ended the parsed part. + * @param pdata Pointer to a char * that receives the primary data word. + * @param ptype [out] Optional. Receives the extracted 'type=' value. + * @param pfilename [out] Optional. Receives the extracted 'filename=' value. + * @param pencoder [out] Optional. Receives the extracted 'encoder=' value. + * @param pheaders [out] Optional. Receives a pointer to a curl_slist + * containing extracted 'headers='. + * + * @return The character that terminated the parsing (casted to int), + * or -1 on memory or parsing error. + */ + static int get_param_part(char endchar, char **str, char **pdata, char **ptype, char **pfilename, char **pencoder, @@ -509,108 +699,16 @@ static int get_param_part(char endchar, while(p++ && ISBLANK(*p)) ; - if(!endct && checkprefix("type=", p)) { - size_t tlen; - for(p += 5; ISBLANK(*p); p++) - ; - /* set type pointer */ - type = p; - - /* find end of content-type */ - tlen = strcspn(p, "()<>@,;:\\\"[]?=\r\n "); - p += tlen; - endct = p; - sep = *p; - } - else if(checkprefix("filename=", p)) { - if(endct) { - *endct = '\0'; - endct = NULL; - } - for(p += 9; ISBLANK(*p); p++) - ; - tp = p; - filename = get_param_word(&p, &endpos, endchar); - /* If not quoted, strip trailing spaces. */ - if(filename == tp) - while(endpos > filename && ISBLANK(endpos[-1])) - endpos--; - sep = *p; - *endpos = '\0'; - } + if(!endct && checkprefix("type=", p)) + param_type(&p, &type, &endct, &sep); + else if(checkprefix("filename=", p)) + param_filename(&p, &endct, &filename, endchar, &sep); else if(checkprefix("headers=", p)) { - if(endct) { - *endct = '\0'; - endct = NULL; - } - p += 8; - if(*p == '@' || *p == '<') { - char *hdrfile; - FILE *fp; - /* Read headers from a file. */ - do { - p++; - } while(ISBLANK(*p)); - tp = p; - hdrfile = get_param_word(&p, &endpos, endchar); - /* If not quoted, strip trailing spaces. */ - if(hdrfile == tp) - while(endpos > hdrfile && ISBLANK(endpos[-1])) - endpos--; - sep = *p; - *endpos = '\0'; - fp = curlx_fopen(hdrfile, FOPEN_READTEXT); - if(!fp) { - char errbuf[STRERROR_LEN]; - warnf("Cannot read from %s: %s", hdrfile, - curlx_strerror(errno, errbuf, sizeof(errbuf))); - } - else { - int i = read_field_headers(fp, &headers); - - curlx_fclose(fp); - if(i) { - curl_slist_free_all(headers); - return -1; - } - } - } - else { - char *hdr; - - while(ISBLANK(*p)) - p++; - tp = p; - hdr = get_param_word(&p, &endpos, endchar); - /* If not quoted, strip trailing spaces. */ - if(hdr == tp) - while(endpos > hdr && ISBLANK(endpos[-1])) - endpos--; - sep = *p; - *endpos = '\0'; - if(slist_append(&headers, hdr)) { - errorf("Out of memory for field header"); - curl_slist_free_all(headers); - return -1; - } - } - } - else if(checkprefix("encoder=", p)) { - if(endct) { - *endct = '\0'; - endct = NULL; - } - for(p += 8; ISBLANK(*p); p++) - ; - tp = p; - encoder = get_param_word(&p, &endpos, endchar); - /* If not quoted, strip trailing spaces. */ - if(encoder == tp) - while(endpos > encoder && ISSPACE(endpos[-1])) - endpos--; - sep = *p; - *endpos = '\0'; + if(param_headers(&p, &endct, &headers, endchar, &sep)) + return -1; } + else if(checkprefix("encoder=", p)) + param_encoder(&p, &endct, &encoder, endchar, &sep); else if(endct) { /* This is part of content type. */ for(endct = p; *p && *p != ';' && *p != endchar; p++) @@ -692,7 +790,7 @@ static int get_param_part(char endchar, * file and do like this: * * 'name=foo;headers=@headerfile' or why not - * 'name=@filemame;headers=@headerfile' + * 'name=@filename;headers=@headerfile' * * To upload a file, but to fake the filename that is included in the * formpost, do like this: @@ -701,8 +799,8 @@ static int get_param_part(char endchar, * 'name=@filename;filename="play, play, and play.txt"' * * If filename/path contains ',' or ';', it must be quoted by double-quotes, - * else curl fails to figure out the correct filename. if the filename - * tobe quoted contains '"' or '\', '"' and '\' must be escaped by backslash. + * else curl fails to figure out the correct filename. if the filename to be + * quoted contains '"' or '\', '"' and '\' must be escaped by backslash. * ***************************************************************************/ @@ -826,7 +924,7 @@ int formparse(const char *input, SET_TOOL_MIME_PTR(part, encoder); /* *contp could be '\0', so we check with the delimiter */ - } while(sep); /* loop if there is another filename */ + } while(sep == ','); /* loop if there is another filename */ part = (*mimecurrent)->subparts; /* Set name on group. */ } else { diff --git a/src/tool_formparse.h b/src/tool_formparse.h index d2222330eef9..9df5cb7b7a8f 100644 --- a/src/tool_formparse.h +++ b/src/tool_formparse.h @@ -57,10 +57,6 @@ struct tool_mime { curl_off_t curpos; /* Stdin current read position. */ }; -size_t tool_mime_stdin_read(char *buffer, - size_t size, size_t nitems, void *arg); -int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence); - int formparse(const char *input, struct tool_mime **mimeroot, struct tool_mime **mimecurrent, diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 176d3ebc3849..ae3958d8c58f 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -43,10 +43,8 @@ static ParameterError getstr(char **str, const char *val, bool allowblank) { - if(*str) { - curlx_free(*str); - *str = NULL; - } + if(*str) + curlx_safefree(*str); DEBUGASSERT(val); if(!allowblank && !val[0]) return PARAM_BLANK_STRING; @@ -61,10 +59,8 @@ static ParameterError getstr(char **str, const char *val, bool allowblank) static ParameterError getstrn(char **str, const char *val, size_t len, bool allowblank) { - if(*str) { - curlx_free(*str); - *str = NULL; - } + if(*str) + curlx_safefree(*str); DEBUGASSERT(val); if(!allowblank && !val[0]) return PARAM_BLANK_STRING; @@ -77,7 +73,7 @@ static ParameterError getstrn(char **str, const char *val, } /* this array MUST be alphasorted based on the 'lname' */ -static const struct LongShort aliases[]= { +static const struct LongShort aliases[] = { {"abstract-unix-socket", ARG_FILE, ' ', C_ABSTRACT_UNIX_SOCKET}, {"alpn", ARG_BOOL|ARG_NO|ARG_TLS, ' ', C_ALPN}, {"alt-svc", ARG_STRG, ' ', C_ALT_SVC}, @@ -174,6 +170,12 @@ static const struct LongShort aliases[]= { {"http2-prior-knowledge", ARG_NONE, ' ', C_HTTP2_PRIOR_KNOWLEDGE}, {"http3", ARG_NONE|ARG_TLS, ' ', C_HTTP3}, {"http3-only", ARG_NONE|ARG_TLS, ' ', C_HTTP3_ONLY}, +#ifndef CURL_DISABLE_HTTPSIG + {"httpsig-algo", ARG_STRG, ' ', C_HTTPSIG_ALGORITHM}, + {"httpsig-headers", ARG_STRG, ' ', C_HTTPSIG_HEADERS}, + {"httpsig-key", ARG_FILE|ARG_CLEAR, ' ', C_HTTPSIG_KEY}, + {"httpsig-keyid", ARG_STRG, ' ', C_HTTPSIG_KEYID}, +#endif {"ignore-content-length", ARG_BOOL, ' ', C_IGNORE_CONTENT_LENGTH}, {"include", ARG_BOOL, ' ', C_INCLUDE}, {"insecure", ARG_BOOL, 'k', C_INSECURE}, @@ -254,6 +256,7 @@ static const struct LongShort aliases[]= { {"proxy-digest", ARG_BOOL, ' ', C_PROXY_DIGEST}, {"proxy-header", ARG_STRG, ' ', C_PROXY_HEADER}, {"proxy-http2", ARG_BOOL, ' ', C_PROXY_HTTP2}, + {"proxy-http3", ARG_BOOL, ' ', C_PROXY_HTTP3}, {"proxy-insecure", ARG_BOOL, ' ', C_PROXY_INSECURE}, {"proxy-key", ARG_FILE|ARG_TLS, ' ', C_PROXY_KEY}, {"proxy-key-type", ARG_STRG|ARG_TLS, ' ', C_PROXY_KEY_TYPE}, @@ -267,9 +270,11 @@ static const struct LongShort aliases[]= { {"proxy-ssl-auto-client-cert", ARG_BOOL|ARG_TLS, ' ', C_PROXY_SSL_AUTO_CLIENT_CERT}, {"proxy-tls13-ciphers", ARG_STRG|ARG_TLS, ' ', C_PROXY_TLS13_CIPHERS}, - {"proxy-tlsauthtype", ARG_STRG|ARG_TLS, ' ', C_PROXY_TLSAUTHTYPE}, - {"proxy-tlspassword", ARG_STRG|ARG_TLS|ARG_CLEAR, ' ', C_PROXY_TLSPASSWORD}, - {"proxy-tlsuser", ARG_STRG|ARG_TLS|ARG_CLEAR, ' ', C_PROXY_TLSUSER}, + {"proxy-tlsauthtype", ARG_STRG|ARG_TLS|ARG_DEPR, ' ', C_PROXY_TLSAUTHTYPE}, + {"proxy-tlspassword", ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', + C_PROXY_TLSPASSWORD}, + {"proxy-tlsuser", ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', + C_PROXY_TLSUSER}, {"proxy-tlsv1", ARG_NONE|ARG_TLS, ' ', C_PROXY_TLSV1}, {"proxy-user", ARG_STRG|ARG_CLEAR, 'U', C_PROXY_USER}, {"proxy1.0", ARG_STRG, ' ', C_PROXY1_0}, @@ -341,9 +346,9 @@ static const struct LongShort aliases[]= { {"tls-earlydata", ARG_BOOL|ARG_TLS, ' ', C_TLS_EARLYDATA}, {"tls-max", ARG_STRG|ARG_TLS, ' ', C_TLS_MAX}, {"tls13-ciphers", ARG_STRG|ARG_TLS, ' ', C_TLS13_CIPHERS}, - {"tlsauthtype", ARG_STRG|ARG_TLS, ' ', C_TLSAUTHTYPE}, - {"tlspassword", ARG_STRG|ARG_TLS|ARG_CLEAR, ' ', C_TLSPASSWORD}, - {"tlsuser", ARG_STRG|ARG_TLS|ARG_CLEAR, ' ', C_TLSUSER}, + {"tlsauthtype", ARG_STRG|ARG_TLS|ARG_DEPR, ' ', C_TLSAUTHTYPE}, + {"tlspassword", ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', C_TLSPASSWORD}, + {"tlsuser", ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', C_TLSUSER}, {"tlsv1", ARG_NONE|ARG_TLS, '1', C_TLSV1}, {"tlsv1.0", ARG_NONE|ARG_TLS, ' ', C_TLSV1_0}, {"tlsv1.1", ARG_NONE|ARG_TLS, ' ', C_TLSV1_1}, @@ -542,11 +547,11 @@ struct sizeunit { static const struct sizeunit *getunit(char unit) { static const struct sizeunit list[] = { - {'p', (curl_off_t)1125899906842624, 16 }, /* Peta */ - {'t', (curl_off_t)1099511627776, 13 }, /* Tera */ - {'g', 1073741824, 10 }, /* Giga */ - {'m', 1048576, 7 }, /* Mega */ - {'k', 1024, 4 }, /* Kilo */ + { 'p', (curl_off_t)1125899906842624, 16 }, /* Peta */ + { 't', (curl_off_t)1099511627776, 13 }, /* Tera */ + { 'g', 1073741824, 10 }, /* Giga */ + { 'm', 1048576, 7 }, /* Mega */ + { 'k', 1024, 4 }, /* Kilo */ }; size_t i; @@ -1452,7 +1457,7 @@ static ParameterError parse_range(struct OperationConfig *config, curlx_str_single(&nextarg, '-')) { /* Specifying a range WITHOUT A DASH does create an illegal HTTP range (and does not actually be range by definition). The man page previously - claimed that to be a good way, why this code is added to work-around + claimed that to be a good way, why this code is added to work around it. */ char buffer[32]; warnf("A specified range MUST include at least one dash (-). " @@ -1630,12 +1635,12 @@ static ParameterError parse_time_cond(struct OperationConfig *config, config->timecond = CURL_TIMECOND_IFMODSINCE; break; case '-': - /* If-Unmodified-Since: (section 14.24 in RFC2068) */ + /* If-Unmodified-Since: (section 14.24 in RFC2068) */ config->timecond = CURL_TIMECOND_IFUNMODSINCE; nextarg++; break; case '=': - /* Last-Modified: (section 14.29 in RFC2068) */ + /* Last-Modified: (section 14.29 in RFC2068) */ config->timecond = CURL_TIMECOND_LASTMOD; nextarg++; break; @@ -1720,8 +1725,7 @@ static ParameterError parse_upload_flags(struct OperationConfig *config, } /* if 'toggle' is TRUE, set the 'bits' in 'modify'. - if 'toggle' is FALSE, clear the 'bits' in 'modify' -*/ + if 'toggle' is FALSE, clear the 'bits' in 'modify' */ static void togglebit(bool toggle, unsigned long *modify, unsigned long bits) { if(toggle) @@ -1911,6 +1915,10 @@ static ParameterError opt_bool(struct OperationConfig *config, if(config->ftp_ssl) warnf("--%s is an insecure option, consider --ssl-reqd instead", a->lname); + if(toggle && config->ftp_ssl_control) { + config->ftp_ssl_control = FALSE; + warnf("--%s overrides --ftp-ssl-control", a->lname); + } break; case C_FTP_SSL_CCC: /* --ftp-ssl-ccc */ config->ftp_ssl_ccc = toggle; @@ -1962,6 +1970,10 @@ static ParameterError opt_bool(struct OperationConfig *config, break; case C_FTP_SSL_CONTROL: /* --ftp-ssl-control */ config->ftp_ssl_control = toggle; + if(toggle && config->ftp_ssl) { + config->ftp_ssl = FALSE; + warnf("--%s overrides --ssl", a->lname); + } break; case C_RAW: /* --raw */ config->raw = toggle; @@ -2028,6 +2040,18 @@ static ParameterError opt_bool(struct OperationConfig *config, config->proxyver = toggle ? CURLPROXY_HTTPS2 : CURLPROXY_HTTPS; break; + case C_PROXY_HTTP3: /* --proxy-http3 */ +#ifndef USE_PROXY_HTTP3 + if(toggle) + return PARAM_LIBCURL_DOESNT_SUPPORT; + config->proxyver = CURLPROXY_HTTPS; +#else + if(!feature_httpsproxy || !feature_http3) + return PARAM_LIBCURL_DOESNT_SUPPORT; + + config->proxyver = toggle ? CURLPROXY_HTTPS3 : CURLPROXY_HTTPS; +#endif + break; case C_APPEND: /* --append */ config->ftp_append = toggle; break; @@ -2121,7 +2145,7 @@ static ParameterError opt_bool(struct OperationConfig *config, case C_HEAD: /* --head */ config->no_body = toggle; config->show_headers = toggle; - if(SetHTTPrequest((config->no_body) ? TOOL_HTTPREQ_HEAD : + if(SetHTTPrequest(config->no_body ? TOOL_HTTPREQ_HEAD : TOOL_HTTPREQ_GET, &config->httpreq)) return PARAM_BAD_USE; break; @@ -2345,6 +2369,12 @@ static ParameterError opt_file(struct OperationConfig *config, case C_UPLOAD_FILE: /* --upload-file */ err = parse_upload_file(config, nextarg); break; + case C_HTTPSIG_KEY: /* --httpsig-key */ + if(!feature_httpsig) + err = PARAM_LIBCURL_DOESNT_SUPPORT; + else + err = getstr(&config->httpsig_key, nextarg, DENY_BLANK); + break; } return err; } @@ -2458,7 +2488,7 @@ static ParameterError opt_string(struct OperationConfig *config, { ParameterError err = PARAM_OK; curl_off_t value; - static const char *redir_protos[] = { + static const char * const redir_protos[] = { "http", "https", "ftp", @@ -2539,6 +2569,26 @@ static ParameterError opt_string(struct OperationConfig *config, config->authtype |= CURLAUTH_AWS_SIGV4; err = getstr(&config->aws_sigv4, nextarg, ALLOW_BLANK); break; + case C_HTTPSIG_ALGORITHM: /* --httpsig-algo */ + if(!feature_httpsig) + err = PARAM_LIBCURL_DOESNT_SUPPORT; + else { + config->authtype |= CURLAUTH_HTTPSIG; + err = getstr(&config->httpsig_algorithm, nextarg, DENY_BLANK); + } + break; + case C_HTTPSIG_KEYID: /* --httpsig-keyid */ + if(!feature_httpsig) + err = PARAM_LIBCURL_DOESNT_SUPPORT; + else + err = getstr(&config->httpsig_keyid, nextarg, DENY_BLANK); + break; + case C_HTTPSIG_HEADERS: /* --httpsig-headers */ + if(!feature_httpsig) + err = PARAM_LIBCURL_DOESNT_SUPPORT; + else + err = getstr(&config->httpsig_headers, nextarg, DENY_BLANK); + break; case C_INTERFACE: /* --interface */ /* interface */ err = getstr(&config->iface, nextarg, DENY_BLANK); @@ -2768,31 +2818,7 @@ static ParameterError opt_string(struct OperationConfig *config, } break; case C_HOSTPUBSHA256: /* --hostpubsha256 */ - if(!feature_libssh2) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); - break; - case C_TLSUSER: /* --tlsuser */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->tls_username, nextarg, DENY_BLANK); - break; - case C_TLSPASSWORD: /* --tlspassword */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->tls_password, nextarg, ALLOW_BLANK); - break; - case C_TLSAUTHTYPE: /* --tlsauthtype */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else { - err = getstr(&config->tls_authtype, nextarg, DENY_BLANK); - if(!err && config->tls_authtype && strcmp(config->tls_authtype, "SRP")) - err = PARAM_LIBCURL_DOESNT_SUPPORT; /* only support TLS-SRP */ - } + err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); break; case C_PINNEDPUBKEY: /* --pinnedpubkey */ err = getstr(&config->pinnedpubkey, nextarg, DENY_BLANK); @@ -2800,28 +2826,6 @@ static ParameterError opt_string(struct OperationConfig *config, case C_PROXY_PINNEDPUBKEY: /* --proxy-pinnedpubkey */ err = getstr(&config->proxy_pinnedpubkey, nextarg, DENY_BLANK); break; - case C_PROXY_TLSUSER: /* --proxy-tlsuser */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->proxy_tls_username, nextarg, ALLOW_BLANK); - break; - case C_PROXY_TLSPASSWORD: /* --proxy-tlspassword */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->proxy_tls_password, nextarg, DENY_BLANK); - break; - case C_PROXY_TLSAUTHTYPE: /* --proxy-tlsauthtype */ - if(!feature_tls_srp) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else { - err = getstr(&config->proxy_tls_authtype, nextarg, DENY_BLANK); - if(!err && config->proxy_tls_authtype && - strcmp(config->proxy_tls_authtype, "SRP")) - err = PARAM_LIBCURL_DOESNT_SUPPORT; /* only support TLS-SRP */ - } - break; case C_PROXY_CERT_TYPE: /* --proxy-cert-type */ err = getstr(&config->proxy_cert_type, nextarg, DENY_BLANK); break; @@ -2902,7 +2906,8 @@ static ParameterError opt_string(struct OperationConfig *config, case C_PROXY: /* --proxy */ /* --proxy */ err = getstr(&config->proxy, nextarg, ALLOW_BLANK); - if(config->proxyver != CURLPROXY_HTTPS2) + if(config->proxyver != CURLPROXY_HTTPS2 && + config->proxyver != CURLPROXY_HTTPS3) config->proxyver = CURLPROXY_HTTP; break; case C_REQUEST: /* --request */ @@ -3054,6 +3059,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ *usedarg = consumearg; /* mark it as used */ } if(a->desc & ARG_DEPR) { + if(a->desc & ARG_CLEAR) + cleanarg(CURL_UNCONST(nextarg)); opt_depr(a); break; } diff --git a/src/tool_getparam.h b/src/tool_getparam.h index e137cc322f98..92495a4de670 100644 --- a/src/tool_getparam.h +++ b/src/tool_getparam.h @@ -125,6 +125,10 @@ typedef enum { C_HTTP2_PRIOR_KNOWLEDGE, C_HTTP3, C_HTTP3_ONLY, + C_HTTPSIG_ALGORITHM, + C_HTTPSIG_HEADERS, + C_HTTPSIG_KEY, + C_HTTPSIG_KEYID, C_IGNORE_CONTENT_LENGTH, C_INCLUDE, C_INSECURE, @@ -201,6 +205,7 @@ typedef enum { C_PROXY_DIGEST, C_PROXY_HEADER, C_PROXY_HTTP2, + C_PROXY_HTTP3, C_PROXY_INSECURE, C_PROXY_KEY, C_PROXY_KEY_TYPE, diff --git a/src/tool_getpass.c b/src/tool_getpass.c index 68a16cab3e61..f0eeebcc4164 100644 --- a/src/tool_getpass.c +++ b/src/tool_getpass.c @@ -58,7 +58,7 @@ char *getpass_r(const char *prompt, char *buffer, size_t buflen) long sts; short chan; - /* iosbdef.h was not in VAX V7.2 or CC 6.4 */ + /* iosbdef.h was not in VAX v7.2 or CC 6.4 */ struct _isb { short int iosb$w_status; /* status */ short int iosb$w_bcnt; /* byte count */ diff --git a/src/tool_help.c b/src/tool_help.c index 212972f38248..597a6a554d3f 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -51,6 +51,7 @@ static const struct category_descriptors categories[] = { { "http", "HTTP and HTTPS protocol", CURLHELP_HTTP }, { "imap", "IMAP protocol", CURLHELP_IMAP }, { "ldap", "LDAP protocol", CURLHELP_LDAP }, + { "mqtt", "MQTT protocol", CURLHELP_MQTT }, { "output", "File system output", CURLHELP_OUTPUT }, { "pop3", "POP3 protocol", CURLHELP_POP3 }, { "post", "HTTP POST specific", CURLHELP_POST }, @@ -166,8 +167,9 @@ void inithelpscan(struct scan_ctx *ctx, ctx->flen = strlen(arg); ctx->endarg = endarg; ctx->elen = strlen(endarg); - DEBUGASSERT((ctx->elen < sizeof(ctx->rbuf)) || - (ctx->flen < sizeof(ctx->rbuf))); + DEBUGASSERT((ctx->elen < sizeof(ctx->rbuf)) && + (ctx->flen < sizeof(ctx->rbuf)) && + (ctx->tlen < sizeof(ctx->rbuf))); ctx->show = 0; ctx->olen = 0; memset(ctx->rbuf, 0, sizeof(ctx->rbuf)); diff --git a/src/tool_help.h b/src/tool_help.h index 6a2ecdb0622e..d8562255d3c7 100644 --- a/src/tool_help.h +++ b/src/tool_help.h @@ -85,6 +85,7 @@ struct helptxt { #define CURLHELP_TLS (1 << 23) #define CURLHELP_UPLOAD (1 << 24) #define CURLHELP_VERBOSE (1 << 25) +#define CURLHELP_MQTT (1 << 26) #define CURLHELP_ALL 0xfffffffU diff --git a/src/tool_helpers.c b/src/tool_helpers.c index a23c3a80e75b..1b998d2ca610 100644 --- a/src/tool_helpers.c +++ b/src/tool_helpers.c @@ -29,8 +29,8 @@ #include "tool_helpers.h" /* -** Helper functions that are used from more than one source file. -*/ + * Helper functions that are used from more than one source file. + */ const char *param2text(ParameterError error) { @@ -77,7 +77,7 @@ const char *param2text(ParameterError error) int SetHTTPrequest(HttpReq req, HttpReq *store) { /* this mirrors the HttpReq enum in tool_sdecls.h */ - const char *reqname[] = { + static const char * const reqname[] = { "", /* unspec */ "GET (-G, --get)", "HEAD (-I, --head)", @@ -101,7 +101,7 @@ int SetHTTPrequest(HttpReq req, HttpReq *store) void customrequest_helper(HttpReq req, const char *method) { /* this mirrors the HttpReq enum in tool_sdecls.h */ - const char *dflt[] = { + static const char * const dflt[] = { "GET", "GET", "HEAD", diff --git a/src/tool_libinfo.c b/src/tool_libinfo.c index 5a5382c00701..dfb23912f345 100644 --- a/src/tool_libinfo.c +++ b/src/tool_libinfo.c @@ -27,7 +27,7 @@ /* global variable definitions, for libcurl runtime info */ -static const char *no_protos = NULL; +static const char * const no_protos = NULL; curl_version_info_data *curlinfo = NULL; const char * const *built_in_protos = &no_protos; @@ -69,14 +69,13 @@ bool feature_brotli = FALSE; bool feature_hsts = FALSE; bool feature_http2 = FALSE; bool feature_http3 = FALSE; +bool feature_httpsig = FALSE; bool feature_httpsproxy = FALSE; bool feature_libz = FALSE; -bool feature_libssh2 = FALSE; bool feature_ntlm = FALSE; bool feature_ntlm_wb = FALSE; bool feature_spnego = FALSE; bool feature_ssl = FALSE; -bool feature_tls_srp = FALSE; bool feature_zstd = FALSE; bool feature_ech = FALSE; bool feature_ssls_export = FALSE; @@ -99,6 +98,7 @@ static struct feature_name_presentp { { "HTTP2", &feature_http2, CURL_VERSION_HTTP2 }, { "HTTP3", &feature_http3, CURL_VERSION_HTTP3 }, { "HTTPS-proxy", &feature_httpsproxy, CURL_VERSION_HTTPS_PROXY }, + { "HTTPSIG", &feature_httpsig, 0 }, { "IDN", NULL, CURL_VERSION_IDN }, { "IPv6", NULL, CURL_VERSION_IPV6 }, { "Kerberos", NULL, CURL_VERSION_KERBEROS5 }, @@ -113,7 +113,6 @@ static struct feature_name_presentp { { "SSPI", NULL, CURL_VERSION_SSPI }, { "SSLS-EXPORT", &feature_ssls_export, 0 }, { "threadsafe", NULL, CURL_VERSION_THREADSAFE }, - { "TLS-SRP", &feature_tls_srp, CURL_VERSION_TLSAUTH_SRP }, { "Unicode", NULL, CURL_VERSION_UNICODE }, { "UnixSockets", NULL, CURL_VERSION_UNIX_SOCKETS }, { "zstd", &feature_zstd, CURL_VERSION_ZSTD }, @@ -183,9 +182,6 @@ CURLcode get_libcurl_info(void) ++feature_count; } - feature_libssh2 = curlinfo->age >= CURLVERSION_FOURTH && - curlinfo->libssh_version && - !strncmp("libssh2", curlinfo->libssh_version, 7); return CURLE_OK; } diff --git a/src/tool_libinfo.h b/src/tool_libinfo.h index ddc41a133867..4ca507ad66cb 100644 --- a/src/tool_libinfo.h +++ b/src/tool_libinfo.h @@ -52,14 +52,13 @@ extern bool feature_brotli; extern bool feature_hsts; extern bool feature_http2; extern bool feature_http3; +extern bool feature_httpsig; extern bool feature_httpsproxy; extern bool feature_libz; -extern bool feature_libssh2; extern bool feature_ntlm; extern bool feature_ntlm_wb; extern bool feature_spnego; extern bool feature_ssl; -extern bool feature_tls_srp; extern bool feature_zstd; extern bool feature_ech; extern bool feature_ssls_export; diff --git a/src/tool_listhelp.c b/src/tool_listhelp.c index 864771bfba88..b6d3ddfd1de9 100644 --- a/src/tool_listhelp.c +++ b/src/tool_listhelp.c @@ -36,7 +36,7 @@ const struct helptxt helptext[] = { { " --abstract-unix-socket ", "Connect via abstract Unix domain socket", - CURLHELP_CONNECTION }, + CURLHELP_CONNECTION | CURLHELP_HTTP }, { " --alt-svc ", "Enable alt-svc with this cache file", CURLHELP_HTTP }, @@ -51,7 +51,7 @@ const struct helptxt helptext[] = { CURLHELP_AUTH | CURLHELP_HTTP }, { " --basic", "HTTP Basic Authentication", - CURLHELP_AUTH }, + CURLHELP_AUTH | CURLHELP_HTTP }, { " --ca-native", "Load CA certs from the OS", CURLHELP_TLS }, @@ -78,7 +78,7 @@ const struct helptxt helptext[] = { CURLHELP_HTTP }, { " --compressed-ssh", "Enable SSH compression", - CURLHELP_SCP | CURLHELP_SSH }, + CURLHELP_SCP | CURLHELP_SFTP | CURLHELP_SSH }, { "-K, --config ", "Read config from a file", CURLHELP_CURL }, @@ -113,8 +113,9 @@ const struct helptxt helptext[] = { "(EC) TLS key exchange algorithms to request", CURLHELP_TLS }, { "-d, --data ", - "HTTP POST data", - CURLHELP_IMPORTANT | CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD }, + "Post data", + CURLHELP_IMPORTANT | CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD | + CURLHELP_MQTT }, { " --data-ascii ", "HTTP POST ASCII data", CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD }, @@ -211,7 +212,8 @@ const struct helptxt helptext[] = { CURLHELP_SMTP }, { " --form-escape", "Escape form fields using backslash", - CURLHELP_HTTP | CURLHELP_UPLOAD | CURLHELP_POST }, + CURLHELP_HTTP | CURLHELP_UPLOAD | CURLHELP_POST | CURLHELP_IMAP | + CURLHELP_SMTP }, { " --form-string ", "Specify multipart MIME data", CURLHELP_HTTP | CURLHELP_UPLOAD | CURLHELP_POST | CURLHELP_SMTP | @@ -303,6 +305,26 @@ const struct helptxt helptext[] = { { " --http3-only", "Use HTTP/3 only", CURLHELP_HTTP }, +#ifndef CURL_DISABLE_HTTPSIG + { " --httpsig-algo ", + "Algorithm for HTTP Message Signatures", + CURLHELP_AUTH | CURLHELP_HTTP }, +#endif +#ifndef CURL_DISABLE_HTTPSIG + { " --httpsig-headers ", + "Components to sign for HTTP Message Signatures", + CURLHELP_AUTH | CURLHELP_HTTP }, +#endif +#ifndef CURL_DISABLE_HTTPSIG + { " --httpsig-key ", + "Key for HTTP Message Signatures", + CURLHELP_AUTH | CURLHELP_HTTP }, +#endif +#ifndef CURL_DISABLE_HTTPSIG + { " --httpsig-keyid ", + "Key identifier for HTTP Message Signatures", + CURLHELP_AUTH | CURLHELP_HTTP }, +#endif { " --ignore-content-length", "Ignore the size of the remote resource", CURLHELP_HTTP | CURLHELP_FTP }, @@ -315,9 +337,11 @@ const struct helptxt helptext[] = { { " --ip-tos ", "Set IP Type of Service or Traffic Class", CURLHELP_CONNECTION }, +#ifndef CURL_DISABLE_IPFS { " --ipfs-gateway ", "Gateway for IPFS", CURLHELP_CURL }, +#endif { "-4, --ipv4", "Resolve names to IPv4 addresses", CURLHELP_CONNECTION | CURLHELP_DNS }, @@ -338,13 +362,13 @@ const struct helptxt helptext[] = { CURLHELP_CONNECTION | CURLHELP_TIMEOUT }, { " --key ", "Private key filename", - CURLHELP_TLS | CURLHELP_SSH }, + CURLHELP_TLS | CURLHELP_SFTP | CURLHELP_SCP | CURLHELP_SSH }, { " --key-type ", "Private key file type (DER/PEM/ENG)", CURLHELP_TLS }, { " --knownhosts ", "Specify knownhosts path", - CURLHELP_SSH }, + CURLHELP_SFTP | CURLHELP_SCP | CURLHELP_SSH }, { " --krb ", "Enable Kerberos with security ", CURLHELP_DEPRECATED }, @@ -387,7 +411,7 @@ const struct helptxt helptext[] = { CURLHELP_CURL }, { " --max-filesize ", "Maximum file size to download", - CURLHELP_CONNECTION }, + CURLHELP_CONNECTION | CURLHELP_FTP | CURLHELP_HTTP | CURLHELP_MQTT }, { " --max-redirs ", "Maximum number of redirects allowed", CURLHELP_HTTP }, @@ -448,7 +472,7 @@ const struct helptxt helptext[] = { { " --oauth2-bearer ", "OAuth 2 Bearer Token", CURLHELP_AUTH | CURLHELP_IMAP | CURLHELP_POP3 | CURLHELP_SMTP | - CURLHELP_LDAP }, + CURLHELP_LDAP | CURLHELP_HTTP }, { " --out-null", "Discard response data into the void", CURLHELP_OUTPUT }, @@ -472,7 +496,8 @@ const struct helptxt helptext[] = { CURLHELP_CONNECTION | CURLHELP_CURL | CURLHELP_GLOBAL }, { " --pass ", "Passphrase for the private key", - CURLHELP_SSH | CURLHELP_TLS | CURLHELP_AUTH }, + CURLHELP_TLS | CURLHELP_SFTP | CURLHELP_SCP | CURLHELP_SSH | + CURLHELP_AUTH }, { " --path-as-is", "Do not squash .. sequences in URL path", CURLHELP_CURL }, @@ -538,10 +563,13 @@ const struct helptxt helptext[] = { CURLHELP_PROXY | CURLHELP_TLS }, { " --proxy-header
", "Pass custom header(s) to proxy", - CURLHELP_PROXY }, + CURLHELP_PROXY | CURLHELP_HTTP }, { " --proxy-http2", "Use HTTP/2 with HTTPS proxy", CURLHELP_HTTP | CURLHELP_PROXY }, + { " --proxy-http3", + "Use HTTP/3 with HTTPS proxy", + CURLHELP_HTTP | CURLHELP_PROXY }, { " --proxy-insecure", "Skip HTTPS proxy cert verification", CURLHELP_PROXY | CURLHELP_TLS }, @@ -619,7 +647,7 @@ const struct helptxt helptext[] = { CURLHELP_HTTP }, { "-J, --remote-header-name", "Use the header-provided filename", - CURLHELP_OUTPUT }, + CURLHELP_OUTPUT | CURLHELP_HTTP }, { "-O, --remote-name", "Write output to file named as remote file", CURLHELP_IMPORTANT | CURLHELP_OUTPUT }, @@ -659,10 +687,12 @@ const struct helptxt helptext[] = { CURLHELP_CURL | CURLHELP_TIMEOUT }, { " --sasl-authzid ", "Identity for SASL PLAIN authentication", - CURLHELP_AUTH }, + CURLHELP_AUTH | CURLHELP_IMAP | CURLHELP_SMTP | CURLHELP_POP3 | + CURLHELP_LDAP }, { " --sasl-ir", "Initial response in SASL authentication", - CURLHELP_AUTH }, + CURLHELP_AUTH | CURLHELP_IMAP | CURLHELP_POP3 | CURLHELP_LDAP | + CURLHELP_SMTP }, { " --service-name ", "SPNEGO service name", CURLHELP_AUTH }, @@ -671,7 +701,8 @@ const struct helptxt helptext[] = { CURLHELP_CURL | CURLHELP_GLOBAL }, { "-i, --show-headers", "Show response headers in output", - CURLHELP_IMPORTANT | CURLHELP_VERBOSE | CURLHELP_OUTPUT }, + CURLHELP_IMPORTANT | CURLHELP_VERBOSE | CURLHELP_OUTPUT | CURLHELP_HTTP | + CURLHELP_FTP }, { " --sigalgs ", "TLS signature algorithms to use", CURLHELP_TLS }, @@ -714,7 +745,7 @@ const struct helptxt helptext[] = { { " --ssl", "Try enabling TLS", CURLHELP_TLS | CURLHELP_IMAP | CURLHELP_POP3 | CURLHELP_SMTP | - CURLHELP_LDAP }, + CURLHELP_LDAP | CURLHELP_FTP }, { " --ssl-allow-beast", "Allow security flaw to improve interop", CURLHELP_TLS }, @@ -727,7 +758,7 @@ const struct helptxt helptext[] = { { " --ssl-reqd", "Require SSL/TLS", CURLHELP_TLS | CURLHELP_IMAP | CURLHELP_POP3 | CURLHELP_SMTP | - CURLHELP_LDAP }, + CURLHELP_LDAP | CURLHELP_FTP }, { " --ssl-revoke-best-effort", "Ignore missing cert CRL dist points", CURLHELP_TLS }, @@ -820,13 +851,13 @@ const struct helptxt helptext[] = { CURLHELP_VERBOSE | CURLHELP_GLOBAL }, { " --unix-socket ", "Connect through this Unix domain socket", - CURLHELP_CONNECTION }, + CURLHELP_CONNECTION | CURLHELP_HTTP }, { "-T, --upload-file ", "Transfer local FILE to destination", - CURLHELP_IMPORTANT | CURLHELP_UPLOAD }, + CURLHELP_IMPORTANT | CURLHELP_UPLOAD | CURLHELP_IMAP }, { " --upload-flags ", "IMAP upload behavior", - CURLHELP_CURL | CURLHELP_OUTPUT }, + CURLHELP_CURL | CURLHELP_OUTPUT | CURLHELP_IMAP | CURLHELP_UPLOAD }, { " --url ", "URL(s) to work with", CURLHELP_CURL }, diff --git a/src/tool_main.c b/src/tool_main.c index e5e4659b1585..d3eff9c91956 100644 --- a/src/tool_main.c +++ b/src/tool_main.c @@ -73,7 +73,6 @@ int _CRT_glob = 0; /* if we build a static library for unit tests, there is no main() function */ #ifndef UNITTESTS -#if defined(HAVE_PIPE) && defined(HAVE_FCNTL) /* * Ensure that file descriptors 0, 1 and 2 (stdin, stdout, stderr) are * open before starting to run. Otherwise, the first three network @@ -86,17 +85,27 @@ int _CRT_glob = 0; */ static int main_checkfds(void) { - int fd[2]; - while((fcntl(STDIN_FILENO, F_GETFD) == -1) || - (fcntl(STDOUT_FILENO, F_GETFD) == -1) || - (fcntl(STDERR_FILENO, F_GETFD) == -1)) - if(pipe(fd)) +#if defined(HAVE_FCNTL) && defined(HAVE_PIPE) + const char * const devnull = "/dev/null"; + int fd; + while((fcntl(STDIN_FILENO, F_GETFD) == -1)) { + fd = curlx_open(devnull, O_RDONLY); + if(fd < 0) return 1; + } + while((fcntl(STDOUT_FILENO, F_GETFD) == -1)) { + fd = curlx_open(devnull, O_WRONLY); + if(fd < 0) + return 1; + } + while((fcntl(STDERR_FILENO, F_GETFD) == -1)) { + fd = curlx_open(devnull, O_WRONLY); + if(fd < 0) + return 1; + } +#endif /* HAVE_FCNTL && HAVE_PIPE */ return 0; } -#else -#define main_checkfds() 0 -#endif #ifdef CURL_MEMDEBUG static void memory_tracking_init(void) @@ -129,8 +138,8 @@ static void memory_tracking_init(void) #endif /* -** curl tool main function. -*/ + * curl tool main function. + */ #ifdef _UNICODE #ifdef CURL_HAVE_DIAG /* GCC does not know about wmain() */ @@ -150,7 +159,7 @@ int main(int argc, char *argv[]) #ifdef _WIN32 /* Undocumented diagnostic option to list the full paths of all loaded modules. This is purposely pre-init. */ - if(argc == 2 && !_tcscmp(argv[1], _T("--dump-module-paths"))) { + if(argc == 2 && !_tcscmp(argv[1], _TEXT("--dump-module-paths"))) { struct curl_slist *item, *head = GetLoadedModulePaths(); for(item = head; item; item = item->next) curl_mprintf("%s\n", item->data); @@ -161,7 +170,7 @@ int main(int argc, char *argv[]) /* win32_init must be called before other init routines. */ result = win32_init(); if(result) { - errorf("(%d) Windows-specific init failed", result); + errorf("(%d) Windows-specific init failed", (int)result); return (int)result; } #endif diff --git a/src/tool_msgs.c b/src/tool_msgs.c index 2e1245503f6b..50a2fa086075 100644 --- a/src/tool_msgs.c +++ b/src/tool_msgs.c @@ -74,11 +74,11 @@ static void voutf(const char *prefix, const char *fmt, va_list ap) /* * Emit 'note' formatted message on configured 'errors' stream, if verbose was - * selected. + * selected and mute (--silent) was not. */ void notef(const char *fmt, ...) { - if(global && global->tracetype) { + if(global && global->tracetype && !global->silent) { va_list ap; va_start(ap, fmt); voutf(NOTE_PREFIX, fmt, ap); diff --git a/src/tool_operate.c b/src/tool_operate.c index 3ad30ca4c4e8..439932f5b6cd 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -178,7 +178,6 @@ static curl_off_t VmsSpecialSize(const char *name, case FAB$C_VAR: case FAB$C_VFC: return vms_realfilesize(name, stat_buf); - break; default: return stat_buf->st_size; } @@ -616,7 +615,7 @@ static CURLcode post_check_result(struct per_transfer *per, CURLcode result) if(!config->synthetic_error && result && (!global->silent || global->showerror)) { const char *msg = per->errorbuffer; - curl_mfprintf(tool_stderr, "curl: (%d) %s\n", result, + curl_mfprintf(tool_stderr, "curl: (%d) %s\n", (int)result, msg[0] ? msg : curl_easy_strerror(result)); if(result == CURLE_PEER_FAILED_VERIFICATION) fputs(CURL_CA_CERT_ERRORMSG, tool_stderr); @@ -651,7 +650,7 @@ static CURLcode post_output_handling(struct per_transfer *per, /* Set file extended attributes */ if(!result && config->xattr && outs->fopened && outs->stream) { - rc = fwrite_xattr(curl, per->url, fileno(outs->stream)); + rc = fwrite_xattr(curl, per->url, fileno(outs->stream), outs->filename); if(rc) { char errbuf[STRERROR_LEN]; warnf("Error setting extended attributes on '%s': %s", outs->filename, @@ -671,7 +670,7 @@ static CURLcode post_output_handling(struct per_transfer *per, return CURLE_WRITE_ERROR; } - if(!outs->regular_file && outs->stream) { + if(!outs->regular_file && outs->stream && !outs->out_null) { /* Dump standard stream buffered data */ rc = fflush(outs->stream); if(!result && rc) { @@ -697,7 +696,7 @@ static CURLcode post_close_output(struct per_transfer *per, if(!result && rc) { /* something went wrong in the writing process */ result = CURLE_WRITE_ERROR; - errorf("curl: (%d) Failed writing body", result); + errorf("curl: (%d) Failed writing body", (int)result); } if(result && config->rm_partial) { curlx_struct_stat st; @@ -722,6 +721,7 @@ static CURLcode post_close_output(struct per_transfer *per, } return result; } + /* * Call this after a transfer has completed. */ @@ -855,7 +855,7 @@ static CURLcode append2query(struct OperationConfig *config, if(uerr) { result = urlerr_cvt(uerr); errorf("(%d) Could not parse the URL, " - "failed to set query", result); + "failed to set query", (int)result); config->synthetic_error = TRUE; } else { @@ -1052,11 +1052,13 @@ static CURLcode setup_outfile(struct OperationConfig *config, return result; } } - else if(glob_inuse(&state->urlglob)) { - /* fill '#1' ... '#9' terms from URL pattern */ + else if(glob_inuse(&state->urlglob) || glob_inuse(&state->inglob)) { + /* expand '#1' ... '#9' references from URL pattern and named references + from the upload file glob */ SANITIZEcode sc; CURLcode result = - glob_match_url(&per->outfile, u->outfile, &state->urlglob, &sc); + glob_match_url(&per->outfile, u->outfile, &state->urlglob, + glob_inuse(&state->inglob) ? &state->inglob : NULL, &sc); if(sc) { if(sc == SANITIZE_ERR_OUT_OF_MEMORY) @@ -1067,7 +1069,12 @@ static CURLcode setup_outfile(struct OperationConfig *config, } else if(result) { /* bad globbing */ - warnf("bad output glob"); + if(state->urlglob.error) { + glob_show_error(&state->urlglob, u->outfile, tool_stderr, result); + config->synthetic_error = TRUE; + } + else + warnf("bad output glob"); return result; } if(!*per->outfile) { @@ -1545,7 +1552,13 @@ static CURLcode add_parallel_transfers(CURLM *multi, CURLSH *share, return CURLE_UNKNOWN_OPTION; } - if(nxfers < (curl_off_t)(global->parallel_max * 2)) { + if(all_added >= global->parallel_max) { + /* we are at max parallelism, no need to create more transfers */ + *morep = TRUE; /* pretend there are, we have not checked */ + return CURLE_OK; + } + /* if the list is empty, create one to kickstart the loop */ + if(!transfers) { bool skipped = FALSE; do { result = create_transfer(share, addedp, &skipped); @@ -1553,6 +1566,7 @@ static CURLcode add_parallel_transfers(CURLM *multi, CURLSH *share, return result; } while(skipped); } + /* add transfers until max parallelism is achieved again */ for(per = transfers; per && (all_added < global->parallel_max); per = per->next) { if(per->added || per->skip) @@ -1701,9 +1715,7 @@ static int cb_timeout(CURLM *multi, long timeout_ms, void *userp) static struct contextuv *create_context(curl_socket_t sockfd, struct datauv *uv) { - struct contextuv *c; - - c = (struct contextuv *)curlx_malloc(sizeof(*c)); + struct contextuv *c = curlx_malloc(sizeof(*c)); c->sockfd = sockfd; c->uv = uv; @@ -1762,7 +1774,8 @@ static int cb_socket(CURL *easy, curl_socket_t s, int action, } break; default: - abort(); + DEBUGASSERT(0); + return -1; } return 0; @@ -2146,8 +2159,7 @@ static CURLcode serial_transfers(CURLSH *share) } } if(returncode) - /* returncode errors have priority */ - result = returncode; + result = returncode; /* returncode errors have priority */ if(result) single_transfer_cleanup(); @@ -2155,33 +2167,60 @@ static CURLcode serial_transfers(CURLSH *share) return result; } -static CURLcode is_using_schannel(int *pusing) +#ifdef _WIN32 +/* returns TRUE if using Schannel or if there is an error, passes back result + in 'resultp' */ +static bool win32_using_schannel(CURLcode *resultp) { - CURLcode result = CURLE_OK; static int using_schannel = -1; /* -1 = not checked 0 = nope 1 = yes */ + *resultp = CURLE_OK; if(using_schannel == -1) { - CURL *curltls = curl_easy_init(); + CURL *curl = curl_easy_init(); /* The TLS backend remains, so keep the info */ const struct curl_tlssessioninfo *tls_backend_info = NULL; - if(!curltls) - result = CURLE_OUT_OF_MEMORY; + if(!curl) + *resultp = CURLE_OUT_OF_MEMORY; else { - result = curl_easy_getinfo(curltls, CURLINFO_TLS_SSL_PTR, - &tls_backend_info); - if(!result) + *resultp = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, + &tls_backend_info); + if(!*resultp) using_schannel = (tls_backend_info->backend == CURLSSLBACKEND_SCHANNEL); } - curl_easy_cleanup(curltls); + curl_easy_cleanup(curl); + if(*resultp) + return TRUE; + } + return using_schannel == 1; +} + +static CURLcode win32_setup_certs(struct OperationConfig *config) +{ + if(!config->capath && !config->cacert) { +#ifdef CURL_CA_SEARCH_SAFE + char *cacert = NULL; + FILE *cafile = tool_execpath("curl-ca-bundle.crt", &cacert); + if(cafile) { + curlx_fclose(cafile); + config->cacert = curlx_strdup(cacert); + if(!config->cacert) + return CURLE_OUT_OF_MEMORY; + } +#elif !defined(CURL_WINDOWS_UWP) && !defined(CURL_DISABLE_CA_SEARCH) + CURLcode result = FindWin32CACert(config, TEXT("curl-ca-bundle.crt")); if(result) return result; +#endif } - *pusing = using_schannel; - return result; + return CURLE_OK; } +#else +#define win32_setup_certs(x) CURLE_OK +#define win32_using_schannel(x) FALSE +#endif /* Set the CA cert locations specified in the environment. For Windows if no * environment-specified filename is found then check for CA bundle default @@ -2197,70 +2236,47 @@ static CURLcode is_using_schannel(int *pusing) static CURLcode cacertpaths(struct OperationConfig *config) { char *env; - CURLcode result; - int using_schannel; + CURLcode result = CURLE_OK; if(!feature_ssl || config->cacert || config->capath || (config->insecure_ok && (!config->doh_url || config->doh_insecure_ok))) return CURLE_OK; - result = is_using_schannel(&using_schannel); - if(result || using_schannel) + if(win32_using_schannel(&result)) return result; env = curl_getenv("CURL_CA_BUNDLE"); if(env) { config->cacert = curlx_strdup(env); curl_free(env); - if(!config->cacert) { + if(!config->cacert) result = CURLE_OUT_OF_MEMORY; - goto fail; - } } else { env = curl_getenv("SSL_CERT_DIR"); if(env) { config->capath = curlx_strdup(env); curl_free(env); - if(!config->capath) { + if(!config->capath) result = CURLE_OUT_OF_MEMORY; - goto fail; - } } - env = curl_getenv("SSL_CERT_FILE"); - if(env) { - config->cacert = curlx_strdup(env); - curl_free(env); - if(!config->cacert) { - result = CURLE_OUT_OF_MEMORY; - goto fail; + if(!result) { + env = curl_getenv("SSL_CERT_FILE"); + if(env) { + config->cacert = curlx_strdup(env); + curl_free(env); + if(!config->cacert) + result = CURLE_OUT_OF_MEMORY; } } } -#ifdef _WIN32 - if(!config->capath && !config->cacert) { -#ifdef CURL_CA_SEARCH_SAFE - char *cacert = NULL; - FILE *cafile = tool_execpath("curl-ca-bundle.crt", &cacert); - if(cafile) { - curlx_fclose(cafile); - config->cacert = curlx_strdup(cacert); - if(!config->cacert) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - } -#elif !defined(CURL_WINDOWS_UWP) && !defined(CURL_DISABLE_CA_SEARCH) - result = FindWin32CACert(config, TEXT("curl-ca-bundle.crt")); - if(result) - goto fail; -#endif + if(!result) + result = win32_setup_certs(config); + if(result) { + curlx_safefree(config->capath); + curlx_safefree(config->cacert); } -#endif - return CURLE_OK; -fail: - curlx_safefree(config->capath); return result; } @@ -2452,7 +2468,7 @@ CURLcode operate(int argc, argv_item_t argv[]) } else { if(global->libcurl) { - /* Initialise the libcurl source output */ + /* Initialize the libcurl source output */ result = easysrc_init(); } diff --git a/src/tool_operhlp.c b/src/tool_operhlp.c index 0e360cbcbde5..316393abb8af 100644 --- a/src/tool_operhlp.c +++ b/src/tool_operhlp.c @@ -175,6 +175,8 @@ CURLcode add_file_name_to_url(CURL *curl, char **inurlp, const char *filename) return result; } +#define DEFAULT_FILENAME "curl_response" + /* Extracts the name portion of the URL. * Returns a pointer to a heap-allocated string or NULL if * no name part, at location indicated by first argument. @@ -218,8 +220,9 @@ CURLcode get_url_file_name(char **filename, const char *url, SANITIZEcode *sc) } else { /* no slash => empty string, use default */ - *filename = curlx_strdup("curl_response"); - warnf("No remote filename, uses \"%s\"", *filename); + *filename = curlx_strdup(DEFAULT_FILENAME); + if(*filename) + warnf("No remote filename, uses \"" DEFAULT_FILENAME "\""); } curl_free(path); diff --git a/src/tool_paramhlp.c b/src/tool_paramhlp.c index 18e2d1a62578..e425b36411b4 100644 --- a/src/tool_paramhlp.c +++ b/src/tool_paramhlp.c @@ -64,8 +64,7 @@ struct getout *new_getout(struct OperationConfig *config) countcrlf TRUE - return number of bytes from the start that are ONLY CR or LF or NULL. - -*/ + */ static size_t memcrlf(char *orig, bool countcrlf, /* TRUE if we count CRLF, FALSE if we count non-CRLF */ @@ -298,7 +297,7 @@ ParameterError secs2ms(long *val, const char *str) { curl_off_t secs; long ms = 0; - const unsigned int digs[] = { + static const unsigned int digs[] = { 1, 10, 100, @@ -483,7 +482,8 @@ ParameterError proto2num(const char * const *val, char **ostr, const char *str) if no protocols are allowed */ if(action == set) protoset[0] = NULL; - warnf("unrecognized protocol '%s'", buffer); + errorf("unrecognized protocol '%s'", buffer); + return PARAM_BAD_USE; } } if(next) diff --git a/src/tool_parsecfg.c b/src/tool_parsecfg.c index f5910f404e77..e7d3a3f87827 100644 --- a/src/tool_parsecfg.c +++ b/src/tool_parsecfg.c @@ -77,15 +77,18 @@ static int unslashquote(const char *line, struct dynbuf *param) return 0; /* ok */ } -/* return 0 on everything-is-fine, and non-zero otherwise */ -ParameterError parseconfig(const char *filename, int max_recursive, - char **resolved) +/* + * Open the config file. When filename is NULL, tries to find .curlrc in the + * home directory (and on Windows, in the executable directory). Updates + * *namep to the effective filename and *pathalloc to any allocated path + * that must be freed by the caller. Returns the opened FILE or NULL. + */ +static FILE *open_config_file(const char *filename, + const char **namep, + char **pathalloc) { FILE *file = NULL; - bool usedarg = FALSE; - ParameterError err = PARAM_OK; - struct OperationConfig *config = global->last; - char *pathalloc = NULL; + *pathalloc = NULL; if(!filename) { /* NULL means load .curlrc from homedir! */ @@ -94,9 +97,9 @@ ParameterError parseconfig(const char *filename, int max_recursive, file = curlx_fopen(curlrc, FOPEN_READTEXT); if(!file) { curlx_free(curlrc); - return PARAM_READ_ERROR; + return NULL; } - filename = pathalloc = curlrc; + *namep = *pathalloc = curlrc; } #ifdef _WIN32 else { @@ -107,16 +110,154 @@ ParameterError parseconfig(const char *filename, int max_recursive, file = tool_execpath("_curlrc", &fullp); if(file) /* this is the filename we read from */ - filename = fullp; + *namep = fullp; } #endif } else { if(strcmp(filename, "-")) file = curlx_fopen(filename, FOPEN_READTEXT); - else + else { file = stdin; + *namep = ""; + } } + return file; +} + +/* + * Extract the parameter value from a config line. The line pointer should + * be positioned after the option keyword has been null-terminated. + * Skips separators and whitespace, then handles quoted and unquoted + * parameter values. Sets *param_out to the parameter string; unquoted empty + * values set it to NULL, while quoted empty values become an empty string. + */ +static ParameterError extract_param(char *line, + bool dashed_option, + struct dynbuf *pbuf, + const char *filename, + int lineno, + const char *option, + char **param_out) +{ + /* pass spaces and separator(s) */ + while(ISBLANK(*line) || ISSEP(*line, dashed_option)) + line++; + + /* the parameter starts here (unless quoted) */ + if(*line == '\"') { + /* quoted parameter, do the quote dance */ + int rc = unslashquote(++line, pbuf); + if(rc) + return PARAM_BAD_USE; + *param_out = curlx_dyn_len(pbuf) ? curlx_dyn_ptr(pbuf) : CURL_UNCONST(""); + } + else { + if(*line == '\'') { + warnf("%s:%d Option '%s' uses argument with leading single quote. " + "It is probably a mistake. Consider double quotes.", + filename, lineno, option); + } + *param_out = line; /* parameter starts here */ + while(*line && !ISSPACE(*line)) /* stop also on CRLF */ + line++; + + if(*line) { + *line = '\0'; /* null-terminate */ + + /* to detect mistakes better, see if there is data following */ + line++; + /* pass all spaces */ + while(ISBLANK(*line)) + line++; + + switch(*line) { + case '\0': + case '\r': + case '\n': + case '#': /* comment */ + break; + default: + warnf("%s:%d Option '%s' uses argument with unquoted whitespace. " + "This may cause side-effects. Consider double quotes.", + filename, lineno, option); + } + } + if(!**param_out) + /* do this so getparameter can check for required parameters. + Otherwise it always thinks there is a parameter. */ + *param_out = NULL; + } + return PARAM_OK; +} + +/* + * Process the result from getparameter. Handles PARAM_NEXT_OPERATION + * by allocating a new config, and reports errors for other non-OK results. + * Updates *configp if a new operation config is allocated. + * Returns PARAM_OK if processing should continue, or an error code. + */ +static ParameterError process_config_result(ParameterError res, + struct OperationConfig **configp, + const char *param, + bool usedarg, + const char *filename, + int lineno, + const char *option) +{ + if(!res && param && *param && !usedarg) + /* we passed in a parameter that was not used! */ + res = PARAM_GOT_EXTRA_PARAMETER; + + if(res == PARAM_NEXT_OPERATION) { + struct OperationConfig *config = *configp; + if(config->url_list && config->url_list->url) { + /* Allocate the next config */ + config->next = config_alloc(); + if(config->next) { + /* Update the last operation pointer */ + global->last = config->next; + + /* Move onto the new config */ + config->next->prev = config; + *configp = config->next; + } + else + res = PARAM_NO_MEM; + } + } + + if(res != PARAM_OK && res != PARAM_NEXT_OPERATION) { + const char *display = filename; + /* the help request is not really an error */ + if(!strcmp(filename, "-")) + display = ""; + if(res != PARAM_HELP_REQUESTED && + res != PARAM_MANUAL_REQUESTED && + res != PARAM_VERSION_INFO_REQUESTED && + res != PARAM_ENGINES_REQUESTED && + res != PARAM_CA_EMBED_REQUESTED) { + const char *reason = param2text(res); + errorf("%s:%d config file option '%s' %s", + display, lineno, option, reason); + if(res == PARAM_OPTION_UNKNOWN) + res = PARAM_CONFIG_OPTION_UNKNOWN; + return res; + } + } + return PARAM_OK; +} + +ParameterError parseconfig(const char *filename, int max_recursive, + char **resolved) +{ + FILE *file = NULL; + bool usedarg = FALSE; + ParameterError err = PARAM_OK; + struct OperationConfig *config = global->last; + char *pathalloc = NULL; + + file = open_config_file(filename, &filename, &pathalloc); if(file) { char *line; @@ -151,109 +292,20 @@ ParameterError parseconfig(const char *filename, int max_recursive, /* ... and has ended here */ if(*line) - *line++ = '\0'; /* null-terminate, we have a local copy of the data */ - -#ifdef DEBUG_CONFIG - curl_mfprintf(tool_stderr, "GOT: %s\n", option); -#endif - - /* pass spaces and separator(s) */ - while(ISBLANK(*line) || ISSEP(*line, dashed_option)) - line++; - - /* the parameter starts here (unless quoted) */ - if(*line == '\"') { - /* quoted parameter, do the quote dance */ - int rc = unslashquote(++line, &pbuf); - if(rc) { - err = PARAM_BAD_USE; - break; - } - param = curlx_dyn_len(&pbuf) ? curlx_dyn_ptr(&pbuf) : CURL_UNCONST(""); - } - else { - if(*line == '\'') { - warnf("%s:%d Option '%s' uses argument with leading single quote. " - "It is probably a mistake. Consider double quotes.", - filename, lineno, option); - } - param = line; /* parameter starts here */ - while(*line && !ISSPACE(*line)) /* stop also on CRLF */ - line++; - - if(*line) { - *line = '\0'; /* null-terminate */ + *line++ = '\0'; /* null-terminate, we have a local copy */ - /* to detect mistakes better, see if there is data following */ - line++; - /* pass all spaces */ - while(ISBLANK(*line)) - line++; - - switch(*line) { - case '\0': - case '\r': - case '\n': - case '#': /* comment */ - break; - default: - warnf("%s:%d Option '%s' uses argument with unquoted whitespace. " - "This may cause side-effects. Consider double quotes.", - filename, lineno, option); - } - } - if(!*param) - /* do this so getparameter can check for required parameters. - Otherwise it always thinks there is a parameter. */ - param = NULL; - } + /* if there is a parameter for this option, extract it */ + err = extract_param(line, dashed_option, &pbuf, filename, lineno, + option, ¶m); + if(err) + break; -#ifdef DEBUG_CONFIG - curl_mfprintf(tool_stderr, "PARAM: \"%s\"\n", - (param ? param : "(null)")); -#endif res = getparameter(option, param, &usedarg, config, max_recursive); - config = global->last; - if(!res && param && *param && !usedarg) - /* we passed in a parameter that was not used! */ - res = PARAM_GOT_EXTRA_PARAMETER; - - if(res == PARAM_NEXT_OPERATION) { - if(config->url_list && config->url_list->url) { - /* Allocate the next config */ - config->next = config_alloc(); - if(config->next) { - /* Update the last operation pointer */ - global->last = config->next; - - /* Move onto the new config */ - config->next->prev = config; - config = config->next; - } - else - res = PARAM_NO_MEM; - } - } + config = global->last; - if(res != PARAM_OK && res != PARAM_NEXT_OPERATION) { - /* the help request is not really an error */ - if(!strcmp(filename, "-")) { - filename = ""; - } - if(res != PARAM_HELP_REQUESTED && - res != PARAM_MANUAL_REQUESTED && - res != PARAM_VERSION_INFO_REQUESTED && - res != PARAM_ENGINES_REQUESTED && - res != PARAM_CA_EMBED_REQUESTED) { - const char *reason = param2text(res); - errorf("%s:%d config file option '%s' %s", - filename, lineno, option, reason); - if(res == PARAM_OPTION_UNKNOWN) - res = PARAM_CONFIG_OPTION_UNKNOWN; - err = res; - } - } + err = process_config_result(res, &config, param, usedarg, filename, + lineno, option); } curlx_dyn_free(&buf); curlx_dyn_free(&pbuf); diff --git a/src/tool_parsecfg.h b/src/tool_parsecfg.h index 860b9df38eda..54e826fd1061 100644 --- a/src/tool_parsecfg.h +++ b/src/tool_parsecfg.h @@ -29,6 +29,6 @@ #define CONFIG_MAX_LEVELS 5 ParameterError parseconfig(const char *filename, int max_recursive, char **resolved); -bool my_get_line(FILE *fp, struct dynbuf *db, bool *error); +bool my_get_line(FILE *input, struct dynbuf *buf, bool *error); #endif /* HEADER_CURL_TOOL_PARSECFG_H */ diff --git a/src/tool_progress.c b/src/tool_progress.c index 2f15e8f6ae8d..d3ee694fea32 100644 --- a/src/tool_progress.c +++ b/src/tool_progress.c @@ -30,12 +30,12 @@ but never longer than 5 columns (+ one zero byte). Add suffix k, M, G when suitable... - Unit test @1622 -*/ + Unit test 1622 + */ UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen) { /* a signed 64-bit value is 8192 petabytes maximum */ - const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; + static const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; int k = 0; if(bytes < 100000) { curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); @@ -90,7 +90,7 @@ int xferinfo_cb(void *clientp, /* Provide a time string that is 8 letters long (plus the zero byte) - Unit test @1622 + Unit test 1622 */ UNITTEST void timebuf(char *r, size_t rlen, curl_off_t seconds) { @@ -152,9 +152,9 @@ static void add_offt(curl_off_t *val, curl_off_t add) } /* - |DL% UL% Dled Uled Xfers Live Total Current Left Speed - | 6 -- 9.9G 0 2 2 0:00:40 0:00:02 0:00:37 4087M -*/ + |DL% UL% Dled Uled Xfers Live Total Current Left Speed + | 6 -- 9.9G 0 2 2 0:00:40 0:00:02 0:00:37 4087M + */ bool progress_meter(CURLM *multi, struct curltime *start, bool final) { static struct curltime stamp; diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 6dbf8baa4cdb..51e22756aa4a 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -226,8 +226,10 @@ static char *c_escape(const char *str, curl_off_t len) result = curlx_dyn_addn(&escaped, str, s - str); if(!result) - (void)!curlx_dyn_addn(&escaped, "...", cutoff); + result = curlx_dyn_addn(&escaped, "...", cutoff); + if(result) + return NULL; return curlx_dyn_ptr(&escaped); } @@ -607,23 +609,24 @@ CURLcode tool_setopt_slist(CURL *curl, const char *name, CURLoption tag, CURLcode tool_setopt_long(CURL *curl, const char *name, CURLoption tag, long lval) { - long defval = 0L; - const struct NameValue *nv = NULL; CURLcode result = CURLE_OK; DEBUGASSERT(tag < CURLOPTTYPE_OBJECTPOINT); - for(nv = setopt_nv_CURLNONZERODEFAULTS; nv->name; nv++) { - if(!strcmp(name, nv->name)) { - defval = nv->value; - break; /* found it */ - } - } - result = curl_easy_setopt(curl, tag, lval); - if((lval != defval) && global->libcurl && !result) { + if(!result && global->libcurl) { + const struct NameValue *nv = NULL; + long defval = 0L; /* we only use this for real if --libcurl was used */ - result = easysrc_addf(&easysrc_code, "curl_easy_setopt(curl, %s, %ldL);", - name, lval); + for(nv = setopt_nv_CURLNONZERODEFAULTS; nv->name; nv++) { + if(!strcmp(name, nv->name)) { + defval = nv->value; + break; /* found it */ + } + } + if(lval != defval) { + result = easysrc_addf(&easysrc_code, "curl_easy_setopt(curl, %s, %ldL);", + name, lval); + } } return result; } @@ -678,28 +681,23 @@ CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...) /* setopt wrapper for setting strings */ CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, - const char *name, CURLoption tag, ...) + const char *name, CURLoption tag, + const char *value) { - char *str; - va_list arg; CURLcode result; DEBUGASSERT(tag >= CURLOPTTYPE_OBJECTPOINT); DEBUGASSERT((tag < CURLOPTTYPE_OFF_T) || (tag >= CURLOPTTYPE_BLOB)); DEBUGASSERT(tag < CURLOPTTYPE_BLOB); DEBUGASSERT(tag < CURLOPTTYPE_FUNCTIONPOINT); - va_start(arg, tag); - /* argument is a string */ - str = va_arg(arg, char *); - - result = curl_easy_setopt(curl, tag, str); - if(global->libcurl && str && !result) { + result = curl_easy_setopt(curl, tag, value); + if(global->libcurl && value && !result) { /* we only use this if --libcurl was used */ curl_off_t len = ZERO_TERMINATED; char *escaped; if(tag == CURLOPT_POSTFIELDS) len = curlx_dyn_len(&config->postdata); - escaped = c_escape(str, len); + escaped = c_escape(value, len); if(escaped) { result = easysrc_addf(&easysrc_code, "curl_easy_setopt(curl, %s, \"%s\");", @@ -710,16 +708,7 @@ CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, result = CURLE_OUT_OF_MEMORY; } - va_end(arg); return result; } #endif /* CURL_DISABLE_LIBCURL_OPTION */ - -/* return TRUE if the error code is "lethal" */ -bool setopt_bad(CURLcode result) -{ - return result && - (result != CURLE_NOT_BUILT_IN) && - (result != CURLE_UNKNOWN_OPTION); -} diff --git a/src/tool_setopt.h b/src/tool_setopt.h index ee4d2a6cd155..a737fcddf54f 100644 --- a/src/tool_setopt.h +++ b/src/tool_setopt.h @@ -29,9 +29,6 @@ * Macros used in operate() */ -/* return TRUE if the error code is "lethal" */ -bool setopt_bad(CURLcode result); - #ifndef CURL_DISABLE_LIBCURL_OPTION /* Associate symbolic names with option values */ @@ -99,7 +96,7 @@ CURLcode tool_setopt_offt(CURL *curl, const char *name, CURLoption tag, curl_off_t lval); CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, const char *name, CURLoption tag, - ...) WARN_UNUSED_RESULT; + const char *value) WARN_UNUSED_RESULT; CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...); #define my_setopt_long(x, y, z) tool_setopt_long(x, #y, y, z) diff --git a/src/tool_setup.h b/src/tool_setup.h index 7a5f597bbbd6..7e77c114f1c4 100644 --- a/src/tool_setup.h +++ b/src/tool_setup.h @@ -102,6 +102,8 @@ int toolx_ftruncate_win32(int fd, curl_off_t where); #elif defined(__DJGPP__) int toolx_ftruncate_djgpp(int fd, curl_off_t where); #define toolx_ftruncate toolx_ftruncate_djgpp +#elif defined(__AMIGA__) +#define toolx_ftruncate(f, o) ftruncate(f, (off_t)(o)) #else #define toolx_ftruncate ftruncate #endif diff --git a/src/tool_ssls.c b/src/tool_ssls.c index a492d8e1e3d9..1f59fbcfe7b8 100644 --- a/src/tool_ssls.c +++ b/src/tool_ssls.c @@ -85,7 +85,7 @@ CURLcode tool_ssls_load(struct OperationConfig *config, c = memchr(line, ':', strlen(line)); if(!c) { - warnf("unrecognized line %d in ssl session file %s", i, filename); + warnf("unrecognized line %d in SSL session file %s", i, filename); continue; } *c = '\0'; @@ -109,7 +109,7 @@ CURLcode tool_ssls_load(struct OperationConfig *config, result = curl_easy_ssls_import(easy, NULL, shmac, shmac_len, sdata, sdata_len); if(result) { - warnf("import of session from line %d rejected(%d)", i, result); + warnf("import of session from line %d rejected(%d)", i, (int)result); continue; } ++imported; @@ -161,25 +161,25 @@ static CURLcode tool_ssls_exp(CURL *easy, void *userptr, if(result) goto out; result = CURLE_WRITE_ERROR; - if(enc_len != fwrite(enc, 1, enc_len, ctx->fp)) + if(fwrite(enc, 1, enc_len, ctx->fp) != enc_len) goto out; - if(EOF == fputc(':', ctx->fp)) + if(fputc(':', ctx->fp) == EOF) goto out; curlx_safefree(enc); result = curlx_base64_encode(sdata, sdata_len, &enc, &enc_len); if(result) goto out; result = CURLE_WRITE_ERROR; - if(enc_len != fwrite(enc, 1, enc_len, ctx->fp)) + if(fwrite(enc, 1, enc_len, ctx->fp) != enc_len) goto out; - if(EOF == fputc('\n', ctx->fp)) + if(fputc('\n', ctx->fp) == EOF) goto out; result = CURLE_OK; ctx->exported++; out: if(result) warnf("Warning: error saving SSL session for '%s': %d", session_key, - result); + (int)result); curlx_free(enc); return result; } diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c index ad1c8087da44..88deedcb4cfd 100644 --- a/src/tool_urlglob.c +++ b/src/tool_urlglob.c @@ -56,6 +56,7 @@ static CURLcode glob_fixed(struct URLGlob *glob, char *fixed, size_t len) pat->c.set.palloc = 1; pat->c.set.size = 1; + pat->name = NULL; /* unnamed */ return CURLE_OK; } @@ -89,7 +90,7 @@ static int multiply(curl_off_t *amount, curl_off_t with) static CURLcode glob_set(struct URLGlob *glob, const char **patternp, size_t *posp, curl_off_t *amount, - int globindex) + int globindex, const struct Curl_str *name) { /* processes a set expression with the point behind the opening '{' ','-separated elements are collected until the next closing '}' @@ -98,16 +99,16 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp, bool done = FALSE; const char *pattern = *patternp; const char *opattern = pattern; - size_t opos = *posp - 1; CURLcode result = CURLE_OK; size_t size = 0; char **elem = NULL; size_t palloc = 0; /* start with this */ + DEBUGASSERT(name); while(!done) { switch(*pattern) { case '\0': /* URL ended while set was still open */ - result = globerror(glob, "unmatched brace", opos, CURLE_URL_MALFORMAT); + result = globerror(glob, "unmatched brace", *posp, CURLE_URL_MALFORMAT); goto error; case '{': @@ -198,6 +199,15 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp, pat->c.set.size = size; pat->c.set.idx = 0; pat->c.set.palloc = palloc; + if(curlx_strlen(name)) { + pat->name = curlx_memdup0(curlx_str(name), curlx_strlen(name)); + if(!pat->name) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + else + pat->name = NULL; /* no name */ return CURLE_OK; error: @@ -212,7 +222,7 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp, static CURLcode glob_range(struct URLGlob *glob, const char **patternp, size_t *posp, curl_off_t *amount, - int globindex) + int globindex, const struct Curl_str *name) { /* processes a range expression with the point behind the opening '[' - char range: e.g. "a-z]", "B-Q]" @@ -224,8 +234,10 @@ static CURLcode glob_range(struct URLGlob *glob, const char **patternp, const char *pattern = *patternp; const char *c; + DEBUGASSERT(name); pat = &glob->pattern[glob->pnum]; pat->globindex = globindex; + pat->name = NULL; /* no name (so far) */ if(ISALPHA(*pattern)) { /* character range detected */ @@ -324,8 +336,10 @@ static CURLcode glob_range(struct URLGlob *glob, const char **patternp, /* the pattern is not well-formed */ return globerror(glob, "bad range", *posp, CURLE_URL_MALFORMAT); - /* typecasting to ints are fine here since we make sure above that we - are within 31 bits */ + if((CURL_OFF_T_MAX - step_n) < max_n) + return globerror(glob, "range end/step overflow", *posp, + CURLE_URL_MALFORMAT); + pat->c.num.idx = pat->c.num.min = min_n; pat->c.num.max = max_n; pat->c.num.step = step_n; @@ -338,6 +352,11 @@ static CURLcode glob_range(struct URLGlob *glob, const char **patternp, return globerror(glob, "bad range specification", *posp, CURLE_URL_MALFORMAT); + if(curlx_strlen(name)) { + pat->name = curlx_memdup0(curlx_str(name), curlx_strlen(name)); + if(!pat->name) + return CURLE_OUT_OF_MEMORY; + } *patternp = pattern; return CURLE_OK; } @@ -405,12 +424,30 @@ static CURLcode add_glob(struct URLGlob *glob, size_t pos) return CURLE_OK; } +/* returns the named glob pattern (case sensitively) if it exists, otherwise + NULL */ +static struct URLPattern *glob_find_name(struct URLGlob *glob, + struct Curl_str *name) +{ + size_t i; + /* find the correct glob entry */ + for(i = 0; i < glob->pnum; i++) { + if(glob->pattern[i].name && + curlx_str_cmp(name, glob->pattern[i].name)) + return &glob->pattern[i]; + } + return NULL; /* no match */ +} + +#define MAX_GLOBNAME_LEN 64 + static CURLcode glob_parse(struct URLGlob *glob, const char *pattern, size_t pos, curl_off_t *amount) { /* processes a literal string component of a URL special characters '{' and '[' branch to set/range processing functions */ + const char *ipattern = pattern; /* start position */ CURLcode result = CURLE_OK; int globindex = 0; /* count "actual" globs */ @@ -462,21 +499,40 @@ static CURLcode glob_parse(struct URLGlob *glob, const char *pattern, curlx_dyn_reset(&glob->buf); } else { + struct Curl_str name; if(!*pattern) /* done */ break; - else if(*pattern == '{') { - /* process set pattern */ + else if((*pattern == '{') || (*pattern == '[')) { + bool set = (*pattern == '{'); + const char *start; pattern++; pos++; - result = glob_set(glob, &pattern, &pos, amount, globindex++); - if(!result) - result = add_glob(glob, pos); - } - else if(*pattern == '[') { - /* process range pattern */ - pattern++; - pos++; - result = glob_range(glob, &pattern, &pos, amount, globindex++); + start = pattern; + /* fetch the name, if provided */ + if(curlx_str_single(&pattern, '<') || + curlx_str_until(&pattern, &name, MAX_GLOBNAME_LEN, '>') || + curlx_str_single(&pattern, '>')) { + /* Not a proper name. This is not reporting errors on syntax errors + on purpose: it means that if there is an existing use case that + uses what looks like a broken named-glob syntax (now introduced) + we let that function like before. */ + curlx_str_init(&name); + pattern = start; /* reset any partial patch */ + } + else { + /* check that the name is not already used */ + struct URLPattern *p = glob_find_name(glob, &name); + if(p) + return globerror(glob, "Duplicate glob name", pattern - ipattern, + CURLE_URL_MALFORMAT); + pos += (pattern - start); + } + if(set) + result = glob_set(glob, &pattern, &pos, amount, globindex++, &name); + else + result = glob_range(glob, &pattern, &pos, amount, globindex++, + &name); + if(!result) result = add_glob(glob, pos); } @@ -490,6 +546,26 @@ bool glob_inuse(struct URLGlob *glob) return glob->palloc ? TRUE : FALSE; } +/* a glob error has been confirmed, this outputs details about it to the set + error stream */ +void glob_show_error(struct URLGlob *glob, const char *url, FILE *error, + CURLcode result) +{ + char text[512]; + const char *t; + if(glob->pos) { + curl_msnprintf(text, sizeof(text), "%s in position %zu:\n%s\n%*s^", + glob->error, + glob->pos, url, (int)glob->pos - 1, " "); + t = text; + } + else + t = glob->error; + + /* send error description to the error-stream */ + curl_mfprintf(error, "curl: (%d) %s\n", (int)result, t); +} + CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum, FILE *error) { @@ -509,21 +585,8 @@ CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum, result = glob_parse(glob, url, 1, &amount); if(result) { - if(error && glob->error) { - char text[512]; - const char *t; - if(glob->pos) { - curl_msnprintf(text, sizeof(text), "%s in URL position %zu:\n%s\n%*s^", - glob->error, - glob->pos, url, (int)glob->pos - 1, " "); - t = text; - } - else - t = glob->error; - - /* send error description to the error-stream */ - curl_mfprintf(error, "curl: (%d) %s\n", result, t); - } + if(error && glob->error) + glob_show_error(glob, url, error, result); *urlnum = 1; return result; } @@ -539,12 +602,13 @@ void glob_cleanup(struct URLGlob *glob) for(i = 0; i < glob->pnum; i++) { DEBUGASSERT(glob->pattern[i].type); if((glob->pattern[i].type == GLOB_SET) && - (glob->pattern[i].c.set.elem)) { + glob->pattern[i].c.set.elem) { curl_off_t elem; for(elem = 0; elem < glob->pattern[i].c.set.size; elem++) curlx_safefree(glob->pattern[i].c.set.elem[elem]); curlx_safefree(glob->pattern[i].c.set.elem); } + curlx_safefree(glob->pattern[i].name); } curlx_safefree(glob->pattern); glob->palloc = 0; @@ -572,7 +636,7 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) pat = &glob->pattern[glob->pnum - 1 - i]; switch(pat->type) { case GLOB_SET: - if((pat->c.set.elem) && (++pat->c.set.idx == pat->c.set.size)) { + if(pat->c.set.elem && (++pat->c.set.idx == pat->c.set.size)) { pat->c.set.idx = 0; carry = TRUE; } @@ -638,9 +702,11 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) #define MAX_OUTPUT_GLOB_LENGTH (1024 * 1024) CURLcode glob_match_url(char **output, const char *filename, - struct URLGlob *glob, SANITIZEcode *sc) + struct URLGlob *glob, struct URLGlob *glob2, + SANITIZEcode *sc) { struct dynbuf dyn; + const char *ifilename = filename; *output = NULL; *sc = SANITIZE_ERR_OK; @@ -648,11 +714,11 @@ CURLcode glob_match_url(char **output, const char *filename, while(*filename) { CURLcode result = CURLE_OK; - if(*filename == '#' && ISDIGIT(filename[1])) { - const char *ptr = filename; + struct URLPattern *pat = NULL; + if(glob_inuse(glob) && *filename == '#' && ISDIGIT(filename[1])) { + /* a numbered glob reference */ + const char *ptr = filename++; curl_off_t num; - struct URLPattern *pat = NULL; - filename++; if(!curlx_str_number(&filename, &num, glob->pnum) && num) { size_t i; num--; /* make it zero based */ @@ -664,31 +730,53 @@ CURLcode glob_match_url(char **output, const char *filename, } } } - - if(pat) { - switch(pat->type) { - case GLOB_SET: - if(pat->c.set.elem) - result = curlx_dyn_add(&dyn, pat->c.set.elem[pat->c.set.idx]); - break; - case GLOB_ASCII: { - char letter = (char)pat->c.ascii.letter; - result = curlx_dyn_addn(&dyn, &letter, 1); - break; - } - case GLOB_NUM: - result = curlx_dyn_addf(&dyn, "%0*" CURL_FORMAT_CURL_OFF_T, - pat->c.num.npad, pat->c.num.idx); - break; - default: - DEBUGASSERT(0); + if(!pat) + filename = ptr; + } + else if(*filename == '#' && (filename[1] == '<')) { + /* a named glob reference */ + struct Curl_str name; + const char *ptr = filename; + filename += 2; /* pass both leading bytes */ + if(!curlx_str_until(&filename, &name, MAX_GLOBNAME_LEN, '>') && + !curlx_str_single(&filename, '>')) { + /* find the correct glob entry */ + if(glob_inuse(glob)) + pat = glob_find_name(glob, &name); + if(!pat && glob2 && glob_inuse(glob2)) + /* scan the second glob list if there is one */ + pat = glob_find_name(glob2, &name); + if(!pat) { + /* when the name is given correctly, it needs to be an existing glob + name, which makes this an error */ curlx_dyn_free(&dyn); - return CURLE_FAILED_INIT; + return globerror(glob, "no glob exists with this name", + filename - ifilename, CURLE_BAD_FUNCTION_ARGUMENT); } } - else - /* #[num] out of range, use the #[num] in the output */ - result = curlx_dyn_addn(&dyn, ptr, filename - ptr); + if(!pat) + filename = ptr; + } + if(pat) { + switch(pat->type) { + case GLOB_SET: + if(pat->c.set.elem) + result = curlx_dyn_add(&dyn, pat->c.set.elem[pat->c.set.idx]); + break; + case GLOB_ASCII: { + char letter = (char)pat->c.ascii.letter; + result = curlx_dyn_addn(&dyn, &letter, 1); + break; + } + case GLOB_NUM: + result = curlx_dyn_addf(&dyn, "%0*" CURL_FORMAT_CURL_OFF_T, + pat->c.num.npad, pat->c.num.idx); + break; + default: + DEBUGASSERT(0); + curlx_dyn_free(&dyn); + return CURLE_FAILED_INIT; + } } else result = curlx_dyn_addn(&dyn, filename++, 1); diff --git a/src/tool_urlglob.h b/src/tool_urlglob.h index ad0f144fd232..e891258aa46e 100644 --- a/src/tool_urlglob.h +++ b/src/tool_urlglob.h @@ -33,6 +33,7 @@ typedef enum { struct URLPattern { globtype type; + char *name; /* if not NULL */ int globindex; /* the number of this particular glob or -1 if not used within {} or [] */ union { @@ -71,11 +72,15 @@ struct URLGlob { size_t pos; /* column position of error or 0 */ }; +void glob_show_error(struct URLGlob *glob, const char *url, FILE *error, + CURLcode result); + CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum, FILE *error); CURLcode glob_next_url(char **globbed, struct URLGlob *glob); CURLcode glob_match_url(char **output, const char *filename, - struct URLGlob *glob, SANITIZEcode *sc); + struct URLGlob *glob, struct URLGlob *glob2, + SANITIZEcode *sc); void glob_cleanup(struct URLGlob *glob); bool glob_inuse(struct URLGlob *glob); diff --git a/src/tool_vms.c b/src/tool_vms.c index 74eb210ab546..ced0efe2ed15 100644 --- a/src/tool_vms.c +++ b/src/tool_vms.c @@ -59,7 +59,7 @@ int is_vms_shell(void) } /* Have to make sure some one did not set shell to DCL */ - if(strcmp(shell, "DCL") == 0) { + if(!strcmp(shell, "DCL")) { vms_shell = 1; return 1; } @@ -154,7 +154,7 @@ static void decc_init(void) decc_init_done = 1; /* Loop through all items in the decc_feat_array[]. */ - for(i = 0; decc_feat_array[i].name != NULL; i++) { + for(i = 0; decc_feat_array[i].name; i++) { /* Get the feature index. */ feat_index = decc$feature_get_index(decc_feat_array[i].name); diff --git a/src/tool_writeout.c b/src/tool_writeout.c index ad8c77c962e9..7a0a20a9d9fc 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -45,7 +45,7 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, bool use_json) { - bool valid = false; + bool valid = FALSE; curl_off_t us = 0; (void)per; @@ -54,7 +54,7 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, if(wovar->ci) { if(!curl_easy_getinfo(per->curl, wovar->ci, &us)) - valid = true; + valid = TRUE; } else { DEBUGASSERT(0); @@ -173,7 +173,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, bool use_json) { - bool valid = false; + bool valid = FALSE; const char *strinfo = NULL; const char *freestr = NULL; struct dynbuf buf; @@ -189,7 +189,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, while(m->str) { if(m->num == version) { strinfo = m->str; - valid = true; + valid = TRUE; break; } m++; @@ -198,7 +198,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, } else { if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo) - valid = true; + valid = TRUE; } } else { @@ -243,7 +243,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, if(!strinfo) /* maybe not a TLS protocol */ strinfo = ""; - valid = true; + valid = TRUE; } } else @@ -251,21 +251,21 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, break; case VAR_ERRORMSG: if(per_result) { - strinfo = (per->errorbuffer[0]) ? per->errorbuffer : + strinfo = per->errorbuffer[0] ? per->errorbuffer : curl_easy_strerror(per_result); - valid = true; + valid = TRUE; } break; case VAR_EFFECTIVE_FILENAME: if(per->outs.filename) { strinfo = per->outs.filename; - valid = true; + valid = TRUE; } break; case VAR_INPUT_URL: if(per->url) { strinfo = per->url; - valid = true; + valid = TRUE; } break; case VAR_INPUT_URLSCHEME: @@ -291,7 +291,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, if(per->url) { if(!urlpart(per, wovar->id, &strinfo)) { freestr = strinfo; - valid = true; + valid = TRUE; } } break; @@ -324,33 +324,33 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, bool use_json) { - bool valid = false; + bool valid = FALSE; long longinfo = 0; DEBUGASSERT(wovar->writefunc == writeLong); if(wovar->ci) { if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo)) - valid = true; + valid = TRUE; } else { switch(wovar->id) { case VAR_NUM_RETRY: longinfo = per->num_retries; - valid = true; + valid = TRUE; break; case VAR_NUM_CERTS: certinfo(per); longinfo = per->certinfo ? per->certinfo->num_of_certs : 0; - valid = true; + valid = TRUE; break; case VAR_NUM_HEADERS: longinfo = per->num_headers; - valid = true; + valid = TRUE; break; case VAR_EXITCODE: longinfo = (long)per_result; - valid = true; + valid = TRUE; break; default: DEBUGASSERT(0); @@ -380,7 +380,7 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, bool use_json) { - bool valid = false; + bool valid = FALSE; curl_off_t offinfo = 0; (void)per; @@ -389,14 +389,14 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, if(wovar->ci) { if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo)) - valid = true; + valid = TRUE; } else { switch(wovar->id) { case VAR_URLNUM: if(per->urlnum <= INT_MAX) { offinfo = per->urlnum; - valid = true; + valid = TRUE; } break; default: @@ -562,18 +562,39 @@ static const char *outtime(const char *ptr, /* %time{ ... */ vlen = end - ptr; curlx_dyn_init(&format, 1024); - /* insert sub-seconds for %f */ - /* insert +0000 for %z because it is otherwise not portable */ - /* insert UTC for %Z because it is otherwise not portable */ + /* Insert: + - sub-seconds for %f + - epoch seconds for %s; strftime %s uses mktime() and assumes + local time, which breaks UTC output on non-UTC hosts + - +0000 for %z because it is otherwise not portable + - UTC for %Z because it is otherwise not portable + - Keep '%%' as-is so that strftime() makes a single % out of them + */ for(i = 0; !result && i < vlen; i++) { - if((i < vlen - 1) && ptr[i] == '%' && - ((ptr[i + 1] == 'f') || ((ptr[i + 1] | 0x20) == 'z'))) { - if(ptr[i + 1] == 'f') + if((i < vlen - 1) && ptr[i] == '%') { + switch(ptr[i + 1]) { + case 'f': result = curlx_dyn_addf(&format, "%06u", usecs); - else if(ptr[i + 1] == 'Z') + break; + case 's': { + /* time_t might be either 32 or 64 bits big */ + curl_off_t tsecs = secs; + result = curlx_dyn_addf(&format, "%" CURL_FORMAT_CURL_OFF_T, tsecs); + break; + } + case 'Z': result = curlx_dyn_addn(&format, "UTC", 3); - else + break; + case 'z': result = curlx_dyn_addn(&format, "+0000", 5); + break; + case '%': + result = curlx_dyn_addn(&format, "%%", 2); + break; + default: + result = curlx_dyn_addn(&format, &ptr[i], 1); + continue; + } i++; } else @@ -787,7 +808,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, headerJSON(stream, per); break; default: - (void)wv->writefunc(stream, wv, per, per_result, false); + (void)wv->writefunc(stream, wv, per, per_result, FALSE); break; } } diff --git a/src/tool_writeout_json.c b/src/tool_writeout_json.c index 31f528ac4710..3b6f7df73fd8 100644 --- a/src/tool_writeout_json.c +++ b/src/tool_writeout_json.c @@ -32,8 +32,7 @@ /* provide the given string in dynbuf as a quoted json string, but without the outer quotes. The buffer is not inited by this function. - Return 0 on success, non-zero on error. -*/ + Return 0 on success, non-zero on error. */ int jsonquoted(const char *in, size_t len, struct dynbuf *out, bool lowercase) { const unsigned char *i = (const unsigned char *)in; @@ -105,7 +104,7 @@ void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], for(i = 0; i < nentries; i++) { if(mappings[i].writefunc && - mappings[i].writefunc(stream, &mappings[i], per, per_result, true)) + mappings[i].writefunc(stream, &mappings[i], per, per_result, TRUE)) fputs(",", stream); } diff --git a/src/tool_xattr.c b/src/tool_xattr.c index cdf8296307a5..7bf487a07783 100644 --- a/src/tool_xattr.c +++ b/src/tool_xattr.c @@ -27,19 +27,6 @@ #ifdef USE_XATTR -/* mapping table of curl metadata to extended attribute names */ -static const struct xattr_mapping { - const char *attr; /* name of the xattr */ - CURLINFO info; -} mappings[] = { - /* mappings proposed by - * https://freedesktop.org/wiki/CommonExtendedAttributes/ - */ - { "user.xdg.referrer.url", CURLINFO_REFERER }, - { "user.mime_type", CURLINFO_CONTENT_TYPE }, - { NULL, CURLINFO_NONE } /* last element, abort here */ -}; - /* returns a new URL that needs to be freed */ /* @unittest: 1621 */ UNITTEST char *stripcredentials(const char *url) @@ -74,6 +61,20 @@ UNITTEST char *stripcredentials(const char *url) return NULL; } +#ifndef _WIN32 +/* mapping table of curl metadata to extended attribute names */ +static const struct xattr_mapping { + const char *attr; /* name of the xattr */ + CURLINFO info; +} mappings[] = { + /* mappings proposed by + * https://freedesktop.org/wiki/CommonExtendedAttributes/ + */ + { "user.xdg.referrer.url", CURLINFO_REFERER }, + { "user.mime_type", CURLINFO_CONTENT_TYPE }, + { NULL, CURLINFO_NONE } /* last element, abort here */ +}; + static int xattr(int fd, const char *attr, /* name of the xattr */ const char *value) @@ -102,13 +103,70 @@ static int xattr(int fd, } return err; } +#else +static int win32_file_stream(CURL *curl, FILE *fs, const char *url) +{ + int err = 1; + char *value = NULL; + char *nurl = stripcredentials(url); + CURLcode result = curl_easy_getinfo(curl, CURLINFO_REFERER, &value); + + if(nurl && !result) { + err = 0; + err |= (fputs("[ZoneTransfer]\n", fs) == EOF); + if(value) { + err |= (fputs("ReferrerUrl=", fs) == EOF); + err |= (fputs(value, fs) == EOF); + err |= (fputs("\n", fs) == EOF); + } + err |= (fputs("HostUrl=", fs) == EOF); + err |= (fputs(nurl, fs) == EOF); + err |= (fputs("\n", fs) == EOF); + } + curl_free(nurl); + return err; +} +#endif /* !_WIN32 */ + /* store metadata from the curl request alongside the downloaded * file using extended attributes */ -int fwrite_xattr(CURL *curl, const char *url, int fd) +int fwrite_xattr(CURL *curl, const char *url, int fd, const char *filename) { + int err; +#ifdef _WIN32 + char *fn_abs, *fn_stream; + FILE *fs; + (void)fd; + + /* convert to absolute path to prevent Windows interpreting a 'X:' + filename as 'drive-letter:'. */ + fn_abs = _fullpath(NULL, filename, 0); + if(!fn_abs) + return 1; + + fn_stream = curl_maprintf("%s:%s", fn_abs, "Zone.Identifier"); + /* !checksrc! disable BANNEDFUNC 1 */ + free(fn_abs); /* allocated by CRT, use system free() */ + if(!fn_stream) + return 1; + + fs = curlx_fopen(fn_stream, FOPEN_WRITETEXT); + curl_free(fn_stream); + if(!fs) + return 1; + +#ifdef DEBUGBUILD + if(getenv("CURL_FAKE_XATTR")) + win32_file_stream(curl, stdout, url); +#endif + err = win32_file_stream(curl, fs, url); + curlx_fclose(fs); +#else int i = 0; - int err = xattr(fd, "user.creator", "curl"); + (void)filename; + + err = xattr(fd, "user.creator", "curl"); /* loop through all xattr-curlinfo pairs and abort on a set error */ while(!err && mappings[i].attr) { @@ -125,6 +183,7 @@ int fwrite_xattr(CURL *curl, const char *url, int fd) err = xattr(fd, "user.xdg.origin.url", nurl); curl_free(nurl); } +#endif return err; } #endif diff --git a/src/tool_xattr.h b/src/tool_xattr.h index 6720ccb2bd9a..0795ba8a6704 100644 --- a/src/tool_xattr.h +++ b/src/tool_xattr.h @@ -30,20 +30,21 @@ # define USE_XATTR #elif (defined(__FreeBSD_version) && (__FreeBSD_version > 500000)) || \ defined(__MidnightBSD_version) -# include # include # define USE_XATTR +#elif defined(_WIN32) +# define USE_XATTR #endif #ifdef USE_XATTR -int fwrite_xattr(CURL *curl, const char *url, int fd); +int fwrite_xattr(CURL *curl, const char *url, int fd, const char *filename); #ifdef UNITTESTS UNITTEST char *stripcredentials(const char *url); #endif #else -#define fwrite_xattr(a, b, c) 0 +#define fwrite_xattr(a, b, c, d) 0 #endif #endif /* HEADER_CURL_TOOL_XATTR_H */ diff --git a/src/var.c b/src/var.c index 79ff888dbb99..464b0c1c2cec 100644 --- a/src/var.c +++ b/src/var.c @@ -62,15 +62,15 @@ static const struct tool_var *varcontent(const char *name, size_t nlen) (!strncmp(ptr, name, len) && ENDOFFUNC((ptr)[len])) #define FUNC_TRIM "trim" -#define FUNC_TRIM_LEN (sizeof(FUNC_TRIM) - 1) +#define FUNC_TRIM_LEN CURL_CSTRLEN(FUNC_TRIM) #define FUNC_JSON "json" -#define FUNC_JSON_LEN (sizeof(FUNC_JSON) - 1) +#define FUNC_JSON_LEN CURL_CSTRLEN(FUNC_JSON) #define FUNC_URL "url" -#define FUNC_URL_LEN (sizeof(FUNC_URL) - 1) +#define FUNC_URL_LEN CURL_CSTRLEN(FUNC_URL) #define FUNC_B64 "b64" -#define FUNC_B64_LEN (sizeof(FUNC_B64) - 1) +#define FUNC_B64_LEN CURL_CSTRLEN(FUNC_B64) #define FUNC_64DEC "64dec" /* base64 decode */ -#define FUNC_64DEC_LEN (sizeof(FUNC_64DEC) - 1) +#define FUNC_64DEC_LEN CURL_CSTRLEN(FUNC_64DEC) static ParameterError varfunc(char *c, /* content */ size_t clen, /* content length */ @@ -78,7 +78,7 @@ static ParameterError varfunc(char *c, /* content */ size_t flen, /* function string length */ struct dynbuf *out) { - bool alloc = FALSE; + char *allocptr = NULL; ParameterError err = PARAM_OK; const char *finput = f; @@ -185,19 +185,18 @@ static ParameterError varfunc(char *c, /* content */ err = PARAM_EXPAND_ERROR; break; } - if(alloc) - curlx_free(c); + if(allocptr) + curlx_free(allocptr); clen = curlx_dyn_len(out); - c = curlx_memdup0(curlx_dyn_ptr(out), clen); + allocptr = c = curlx_memdup0(curlx_dyn_ptr(out), clen); if(!c) { err = PARAM_NO_MEM; break; } - alloc = TRUE; } - if(alloc) - curlx_free(c); + if(allocptr) + curlx_free(allocptr); if(err) curlx_dyn_free(out); return err; @@ -317,7 +316,7 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) if(result) return PARAM_NO_MEM; - added = true; + added = TRUE; } } line = &clp[2]; @@ -355,7 +354,7 @@ static ParameterError addvariable(const char *name, p = curlx_calloc(1, sizeof(struct tool_var) + nlen); if(p) { memcpy(p->name, name, nlen); - /* the null termination byte is already present from above */ + /* the null-termination byte is already present from above */ p->content = contalloc ? content : curlx_memdup0(content, clen); if(p->content) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9835bcec725f..c6dd72983267 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,9 +49,12 @@ add_custom_target(testdeps) add_dependencies(testdeps "tt") add_subdirectory(http) +add_subdirectory(perf) add_subdirectory(server) add_subdirectory(libtest) -add_subdirectory(tunit) +if(BUILD_CURL_EXE) + add_subdirectory(tunit) +endif() add_subdirectory(unit) add_subdirectory(certs) @@ -82,8 +85,8 @@ function(curl_add_runtests _targetname _test_flags) if(_setenvs) set(_setenvs "${CMAKE_COMMAND}" -E env ${_setenvs}) endif() - # Use a special '$TFLAGS' placeholder as last argument which will be - # replaced by the contents of the environment variable in runtests.pl. + # Use a special '$TFLAGS' placeholder as last argument which is replaced + # by the contents of the environment variable in runtests.pl. # This is a workaround for CMake's limitation where commands executed by # 'make' or 'ninja' cannot portably reference environment variables. string(REPLACE " " ";" _test_flags_list "${_test_flags}") @@ -110,7 +113,7 @@ function(curl_add_pytests _targetname _test_flags) endif() string(REPLACE " " ";" _test_flags_list "${_test_flags}") add_custom_target(${_targetname} - COMMAND pytest ${_test_flags_list} "${CMAKE_CURRENT_SOURCE_DIR}/http" + COMMAND pytest ${_test_flags_list} "${CMAKE_CURRENT_SOURCE_DIR}/http${_CURL_PYTEST}" DEPENDS "${_depends}" VERBATIM USES_TERMINAL ) diff --git a/tests/Makefile.am b/tests/Makefile.am index a77c6259eba8..8273f83bb9c7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -95,7 +95,7 @@ BUILD_UNIT = DIST_UNIT = unit tunit endif -SUBDIRS = certs data server libtest http $(BUILD_UNIT) +SUBDIRS = certs data perf server libtest http $(BUILD_UNIT) DIST_SUBDIRS = $(SUBDIRS) $(DIST_UNIT) PERLFLAGS = -I$(srcdir) @@ -118,6 +118,7 @@ TEST_COMMON += !documentation endif TEST = srcdir=$(srcdir) @PERL@ $(PERLFLAGS) $(srcdir)/runtests.pl $(TEST_COMMON) +TEST_A = -a TEST_Q = -a -s TEST_AM = -a -am TEST_F = -a -p -r @@ -136,13 +137,14 @@ endif # make sure that PERL is pointing to an executable perlcheck: - @if ! test -x "@PERL@"; then echo "No perl!"; exit 2; fi + @if ! test -x "@PERL@"; then echo 'No perl!'; exit 2; fi build-certs: perlcheck (cd certs && $(MAKE)) # alias for 'test' to match CMake, where 'test' is a reserved target -tests: test +tests: perlcheck all + $(TEST) $(TEST_A) $(TFLAGS) test: perlcheck all $(TEST) $(TFLAGS) @@ -175,11 +177,12 @@ ci-pytest: all checksrc: (cd libtest && $(MAKE) checksrc) - (cd unit && $(MAKE) checksrc) - (cd tunit && $(MAKE) checksrc) + (cd perf && $(MAKE) checksrc) (cd server && $(MAKE) checksrc) + (cd tunit && $(MAKE) checksrc) + (cd unit && $(MAKE) checksrc) -all-local: $(MANFILES) build-certs +all-local: build-certs distclean: - rm -f $(MANFILES) + rm -f $(CLEANFILES) diff --git a/tests/allversions.pm b/tests/allversions.pm index 8ae47fc59988..edd4ec1139fd 100644 --- a/tests/allversions.pm +++ b/tests/allversions.pm @@ -32,7 +32,7 @@ our %pastversion; sub allversions { my ($file) = @_; - open(A, "<$file") || + open(A, "<", $file) or die "cannot open the versions file $file\n"; my $before = 1; my $relcount; @@ -42,7 +42,7 @@ sub allversions { } elsif(!$before && /^- ([0-9.]+): (.*)/) { - $pastversion{$1}=$2; + $pastversion{$1} = $2; $relcount++; } } diff --git a/tests/appveyor.pm b/tests/appveyor.pm index f332bc2b277a..b3f8df9b3175 100644 --- a/tests/appveyor.pm +++ b/tests/appveyor.pm @@ -32,9 +32,9 @@ BEGIN { use base qw(Exporter); our @EXPORT = qw( - appveyor_check_environment - appveyor_create_test_result - appveyor_update_test_result + appveyor_check_environment + appveyor_create_test_result + appveyor_update_test_result ); } @@ -48,12 +48,12 @@ sub appveyor_check_environment { } sub appveyor_create_test_result { - my ($curl, $testnum, $testname)=@_; + my ($curl, $testnum, $testname) = @_; $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; - my $appveyor_baseurl="$ENV{'APPVEYOR_API_URL'}"; - my $appveyor_result=`$curl --silent --noproxy '*' \\ + my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; + my $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ --data ' { @@ -63,14 +63,14 @@ sub appveyor_create_test_result { "outcome": "Running" } ' \\ - '$appveyor_baseurl/api/tests'`; + '$appveyor_baseurl/api/tests'); print "AppVeyor API result: $appveyor_result\n" if($appveyor_result); - $APPVEYOR_TEST_NAMES{$testnum}=$testname; + $APPVEYOR_TEST_NAMES{$testnum} = $testname; } sub appveyor_update_test_result { - my ($curl, $testnum, $error, $start, $stop)=@_; - my $testname=$APPVEYOR_TEST_NAMES{$testnum}; + my ($curl, $testnum, $error, $start, $stop) = @_; + my $testname = $APPVEYOR_TEST_NAMES{$testnum}; if(!defined $testname) { return; } @@ -96,8 +96,8 @@ sub appveyor_update_test_result { $appveyor_outcome = 'Failed'; $appveyor_category = 'Error'; } - my $appveyor_baseurl="$ENV{'APPVEYOR_API_URL'}"; - my $appveyor_result=`$curl --silent --noproxy '*' --request PUT \\ + my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; + my $appveyor_result = qx($curl --silent --noproxy '*' --request PUT \\ --header 'Content-Type: application/json' \\ --data ' { @@ -109,10 +109,10 @@ sub appveyor_update_test_result { "ErrorMessage": "Test $testnum $appveyor_outcome" } ' \\ - '$appveyor_baseurl/api/tests'`; + '$appveyor_baseurl/api/tests'); print "AppVeyor API result: $appveyor_result\n" if($appveyor_result); if($appveyor_category eq 'Error') { - $appveyor_result=`$curl --silent --noproxy '*' \\ + $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ --data ' { @@ -121,7 +121,7 @@ sub appveyor_update_test_result { "details": "Test $testnum $appveyor_outcome" } ' \\ - '$appveyor_baseurl/api/build/messages'`; + '$appveyor_baseurl/api/build/messages'); print "AppVeyor API result: $appveyor_result\n" if($appveyor_result); } } diff --git a/tests/azure.pm b/tests/azure.pm index 2810f48e1777..e449a07fd485 100644 --- a/tests/azure.pm +++ b/tests/azure.pm @@ -53,9 +53,9 @@ sub azure_check_environment { } sub azure_create_test_run { - my ($curl)=@_; - my $azure_baseurl="$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; - my $azure_run=`$curl --silent --noproxy "*" \\ + my ($curl) = @_; + my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; + my $azure_run = qx($curl --silent --noproxy "*" \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ --header "Content-Type: application/json" \\ --data " @@ -65,7 +65,7 @@ sub azure_create_test_run { 'build': {'id': '$ENV{'BUILD_BUILDID'}'} } " \\ - "$azure_baseurl/_apis/test/runs?api-version=5.1"`; + "$azure_baseurl/_apis/test/runs?api-version=5.1"); if($azure_run =~ /"id":(\d+)/) { return $1; } @@ -73,13 +73,13 @@ sub azure_create_test_run { } sub azure_create_test_result { - my ($curl, $azure_run_id, $testnum, $testname)=@_; + my ($curl, $azure_run_id, $testnum, $testname) = @_; $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; - my $title_testnum=sprintf("%04d", $testnum); - my $azure_baseurl="$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; - my $azure_result=`$curl --silent --noproxy '*' \\ + my $title_testnum = sprintf("%04d", $testnum); + my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; + my $azure_result = qx($curl --silent --noproxy '*' \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ --header 'Content-Type: application/json' \\ --data ' @@ -94,7 +94,7 @@ sub azure_create_test_result { } ] ' \\ - '$azure_baseurl/_apis/test/runs/$azure_run_id/results?api-version=5.1'`; + '$azure_baseurl/_apis/test/runs/$azure_run_id/results?api-version=5.1'); if($azure_result =~ /\[\{"id":(\d+)/) { return $1; } @@ -102,7 +102,7 @@ sub azure_create_test_result { } sub azure_update_test_result { - my ($curl, $azure_run_id, $azure_result_id, $testnum, $error, $start, $stop)=@_; + my ($curl, $azure_run_id, $azure_result_id, $testnum, $error, $start, $stop) = @_; if(!defined $stop) { $stop = $start; } @@ -122,8 +122,8 @@ sub azure_update_test_result { else { $azure_outcome = 'Failed'; } - my $azure_baseurl="$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; - my $azure_result=`$curl --silent --noproxy '*' --request PATCH \\ + my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; + my $azure_result = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ --header "Content-Type: application/json" \\ --data ' @@ -137,7 +137,7 @@ sub azure_update_test_result { } ] ' \\ - '$azure_baseurl/_apis/test/runs/$azure_run_id/results?api-version=5.1'`; + '$azure_baseurl/_apis/test/runs/$azure_run_id/results?api-version=5.1'); if($azure_result =~ /\[\{"id":(\d+)/) { return $1; } @@ -145,9 +145,9 @@ sub azure_update_test_result { } sub azure_update_test_run { - my ($curl, $azure_run_id)=@_; - my $azure_baseurl="$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; - my $azure_run=`$curl --silent --noproxy '*' --request PATCH \\ + my ($curl, $azure_run_id) = @_; + my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; + my $azure_run = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ --header 'Content-Type: application/json' \\ --data ' @@ -155,7 +155,7 @@ sub azure_update_test_run { "state": "Completed" } ' \\ - '$azure_baseurl/_apis/test/runs/$azure_run_id?api-version=5.1'`; + '$azure_baseurl/_apis/test/runs/$azure_run_id?api-version=5.1'); if($azure_run =~ /"id":(\d+)/) { return $1; } diff --git a/tests/certs/CMakeLists.txt b/tests/certs/CMakeLists.txt index ebe6fbcfa7fb..a96593a24e0e 100644 --- a/tests/certs/CMakeLists.txt +++ b/tests/certs/CMakeLists.txt @@ -39,7 +39,7 @@ if(NOT _CURL_SKIP_BUILD_CERTS) endif() add_custom_target(clean-certs - COMMAND ${CMAKE_COMMAND} -E remove ${GENERATEDCERTS} + COMMAND ${CMAKE_COMMAND} -E rm -f ${GENERATEDCERTS} "test-*.csr" "test-*.der" "test-*.keyenc" diff --git a/tests/certs/Makefile.am b/tests/certs/Makefile.am index d28b1674da3d..071855d3a6e0 100644 --- a/tests/certs/Makefile.am +++ b/tests/certs/Makefile.am @@ -23,19 +23,19 @@ ########################################################################### AUTOMAKE_OPTIONS = foreign -# Get CERTCONFIG_CA, CERTCONFIGS, GENERATEDCERTS, SRPFILES variables +# Get CERTCONFIG_CA, CERTCONFIGS, GENERATEDCERTS variables include Makefile.inc -EXTRA_DIST = $(CERTCONFIG_CA) $(CERTCONFIGS) $(SRPFILES) CMakeLists.txt \ +EXTRA_DIST = $(CERTCONFIG_CA) $(CERTCONFIGS) CMakeLists.txt \ genserv.pl -DISTCLEANFILES = $(GENERATEDCERTS) +CLEANFILES = $(GENERATEDCERTS) all-am: test-ca.cacert # Rebuild the certificates -# Generate all certs in a single shot, but declare just a single target file +# Generate all certs in a single shot, but declare only a single target file # to support GNU Make <4.3 without the "grouped explicit targets" feature. test-ca.cacert: $(CERTCONFIG_CA) $(CERTCONFIGS) genserv.pl @PERL@ $(srcdir)/genserv.pl test $(CERTCONFIGS) diff --git a/tests/certs/Makefile.inc b/tests/certs/Makefile.inc index 06516a09d5d0..9c0d5ff3ec56 100644 --- a/tests/certs/Makefile.inc +++ b/tests/certs/Makefile.inc @@ -82,7 +82,3 @@ GENERATEDCERTS = \ test-localhost0h.pem \ test-localhost0h.pub.der \ test-localhost0h.pub.pem - -SRPFILES = \ - srp-verifier-conf \ - srp-verifier-db diff --git a/tests/certs/genserv.pl b/tests/certs/genserv.pl index 96e77449aaa0..0eb6c713f456 100755 --- a/tests/certs/genserv.pl +++ b/tests/certs/genserv.pl @@ -32,7 +32,7 @@ use Symbol 'gensym'; sub opensslfail { - die "Missing or broken 'openssl' tool. openssl 1.0.2+ is required. ". + die "Missing or unsupported 'openssl' tool. openssl 1.0.2+ required. ". "Without it, this script cannot generate the necessary certificates ". "the curl test suite needs for all its TLS related tests."; } @@ -52,7 +52,7 @@ sub opensslfail { sub redir { my $outfn = shift if($_[0] =~ /^>/); my $hideerr = shift if($_[0] =~ /^2>/); - open(my $outfd, $outfn) || die if($outfn); + open(my $outfd, $outfn) or die if($outfn); my $pid = open3(my $in, my $out, my $err = gensym, @_); if(!$hideerr) { while(<$err>) { print STDERR $_; }; } if($outfn) { while(<$out>) { print $outfd $_; }; close($outfd); } @@ -79,6 +79,7 @@ sub redir { } } if(!$found) { + printf "PATH used: %s\n", join(', ', File::Spec->path()); opensslfail(); } } diff --git a/tests/certs/srp-verifier-conf b/tests/certs/srp-verifier-conf deleted file mode 100644 index 67825ceabd9f..000000000000 --- a/tests/certs/srp-verifier-conf +++ /dev/null @@ -1,3 +0,0 @@ -1:Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Zjl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ:2 -2:dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTau/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtmcIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx:2 -3:2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJtL8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL.NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nqha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXqvp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUDo/DQEyW.d4H.UIlzp:2 diff --git a/tests/certs/srp-verifier-db b/tests/certs/srp-verifier-db deleted file mode 100644 index 2f851a342dc6..000000000000 --- a/tests/certs/srp-verifier-db +++ /dev/null @@ -1,2 +0,0 @@ -jsmith:34fPk7u.w3R/M1k2sQ9F.04GZqLKAsqDn44CHGu7ML0M8VWwu1p79OLxi6jRhSNdSM46Kx9GRVyJLXz7eok53..A6X5p3NdnMSYX8WwYrDmuseHDr.eua7gjd04S4EoY4ZuKix2.WGAsMTwk86AmTvcqyzqsH7GDhGOHEhjP5zs:lTjBBoK04K9vTKiL10rI/:1 -alice:3IIP1g1HDTN6VEUr8DUkMleocoC1cpuFZnmunDaGhMyIsw8LAwCc7ZapWaC66gZSyis4ezSuCqvhsJdwdc.0es2UrH6PBkBQflcQDuC.dEpjhWgAcH2Dw.2qU.E0ApQzLkcKOjXMQ2R6jMBL14kEUPjjHS3aa16yB.Afj3bNPdf:1JxU4GkweUEii6.b0grkzU:1 diff --git a/tests/cmake/test.c b/tests/cmake/test.c index 75c6bf913e12..dc27c6ce0d7e 100644 --- a/tests/cmake/test.c +++ b/tests/cmake/test.c @@ -24,7 +24,7 @@ #include #include -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { (void)argc; puts("libcurl C test:"); diff --git a/tests/cmake/test.cpp b/tests/cmake/test.cpp index f5fd4ecc2a5c..18a76d8561b6 100644 --- a/tests/cmake/test.cpp +++ b/tests/cmake/test.cpp @@ -34,7 +34,7 @@ class CurlClass { } }; -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { (void)argc; std::cout << "libcurl C++ test:" << std::endl; diff --git a/tests/cmake/test.sh b/tests/cmake/test.sh index bcc1bd37dc5d..b9bb1fc771e7 100755 --- a/tests/cmake/test.sh +++ b/tests/cmake/test.sh @@ -7,7 +7,7 @@ set -eu -cd "$(dirname "$0")" +cd -- "$(dirname "$0")" mode="${1:-all}"; shift @@ -31,7 +31,7 @@ runresults() { if [ "${mode}" = 'all' ] || [ "${mode}" = 'ExternalProject' ]; then (cd "${src}"; git archive --format=tar HEAD) | gzip > source.tar.gz - src="${PWD}/source.tar.gz" + src="$(pwd)/source.tar.gz" sha="$(sha256sum "${src}" | grep -a -i -o -w -E '[0-9a-f]{64}')" bldc='bld-externalproject' rm -rf "${bldc}" @@ -43,7 +43,7 @@ if [ "${mode}" = 'all' ] || [ "${mode}" = 'ExternalProject' ]; then fi if [ "${mode}" = 'all' ] || [ "${mode}" = 'FetchContent' ]; then - src="${PWD}/${src}" + src="$(pwd)/${src}" bldc='bld-fetchcontent' rm -rf "${bldc}" "${cmake_consumer}" -B "${bldc}" -G "${gen}" ${cmake_opts} -DCMAKE_UNITY_BUILD=ON ${TEST_CMAKE_FLAGS:-} "$@" \ @@ -70,9 +70,9 @@ if [ "${mode}" = 'all' ] || [ "${mode}" = 'add_subdirectory' ]; then fi if [ "${mode}" = 'all' ] || [ "${mode}" = 'find_package' ]; then - src="${PWD}/${src}" + src="$(pwd)/${src}" bldp='bld-curl' - prefix="${PWD}/${bldp}/_pkg" + prefix="$(pwd)/${bldp}/_pkg" rm -rf "${bldp}" "${cmake_provider}" -B "${bldp}" -S "${src}" -G "${gen}" ${cmake_opts} -DCMAKE_UNITY_BUILD=ON ${TEST_CMAKE_FLAGS:-} ${TEST_CMAKE_FLAGS_PROVIDER:-} "$@" \ -DBUILD_SHARED_LIBS=ON \ diff --git a/tests/data/DISABLED b/tests/data/DISABLED index 8607866fd039..d4c90a0df913 100644 --- a/tests/data/DISABLED +++ b/tests/data/DISABLED @@ -23,14 +23,10 @@ ########################################################################### # # This file can be used to specify test cases that should not run when all -# test cases are run by runtests.pl. Just add the plain test case numbers, one +# test cases are run by runtests.pl. Add the plain test case numbers, one # per line. # Lines starting with '#' letters are treated as comments. # -# Uses SRP to "a server not supporting it" but modern stunnel versions -# will silently accept it and remain happy -323 -# 594 836 882 @@ -43,7 +39,7 @@ 1184 1209 1211 -# fnmatch differences are just too common to make testing them sensible +# fnmatch differences are too common to make testing them sensible 1307 1316 1512 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 5a517df9f3c9..3b32fce7bb30 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -25,276 +25,2130 @@ all: install: test: +# The helper script ./scripts/testnum provides a set of available test +# numbers. + # this list is in numerical order -TESTCASES = test1 test2 test3 test4 test5 test6 test7 test8 test9 \ -test10 test11 test12 test13 test14 test15 test16 test17 test18 test19 \ -test20 test21 test22 test23 test24 test25 test26 test27 test28 test29 \ -test30 test31 test32 test33 test34 test35 test36 test37 test38 test39 \ -test40 test41 test42 test43 test44 test45 test46 test47 test48 test49 \ -test50 test51 test52 test53 test54 test55 test56 test57 test58 test59 \ -test60 test61 test62 test63 test64 test65 test66 test67 test68 test69 \ -test70 test71 test72 test73 test74 test75 test76 test77 test78 test79 \ -test80 test81 test82 test83 test84 test85 test86 test87 test88 test89 \ -test90 test91 test92 test93 test94 test95 test96 test97 test98 test99 \ -test100 test101 test102 test103 test104 test105 test106 test107 test108 \ -test109 test110 test111 test112 test113 test114 test115 test116 test117 \ -test118 test119 test120 test121 test122 test123 test124 test125 test126 \ -test127 test128 test129 test130 test131 test132 test133 test134 test135 \ -test136 test137 test138 test139 test140 test141 test142 test143 test144 \ -test145 test146 test147 test148 test149 test150 test151 test152 test153 \ -test154 test155 test156 test157 test158 test159 test160 test161 test162 \ -test163 test164 test165 test166 test167 test168 test169 test170 test171 \ -test172 test173 test174 test175 test176 test177 test178 test179 test180 \ -test181 test182 test183 test184 test185 test186 test187 test188 test189 \ -test190 test191 test192 test193 test194 test195 test196 test197 test198 \ -test199 test200 test201 test202 test203 test204 test205 test206 test207 \ -test208 test209 test210 test211 test212 test213 test214 test215 test216 \ -test217 test218 test219 test220 test221 test222 test223 test224 test225 \ -test226 test227 test228 test229 test230 test231 test232 test233 test234 \ -test235 test236 test237 test238 test239 test240 test241 test242 test243 \ -test244 test245 test246 test247 test248 test249 test250 test251 test252 \ -test253 test254 test255 test256 test257 test258 test259 test260 test261 \ -test262 test263 test264 test265 test266 test267 test268 test269 test270 \ -test271 test272 test273 test274 test275 test276 test277 test278 test279 \ -test280 test281 test282 test283 test284 test285 test286 test287 test288 \ -test289 test290 test291 test292 test293 test294 test295 test296 test297 \ -test298 test299 test300 test301 test302 test303 test304 test305 test306 \ -test307 test308 test309 test310 test311 test312 test313 test314 test315 \ -test316 test317 test318 test319 test320 test321 test322 test323 test324 \ -test325 test326 test327 test328 test329 test330 test331 test332 test333 \ -test334 test335 test336 test337 test338 test339 test340 test341 test342 \ -test343 test344 test345 test346 test347 test348 test349 test350 test351 \ -test352 test353 test354 test355 test356 test357 test358 test359 test360 \ -test361 test362 test363 test364 test365 test366 test367 test368 test369 \ -test370 test371 test372 test373 test374 test375 test376 test378 test379 \ -test380 test381 test383 test384 test385 test386 test387 test388 test389 \ -test390 test391 test392 test393 test394 test395 test396 test397 test398 \ -test399 test400 test401 test402 test403 test404 test405 test406 test407 \ -test408 test409 test410 test411 test412 test413 test414 test415 test416 \ -test417 test418 test419 test420 test421 test422 test423 test424 test425 \ -test426 test427 test428 test429 test430 test431 test432 test433 test434 \ -test435 test436 test437 test438 test439 test440 test441 test442 test443 \ -test444 test445 test446 test447 test448 test449 test450 test451 test452 \ -test453 test454 test455 test456 test457 test458 test459 test460 test461 \ -test462 test463 test467 test468 test469 test470 test471 test472 test473 \ -test474 test475 test476 test477 test478 test479 test480 test481 test482 \ -test483 test484 test485 test486 test487 test488 test489 test490 test491 \ -test492 test493 test494 test495 test496 test497 test498 test499 test500 \ -test501 test502 test503 test504 test505 test506 test507 test508 test509 \ -test510 test511 test512 test513 test514 test515 test516 test517 test518 \ -test519 test520 test521 test522 test523 test524 test525 test526 test527 \ -test528 test529 test530 test531 test532 test533 test534 test535 test536 \ -test537 test538 test539 test540 test541 test542 test543 test544 test545 \ -test546 test547 test548 test549 test550 test551 test552 test553 test554 \ -test555 test556 test557 test558 test559 test560 test561 test562 test563 \ -test564 test565 test566 test567 test568 test569 test570 test571 test572 \ -test573 test574 test575 test576 test577 test578 test579 test580 test581 \ -test582 test583 test584 test585 test586 test587 test588 test589 test590 \ -test591 test592 test593 test594 test595 test596 test597 test598 test599 \ -test600 test601 test602 test603 test604 test605 test606 test607 test608 \ -test609 test610 test611 test612 test613 test614 test615 test616 test617 \ -test618 test619 test620 test621 test622 test623 test624 test625 test626 \ -test627 test628 test629 test630 test631 test632 test633 test634 test635 \ -test636 test637 test638 test639 test640 test641 test642 test643 test644 \ -test645 test646 test647 test648 test649 test650 test651 test652 test653 \ -test654 test655 test656 test658 test659 test660 test661 test662 test663 \ -test664 test665 test666 test667 test668 test669 test670 test671 test672 \ -test673 test674 test675 test676 test677 test678 test679 test680 test681 \ -test682 test683 test684 test685 test686 test687 test688 test689 test690 \ -test691 test692 test693 test694 test695 test696 test697 test698 test699 \ -test700 test701 test702 test703 test704 test705 test706 test707 test708 \ -test709 test710 test711 test712 test713 test714 test715 test716 test717 \ -test718 test719 test720 test721 test722 test723 test724 test725 test726 \ -test727 test728 test729 test730 test731 test732 test733 test734 test735 \ -test736 test737 test738 test739 test740 test741 test742 test743 test744 \ -test745 test746 test747 test748 test749 test750 test751 test752 test753 \ -test754 test755 test756 test757 test758 test759 test760 test761 test762 \ -test763 test764 test765 test766 test767 test768 test769 test770 test771 \ -test772 test773 test774 test775 test776 test777 test778 test779 test780 \ -test781 test782 test783 test784 test785 test786 test787 test788 test789 \ -test790 test791 test792 test793 test794 test795 test796 test797 test798 \ -test799 test800 test801 test802 test803 test804 test805 test806 test807 \ -test808 test809 test810 test811 test812 test813 test814 test815 test816 \ -test817 test818 test819 test820 test821 test822 test823 test824 test825 \ -test826 test827 test828 test829 test830 test831 test832 test833 test834 \ -test835 test836 test837 test838 test839 test840 test841 test842 test843 \ -test844 test845 test846 test847 test848 test849 test850 test851 test852 \ -test853 test854 test855 test856 test857 test858 test859 test860 test861 \ -test862 test863 test864 test865 test866 test867 test868 test869 test870 \ -test871 test872 test873 test874 test875 test876 test877 test878 test879 \ -test880 test881 test882 test883 test884 test885 test886 test887 test888 \ -test889 test890 test891 test892 test893 test894 test895 test896 test897 \ -test898 test899 test900 test901 test902 test903 test904 test905 test906 \ -test907 test908 test909 test910 test911 test912 test913 test914 test915 \ -test916 test917 test918 test919 test920 test921 test922 test923 test924 \ -test925 test926 test927 test928 test929 test930 test931 test932 test933 \ -test934 test935 test936 test937 test938 test939 test940 test941 test942 \ -test943 test944 test945 test946 test947 test948 test949 test950 test951 \ -test952 test953 test954 test955 test956 test957 test958 test959 test960 \ -test961 test962 test963 test964 test965 test966 test967 test968 test969 \ -test970 test971 test972 test973 test974 test975 test976 test977 test978 \ -test979 test980 test981 test982 test983 test984 test985 test986 test987 \ -test988 test989 test990 test991 test992 test993 test994 test995 test996 \ -test997 test998 test999 test1000 test1001 test1002 test1003 test1004 \ -test1005 test1006 test1007 test1008 test1009 test1010 test1011 test1012 \ -test1013 test1014 test1015 test1016 test1017 test1018 test1019 test1020 \ -test1021 test1022 test1023 test1024 test1025 test1026 test1027 test1028 \ -test1029 test1030 test1031 test1032 test1033 test1034 test1035 test1036 \ -test1037 test1038 test1039 test1040 test1041 test1042 test1043 test1044 \ -test1045 test1046 test1047 test1048 test1049 test1050 test1051 test1052 \ -test1053 test1054 test1055 test1056 test1057 test1058 test1059 test1060 \ -test1061 test1062 test1063 test1064 test1065 test1066 test1067 test1068 \ -test1069 test1070 test1071 test1072 test1073 test1074 test1075 test1076 \ -test1077 test1078 test1079 test1080 test1081 test1082 test1083 test1084 \ -test1085 test1086 test1087 test1088 test1089 test1090 test1091 test1092 \ -test1093 test1094 test1095 test1096 test1097 test1098 test1099 test1100 \ -test1101 test1102 test1103 test1104 test1105 test1106 test1107 test1108 \ -test1109 test1110 test1111 test1112 test1113 test1114 test1115 test1116 \ -test1117 test1118 test1119 test1120 test1121 test1122 test1123 test1124 \ -test1125 test1126 test1127 test1128 test1129 test1130 test1131 test1132 \ -test1133 test1134 test1135 test1136 test1137 test1138 test1139 test1140 \ -test1141 test1142 test1143 test1144 test1145 test1146 test1147 test1148 \ -test1149 test1150 test1151 test1152 test1153 test1154 test1155 test1156 \ -test1157 test1158 test1159 test1160 test1161 test1162 test1163 test1164 \ -test1165 test1166 test1167 test1168 test1169 test1170 test1171 test1172 \ -test1173 test1174 test1175 test1176 test1177 test1178 test1179 test1180 \ -test1181 test1182 test1183 test1184 test1185 test1186 test1187 test1188 \ -test1189 test1190 test1190 test1191 test1192 test1193 test1194 test1195 \ -test1196 test1197 test1198 test1199 test1200 test1201 test1202 test1203 \ -test1204 test1205 test1206 test1207 test1208 test1209 test1210 test1211 \ -test1212 test1213 test1214 test1215 test1216 test1217 test1218 test1219 \ -test1220 test1221 test1222 test1223 test1224 test1225 test1226 test1227 \ -test1228 test1229 test1230 test1231 test1232 test1233 test1234 test1235 \ -test1236 test1237 test1238 test1239 test1240 test1241 test1242 test1243 \ -test1244 test1245 test1246 test1247 test1248 test1249 test1250 test1251 \ -test1252 test1253 test1254 test1255 test1256 test1257 test1258 test1259 \ -test1260 test1261 test1262 test1263 test1264 test1265 test1266 test1267 \ -test1268 test1269 test1270 test1271 test1272 test1273 test1274 test1275 \ -test1276 test1277 test1278 test1279 test1280 test1281 test1282 test1283 \ -test1284 test1285 test1286 test1287 test1288 test1289 test1290 test1291 \ -test1292 test1293 test1294 test1295 test1296 test1297 test1298 test1299 \ -test1300 test1301 test1302 test1303 test1304 test1305 test1306 test1307 \ -test1308 test1309 test1310 test1311 test1312 test1313 test1314 test1315 \ -test1316 test1317 test1318 test1319 test1320 test1321 test1322 test1323 \ -test1324 test1325 test1326 test1327 test1328 test1329 test1330 test1331 \ -test1332 test1333 test1334 test1335 test1336 test1337 test1338 test1339 \ -test1340 test1341 test1342 test1343 test1344 test1345 test1346 test1347 \ -test1348 test1349 test1350 test1351 test1352 test1353 test1354 test1355 \ -test1356 test1357 test1358 test1359 test1360 test1361 test1362 test1363 \ -test1364 test1365 test1366 test1367 test1368 test1369 test1370 test1371 \ -test1372 test1373 test1374 test1375 test1376 test1377 test1378 test1379 \ -test1380 test1381 test1382 test1383 test1384 test1385 test1386 test1387 \ -test1388 test1389 test1390 test1391 test1392 test1393 test1394 test1395 \ -test1396 test1397 test1398 test1399 test1400 test1401 test1402 test1403 \ -test1404 test1405 test1406 test1407 test1408 test1409 test1410 test1411 \ -test1412 test1413 test1414 test1415 test1416 test1417 test1418 test1419 \ -test1420 test1421 test1422 test1423 test1424 test1425 test1426 test1427 \ -test1428 test1429 test1430 test1431 test1432 test1433 test1434 test1435 \ -test1436 test1437 test1438 test1439 test1440 test1441 test1442 test1443 \ -test1444 test1445 test1446 test1447 test1448 test1449 test1450 test1451 \ -test1452 test1453 test1454 test1455 test1456 test1457 test1458 test1459 \ -test1460 test1461 test1462 test1463 test1464 test1465 test1466 test1467 \ -test1468 test1469 test1470 test1471 test1472 test1473 test1474 test1475 \ -test1476 test1477 test1478 test1479 test1480 test1481 test1482 test1483 \ -test1484 test1485 test1486 test1487 test1488 test1489 test1490 test1491 \ -test1492 test1493 test1494 test1495 test1496 test1497 test1498 test1499 \ -test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \ -test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \ -test1516 test1517 test1518 test1519 test1520 test1521 test1522 test1523 \ -test1524 test1525 test1526 test1527 test1528 test1529 test1530 test1531 \ -test1532 test1533 test1534 test1535 test1536 test1537 test1538 test1539 \ -test1540 test1541 test1542 test1543 test1544 test1545 test1546 test1547 \ -test1548 test1549 test1550 test1551 test1552 test1553 test1554 test1555 \ -test1556 test1557 test1558 test1559 test1560 test1561 test1562 test1563 \ -test1564 test1565 test1566 test1567 test1568 test1569 test1570 test1571 \ -test1572 test1573 test1574 test1575 test1576 test1577 test1578 test1579 \ -test1580 test1581 test1582 test1583 test1584 test1585 test1586 test1587 \ -test1588 \ -\ -test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \ -test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \ -test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \ -test1614 test1615 test1616 test1617 test1618 test1619 test1620 test1621 \ -test1622 test1623 test1624 test1625 test1626 test1627 \ -\ -test1630 test1631 test1632 test1633 test1634 test1635 test1636 test1637 \ -test1638 test1639 test1640 test1641 test1642 test1643 test1644 \ -\ -test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \ -test1658 test1659 test1660 test1661 test1662 test1663 test1664 test1665 \ -test1666 test1667 test1668 test1669 \ -\ -test1670 test1671 test1672 test1673 test1674 test1675 \ -\ -test1680 test1681 test1682 test1683 test1684 test1685 \ -\ -test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \ -test1708 test1709 test1710 test1711 test1712 test1713 test1714 test1715 \ -test1720 \ -\ -test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1851 \ -\ -test1900 test1901 test1902 test1903 test1904 test1905 test1906 test1907 \ -test1908 test1909 test1910 test1911 test1912 test1913 test1914 test1915 \ -test1916 test1917 test1918 test1919 test1920 \ -\ -test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \ -test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ -test1955 test1956 test1957 test1958 test1959 test1960 test1964 test1965 \ -test1966 \ -\ -test1970 test1971 test1972 test1973 test1974 test1975 test1976 test1977 \ -test1978 test1979 test1980 test1981 test1982 test1983 test1984 \ -\ -test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \ -test2008 test2009 test2010 test2011 test2012 test2013 \ -\ - test2023 \ -test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \ -test2032 test2033 test2034 test2035 test2037 test2038 test2039 \ -test2040 test2041 test2042 test2043 test2044 test2045 test2046 test2047 \ -test2048 test2049 test2050 test2051 test2052 test2053 test2054 test2055 \ -test2056 test2057 test2058 test2059 test2060 test2061 test2062 test2063 \ -test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \ -test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \ -test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \ -test2088 test2089 test2090 test2091 \ -test2100 test2101 test2102 test2103 test2104 \ -\ -test2200 test2201 test2202 test2203 test2204 test2205 \ -\ -test2300 test2301 test2302 test2303 test2304 test2306 test2307 test2308 \ -test2309 \ -\ -test2400 test2401 test2402 test2403 test2404 test2405 test2406 test2407 \ -\ -test2500 test2501 test2502 test2503 test2504 test2505 test2506 \ -\ -test2600 test2601 test2602 test2603 test2604 test2605 \ -\ -test2700 test2701 test2702 test2703 test2704 test2705 test2706 test2707 \ -test2708 test2709 test2710 test2711 test2712 test2713 test2714 test2715 \ -test2716 test2717 test2718 test2719 test2720 test2721 test2722 test2723 \ -\ -test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ -test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ -test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \ -test3024 test3025 test3026 test3027 test3028 test3029 test3030 test3031 \ -test3032 test3033 test3034 test3035 test3036 \ -\ -test3100 test3101 test3102 test3103 test3104 test3105 \ -\ -test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 \ -test3208 test3209 test3210 test3211 test3212 test3213 test3214 test3215 \ -test3216 test3217 test3218 test3219 test3220 \ -\ -test3300 test3301 \ -\ -test4000 test4001 +TESTCASES = \ + test1 \ + test2 \ + test3 \ + test4 \ + test5 \ + test6 \ + test7 \ + test8 \ + test9 \ + test10 \ + test11 \ + test12 \ + test13 \ + test14 \ + test15 \ + test16 \ + test17 \ + test18 \ + test19 \ + test20 \ + test21 \ + test22 \ + test23 \ + test24 \ + test25 \ + test26 \ + test27 \ + test28 \ + test29 \ + test30 \ + test31 \ + test32 \ + test33 \ + test34 \ + test35 \ + test36 \ + test37 \ + test38 \ + test39 \ + test40 \ + test41 \ + test42 \ + test43 \ + test44 \ + test45 \ + test46 \ + test47 \ + test48 \ + test49 \ + test50 \ + test51 \ + test52 \ + test53 \ + test54 \ + test55 \ + test56 \ + test57 \ + test58 \ + test59 \ + test60 \ + test61 \ + test62 \ + test63 \ + test64 \ + test65 \ + test66 \ + test67 \ + test68 \ + test69 \ + test70 \ + test71 \ + test72 \ + test73 \ + test74 \ + test75 \ + test76 \ + test77 \ + test78 \ + test79 \ + test80 \ + test81 \ + test82 \ + test83 \ + test84 \ + test85 \ + test86 \ + test87 \ + test88 \ + test89 \ + test90 \ + test91 \ + test92 \ + test93 \ + test94 \ + test95 \ + test96 \ + test97 \ + test98 \ + test99 \ + test100 \ + test101 \ + test102 \ + test103 \ + test104 \ + test105 \ + test106 \ + test107 \ + test108 \ + test109 \ + test110 \ + test111 \ + test112 \ + test113 \ + test114 \ + test115 \ + test116 \ + test117 \ + test118 \ + test119 \ + test120 \ + test121 \ + test122 \ + test123 \ + test124 \ + test125 \ + test126 \ + test127 \ + test128 \ + test129 \ + test130 \ + test131 \ + test132 \ + test133 \ + test134 \ + test135 \ + test136 \ + test137 \ + test138 \ + test139 \ + test140 \ + test141 \ + test142 \ + test143 \ + test144 \ + test145 \ + test146 \ + test147 \ + test148 \ + test149 \ + test150 \ + test151 \ + test152 \ + test153 \ + test154 \ + test155 \ + test156 \ + test157 \ + test158 \ + test159 \ + test160 \ + test161 \ + test162 \ + test163 \ + test164 \ + test165 \ + test166 \ + test167 \ + test168 \ + test169 \ + test170 \ + test171 \ + test172 \ + test173 \ + test174 \ + test175 \ + test176 \ + test177 \ + test178 \ + test179 \ + test180 \ + test181 \ + test182 \ + test183 \ + test184 \ + test185 \ + test186 \ + test187 \ + test188 \ + test189 \ + test190 \ + test191 \ + test192 \ + test193 \ + test194 \ + test195 \ + test196 \ + test197 \ + test198 \ + test199 \ + test200 \ + test201 \ + test202 \ + test203 \ + test204 \ + test205 \ + test206 \ + test207 \ + test208 \ + test209 \ + test210 \ + test211 \ + test212 \ + test213 \ + test214 \ + test215 \ + test216 \ + test217 \ + test218 \ + test219 \ + test220 \ + test221 \ + test222 \ + test223 \ + test224 \ + test225 \ + test226 \ + test227 \ + test228 \ + test229 \ + test230 \ + test231 \ + test232 \ + test233 \ + test234 \ + test235 \ + test236 \ + test237 \ + test238 \ + test239 \ + test240 \ + test241 \ + test242 \ + test243 \ + test244 \ + test245 \ + test246 \ + test247 \ + test248 \ + test249 \ + test250 \ + test251 \ + test252 \ + test253 \ + test254 \ + test255 \ + test256 \ + test257 \ + test258 \ + test259 \ + test260 \ + test261 \ + test262 \ + test263 \ + test264 \ + test265 \ + test266 \ + test267 \ + test268 \ + test269 \ + test270 \ + test271 \ + test272 \ + test273 \ + test274 \ + test275 \ + test276 \ + test277 \ + test278 \ + test279 \ + test280 \ + test281 \ + test282 \ + test283 \ + test284 \ + test285 \ + test286 \ + test287 \ + test288 \ + test289 \ + test290 \ + test291 \ + test292 \ + test293 \ + test294 \ + test295 \ + test296 \ + test297 \ + test298 \ + test299 \ + test300 \ + test301 \ + test302 \ + test303 \ + test304 \ + test305 \ + test306 \ + test307 \ + test308 \ + test309 \ + test310 \ + test311 \ + test312 \ + test313 \ + test314 \ + test315 \ + test316 \ + test317 \ + test318 \ + test319 \ + test320 \ + test321 \ + test322 \ + test325 \ + test326 \ + test327 \ + test328 \ + test329 \ + test330 \ + test331 \ + test332 \ + test333 \ + test334 \ + test335 \ + test336 \ + test337 \ + test338 \ + test339 \ + test340 \ + test341 \ + test342 \ + test343 \ + test344 \ + test345 \ + test346 \ + test347 \ + test348 \ + test349 \ + test350 \ + test351 \ + test352 \ + test353 \ + test354 \ + test355 \ + test356 \ + test357 \ + test358 \ + test359 \ + test360 \ + test361 \ + test362 \ + test363 \ + test364 \ + test365 \ + test366 \ + test367 \ + test368 \ + test369 \ + test370 \ + test371 \ + test372 \ + test373 \ + test374 \ + test375 \ + test376 \ + test378 \ + test379 \ + test380 \ + test381 \ + test383 \ + test384 \ + test385 \ + test386 \ + test387 \ + test388 \ + test389 \ + test390 \ + test391 \ + test392 \ + test393 \ + test394 \ + test395 \ + test396 \ + test397 \ + test398 \ + test399 \ + test400 \ + test401 \ + test402 \ + test403 \ + test404 \ + test405 \ + test406 \ + test407 \ + test408 \ + test409 \ + test410 \ + test411 \ + test412 \ + test413 \ + test414 \ + test415 \ + test416 \ + test417 \ + test418 \ + test419 \ + test420 \ + test421 \ + test422 \ + test423 \ + test424 \ + test425 \ + test426 \ + test427 \ + test428 \ + test429 \ + test430 \ + test431 \ + test432 \ + test433 \ + test434 \ + test435 \ + test436 \ + test437 \ + test438 \ + test439 \ + test440 \ + test441 \ + test442 \ + test443 \ + test444 \ + test445 \ + test446 \ + test447 \ + test448 \ + test449 \ + test450 \ + test451 \ + test452 \ + test453 \ + test454 \ + test455 \ + test456 \ + test457 \ + test458 \ + test459 \ + test460 \ + test461 \ + test462 \ + test463 \ + test467 \ + test468 \ + test469 \ + test470 \ + test471 \ + test472 \ + test473 \ + test474 \ + test475 \ + test476 \ + test477 \ + test478 \ + test479 \ + test480 \ + test481 \ + test482 \ + test483 \ + test484 \ + test485 \ + test486 \ + test487 \ + test488 \ + test489 \ + test490 \ + test491 \ + test492 \ + test493 \ + test494 \ + test495 \ + test496 \ + test497 \ + test498 \ + test499 \ + test500 \ + test501 \ + test502 \ + test503 \ + test504 \ + test505 \ + test506 \ + test507 \ + test508 \ + test509 \ + test510 \ + test511 \ + test512 \ + test513 \ + test514 \ + test515 \ + test516 \ + test517 \ + test518 \ + test519 \ + test520 \ + test521 \ + test522 \ + test523 \ + test524 \ + test525 \ + test526 \ + test527 \ + test528 \ + test529 \ + test530 \ + test531 \ + test532 \ + test533 \ + test534 \ + test535 \ + test536 \ + test537 \ + test538 \ + test539 \ + test540 \ + test541 \ + test542 \ + test543 \ + test544 \ + test545 \ + test546 \ + test547 \ + test548 \ + test549 \ + test550 \ + test551 \ + test552 \ + test553 \ + test554 \ + test555 \ + test556 \ + test557 \ + test558 \ + test559 \ + test560 \ + test561 \ + test562 \ + test563 \ + test564 \ + test565 \ + test566 \ + test567 \ + test568 \ + test569 \ + test570 \ + test571 \ + test572 \ + test573 \ + test574 \ + test575 \ + test576 \ + test577 \ + test578 \ + test579 \ + test580 \ + test581 \ + test582 \ + test583 \ + test584 \ + test585 \ + test586 \ + test587 \ + test588 \ + test589 \ + test590 \ + test591 \ + test592 \ + test593 \ + test594 \ + test595 \ + test596 \ + test597 \ + test598 \ + test599 \ + test600 \ + test601 \ + test602 \ + test603 \ + test604 \ + test605 \ + test606 \ + test607 \ + test608 \ + test609 \ + test610 \ + test611 \ + test612 \ + test613 \ + test614 \ + test615 \ + test616 \ + test617 \ + test618 \ + test619 \ + test620 \ + test621 \ + test622 \ + test623 \ + test624 \ + test625 \ + test626 \ + test627 \ + test628 \ + test629 \ + test630 \ + test631 \ + test632 \ + test633 \ + test634 \ + test635 \ + test636 \ + test637 \ + test638 \ + test639 \ + test640 \ + test641 \ + test642 \ + test643 \ + test644 \ + test645 \ + test646 \ + test647 \ + test648 \ + test649 \ + test650 \ + test651 \ + test652 \ + test653 \ + test654 \ + test655 \ + test656 \ + test658 \ + test659 \ + test660 \ + test661 \ + test662 \ + test663 \ + test664 \ + test665 \ + test666 \ + test667 \ + test668 \ + test669 \ + test670 \ + test671 \ + test672 \ + test673 \ + test674 \ + test675 \ + test676 \ + test677 \ + test678 \ + test679 \ + test680 \ + test681 \ + test682 \ + test683 \ + test684 \ + test685 \ + test686 \ + test687 \ + test688 \ + test689 \ + test690 \ + test691 \ + test692 \ + test693 \ + test694 \ + test695 \ + test696 \ + test697 \ + test698 \ + test699 \ + test700 \ + test701 \ + test702 \ + test703 \ + test704 \ + test705 \ + test706 \ + test707 \ + test708 \ + test709 \ + test710 \ + test711 \ + test712 \ + test713 \ + test714 \ + test715 \ + test716 \ + test717 \ + test718 \ + test719 \ + test720 \ + test721 \ + test722 \ + test723 \ + test724 \ + test725 \ + test726 \ + test727 \ + test728 \ + test729 \ + test730 \ + test731 \ + test732 \ + test733 \ + test734 \ + test735 \ + test736 \ + test737 \ + test738 \ + test739 \ + test740 \ + test741 \ + test742 \ + test743 \ + test744 \ + test745 \ + test746 \ + test747 \ + test748 \ + test749 \ + test750 \ + test751 \ + test752 \ + test753 \ + test754 \ + test755 \ + test756 \ + test757 \ + test758 \ + test759 \ + test760 \ + test761 \ + test762 \ + test763 \ + test764 \ + test765 \ + test766 \ + test767 \ + test768 \ + test769 \ + test770 \ + test771 \ + test772 \ + test773 \ + test774 \ + test775 \ + test776 \ + test777 \ + test778 \ + test779 \ + test780 \ + test781 \ + test782 \ + test783 \ + test784 \ + test785 \ + test786 \ + test787 \ + test788 \ + test789 \ + test790 \ + test791 \ + test792 \ + test793 \ + test794 \ + test795 \ + test796 \ + test797 \ + test798 \ + test799 \ + test800 \ + test801 \ + test802 \ + test803 \ + test804 \ + test805 \ + test806 \ + test807 \ + test808 \ + test809 \ + test810 \ + test811 \ + test812 \ + test813 \ + test814 \ + test815 \ + test816 \ + test817 \ + test818 \ + test819 \ + test820 \ + test821 \ + test822 \ + test823 \ + test824 \ + test825 \ + test826 \ + test827 \ + test828 \ + test829 \ + test830 \ + test831 \ + test832 \ + test833 \ + test834 \ + test835 \ + test836 \ + test837 \ + test838 \ + test839 \ + test840 \ + test841 \ + test842 \ + test843 \ + test844 \ + test845 \ + test846 \ + test847 \ + test848 \ + test849 \ + test850 \ + test851 \ + test852 \ + test853 \ + test854 \ + test855 \ + test856 \ + test857 \ + test858 \ + test859 \ + test860 \ + test861 \ + test862 \ + test863 \ + test864 \ + test865 \ + test866 \ + test867 \ + test868 \ + test869 \ + test870 \ + test871 \ + test872 \ + test873 \ + test874 \ + test875 \ + test876 \ + test877 \ + test878 \ + test879 \ + test880 \ + test881 \ + test882 \ + test883 \ + test884 \ + test885 \ + test886 \ + test887 \ + test888 \ + test889 \ + test890 \ + test891 \ + test892 \ + test893 \ + test894 \ + test895 \ + test896 \ + test897 \ + test898 \ + test899 \ + test900 \ + test901 \ + test902 \ + test903 \ + test904 \ + test905 \ + test906 \ + test907 \ + test908 \ + test909 \ + test910 \ + test911 \ + test912 \ + test913 \ + test914 \ + test915 \ + test916 \ + test917 \ + test918 \ + test919 \ + test920 \ + test921 \ + test922 \ + test923 \ + test924 \ + test925 \ + test926 \ + test927 \ + test928 \ + test929 \ + test930 \ + test931 \ + test932 \ + test933 \ + test934 \ + test935 \ + test936 \ + test937 \ + test938 \ + test939 \ + test940 \ + test941 \ + test942 \ + test943 \ + test944 \ + test945 \ + test946 \ + test947 \ + test948 \ + test949 \ + test950 \ + test951 \ + test952 \ + test953 \ + test954 \ + test955 \ + test956 \ + test957 \ + test958 \ + test959 \ + test960 \ + test961 \ + test962 \ + test963 \ + test964 \ + test965 \ + test966 \ + test967 \ + test968 \ + test969 \ + test970 \ + test971 \ + test972 \ + test973 \ + test974 \ + test975 \ + test976 \ + test977 \ + test978 \ + test979 \ + test980 \ + test981 \ + test982 \ + test983 \ + test984 \ + test985 \ + test986 \ + test987 \ + test988 \ + test989 \ + test990 \ + test991 \ + test992 \ + test993 \ + test994 \ + test995 \ + test996 \ + test997 \ + test998 \ + test999 \ + test1000 \ + test1001 \ + test1002 \ + test1003 \ + test1004 \ + test1005 \ + test1006 \ + test1007 \ + test1008 \ + test1009 \ + test1010 \ + test1011 \ + test1012 \ + test1013 \ + test1014 \ + test1015 \ + test1016 \ + test1017 \ + test1018 \ + test1019 \ + test1020 \ + test1021 \ + test1022 \ + test1023 \ + test1024 \ + test1025 \ + test1026 \ + test1027 \ + test1028 \ + test1029 \ + test1030 \ + test1031 \ + test1032 \ + test1033 \ + test1034 \ + test1035 \ + test1036 \ + test1037 \ + test1038 \ + test1039 \ + test1040 \ + test1041 \ + test1042 \ + test1043 \ + test1044 \ + test1045 \ + test1046 \ + test1047 \ + test1048 \ + test1049 \ + test1050 \ + test1051 \ + test1052 \ + test1053 \ + test1054 \ + test1055 \ + test1056 \ + test1057 \ + test1058 \ + test1059 \ + test1060 \ + test1061 \ + test1062 \ + test1063 \ + test1064 \ + test1065 \ + test1066 \ + test1067 \ + test1068 \ + test1069 \ + test1070 \ + test1071 \ + test1072 \ + test1073 \ + test1074 \ + test1075 \ + test1076 \ + test1077 \ + test1078 \ + test1079 \ + test1080 \ + test1081 \ + test1082 \ + test1083 \ + test1084 \ + test1085 \ + test1086 \ + test1087 \ + test1088 \ + test1089 \ + test1090 \ + test1091 \ + test1092 \ + test1093 \ + test1094 \ + test1095 \ + test1096 \ + test1097 \ + test1098 \ + test1099 \ + test1100 \ + test1101 \ + test1102 \ + test1103 \ + test1104 \ + test1105 \ + test1106 \ + test1107 \ + test1108 \ + test1109 \ + test1110 \ + test1111 \ + test1112 \ + test1113 \ + test1114 \ + test1115 \ + test1116 \ + test1117 \ + test1118 \ + test1119 \ + test1120 \ + test1121 \ + test1122 \ + test1123 \ + test1124 \ + test1125 \ + test1126 \ + test1127 \ + test1128 \ + test1129 \ + test1130 \ + test1131 \ + test1132 \ + test1133 \ + test1134 \ + test1135 \ + test1136 \ + test1137 \ + test1138 \ + test1139 \ + test1140 \ + test1141 \ + test1142 \ + test1143 \ + test1144 \ + test1145 \ + test1146 \ + test1147 \ + test1148 \ + test1149 \ + test1150 \ + test1151 \ + test1152 \ + test1153 \ + test1154 \ + test1155 \ + test1156 \ + test1157 \ + test1158 \ + test1159 \ + test1160 \ + test1161 \ + test1162 \ + test1163 \ + test1164 \ + test1165 \ + test1166 \ + test1167 \ + test1168 \ + test1169 \ + test1170 \ + test1171 \ + test1172 \ + test1173 \ + test1174 \ + test1175 \ + test1176 \ + test1177 \ + test1178 \ + test1179 \ + test1180 \ + test1181 \ + test1182 \ + test1183 \ + test1184 \ + test1185 \ + test1186 \ + test1187 \ + test1188 \ + test1189 \ + test1190 \ + test1191 \ + test1192 \ + test1193 \ + test1194 \ + test1195 \ + test1196 \ + test1197 \ + test1198 \ + test1199 \ + test1200 \ + test1201 \ + test1202 \ + test1203 \ + test1204 \ + test1205 \ + test1206 \ + test1207 \ + test1208 \ + test1209 \ + test1210 \ + test1211 \ + test1212 \ + test1213 \ + test1214 \ + test1215 \ + test1216 \ + test1217 \ + test1218 \ + test1219 \ + test1220 \ + test1221 \ + test1222 \ + test1223 \ + test1224 \ + test1225 \ + test1226 \ + test1227 \ + test1228 \ + test1229 \ + test1230 \ + test1231 \ + test1232 \ + test1233 \ + test1234 \ + test1235 \ + test1236 \ + test1237 \ + test1238 \ + test1239 \ + test1240 \ + test1241 \ + test1242 \ + test1243 \ + test1244 \ + test1245 \ + test1246 \ + test1247 \ + test1248 \ + test1249 \ + test1250 \ + test1251 \ + test1252 \ + test1253 \ + test1254 \ + test1255 \ + test1256 \ + test1257 \ + test1258 \ + test1259 \ + test1260 \ + test1261 \ + test1262 \ + test1263 \ + test1264 \ + test1265 \ + test1266 \ + test1267 \ + test1268 \ + test1269 \ + test1270 \ + test1271 \ + test1272 \ + test1273 \ + test1274 \ + test1275 \ + test1276 \ + test1277 \ + test1278 \ + test1279 \ + test1280 \ + test1281 \ + test1282 \ + test1283 \ + test1284 \ + test1285 \ + test1286 \ + test1287 \ + test1288 \ + test1289 \ + test1290 \ + test1291 \ + test1292 \ + test1293 \ + test1294 \ + test1295 \ + test1296 \ + test1297 \ + test1298 \ + test1299 \ + test1300 \ + test1301 \ + test1302 \ + test1303 \ + test1304 \ + test1305 \ + test1306 \ + test1307 \ + test1308 \ + test1309 \ + test1310 \ + test1311 \ + test1312 \ + test1313 \ + test1314 \ + test1315 \ + test1316 \ + test1317 \ + test1318 \ + test1319 \ + test1320 \ + test1321 \ + test1322 \ + test1323 \ + test1324 \ + test1325 \ + test1326 \ + test1327 \ + test1328 \ + test1329 \ + test1330 \ + test1331 \ + test1332 \ + test1333 \ + test1334 \ + test1335 \ + test1336 \ + test1337 \ + test1338 \ + test1339 \ + test1340 \ + test1341 \ + test1342 \ + test1343 \ + test1344 \ + test1345 \ + test1346 \ + test1347 \ + test1348 \ + test1349 \ + test1350 \ + test1351 \ + test1352 \ + test1353 \ + test1354 \ + test1355 \ + test1356 \ + test1357 \ + test1358 \ + test1359 \ + test1360 \ + test1361 \ + test1362 \ + test1363 \ + test1364 \ + test1365 \ + test1366 \ + test1367 \ + test1368 \ + test1369 \ + test1370 \ + test1371 \ + test1372 \ + test1373 \ + test1374 \ + test1375 \ + test1376 \ + test1377 \ + test1378 \ + test1379 \ + test1380 \ + test1381 \ + test1382 \ + test1383 \ + test1384 \ + test1385 \ + test1386 \ + test1387 \ + test1388 \ + test1389 \ + test1390 \ + test1391 \ + test1392 \ + test1393 \ + test1394 \ + test1395 \ + test1396 \ + test1397 \ + test1398 \ + test1399 \ + test1400 \ + test1401 \ + test1402 \ + test1403 \ + test1404 \ + test1405 \ + test1406 \ + test1407 \ + test1408 \ + test1409 \ + test1410 \ + test1411 \ + test1412 \ + test1413 \ + test1414 \ + test1415 \ + test1416 \ + test1417 \ + test1418 \ + test1419 \ + test1420 \ + test1421 \ + test1422 \ + test1423 \ + test1424 \ + test1425 \ + test1426 \ + test1427 \ + test1428 \ + test1429 \ + test1430 \ + test1431 \ + test1432 \ + test1433 \ + test1434 \ + test1435 \ + test1436 \ + test1437 \ + test1438 \ + test1439 \ + test1440 \ + test1441 \ + test1442 \ + test1443 \ + test1444 \ + test1445 \ + test1446 \ + test1447 \ + test1448 \ + test1449 \ + test1450 \ + test1451 \ + test1452 \ + test1453 \ + test1454 \ + test1455 \ + test1456 \ + test1457 \ + test1458 \ + test1459 \ + test1460 \ + test1461 \ + test1462 \ + test1463 \ + test1464 \ + test1465 \ + test1466 \ + test1467 \ + test1468 \ + test1469 \ + test1470 \ + test1471 \ + test1472 \ + test1473 \ + test1474 \ + test1475 \ + test1476 \ + test1477 \ + test1478 \ + test1479 \ + test1480 \ + test1481 \ + test1482 \ + test1483 \ + test1484 \ + test1485 \ + test1486 \ + test1487 \ + test1488 \ + test1489 \ + test1490 \ + test1491 \ + test1492 \ + test1493 \ + test1494 \ + test1495 \ + test1496 \ + test1497 \ + test1498 \ + test1499 \ + test1500 \ + test1501 \ + test1502 \ + test1503 \ + test1504 \ + test1505 \ + test1506 \ + test1507 \ + test1508 \ + test1509 \ + test1510 \ + test1511 \ + test1512 \ + test1513 \ + test1514 \ + test1515 \ + test1516 \ + test1517 \ + test1518 \ + test1519 \ + test1520 \ + test1521 \ + test1522 \ + test1523 \ + test1524 \ + test1525 \ + test1526 \ + test1527 \ + test1528 \ + test1529 \ + test1530 \ + test1531 \ + test1532 \ + test1533 \ + test1534 \ + test1535 \ + test1536 \ + test1537 \ + test1538 \ + test1539 \ + test1540 \ + test1541 \ + test1542 \ + test1543 \ + test1544 \ + test1545 \ + test1546 \ + test1547 \ + test1548 \ + test1549 \ + test1550 \ + test1551 \ + test1552 \ + test1553 \ + test1554 \ + test1555 \ + test1556 \ + test1557 \ + test1558 \ + test1559 \ + test1560 \ + test1561 \ + test1562 \ + test1563 \ + test1564 \ + test1565 \ + test1566 \ + test1567 \ + test1568 \ + test1569 \ + test1570 \ + test1571 \ + test1572 \ + test1573 \ + test1574 \ + test1575 \ + test1576 \ + test1577 \ + test1578 \ + test1579 \ + test1580 \ + test1581 \ + test1582 \ + test1583 \ + test1584 \ + test1585 \ + test1586 \ + test1587 \ + test1588 \ + test1589 \ + test1590 \ + test1591 \ + test1592 \ + test1593 \ + test1594 \ + test1595 \ + test1596 \ + test1597 \ + test1598 \ + test1599 \ + test1600 \ + test1601 \ + test1602 \ + test1603 \ + test1604 \ + test1605 \ + test1606 \ + test1607 \ + test1608 \ + test1609 \ + test1610 \ + test1611 \ + test1612 \ + test1613 \ + test1614 \ + test1615 \ + test1616 \ + test1617 \ + test1618 \ + test1619 \ + test1620 \ + test1621 \ + test1622 \ + test1623 \ + test1624 \ + test1625 \ + test1626 \ + test1627 \ + test1628 \ + test1629 \ + test1630 \ + test1631 \ + test1632 \ + test1633 \ + test1634 \ + test1635 \ + test1636 \ + test1637 \ + test1638 \ + test1639 \ + test1640 \ + test1641 \ + test1642 \ + test1643 \ + test1644 \ + test1645 \ + test1646 \ + test1647 \ + test1648 \ + test1649 \ + test1650 \ + test1651 \ + test1652 \ + test1653 \ + test1654 \ + test1655 \ + test1656 \ + test1657 \ + test1658 \ + test1659 \ + test1660 \ + test1661 \ + test1662 \ + test1663 \ + test1664 \ + test1665 \ + test1666 \ + test1667 \ + test1668 \ + test1669 \ + test1670 \ + test1671 \ + test1672 \ + test1673 \ + test1674 \ + test1675 \ + test1676 \ + test1677 \ + test1678 \ + test1680 \ + test1681 \ + test1682 \ + test1683 \ + test1684 \ + test1685 \ + test1686 \ + \ + test1700 \ + test1702 \ + test1703 \ + test1704 \ + test1705 \ + test1706 \ + test1707 \ + test1708 \ + test1709 \ + test1710 \ + test1711 \ + test1712 \ + test1713 \ + test1714 \ + test1715 \ + test1720 \ + test1721 \ + test1722 \ + test1723 \ + test1724 \ + test1725 \ + test1740 \ + test1741 \ + \ + test1800 \ + test1801 \ + test1802 \ + test1847 \ + test1848 \ + test1849 \ + test1850 \ + test1851 \ + \ + test1900 \ + test1901 \ + test1902 \ + test1903 \ + test1904 \ + test1905 \ + test1906 \ + test1907 \ + test1908 \ + test1909 \ + test1910 \ + test1911 \ + test1912 \ + test1913 \ + test1914 \ + test1915 \ + test1916 \ + test1917 \ + test1918 \ + test1919 \ + test1920 \ + test1921 \ + test1922 \ + \ + test1933 \ + test1934 \ + test1935 \ + test1936 \ + test1937 \ + test1938 \ + test1939 \ + test1940 \ + test1941 \ + test1942 \ + test1943 \ + test1944 \ + test1945 \ + test1946 \ + test1947 \ + test1948 \ + test1955 \ + test1956 \ + test1957 \ + test1958 \ + test1959 \ + test1960 \ + test1961 \ + \ + test1964 \ + test1965 \ + test1966 \ + test1967 \ + test1970 \ + test1971 \ + test1972 \ + test1973 \ + test1974 \ + test1975 \ + test1976 \ + test1977 \ + test1978 \ + test1979 \ + test1980 \ + test1981 \ + test1982 \ + test1983 \ + test1984 \ + test1985 \ + \ + test2000 \ + test2001 \ + test2002 \ + test2003 \ + test2004 \ + test2005 \ + test2006 \ + test2007 \ + test2008 \ + test2009 \ + test2010 \ + test2011 \ + test2012 \ + test2013 \ + test2014 \ + test2015 \ + \ + test2023 \ + test2024 \ + test2025 \ + test2026 \ + test2027 \ + test2028 \ + test2029 \ + test2030 \ + test2031 \ + test2032 \ + test2033 \ + test2034 \ + test2035 \ + test2036 \ + test2037 \ + test2038 \ + test2039 \ + test2040 \ + test2041 \ + test2042 \ + test2043 \ + test2044 \ + test2045 \ + test2046 \ + test2047 \ + test2048 \ + test2049 \ + test2050 \ + test2051 \ + test2052 \ + test2053 \ + test2054 \ + test2055 \ + test2056 \ + test2057 \ + test2058 \ + test2059 \ + test2060 \ + test2061 \ + test2062 \ + test2063 \ + test2064 \ + test2065 \ + test2066 \ + test2067 \ + test2068 \ + test2069 \ + test2070 \ + test2071 \ + test2072 \ + test2073 \ + test2074 \ + test2075 \ + test2076 \ + test2077 \ + test2078 \ + test2079 \ + test2080 \ + test2081 \ + test2082 \ + test2083 \ + test2084 \ + test2085 \ + test2086 \ + test2087 \ + test2088 \ + test2089 \ + test2090 \ + test2091 \ + test2092 \ + test2093 \ + test2094 \ + test2100 \ + test2101 \ + test2102 \ + test2103 \ + test2104 \ + test2105 \ + test2106 \ + test2107 \ + test2108 \ + test2109 \ + test2110 \ + test2113 \ + test2114 \ + test2115 \ + test2116 \ + test2117 \ + test2118 \ + test2119 \ + \ + test2200 \ + test2201 \ + test2202 \ + test2203 \ + test2204 \ + test2205 \ + test2206 \ + test2207 \ + test2208 \ + \ + test2300 \ + test2301 \ + test2302 \ + test2303 \ + test2304 \ + test2305 \ + test2306 \ + test2307 \ + test2308 \ + test2309 \ + test2310 \ + test2311 \ + test2318 \ + test2349 \ + \ + test2397 \ + \ + test2400 \ + test2401 \ + test2402 \ + test2403 \ + test2404 \ + test2405 \ + test2406 \ + test2407 \ + test2408 \ + test2409 \ + test2410 \ + test2411 \ + test2412 \ + test2413 \ + test2414 \ + \ + test2500 \ + test2501 \ + test2502 \ + test2503 \ + test2504 \ + test2505 \ + test2506 \ + \ + test2600 \ + test2601 \ + test2602 \ + test2603 \ + test2604 \ + test2605 \ + test2606 \ + \ + test2700 \ + test2701 \ + test2702 \ + test2703 \ + test2704 \ + test2705 \ + test2706 \ + test2707 \ + test2708 \ + test2709 \ + test2710 \ + test2711 \ + test2712 \ + test2713 \ + test2714 \ + test2715 \ + test2716 \ + test2717 \ + test2718 \ + test2719 \ + test2720 \ + test2721 \ + test2722 \ + test2723 \ + test2885 \ + test3000 \ + test3001 \ + test3002 \ + test3003 \ + test3004 \ + test3005 \ + test3006 \ + test3007 \ + test3008 \ + test3009 \ + test3010 \ + test3011 \ + test3012 \ + test3013 \ + test3014 \ + test3015 \ + test3016 \ + test3017 \ + test3018 \ + test3019 \ + test3020 \ + test3021 \ + test3022 \ + test3023 \ + test3024 \ + test3025 \ + test3026 \ + test3027 \ + test3028 \ + test3029 \ + test3030 \ + test3031 \ + test3032 \ + test3033 \ + test3034 \ + test3035 \ + test3036 \ + \ + test3100 \ + test3101 \ + test3102 \ + test3103 \ + test3104 \ + test3105 \ + test3106 \ + \ + test3200 \ + test3201 \ + test3202 \ + test3203 \ + test3204 \ + test3205 \ + test3206 \ + test3207 \ + test3208 \ + test3209 \ + test3210 \ + test3211 \ + test3212 \ + test3213 \ + test3214 \ + test3215 \ + test3216 \ + test3217 \ + test3218 \ + test3219 \ + test3220 \ + test3221 \ + test3222 \ + test3223 \ + test3224 \ + test3225 \ + test3226 \ + test3227 \ + test3228 \ + test3229 \ + \ + test3300 \ + test3301 \ + test3302 \ + test3303 \ + test3304 \ + test3305 \ + test3306 \ + \ + test3400 \ + test3401 \ + \ + test4000 \ + test4001 \ + \ + test5000 \ + test5001 \ + test5002 \ + test5003 \ + test5004 \ + test5005 \ + test5006 \ + test5007 \ + test5008 \ + test5009 \ + test5010 \ + test5011 \ + test5012 \ + test5013 \ + test5014 \ + test5015 \ + test5016 \ + test5017 \ + test5018 \ + test5019 \ + test5020 \ + test5021 \ + test5022 \ + test5023 \ + test5024 \ + test5025 \ + test5026 \ + test5027 \ + test5028 -EXTRA_DIST = $(TESTCASES) DISABLED data-xml1 data320.html \ -data1461.txt data1463.txt \ -data1400.c data1401.c data1402.c data1403.c data1404.c data1405.c data1406.c \ -data1407.c data1420.c data1465.c data1481.c \ -data1705-1.md data1705-2.md data1705-3.md data1705-4.md data1705-stdout.1 \ -data1706-1.md data1706-2.md data1706-3.md data1706-4.md data1706-stdout.txt +EXTRA_DIST = \ + $(TESTCASES) \ + data-httpsig-ed25519.key \ + data-httpsig-hmac-sha256.key \ + data-xml1 \ + data1400.c \ + data1401.c \ + data1402.c \ + data1403.c \ + data1404.c \ + data1405.c \ + data1406.c \ + data1407.c \ + data1420.c \ + data1461.txt \ + data1463.txt \ + data1465.c \ + data1481.c \ + data1705-1.md \ + data1705-2.md \ + data1705-3.md \ + data1705-4.md \ + data1705-stdout.1 \ + data1706-1.md \ + data1706-2.md \ + data1706-3.md \ + data1706-4.md \ + data1706-stdout.txt \ + DISABLED diff --git a/tests/data/data-httpsig-ed25519.key b/tests/data/data-httpsig-ed25519.key new file mode 100644 index 000000000000..f0b6688b67d2 --- /dev/null +++ b/tests/data/data-httpsig-ed25519.key @@ -0,0 +1 @@ +9f8362f87a484a954e6e740c5b4c0e84229139a20aa8ab56ff66586f6a7d29c5 diff --git a/tests/data/data-httpsig-hmac-sha256.key b/tests/data/data-httpsig-hmac-sha256.key new file mode 100644 index 000000000000..eef9161772c3 --- /dev/null +++ b/tests/data/data-httpsig-hmac-sha256.key @@ -0,0 +1 @@ +0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef diff --git a/tests/data/data1402.c b/tests/data/data1402.c index 682345485d38..0490c96ea2cf 100644 --- a/tests/data/data1402.c +++ b/tests/data/data1402.c @@ -13,9 +13,9 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "foo=bar&baz=quux"); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)16); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); diff --git a/tests/data/data1404.c b/tests/data/data1404.c index df9364d10404..a09467fa1022 100644 --- a/tests/data/data1404.c +++ b/tests/data/data1404.c @@ -24,6 +24,7 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); mime1 = curl_mime_init(curl); part1 = curl_mime_addpart(mime1); curl_mime_data(part1, "value", CURL_ZERO_TERMINATED); @@ -44,7 +45,6 @@ int main(int argc, char *argv[]) mime2 = NULL; curl_mime_name(part1, "file"); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime1); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); diff --git a/tests/data/data1405.c b/tests/data/data1405.c index ec7a4a39c9a3..e79550806bf2 100644 --- a/tests/data/data1405.c +++ b/tests/data/data1405.c @@ -25,10 +25,10 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "ftp://%HOSTIP:%FTPPORT/%TESTNUMBER"); - curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); curl_easy_setopt(curl, CURLOPT_QUOTE, slist1); curl_easy_setopt(curl, CURLOPT_POSTQUOTE, slist2); curl_easy_setopt(curl, CURLOPT_PREQUOTE, slist3); + curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); /* Here is a list of options the curl code used that cannot get generated diff --git a/tests/data/data1406.c b/tests/data/data1406.c index 30aff8d58875..4f9a3fccafd9 100644 --- a/tests/data/data1406.c +++ b/tests/data/data1406.c @@ -18,8 +18,8 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER"); - curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(curl, CURLOPT_MAIL_FROM, "sender@example.com"); curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, slist1); diff --git a/tests/data/data1407.c b/tests/data/data1407.c index 4f34db365527..b6f312741e18 100644 --- a/tests/data/data1407.c +++ b/tests/data/data1407.c @@ -13,8 +13,8 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "pop3://%HOSTIP:%POP3PORT/%TESTNUMBER"); - curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); curl_easy_setopt(curl, CURLOPT_USERPWD, "user:secret"); + curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); /* Here is a list of options the curl code used that cannot get generated diff --git a/tests/data/data1461.txt b/tests/data/data1461.txt index 286be7c34434..5ddb4e1edda0 100644 --- a/tests/data/data1461.txt +++ b/tests/data/data1461.txt @@ -1,5 +1,5 @@ Usage: curl [options...] - -d, --data HTTP POST data + -d, --data Post data -f, --fail Fail fast with no output on HTTP errors -I, --head Show document info only -H, --header
Pass custom header(s) to server @@ -17,7 +17,7 @@ Usage: curl [options...] This is not the full help; this menu is split into categories. Use "--help category" to get an overview of all categories, which are: auth, connection, curl, deprecated, dns, file, ftp, global, http, imap, ldap,%SP -output, pop3, post, proxy, scp, sftp, smtp, ssh, telnet, tftp, timeout, tls,%SP -upload, verbose. +mqtt, output, pop3, post, proxy, scp, sftp, smtp, ssh, telnet, tftp, timeout,%SP +tls, upload, verbose. Use "--help all" to list all options Use "--help [option]" to view documentation for a given option diff --git a/tests/data/data1465.c b/tests/data/data1465.c index dea3b61988c0..ac3c6db10c9f 100644 --- a/tests/data/data1465.c +++ b/tests/data/data1465.c @@ -13,9 +13,9 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "ab\201cd\000e\\\"\?\r\n\t\001fghi\x1ajklm\xfd"); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)24); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); diff --git a/tests/data/data320.html b/tests/data/data320.html deleted file mode 100644 index 307bb87c473c..000000000000 --- a/tests/data/data320.html +++ /dev/null @@ -1,19 +0,0 @@ - -

This is GnuTLS

- -

Session ID: 003030000100000001000000000000000030330001000000B062410001000000

-
If your browser supports session resuming, then you should see the same session ID, when you press the reload button.
-

Connected as user 'jsmith'.

-

- - - - - -

Protocol version:TLS1.2
Key Exchange:SRP
CompressionNULL
CipherAES-NNN-CBC
MACSHA1
CiphersuiteSRP_SHA_AES_NNN_CBC_SHA1
-


Your HTTP header was:

Host: localhost:9011%CR
-User-Agent: curl-test-suite%CR
-Accept: */*%CR
-%CR
-

- diff --git a/tests/data/test1007 b/tests/data/test1007 index fdd6af2c9a3c..2706c049a55a 100644 --- a/tests/data/test1007 +++ b/tests/data/test1007 @@ -4,7 +4,6 @@ TFTP TFTP WRQ -FAILURE diff --git a/tests/data/test1030 b/tests/data/test1030 index ddf7370eaaf5..cb35a0f085cb 100644 --- a/tests/data/test1030 +++ b/tests/data/test1030 @@ -66,7 +66,7 @@ crypto digest -HTTP PUT with --anyauth authorization (picking Digest) +HTTP PUT with --anyauth, picking Digest. Persistent connection. http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T %LOGDIR/put%TESTNUMBER -u testuser:testpass --anyauth diff --git a/tests/data/test1034 b/tests/data/test1034 index 8d0c470e51a3..be48039b54ca 100644 --- a/tests/data/test1034 +++ b/tests/data/test1034 @@ -6,7 +6,6 @@ HTTP HTTP GET HTTP proxy IDN -FAILURE config file diff --git a/tests/data/test1035 b/tests/data/test1035 index fe8ae115b234..9139a41aa348 100644 --- a/tests/data/test1035 +++ b/tests/data/test1035 @@ -6,7 +6,6 @@ HTTP HTTP GET HTTP proxy IDN -FAILURE diff --git a/tests/data/test1042 b/tests/data/test1042 index 66ac19f4fd74..22b1acc03e04 100644 --- a/tests/data/test1042 +++ b/tests/data/test1042 @@ -5,7 +5,6 @@ HTTP HTTP GET Resume -FAILURE diff --git a/tests/data/test1059 b/tests/data/test1059 index c982ec3ac1a7..1cb2ff836a81 100644 --- a/tests/data/test1059 +++ b/tests/data/test1059 @@ -6,7 +6,6 @@ HTTP HTTP CONNECT proxytunnel FTP -FAILURE diff --git a/tests/data/test1062 b/tests/data/test1062 index b95818d2bf24..f8a0ae146082 100644 --- a/tests/data/test1062 +++ b/tests/data/test1062 @@ -26,7 +26,7 @@ REPLY CWD %repeat[218 x 250-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ftp -FTP with excessively long server command response lines, boundary condition +FTP excessively long server command response lines, boundary condition ftp://%HOSTIP:%FTPPORT/path/%TESTNUMBER diff --git a/tests/data/test1063 b/tests/data/test1063 index 63a2949716de..1ac641149223 100644 --- a/tests/data/test1063 +++ b/tests/data/test1063 @@ -4,7 +4,6 @@ FILE Range -FAILURE @@ -21,7 +20,7 @@ Largefile Invalid large X- range on a file:// -# This range value is 2**32+7, which will be truncated to the valid value 7 +# This range value is 2**32 + 7, which is truncated to the valid value 7 # if the large file support is not working correctly -r 4294967303- file://localhost%FILE_PWD/%LOGDIR/test%TESTNUMBER.txt diff --git a/tests/data/test1075 b/tests/data/test1075 index f5431927039b..6f2a21bba191 100644 --- a/tests/data/test1075 +++ b/tests/data/test1075 @@ -13,7 +13,7 @@ HTTP Basic auth # The test server provides no way to respond differently to a subsequent # Basic authenticated request (we really want to respond with 200 for -# the second), so just respond with 401 for both and let curl deal with it. +# the second), so respond with 401 for both and let curl deal with it. HTTP/1.1 401 Authorization Required Server: testcurl diff --git a/tests/data/test1084 b/tests/data/test1084 index 975c751b160a..66335111f899 100644 --- a/tests/data/test1084 +++ b/tests/data/test1084 @@ -5,7 +5,6 @@ HTTP HTTP GET --interface -FAILURE non-existing host diff --git a/tests/data/test1085 b/tests/data/test1085 index 8a2bbd8c9072..2b692ffee3a7 100644 --- a/tests/data/test1085 +++ b/tests/data/test1085 @@ -6,7 +6,6 @@ HTTP HTTP GET --interface IPv6 -FAILURE non-existing host diff --git a/tests/data/test1086 b/tests/data/test1086 index efb51c413bc5..440aadfa0e75 100644 --- a/tests/data/test1086 +++ b/tests/data/test1086 @@ -6,7 +6,6 @@ FTP EPSV RETR timeout -FAILURE SLOWDOWNDATA flaky timing-dependent diff --git a/tests/data/test1088 b/tests/data/test1088 index 36d8e2496f7e..04484add385c 100644 --- a/tests/data/test1088 +++ b/tests/data/test1088 @@ -75,7 +75,7 @@ contents http -HTTP, proxy with --anyauth and Location: to new host using location-trusted +HTTP, proxy with --anyauth and Location: using location-trusted http://first.host.it.is/we/want/that/page/%TESTNUMBER1000 -x %HOSTIP:%HTTPPORT --user iam:myself --location-trusted --anyauth diff --git a/tests/data/test1096 b/tests/data/test1096 index 0485f7c2e359..971aef1cb177 100644 --- a/tests/data/test1096 +++ b/tests/data/test1096 @@ -5,7 +5,6 @@ FTP PASV RETR -FAILURE # Server-side diff --git a/tests/data/test1099 b/tests/data/test1099 index 7539832ca4f9..a38038713f56 100644 --- a/tests/data/test1099 +++ b/tests/data/test1099 @@ -4,7 +4,6 @@ TFTP TFTP RRQ -FAILURE diff --git a/tests/data/test1105 b/tests/data/test1105 index 7d4df9556cde..45251d3e7ca0 100644 --- a/tests/data/test1105 +++ b/tests/data/test1105 @@ -33,7 +33,7 @@ Set-Cookie: bar=foo%TABbar http -HTTP with cookie parser and header recording +HTTP POST with cookies "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER?parm1=this*that/other/thing%AMPparm2=foobar/%TESTNUMBER" -c %LOGDIR/cookie%TESTNUMBER.txt -d "userid=myname%AMPpassword=mypassword" diff --git a/tests/data/test111 b/tests/data/test111 index 655cc0c275af..10fbfa91cffa 100644 --- a/tests/data/test111 +++ b/tests/data/test111 @@ -5,7 +5,6 @@ FTP EPSV Resume -FAILURE # Server-side diff --git a/tests/data/test1112 b/tests/data/test1112 index 0d9e19ef80ed..45ce8e0740b7 100644 --- a/tests/data/test1112 +++ b/tests/data/test1112 @@ -7,7 +7,6 @@ FTPS EPSV RETR timeout -FAILURE SLOWDOWNDATA timing-dependent diff --git a/tests/data/test1113 b/tests/data/test1113 index 7486952f6e1a..ed606ec667b1 100644 --- a/tests/data/test1113 +++ b/tests/data/test1113 @@ -26,7 +26,7 @@ ftp lib574 -FTP wildcard download - changed fnmatch, 2x perform (DOS LIST response) +FTP wildcard download - changed fnmatch, 2x perform (DOS LIST) "ftp://%HOSTIP:%FTPPORT/fully_simulated/DOS/*.txt" diff --git a/tests/data/test1120 b/tests/data/test1120 index 14500563344e..e5c223aa0679 100644 --- a/tests/data/test1120 +++ b/tests/data/test1120 @@ -7,7 +7,6 @@ PORT RETR 421 timeout -FAILURE # Server-side diff --git a/tests/data/test113 b/tests/data/test113 index 6a7266031d56..68637a12b816 100644 --- a/tests/data/test113 +++ b/tests/data/test113 @@ -3,7 +3,6 @@ FTP -FAILURE # Server-side diff --git a/tests/data/test1130 b/tests/data/test1130 index a23c9d314b4c..abbfc499a1fc 100644 --- a/tests/data/test1130 +++ b/tests/data/test1130 @@ -32,7 +32,7 @@ Content-Type: text/html # We use skip to make the test server never read the full payload off # the socket and instead return the response at once. In actuality, the -# long --expect100-timeout means that it will never get a chance to read this. +# long --expect100-timeout means that it never gets a chance to read this. skip: 100 diff --git a/tests/data/test1131 b/tests/data/test1131 index 85e06047412f..855121d27852 100644 --- a/tests/data/test1131 +++ b/tests/data/test1131 @@ -32,7 +32,7 @@ FAILURE2 # We use skip to make the test server never read the full payload off # the socket and instead return the response at once. In actuality, the -# long --expect100-timeout means that it will never get a chance to read this. +# long --expect100-timeout means that it never gets a chance to read this. skip: 100 diff --git a/tests/data/test1133 b/tests/data/test1133 index bb25c0166625..109d6be5c948 100644 --- a/tests/data/test1133 +++ b/tests/data/test1133 @@ -27,7 +27,7 @@ Mime http -HTTP RFC1867-type formposting with filename/data contains ',', ';', '"' +HTTP formpost with filename/data containing ',', ';', '"' http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER diff --git a/tests/data/test1136 b/tests/data/test1136 index 6d9cde3b4cef..4213e6e45753 100644 --- a/tests/data/test1136 +++ b/tests/data/test1136 @@ -59,7 +59,7 @@ http://www.example.ck/%TESTNUMBER http://www.ck/%TESTNUMBER http://z-1.compute-1 # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -.z-1.compute-1.amazonaws.com TRUE / FALSE 0 test5 forbidden5 +z-1.compute-1.amazonaws.com FALSE / FALSE 0 test5 forbidden5 .www.ck TRUE / FALSE 0 test4 allowed4 .www.example.ck TRUE / FALSE 0 test2 allowed2 diff --git a/tests/data/test114 b/tests/data/test114 index 850584479da4..6972e6a8352d 100644 --- a/tests/data/test114 +++ b/tests/data/test114 @@ -3,7 +3,6 @@ FTP -FAILURE # Server-side diff --git a/tests/data/test1144 b/tests/data/test1144 index a7baf6ef0e58..4446e63212d8 100644 --- a/tests/data/test1144 +++ b/tests/data/test1144 @@ -11,7 +11,7 @@ HTTP/0.9 # Server-side -No headers at all, just data swsclose +No headers at all, only data swsclose Let's get diff --git a/tests/data/test115 b/tests/data/test115 index 00b300a58ef7..5fe1a74a61bf 100644 --- a/tests/data/test115 +++ b/tests/data/test115 @@ -4,7 +4,6 @@ FTP PASV -FAILURE # Server-side diff --git a/tests/data/test1152 b/tests/data/test1152 index ad548e64f194..dfbde558d0af 100644 --- a/tests/data/test1152 +++ b/tests/data/test1152 @@ -3,13 +3,12 @@ FTP -FAILURE # Server-side -REPLY PWD 257 "just one +REPLY PWD 257 "Only one diff --git a/tests/data/test116 b/tests/data/test116 index fe519c5bbffc..3ce674e401f2 100644 --- a/tests/data/test116 +++ b/tests/data/test116 @@ -5,7 +5,6 @@ FTP EPRT PORT -FAILURE EPRT refused diff --git a/tests/data/test117 b/tests/data/test117 index 9142e15c2e9f..6fdbb33a582d 100644 --- a/tests/data/test117 +++ b/tests/data/test117 @@ -3,7 +3,6 @@ FTP -FAILURE # Server-side diff --git a/tests/data/test1177 b/tests/data/test1177 index ae340e8bf5a8..19697e6b1418 100644 --- a/tests/data/test1177 +++ b/tests/data/test1177 @@ -11,7 +11,7 @@ documentation # Client-side -Verify that feature names and CURL_VERSION_* in lib and docs are in sync +Verify feature names and CURL_VERSION_* in lib and docs in sync diff --git a/tests/data/test118 b/tests/data/test118 index 148e7d6e4d9e..67c17065d511 100644 --- a/tests/data/test118 +++ b/tests/data/test118 @@ -5,7 +5,6 @@ FTP PASV RETR -FAILURE # Server-side diff --git a/tests/data/test1185 b/tests/data/test1185 index 217a0a160479..f804c7ba044f 100644 --- a/tests/data/test1185 +++ b/tests/data/test1185 @@ -91,6 +91,10 @@ void startfunc(int a, int b) { int d = impl->magicbad(1); /* member function always allowed */ int e = impl.magicbad(); /* member function always allowed */ + curlx_free(ptr); /* two line + comment */ + ptr = NULL; /* comment more */ + /* comment does not end @@ -227,13 +231,16 @@ void startfunc(int a, int b) { ./%LOGDIR/code1185.c:71:2: warning: // comment (CPPCOMMENTS) // CPP comment ? ^ +./%LOGDIR/code1185.c:78:2: warning: replace curlx_free() + NULL assignment with curlx_safefree() (USESAFEFREE) + ptr = NULL; /* comment more */ + ^ ./%LOGDIR/code1185.c:1:1: error: Missing copyright statement (COPYRIGHT) %SP ^ ./%LOGDIR/code1185.c:1:1: error: Missing closing comment (OPENCOMMENT) %SP ^ -checksrc: 3 errors and 42 warnings +checksrc: 3 errors and 43 warnings 5 diff --git a/tests/data/test119 b/tests/data/test119 index 8b6ef10339f1..5f9140ff2c35 100644 --- a/tests/data/test119 +++ b/tests/data/test119 @@ -5,7 +5,6 @@ FTP PORT RETR -FAILURE # Server-side diff --git a/tests/data/test1196 b/tests/data/test1196 index dc79141cb3a5..118c52a3367f 100644 --- a/tests/data/test1196 +++ b/tests/data/test1196 @@ -28,7 +28,7 @@ mqtt mqtt -MQTT with error in CONNACK +MQTT with "unaccaptable protocol version" CONNACK mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER diff --git a/tests/data/test1208 b/tests/data/test1208 index 124419b6dc1b..8ae1e3acbe8d 100644 --- a/tests/data/test1208 +++ b/tests/data/test1208 @@ -7,7 +7,6 @@ PORT RETR NODATACONN150 timeout -FAILURE flaky timing-dependent diff --git a/tests/data/test1209 b/tests/data/test1209 index 8eb540a22c81..449e8f5fa920 100644 --- a/tests/data/test1209 +++ b/tests/data/test1209 @@ -7,7 +7,6 @@ PORT RETR NODATACONN timeout -FAILURE # Server-side diff --git a/tests/data/test1211 b/tests/data/test1211 index cb66fae8c5be..eb267d9dcbec 100644 --- a/tests/data/test1211 +++ b/tests/data/test1211 @@ -7,7 +7,6 @@ PORT RETR NODATACONN425 timeout -FAILURE # Server-side @@ -26,7 +25,7 @@ NODATACONN425 ftp -FTP PORT and 425 on download +FTP PORT and 425 on download and timeout ftp://%HOSTIP:%FTPPORT/%TESTNUMBER -P - diff --git a/tests/data/test1221 b/tests/data/test1221 index 322a5f878f4c..5ff7add9951c 100644 --- a/tests/data/test1221 +++ b/tests/data/test1221 @@ -44,7 +44,7 @@ content to _?!#$'|%LT%GT # Verify data after the test has been "shot" -POST /%TESTNUMBER?my+name+is+moo%5b%5d%AMPyes=s+i+r%AMPv_alue=content+to+_%3f%21%23%24%27%7c%3c%3e%0a%AMPcontent+to+_%3f%21%23%24%27%7c%3c%3e%0a%AMP%3d%3d HTTP/1.1 +POST /%TESTNUMBER?my+name+is+moo%5B%5D%AMPyes=s+i+r%AMPv_alue=content+to+_%3F%21%23%24%27%7C%3C%3E%0A%AMPcontent+to+_%3F%21%23%24%27%7C%3C%3E%0A%AMP%3D%3D HTTP/1.1 Host: %HOSTIP:%HTTPPORT User-Agent: curl/%VERSION Accept: */* diff --git a/tests/data/test1234 b/tests/data/test1234 index e7ffbf2eec26..bb3cde5974e4 100644 --- a/tests/data/test1234 +++ b/tests/data/test1234 @@ -4,7 +4,6 @@ globbing {} list -FAILURE # Server-side diff --git a/tests/data/test1236 b/tests/data/test1236 index 0d31ac1f4afd..2b7318516d4a 100644 --- a/tests/data/test1236 +++ b/tests/data/test1236 @@ -3,7 +3,6 @@ globbing -FAILURE # Server-side diff --git a/tests/data/test1238 b/tests/data/test1238 index a8fbc8a3f027..bb9b5d2b2b99 100644 --- a/tests/data/test1238 +++ b/tests/data/test1238 @@ -5,7 +5,6 @@ TFTP TFTP RRQ timeout -FAILURE DELAY @@ -15,7 +14,7 @@ DELAY writedelay: 2000 -# ~1200 bytes (so that they do not fit in two 512 byte chunks) +# ~1200 bytes (so that they do not fit in two 512-byte chunks) 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 diff --git a/tests/data/test125 b/tests/data/test125 index ef2591d4a71e..1f28daaf0fcc 100644 --- a/tests/data/test125 +++ b/tests/data/test125 @@ -3,7 +3,6 @@ FTP -FAILURE # Server-side diff --git a/tests/data/test1253 b/tests/data/test1253 index 621ecf9401e1..92eff37774f3 100644 --- a/tests/data/test1253 +++ b/tests/data/test1253 @@ -28,7 +28,7 @@ foo http ---proxy, override NO_PROXY by --noproxy and access target URL through proxy +--proxy, override NO_PROXY by --noproxy and access URL through proxy NO_PROXY=example.com diff --git a/tests/data/test1255 b/tests/data/test1255 index 57780f110d03..fac18021827f 100644 --- a/tests/data/test1255 +++ b/tests/data/test1255 @@ -29,7 +29,7 @@ foo http -http_proxy, override NO_PROXY by --noproxy and access target URL directly +http_proxy, override NO_PROXY by --noproxy and access URL directly http_proxy=http://%HOSTIP:%HTTPPORT diff --git a/tests/data/test1256 b/tests/data/test1256 index d35ee627e187..7db26428ec76 100644 --- a/tests/data/test1256 +++ b/tests/data/test1256 @@ -29,7 +29,7 @@ foo http -http_proxy, override NO_PROXY by --noproxy and target URL through proxy +http_proxy, override NO_PROXY by --noproxy host http_proxy=http://%HOSTIP:%HTTPPORT diff --git a/tests/data/test1257 b/tests/data/test1257 index 40f1028c5c29..fc6f56e33cbd 100644 --- a/tests/data/test1257 +++ b/tests/data/test1257 @@ -29,7 +29,7 @@ foo http -http_proxy, override NO_PROXY by --noproxy and target URL through proxy +http_proxy, override NO_PROXY by --noproxy "" http_proxy=http://%HOSTIP:%HTTPPORT diff --git a/tests/data/test1282 b/tests/data/test1282 index 06cf5170edc6..774f54cffaee 100644 --- a/tests/data/test1282 +++ b/tests/data/test1282 @@ -10,7 +10,7 @@ RETR # Server-side -REPLY PASS 633 XXXXXXXX\x00\x00XXXXXXXX +REPLY PASS 633 XXXXXXXXXXXXXXXX diff --git a/tests/data/test1309 b/tests/data/test1309 index 7e6af3822790..ed38d616e233 100644 --- a/tests/data/test1309 +++ b/tests/data/test1309 @@ -121,7 +121,7 @@ Tree look: 0.49[15] 0.39[14] 0.0[15] -remove pointer 7, payload 718 +remove node 7, payload 718 Tree look: 0.1013[5] 0.1003[4] @@ -172,7 +172,7 @@ Tree look: 0.49[15] 0.39[14] 0.0[15] -remove pointer 8, payload 236 +remove node 8, payload 236 Tree look: 0.1013[7] 0.1003[6] @@ -222,7 +222,7 @@ Tree look: 0.49[7] 0.39[6] 0.0[7] -remove pointer 9, payload 777 +remove node 9, payload 777 Tree look: 0.1013[6] 0.1003[5] @@ -271,7 +271,7 @@ Tree look: 0.49[10] 0.39[9] 0.0[10] -remove pointer 10, payload 295 +remove node 10, payload 295 Tree look: 0.1013[8] 0.1003[7] @@ -319,7 +319,7 @@ Tree look: 0.49[8] 0.39[7] 0.0[8] -remove pointer 11, payload 836 +remove node 11, payload 836 Tree look: 0.1013[5] 0.1003[4] @@ -366,7 +366,7 @@ Tree look: 0.49[11] 0.39[10] 0.0[11] -remove pointer 12, payload 354 +remove node 12, payload 354 Tree look: 0.1013[7] 0.1003[6] @@ -412,7 +412,7 @@ Tree look: 0.49[10] 0.39[9] 0.0[10] -remove pointer 13, payload 895 +remove node 13, payload 895 Tree look: 0.1013[4] 0.1003[3] @@ -457,7 +457,7 @@ Tree look: 0.49[13] 0.39[12] 0.0[13] -remove pointer 14, payload 413 +remove node 14, payload 413 Tree look: 0.1013[6] 0.1003[5] @@ -501,7 +501,7 @@ Tree look: 0.49[11] 0.39[10] 0.0[11] -remove pointer 15, payload 954 +remove node 15, payload 954 Tree look: 0.1013[2] 0.1003[1] @@ -544,7 +544,7 @@ Tree look: 0.49[14] 0.39[13] 0.0[14] -remove pointer 16, payload 472 +remove node 16, payload 472 Tree look: 0.1013[4] 0.1003[3] @@ -586,7 +586,7 @@ Tree look: 0.49[12] 0.39[11] 0.0[12] -remove pointer 17, payload 1013 +remove node 17, payload 1013 Tree look: 0.1003[0] 0.944[2] @@ -627,7 +627,7 @@ Tree look: 0.49[14] 0.39[13] 0.0[14] -remove pointer 18, payload 531 +remove node 18, payload 531 Tree look: 0.1003[2] 0.944[3] @@ -667,7 +667,7 @@ Tree look: 0.49[13] 0.39[12] 0.0[13] -remove pointer 19, payload 49 +remove node 19, payload 49 Tree look: 0.1003[4] 0.944[5] @@ -706,7 +706,7 @@ Tree look: 0.59[6] 0.39[0] 0.0[1] -remove pointer 20, payload 590 +remove node 20, payload 590 Tree look: 0.1003[2] 0.944[3] @@ -744,7 +744,7 @@ Tree look: 0.59[8] 0.39[3] 0.0[4] -remove pointer 21, payload 108 +remove node 21, payload 108 Tree look: 0.1003[4] 0.944[5] @@ -781,7 +781,7 @@ Tree look: 0.59[2] 0.39[1] 0.0[2] -remove pointer 22, payload 649 +remove node 22, payload 649 Tree look: 0.1003[3] 0.944[4] @@ -817,7 +817,7 @@ Tree look: 0.59[5] 0.39[4] 0.0[5] -remove pointer 23, payload 167 +remove node 23, payload 167 Tree look: 0.1003[5] 0.944[6] @@ -852,7 +852,7 @@ Tree look: 0.59[4] 0.39[3] 0.0[4] -remove pointer 24, payload 708 +remove node 24, payload 708 Tree look: 0.1003[3] 0.944[4] @@ -886,7 +886,7 @@ Tree look: 0.59[7] 0.39[6] 0.0[7] -remove pointer 25, payload 226 +remove node 25, payload 226 Tree look: 0.1003[5] 0.944[6] @@ -919,7 +919,7 @@ Tree look: 0.59[5] 0.39[4] 0.0[5] -remove pointer 26, payload 767 +remove node 26, payload 767 Tree look: 0.1003[2] 0.944[3] @@ -951,7 +951,7 @@ Tree look: 0.59[8] 0.39[7] 0.0[8] -remove pointer 27, payload 285 +remove node 27, payload 285 Tree look: 0.1003[4] 0.944[5] @@ -982,7 +982,7 @@ Tree look: 0.59[6] 0.39[5] 0.0[6] -remove pointer 28, payload 826 +remove node 28, payload 826 Tree look: 0.1003[2] 0.944[3] @@ -1012,7 +1012,7 @@ Tree look: 0.59[9] 0.39[8] 0.0[9] -remove pointer 29, payload 344 +remove node 29, payload 344 Tree look: 0.1003[4] 0.944[5] @@ -1041,7 +1041,7 @@ Tree look: 0.59[7] 0.39[6] 0.0[7] -remove pointer 30, payload 885 +remove node 30, payload 885 Tree look: 0.1003[2] 0.944[3] @@ -1069,7 +1069,7 @@ Tree look: 0.59[10] 0.39[9] 0.0[10] -remove pointer 31, payload 403 +remove node 31, payload 403 Tree look: 0.1003[4] 0.944[5] @@ -1096,7 +1096,7 @@ Tree look: 0.59[8] 0.39[7] 0.0[8] -remove pointer 32, payload 944 +remove node 32, payload 944 Tree look: 0.1003[1] 0.934[0] @@ -1122,7 +1122,7 @@ Tree look: 0.59[10] 0.39[9] 0.0[10] -remove pointer 33, payload 462 +remove node 33, payload 462 Tree look: 0.1003[3] 0.934[2] @@ -1147,7 +1147,7 @@ Tree look: 0.59[8] 0.39[7] 0.0[8] -remove pointer 34, payload 1003 +remove node 34, payload 1003 Tree look: 0.934[0] 0.875[2] @@ -1171,7 +1171,7 @@ Tree look: 0.59[10] 0.39[9] 0.0[10] -remove pointer 35, payload 521 +remove node 35, payload 521 Tree look: 0.934[2] 0.875[3] @@ -1194,7 +1194,7 @@ Tree look: 0.59[8] 0.39[7] 0.0[8] -remove pointer 36, payload 39 +remove node 36, payload 39 Tree look: 0.934[4] 0.875[5] @@ -1216,7 +1216,7 @@ Tree look: 0.98[4] 0.59[5] 0.0[0] -remove pointer 37, payload 580 +remove node 37, payload 580 Tree look: 0.934[2] 0.875[3] @@ -1237,7 +1237,7 @@ Tree look: 0.98[6] 0.59[7] 0.0[3] -remove pointer 38, payload 98 +remove node 38, payload 98 Tree look: 0.934[4] 0.875[5] @@ -1257,7 +1257,7 @@ Tree look: 0.118[3] 0.59[0] 0.0[1] -remove pointer 39, payload 639 +remove node 39, payload 639 Tree look: 0.934[3] 0.875[4] @@ -1276,7 +1276,7 @@ Tree look: 0.118[5] 0.59[3] 0.0[4] -remove pointer 40, payload 157 +remove node 40, payload 157 Tree look: 0.934[5] 0.875[6] @@ -1294,7 +1294,7 @@ Tree look: 0.118[0] 0.59[1] 0.0[2] -remove pointer 41, payload 698 +remove node 41, payload 698 Tree look: 0.934[3] 0.875[4] @@ -1311,7 +1311,7 @@ Tree look: 0.118[3] 0.59[4] 0.0[5] -remove pointer 42, payload 216 +remove node 42, payload 216 Tree look: 0.934[5] 0.875[6] @@ -1327,7 +1327,7 @@ Tree look: 0.118[1] 0.59[2] 0.0[3] -remove pointer 43, payload 757 +remove node 43, payload 757 Tree look: 0.934[2] 0.875[3] @@ -1342,7 +1342,7 @@ Tree look: 0.118[3] 0.59[4] 0.0[5] -remove pointer 44, payload 275 +remove node 44, payload 275 Tree look: 0.934[4] 0.875[5] @@ -1356,7 +1356,7 @@ Tree look: 0.118[1] 0.59[2] 0.0[3] -remove pointer 45, payload 816 +remove node 45, payload 816 Tree look: 0.934[1] 0.875[2] @@ -1369,7 +1369,7 @@ Tree look: 0.118[3] 0.59[4] 0.0[5] -remove pointer 46, payload 334 +remove node 46, payload 334 Tree look: 0.934[3] 0.875[4] @@ -1381,7 +1381,7 @@ Tree look: 0.118[1] 0.59[2] 0.0[3] -remove pointer 47, payload 875 +remove node 47, payload 875 Tree look: 0.934[1] 0.659[0] @@ -1392,7 +1392,7 @@ Tree look: 0.118[3] 0.59[4] 0.0[5] -remove pointer 48, payload 393 +remove node 48, payload 393 Tree look: 0.934[3] 0.659[2] @@ -1402,7 +1402,7 @@ Tree look: 0.118[1] 0.59[2] 0.0[3] -remove pointer 49, payload 934 +remove node 49, payload 934 Tree look: 0.659[0] 0.600[1] @@ -1411,7 +1411,7 @@ Tree look: 0.118[3] 0.59[4] 0.0[5] -remove pointer 0, payload 0 +remove node 0, payload 0 Tree look: 0.659[1] 0.600[0] @@ -1419,32 +1419,32 @@ Tree look: 0.177[2] 0.118[1] 0.59[2] -remove pointer 1, payload 541 +remove node 1, payload 541 Tree look: 0.659[2] 0.600[1] 0.177[0] 0.118[1] 0.59[2] -remove pointer 2, payload 59 +remove node 2, payload 59 Tree look: 0.659[3] 0.600[2] 0.177[1] 0.118[0] -remove pointer 3, payload 600 +remove node 3, payload 600 Tree look: 0.659[1] 0.177[0] 0.118[1] -remove pointer 4, payload 118 +remove node 4, payload 118 Tree look: 0.659[1] 0.177[0] -remove pointer 5, payload 659 +remove node 5, payload 659 Tree look: 0.177[0] -remove pointer 6, payload 177 +remove node 6, payload 177 Removing nodes not larger than 0 removed payload 0[0] Removing nodes not larger than 100 @@ -1556,6 +1556,61 @@ removed payload 1003[1] removed payload 1013[0] removed payload 1013[1] removed payload 1013[2] +Removing nodes not larger than 0 +removed payload 0[timeout=0] +Removing nodes not larger than 32 +removed payload 31[timeout=3] +removed payload 9[timeout=5] +removed payload 40[timeout=8] +removed payload 18[timeout=10] +removed payload 49[timeout=13] +removed payload 27[timeout=15] +removed payload 5[timeout=17] +removed payload 36[timeout=20] +removed payload 14[timeout=22] +removed payload 45[timeout=25] +removed payload 23[timeout=27] +removed payload 1[timeout=29] +removed payload 32[timeout=32] +Removing nodes not larger than 64 +removed payload 10[timeout=34] +removed payload 41[timeout=37] +removed payload 19[timeout=39] +removed payload 28[timeout=44] +removed payload 6[timeout=46] +removed payload 37[timeout=49] +removed payload 15[timeout=51] +removed payload 46[timeout=54] +removed payload 24[timeout=56] +removed payload 2[timeout=58] +removed payload 33[timeout=61] +removed payload 11[timeout=63] +Removing nodes not larger than 96 +removed payload 42[timeout=66] +removed payload 20[timeout=68] +removed payload 29[timeout=73] +removed payload 7[timeout=75] +removed payload 38[timeout=78] +removed payload 16[timeout=80] +removed payload 47[timeout=83] +removed payload 25[timeout=85] +removed payload 3[timeout=87] +removed payload 34[timeout=90] +removed payload 12[timeout=92] +removed payload 43[timeout=95] +Removing nodes not larger than 128 +removed payload 21[timeout=97] +removed payload 30[timeout=102] +removed payload 8[timeout=104] +removed payload 39[timeout=107] +removed payload 17[timeout=109] +removed payload 48[timeout=112] +removed payload 26[timeout=114] +removed payload 4[timeout=116] +removed payload 35[timeout=119] +removed payload 13[timeout=121] +removed payload 44[timeout=124] +removed payload 22[timeout=126] diff --git a/tests/data/test1315 b/tests/data/test1315 index 49788684e966..698e26bf532e 100644 --- a/tests/data/test1315 +++ b/tests/data/test1315 @@ -30,7 +30,7 @@ Mime http -HTTP RFC1867-type formposting - -F with three files, one with explicit type +HTTP -F with three files, one with explicit type http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER -F name=value -F 'file=@%LOGDIR/test%TESTNUMBER.txt,%LOGDIR/test%TESTNUMBER.txt;type=magic/content,%LOGDIR/test%TESTNUMBER.txt' diff --git a/tests/data/test1349 b/tests/data/test1349 index 04a6680635a3..ae19f87a7cd4 100644 --- a/tests/data/test1349 +++ b/tests/data/test1349 @@ -47,8 +47,8 @@ fooo mooo -# The final "221 bye bye baby" response to QUIT will not be recorded -# since that is not considered part of this particular transfer! +# The final "221 bye bye baby" response to QUIT is not recorded +# since that is not considered part of this particular transfer. 220- _ _ ____ _ 220- ___| | | | _ \| | diff --git a/tests/data/test1350 b/tests/data/test1350 index 2520df1dab34..8335bda8117f 100644 --- a/tests/data/test1350 +++ b/tests/data/test1350 @@ -47,8 +47,8 @@ fooo mooo -# The final "221 bye bye baby" response to QUIT will not be recorded -# since that is not considered part of this particular transfer! +# The final "221 bye bye baby" response to QUIT is not recorded +# since that is not considered part of this particular transfer. 220- _ _ ____ _ 220- ___| | | | _ \| | diff --git a/tests/data/test1351 b/tests/data/test1351 index 8351e49c68ca..525b23fdb01c 100644 --- a/tests/data/test1351 +++ b/tests/data/test1351 @@ -48,8 +48,8 @@ fooo mooo -# The final "221 bye bye baby" response to QUIT will not be recorded -# since that is not considered part of this particular transfer! +# The final "221 bye bye baby" response to QUIT is not recorded +# since that is not considered part of this particular transfer. 220- _ _ ____ _ 220- ___| | | | _ \| | diff --git a/tests/data/test1352 b/tests/data/test1352 index 4ff3222c6180..725e63fee365 100644 --- a/tests/data/test1352 +++ b/tests/data/test1352 @@ -48,8 +48,8 @@ fooo mooo -# The final "221 bye bye baby" response to QUIT will not be recorded -# since that is not considered part of this particular transfer! +# The final "221 bye bye baby" response to QUIT is not recorded +# since that is not considered part of this particular transfer. 220- _ _ ____ _ 220- ___| | | | _ \| | diff --git a/tests/data/test1353 b/tests/data/test1353 index b2ede58f3b80..ee05e78120e3 100644 --- a/tests/data/test1353 +++ b/tests/data/test1353 @@ -47,8 +47,8 @@ fooo mooo -# The final "221 bye bye baby" response to QUIT will not be recorded -# since that is not considered part of this particular transfer! +# The final "221 bye bye baby" response to QUIT is not recorded +# since that is not considered part of this particular transfer. 220- _ _ ____ _ 220- ___| | | | _ \| | diff --git a/tests/data/test1396 b/tests/data/test1396 index 3e4555633654..88062c82985c 100644 --- a/tests/data/test1396 +++ b/tests/data/test1396 @@ -2,7 +2,6 @@ -unittest curl_easy_escape curl_easy_unescape @@ -10,9 +9,9 @@ curl_easy_unescape # Client-side - -unittest - + +lib%TESTNUMBER + curl_easy_escape and curl_easy_unescape diff --git a/tests/data/test1398 b/tests/data/test1398 index 025c955562fc..dde51f699407 100644 --- a/tests/data/test1398 +++ b/tests/data/test1398 @@ -2,18 +2,17 @@ -unittest curl_msnprintf # Client-side - -unittest - + +lib%TESTNUMBER + -curl_msnprintf unit tests +curl_msnprintf tests diff --git a/tests/data/test1400 b/tests/data/test1400 index cf4aabbb8710..d3279a9e7cd4 100644 --- a/tests/data/test1400 +++ b/tests/data/test1400 @@ -51,7 +51,7 @@ Accept: */* # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with # CURLOPT_INTERLEAVEDATA requires RTSP protocol -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1401 b/tests/data/test1401 index 3f84805e00a0..74d174833922 100644 --- a/tests/data/test1401 +++ b/tests/data/test1401 @@ -62,7 +62,7 @@ X-Men: cyclops, iceman # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1402 b/tests/data/test1402 index 82061a385ca7..6dd608458abc 100644 --- a/tests/data/test1402 +++ b/tests/data/test1402 @@ -54,7 +54,7 @@ foo=bar%AMPbaz=quux # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1403 b/tests/data/test1403 index c3416a330586..14383314d48e 100644 --- a/tests/data/test1403 +++ b/tests/data/test1403 @@ -51,7 +51,7 @@ Accept: */* # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1404 b/tests/data/test1404 index 9fbd55526f16..d1a1d5217c7b 100644 --- a/tests/data/test1404 +++ b/tests/data/test1404 @@ -96,7 +96,7 @@ dummy data # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1405 b/tests/data/test1405 index 853c967ba77e..611dbf720fbf 100644 --- a/tests/data/test1405 +++ b/tests/data/test1405 @@ -65,11 +65,11 @@ QUIT # CURLOPT_USERAGENT and CURLOPT_MAXREDIRS requires HTTP protocol # CURLOPT_INTERLEAVEDATA requires RTSP (HTTP) protocol -# support, IOW depends on configuration - just ignore these. +# support, IOW depends on configuration - ignore these. $_ = '' if /CURLOPT_USERAGENT/ $_ = '' if /CURLOPT_MAXREDIRS/ # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1406 b/tests/data/test1406 index 26ad1c7998fa..d7c4adcfa993 100644 --- a/tests/data/test1406 +++ b/tests/data/test1406 @@ -61,7 +61,7 @@ body . -# These options vary with configurations - just ignore them +# These options vary with configurations - ignore them # CURLOPT_INTERLEAVEDATA requires RTSP (HTTP) protocol $_ = '' if /CURLOPT_MAXREDIRS/ $_ = '' if /CURLOPT_SSL_VERIFYPEER/ diff --git a/tests/data/test1407 b/tests/data/test1407 index 1436786115db..599eb1d3e4ef 100644 --- a/tests/data/test1407 +++ b/tests/data/test1407 @@ -48,7 +48,7 @@ LIST %TESTNUMBER QUIT -# These options vary with configurations - just ignore them +# These options vary with configurations - ignore them # CURLOPT_USERAGENT and CURLOPT_MAXREDIRS requires HTTP protocol # CURLOPT_INTERLEAVEDATA requires RTSP (HTTP) protocol $_ = '' if /CURLOPT_USERAGENT/ diff --git a/tests/data/test1409 b/tests/data/test1409 index 153d4a064453..50d00e44d739 100644 --- a/tests/data/test1409 +++ b/tests/data/test1409 @@ -3,7 +3,6 @@ cmdline -FAILURE diff --git a/tests/data/test1410 b/tests/data/test1410 index 4f1b39113e4c..e5d8660e7881 100644 --- a/tests/data/test1410 +++ b/tests/data/test1410 @@ -3,7 +3,6 @@ cmdline -FAILURE diff --git a/tests/data/test1412 b/tests/data/test1412 index babb42f29b00..ba9165d9f356 100644 --- a/tests/data/test1412 +++ b/tests/data/test1412 @@ -26,7 +26,7 @@ Connection: close This is not the real page -# The second URL will get this response +# The second URL gets this response HTTP/1.1 401 Authorization Required swsclose Server: Apache/1.3.27 (Darwin) PHP/4.1.2 diff --git a/tests/data/test1420 b/tests/data/test1420 index 57fdbe18d89c..cdf51cd0cf35 100644 --- a/tests/data/test1420 +++ b/tests/data/test1420 @@ -54,7 +54,7 @@ A004 FETCH 1 BODY[] A005 LOGOUT -# These options vary with configurations - just ignore them +# These options vary with configurations - ignore them # CURLOPT_INTERLEAVEDATA requires RTSP (HTTP) protocol $_ = '' if /CURLOPT_MAXREDIRS/ $_ = '' if /CURLOPT_SSL_VERIFYPEER/ diff --git a/tests/data/test1422 b/tests/data/test1422 index d51cd6e7f529..b7f856c13ea3 100644 --- a/tests/data/test1422 +++ b/tests/data/test1422 @@ -3,6 +3,7 @@ HTTP +FILE HTTP GET -J diff --git a/tests/data/test1423 b/tests/data/test1423 index 4acc0757b27f..b42d369d7c12 100644 --- a/tests/data/test1423 +++ b/tests/data/test1423 @@ -3,6 +3,7 @@ HTTP +FILE HTTP GET diff --git a/tests/data/test1447 b/tests/data/test1447 index 4b0ed1b584af..ea68ef92300b 100644 --- a/tests/data/test1447 +++ b/tests/data/test1447 @@ -3,7 +3,6 @@ HTTP proxy -FAILURE # Server-side diff --git a/tests/data/test1453 b/tests/data/test1453 index 3b1729633505..5d2166332c46 100644 --- a/tests/data/test1453 +++ b/tests/data/test1453 @@ -3,7 +3,6 @@ Too long tftp filename -FAILURE # Server-side diff --git a/tests/data/test1462 b/tests/data/test1462 index 9e68c6b447f1..240eca4ab2a6 100644 --- a/tests/data/test1462 +++ b/tests/data/test1462 @@ -39,6 +39,7 @@ Unknown category provided, here is a list of all categories: http HTTP and HTTPS protocol imap IMAP protocol ldap LDAP protocol + mqtt MQTT protocol output File system output pop3 POP3 protocol post HTTP POST specific diff --git a/tests/data/test1465 b/tests/data/test1465 index 2c4804d5195a..d8d6115b1fde 100644 --- a/tests/data/test1465 +++ b/tests/data/test1465 @@ -58,7 +58,7 @@ Content-Type: application/x-www-form-urlencoded # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1469 b/tests/data/test1469 index 4ca24418f99b..3b742c1b2c32 100644 --- a/tests/data/test1469 +++ b/tests/data/test1469 @@ -4,7 +4,6 @@ FTP URL -FAILURE diff --git a/tests/data/test1471 b/tests/data/test1471 index ffd3ef59abab..70c454b0a45e 100644 --- a/tests/data/test1471 +++ b/tests/data/test1471 @@ -4,7 +4,6 @@ Onion Tor -FAILURE # Server-side diff --git a/tests/data/test1472 b/tests/data/test1472 index 26fb456808b6..b9e2d6dd67c0 100644 --- a/tests/data/test1472 +++ b/tests/data/test1472 @@ -4,7 +4,6 @@ Onion Tor -FAILURE # Server-side diff --git a/tests/data/test1479 b/tests/data/test1479 index 95dc3d11bc07..ccb6ced8208c 100644 --- a/tests/data/test1479 +++ b/tests/data/test1479 @@ -30,7 +30,7 @@ Data http -HTTP/1.1 response followed by an HTTP/0.9 response over the same connection +HTTP/1.1 response followed by HTTP/0.9 response over same connection http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 diff --git a/tests/data/test1481 b/tests/data/test1481 index 98c018bc2699..8a0d31078533 100644 --- a/tests/data/test1481 +++ b/tests/data/test1481 @@ -54,7 +54,7 @@ Proxy-Connection: Keep-Alive # CURLOPT_SSL_VERIFYPEER, SSH_KNOWNHOSTS and HTTP_VERSION vary with # CURLOPT_INTERLEAVEDATA requires RTSP protocol -# configurations - just ignore them +# configurations - ignore them $_ = '' if /CURLOPT_SSL_VERIFYPEER/ $_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ $_ = '' if /CURLOPT_HTTP_VERSION/ diff --git a/tests/data/test1483 b/tests/data/test1483 index eb1931770644..c7373b2a4615 100644 --- a/tests/data/test1483 +++ b/tests/data/test1483 @@ -51,7 +51,7 @@ writedelay: 10 http -HTTP GET with double chunked in TE header +HTTP GET with two Transfer-Encoding: chunked headers http://%HOSTIP:%HTTPPORT/%TESTNUMBER -D %LOGDIR/heads%TESTNUMBER diff --git a/tests/data/test1498 b/tests/data/test1498 index 67c3f8409d28..362cf7fcf681 100644 --- a/tests/data/test1498 +++ b/tests/data/test1498 @@ -26,9 +26,6 @@ blablabla http - -!win32 - HTTP PUT from stdin using period diff --git a/tests/data/test1509 b/tests/data/test1509 index afb1a6613955..7e57cd0fad9b 100644 --- a/tests/data/test1509 +++ b/tests/data/test1509 @@ -58,7 +58,7 @@ lib%TESTNUMBER proxy -simple multi http:// through proxytunnel with authentication info +http:// through proxytunnel http://the.old.moo.%TESTNUMBER:%HTTPPORT/%TESTNUMBER %HOSTIP:%PROXYPORT diff --git a/tests/data/test1513 b/tests/data/test1513 index 4b119fca7a0e..b84183846df1 100644 --- a/tests/data/test1513 +++ b/tests/data/test1513 @@ -30,7 +30,7 @@ lib%TESTNUMBER return failure immediately from progress callback -# this server/host will not be used for real +# this server/host is not used for real http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1515 b/tests/data/test1515 index ac3ad254b85a..2ad5ac0797b8 100644 --- a/tests/data/test1515 +++ b/tests/data/test1515 @@ -5,13 +5,12 @@ HTTP multi -FAILURE resolve -# Close the connection after the first request. Second request will happen after +# Close the connection after the first request. Second request happens after # the DNS cache timeout elapses and must succeed exactly like the first one. HTTP/1.1 200 OK diff --git a/tests/data/test1516 b/tests/data/test1516 index 704a79164648..02f2c6d84f17 100644 --- a/tests/data/test1516 +++ b/tests/data/test1516 @@ -5,7 +5,6 @@ HTTP multi -FAILURE resolve diff --git a/tests/data/test1520 b/tests/data/test1520 index 07ccdf281c95..c26c08866fb2 100644 --- a/tests/data/test1520 +++ b/tests/data/test1520 @@ -17,7 +17,7 @@ lib%TESTNUMBER -SMTP with CRLF-dot-CRLF in data +SMTP with CRLF-dot-CRLF in upload payload From: different diff --git a/tests/data/test153 b/tests/data/test153 index b2bd6e220b9b..e2b8ebf3fb84 100644 --- a/tests/data/test153 +++ b/tests/data/test153 @@ -56,7 +56,7 @@ Content-Length: 26 This is not the real page -# The second request to the 1002 section will bounce this one back instead +# The second request to the 1002 section bounces this one back instead # thanks to the swsbounce keyword up there HTTP/1.1 200 OK diff --git a/tests/data/test1538 b/tests/data/test1538 index e989fdd17f67..bdfe9478edd4 100644 --- a/tests/data/test1538 +++ b/tests/data/test1538 @@ -94,7 +94,7 @@ e62: Unknown error e63: Maximum file size exceeded e64: Requested SSL level failed e65: Send failed since rewinding of the data stream failed -e66: Failed to initialise SSL crypto engine +e66: Failed to initialize SSL crypto engine e67: Login denied e68: TFTP: File Not Found e69: TFTP: Access Violation @@ -163,20 +163,20 @@ u6: URL decode error, most likely because of rubbish in the input u7: A memory function failed u8: Credentials was passed in the URL when prohibited u9: An unknown part ID was passed to a URL API function -u10: No scheme part in the URL -u11: No user part in the URL -u12: No password part in the URL -u13: No options part in the URL -u14: No host part in the URL -u15: No port part in the URL -u16: No query part in the URL -u17: No fragment part in the URL -u18: No zoneid part in the URL +u10: No scheme present +u11: No user present +u12: No password present +u13: No options present +u14: No host present +u15: No port number present +u16: No query present +u17: No fragment present +u18: No zoneid present u19: Bad file:// URL u20: Bad fragment u21: Bad hostname u22: Bad IPv6 address -u23: Bad login part +u23: Bad login u24: Bad password u25: Bad path u26: Bad query @@ -185,7 +185,8 @@ u28: Unsupported number of slashes following scheme u29: Bad user u30: libcurl lacks IDN support u31: A value or data field is larger than allowed -u32: CURLUcode unknown +u32: Found a backslash where a forward slash was expected +u33: CURLUcode unknown diff --git a/tests/data/test1541 b/tests/data/test1541 index ecba40ff9319..2f030cc904b1 100644 --- a/tests/data/test1541 +++ b/tests/data/test1541 @@ -56,7 +56,7 @@ http lib%TESTNUMBER -chunked with trailers and pausing the receive +CURLINFO timer check http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1553 b/tests/data/test1553 index fbf5390b156b..84f383085225 100644 --- a/tests/data/test1553 +++ b/tests/data/test1553 @@ -34,7 +34,7 @@ Mime imap -IMAP cleanup before a connection was created +IMAP cleanup with non-existing hostname # tool is what to use instead of 'curl' diff --git a/tests/data/test1554 b/tests/data/test1554 index fa071b09b245..38ea1feaa387 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -33,8 +33,6 @@ run 1: foobar and so on fun! run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT -] Mutex lock SHARE [- Mutex unlock SHARE -] Mutex lock SHARE @@ -48,8 +46,6 @@ run 1: foobar and so on fun! run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT -] Mutex lock SHARE [- Mutex unlock SHARE -] Mutex lock SHARE @@ -63,12 +59,12 @@ run 1: foobar and so on fun! run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT -] Mutex lock SHARE [- Mutex unlock SHARE -] Mutex lock SHARE [- Mutex unlock SHARE +-] Mutex lock CONNECT +[- Mutex unlock CONNECT diff --git a/tests/data/test1555 b/tests/data/test1555 index 2cbbd9b0c6d3..1212531686f1 100644 --- a/tests/data/test1555 +++ b/tests/data/test1555 @@ -30,7 +30,7 @@ lib%TESTNUMBER verify api is protected against calls from callbacks -# this server/host will not be used for real +# this server/host is not used for real http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1557 b/tests/data/test1557 index a96647690c39..9b4ed73fa732 100644 --- a/tests/data/test1557 +++ b/tests/data/test1557 @@ -16,7 +16,7 @@ lib%TESTNUMBER -Remove easy handle in pending connections does not leave dangling entry +Remove easy handle in pending connections, no dangling entry hostname.invalid diff --git a/tests/data/test1560 b/tests/data/test1560 index e0d792800b2e..a710c8a683b5 100644 --- a/tests/data/test1560 +++ b/tests/data/test1560 @@ -20,7 +20,6 @@ http pop3 smtp imap -ldap dict ftp @@ -37,7 +36,7 @@ lib%TESTNUMBER success -Allocations: 3100 +Allocations: 3650 diff --git a/tests/data/test157 b/tests/data/test157 index 422d88f69aee..fe9c9bdd920b 100644 --- a/tests/data/test157 +++ b/tests/data/test157 @@ -16,7 +16,7 @@ Server: Apache/1.3.27 (Darwin) PHP/4.1.2 Content-Type: text/html; charset=iso-8859-1 Connection: close -GET received and served just fine. Thank you very much +GET received and served fine. Thank you very much diff --git a/tests/data/test1588 b/tests/data/test1588 index 753e98cd6b6a..30ec8ca91282 100644 --- a/tests/data/test1588 +++ b/tests/data/test1588 @@ -79,25 +79,25 @@ http://test.remote.example.com/path/%TESTNUMBER %HOSTIP %HTTPPORT silly:person c # Verify data after the test has been "shot" -GET http://test.remote.example.com/path/1588 HTTP/1.1 +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 Host: test.remote.example.com Accept: */* Proxy-Connection: Keep-Alive -GET http://test.remote.example.com/path/1588 HTTP/1.1 +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 Host: test.remote.example.com -Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/1588", response="d0b2f000c7e3fca24452b5810713404a" +Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/%TESTNUMBER", response="d0b2f000c7e3fca24452b5810713404a" Accept: */* Proxy-Connection: Keep-Alive -GET http://test.remote.example.com/path/1588 HTTP/1.1 +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 Host: test.remote.example.com Accept: */* Proxy-Connection: Keep-Alive -GET http://test.remote.example.com/path/1588 HTTP/1.1 +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 Host: test.remote.example.com -Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/1588", response="d0b2f000c7e3fca24452b5810713404a" +Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/%TESTNUMBER", response="d0b2f000c7e3fca24452b5810713404a" Accept: */* Proxy-Connection: Keep-Alive diff --git a/tests/data/test1589 b/tests/data/test1589 new file mode 100644 index 000000000000..527edb54cab4 --- /dev/null +++ b/tests/data/test1589 @@ -0,0 +1,108 @@ + + + + +HTTP +HTTP proxy +HTTP proxy Digest auth + + + +# Server-side + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Content-Length: 33 + +And you should ignore this data. + + +# then this is returned when we get proxy-auth + +HTTP/1.1 200 OK +Content-Length: 21 +Server: no + +Nice proxy auth sir! + + + +HTTP/1.1 407 Authorization Required to proxy me my dear +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Content-Length: 33 + +HTTP/1.1 200 OK +Content-Length: 21 +Server: no + +Nice proxy auth sir! +HTTP/1.1 407 Authorization Required to proxy me my dear +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Content-Length: 33 + +HTTP/1.1 200 OK +Content-Length: 21 +Server: no + +Nice proxy auth sir! + + + +# Client-side + + +http +http-proxy + +# tool is what to use instead of 'curl' + +lib%TESTNUMBER + + +!SSPI +crypto +proxy +digest + + +HTTP proxy auth Digest, then change proxy port and do it again + + +http://test.remote.example.com/path/%TESTNUMBER %HOSTIP %HTTPPORT %PROXYPORT silly:person + + + +# Verify data after the test has been "shot" + + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/%TESTNUMBER", response="9a547f8fa81cff330c68095603f3819e" +Accept: */* +Proxy-Connection: Keep-Alive + + + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Proxy-Authorization: Digest username="silly", realm="weirdorealm", nonce="12345", uri="/path/%TESTNUMBER", response="9a547f8fa81cff330c68095603f3819e" +Accept: */* +Proxy-Connection: Keep-Alive + + + + + diff --git a/tests/data/test1609 b/tests/data/test1609 index 5153174a3afe..14058b17cbb6 100644 --- a/tests/data/test1609 +++ b/tests/data/test1609 @@ -2,18 +2,37 @@ -unittest -CURLOPT_RESOLVE +GOPHER +CRLF-in-URL +# Server-side + + + # Client-side - -unittest - + +gopher + -CURLOPT_RESOLVE parsing +Gopher with URL-encoded CR LF in the selector + +gopher://%HOSTIP:%GOPHERPORT/1/sel%0d%0aINJECT/%TESTNUMBER + + +# Verify data after the test has been "shot" + +# 3 - CURLE_URL_MALFORMAT + +3 + +# nothing must reach the server, the selector is rejected before any send + + + + diff --git a/tests/data/test161 b/tests/data/test161 index 16fdaf41862e..23db19005e2e 100644 --- a/tests/data/test161 +++ b/tests/data/test161 @@ -23,7 +23,7 @@ PASV ftp -FTP RETR PASV +FTP RETR partial file ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test1616 b/tests/data/test1616 index dc4ebc13f95e..c0719b091f9f 100644 --- a/tests/data/test1616 +++ b/tests/data/test1616 @@ -13,7 +13,7 @@ uint_hash unittest -Internal uint_hash create/add/destroy testing, exercising clean functions +uint_hash create/add/destroy testing, exercising clean functions diff --git a/tests/data/test162 b/tests/data/test162 index cef8fc2f08e7..e650b7cf4f83 100644 --- a/tests/data/test162 +++ b/tests/data/test162 @@ -6,7 +6,6 @@ HTTP HTTP GET HTTP proxy HTTP proxy NTLM auth -FAILURE @@ -36,7 +35,7 @@ proxy http -HTTP GET asking for --proxy-ntlm when some other authentication is required +HTTP GET --proxy-ntlm when some other authentication is required http://%HOSTIP:%HTTPPORT/%TESTNUMBER --proxy http://%HOSTIP:%HTTPPORT --proxy-user foo:bar --proxy-ntlm --fail diff --git a/tests/data/test1625 b/tests/data/test1625 index 58d661c3850a..c717933b3086 100644 --- a/tests/data/test1625 +++ b/tests/data/test1625 @@ -19,7 +19,7 @@ Curl_compareheader unit test -31 invokes +33 invokes diff --git a/tests/data/test1627 b/tests/data/test1627 index 807352612d04..95fd47d83fb2 100644 --- a/tests/data/test1627 +++ b/tests/data/test1627 @@ -18,7 +18,7 @@ Curl_get_scheme unit test -199 invokes +547 invokes diff --git a/tests/data/test1628 b/tests/data/test1628 new file mode 100644 index 000000000000..f4f1da5d8df4 --- /dev/null +++ b/tests/data/test1628 @@ -0,0 +1,53 @@ + + + + +HTTP +HTTP PUT + + +# Server-side + + +HTTP/1.0 200 OK swsclose +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake + +blablabla + + + + +# Client-side + + +proxy + + +http + + +HTTP PUT from file with weird letters over an HTTP proxy + + +-x http://%HOSTIP:%HTTPPORT http://ssss/ -T %LOGDIR/%TESTNUMBERte[]st.txt + + +a few bytes + + + +# Verify data after the test has been "shot" + + +PUT http://ssss/%TESTNUMBERte%5B%5Dst.txt HTTP/1.1 +Host: ssss +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive +Content-Length: 12 + +a few bytes + + + diff --git a/tests/data/test1629 b/tests/data/test1629 new file mode 100644 index 000000000000..f5e1dcbee4cb --- /dev/null +++ b/tests/data/test1629 @@ -0,0 +1,54 @@ + + + + +HTTP +HTTP GET +--resolve + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 +Set-Cookie: something=1; Domain=co.uk.; Path=/ + +-foo- + + + +# Client-side + + +PSL +cookies + + +http + + +cookies with trailing dot after PSL domain + + +http://foo.co.uk.:%HTTPPORT/ http://bar.co.uk.:%HTTPPORT/ -b "" --resolve foo.co.uk.:%HTTPPORT:%HOSTIP --resolve bar.co.uk.:%HTTPPORT:%HOSTIP + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: foo.co.uk.:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET / HTTP/1.1 +Host: bar.co.uk.:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1631 b/tests/data/test1631 index 2015fc328ac3..f0018516d598 100644 --- a/tests/data/test1631 +++ b/tests/data/test1631 @@ -56,8 +56,8 @@ proxy # Verify data after the test has been "shot" -# The second CONNECT will be made to the dynamic port number the FTP server -# opens for us, so we cannot compare with a known pre-existing number! +# The second CONNECT is made to the dynamic port number the FTP server +# opens for us, so we cannot compare with a known pre-existing number. s/((https.proxy):(\d+))/$2:12345/ diff --git a/tests/data/test1632 b/tests/data/test1632 index 76c8bffcba0f..eda70d0d98b5 100644 --- a/tests/data/test1632 +++ b/tests/data/test1632 @@ -65,8 +65,8 @@ proxy # Verify data after the test has been "shot" -# The second and third CONNECT will be made to the dynamic port number the FTP -# server opens for us, so we cannot compare with known pre-existing numbers! +# The second and third CONNECTs are made to the dynamic port number the FTP +# server opens for us, so we cannot compare with known pre-existing numbers. s/((https.proxy):(\d+))/$2:12345/ diff --git a/tests/data/test1641 b/tests/data/test1641 index a71a59b056dc..46c4f23813b8 100644 --- a/tests/data/test1641 +++ b/tests/data/test1641 @@ -33,7 +33,7 @@ Content-Disposition: filename=name%TESTNUMBER; charset=funny; option=strange http -HTTP GET with -J, a redirect and Content-Disposition in the second response +HTTP GET -J, a redirect and Content-Disposition in the second response http://%HOSTIP:%HTTPPORT/%TESTNUMBER -J -L -O --output-dir %LOGDIR diff --git a/tests/data/test1642 b/tests/data/test1642 index 81f0ae8fc65f..2745b8bb20f8 100644 --- a/tests/data/test1642 +++ b/tests/data/test1642 @@ -32,7 +32,7 @@ Content-Type: text/html http -HTTP GET with -J, redirect, no Content-Disposition use the Location: name +HTTP GET -J, redirect, no Content-Disposition use the Location: name http://%HOSTIP:%HTTPPORT/%TESTNUMBER -J -L -O --output-dir %LOGDIR diff --git a/tests/data/test1645 b/tests/data/test1645 new file mode 100644 index 000000000000..70666ef34c48 --- /dev/null +++ b/tests/data/test1645 @@ -0,0 +1,74 @@ + + + + +HTTP +HTTP GET +cookies +cookiejar +--resolve + + +# Server-side + + + +HTTP/1.1 200 OK +Content-Length: 4 +Content-Type: text/html +Funny-head: yesyes +Set-Cookie: name=value; domain=test.curl; path=/we/want + +boo + + +HTTP/1.1 200 OK +Content-Length: 4 +Content-Type: text/html +Funny-head: yesyes +Set-Cookie: name=value; domain=test.curl; path=/WE/WANT + +boo + + + +# Client-side + + +http + + +cookies for paths using different case + + +http://test.curl:%HTTPPORT/we/want/ http://test.curl:%HTTPPORT/WE/WANT/%TESTNUMBER0002 -c %LOGDIR/jar%TESTNUMBER.txt --resolve test.curl:%HTTPPORT:%HOSTIP + + +cookies + + + +# Verify data after the test has been "shot" + + +GET /we/want/ HTTP/1.1 +Host: test.curl:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /WE/WANT/%TESTNUMBER0002 HTTP/1.1 +Host: test.curl:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +.test.curl TRUE /WE/WANT FALSE 0 name value +.test.curl TRUE /we/want FALSE 0 name value + + + diff --git a/tests/data/test1646 b/tests/data/test1646 new file mode 100644 index 000000000000..6a292ebd2664 --- /dev/null +++ b/tests/data/test1646 @@ -0,0 +1,46 @@ + + + + +netrc +--resolve + + + + + +HTTP/1.1 200 OK +Content-Length: 6 + +12345 + + + +# Client-side + + +http + + +netrc parsing without user match but user in URL + + +--netrc --netrc-file %LOGDIR/netrc%TESTNUMBER http://alice@example.com:%HTTPPORT/%TESTNUMBER --resolve example.com:%HTTPPORT:%HOSTIP + + +machine example.com login bob password sekret + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: example.com:%HTTPPORT +Authorization: Basic %b64[alice:]b64% +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1647 b/tests/data/test1647 new file mode 100644 index 000000000000..a87487fa9f0c --- /dev/null +++ b/tests/data/test1647 @@ -0,0 +1,103 @@ + + + + +HTTP +HTTP GET +HTTP proxy +HTTP proxy Digest auth +multi + + + +# Server-side + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Content-Length: 33 + +And you should ignore this data. + + +# then this is returned when we get proxy-auth + +HTTP/1.1 200 OK +Content-Length: 21 +Server: no + +Nice proxy auth sir! + + + +HTTP/1.1 401 OK +Content-Length: 21 +Server: no + +Denied access. Leave + + + + +# Client-side + + +http +https-proxy +https + +# tool is what to use instead of 'curl' + +lib%TESTNUMBER + + +!SSPI +crypto +proxy +digest +Debug + + +http_proxy=%HOSTIP:%HTTPPORT +https_proxy=https://%HOSTIP:%HTTPSPROXYPORT +CURL_ENTROPY=99376 + + +HTTP proxy auth Digest, then change proxy with env var and do it again + + +http://test.remote.example.com/path/%TESTNUMBER https://another.example.com:%HTTPSPORT/ daniel:monkey123 another:bump456 + + + +# Verify data after the test has been "shot" + + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://test.remote.example.com/path/%TESTNUMBER HTTP/1.1 +Host: test.remote.example.com +Proxy-Authorization: Digest username="daniel", realm="weirdorealm", nonce="12345", uri="/path/%TESTNUMBER", response="7a1672891aff03248887b1a6674b8096" +Accept: */* +Proxy-Connection: Keep-Alive + + + + +CONNECT another.example.com:%HTTPSPORT HTTP/1.1 +Host: another.example.com:%HTTPSPORT +Proxy-Connection: Keep-Alive + + + +# CONNECT fails + +7 + + + diff --git a/tests/data/test1648 b/tests/data/test1648 new file mode 100644 index 000000000000..623f3c9a81ae --- /dev/null +++ b/tests/data/test1648 @@ -0,0 +1,63 @@ + + + + +HTTP +HTTP GET +HTTP proxy +HTTP proxy auth + + + +# Server-side + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Content-Length: 33 + +And you should ignore this data. + + + + +# Client-side + + +http + +# tool is what to use instead of 'curl' + +lib%TESTNUMBER + + +proxy + + +HTTP proxy with auth, change proxy, clear auth + + +%HOSTIP %HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET http://example.com/ HTTP/1.1 +Host: example.com +Proxy-Authorization: Basic %b64[victim:secret]b64% +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://example.com/ HTTP/1.1 +Host: example.com +Accept: */* +Proxy-Connection: Keep-Alive + + + + + diff --git a/tests/data/test1649 b/tests/data/test1649 new file mode 100644 index 000000000000..d2fd7799bd87 --- /dev/null +++ b/tests/data/test1649 @@ -0,0 +1,55 @@ + + + + +HTTP +Referer + + + +# Server-side + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 200 OK +Content-Length: 6 + +hello + + + + +# Client-side + + +http + + + +lib%TESTNUMBER + + +Set referer first then NULL it + + +http://%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Referer: https://secret.example.com/ + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + + diff --git a/tests/data/test1676 b/tests/data/test1676 new file mode 100644 index 000000000000..7f4907fea330 --- /dev/null +++ b/tests/data/test1676 @@ -0,0 +1,21 @@ + + + + +unittest +x509 +DH + + + +# Client-side + + +unittest + + +x509 DH public key parameter extraction + + + + diff --git a/tests/data/test1677 b/tests/data/test1677 new file mode 100644 index 000000000000..e06dc7679cf1 --- /dev/null +++ b/tests/data/test1677 @@ -0,0 +1,77 @@ + + + + +HTTP +HTTP POST +chunked Transfer-Encoding +Content-Length + + + +# Regression test for bug where curl stops reading chunked response +# when both Content-Length: 0 and Transfer-Encoding: chunked headers +# are present. Per RFC 7230 Section 3.3.3, Transfer-Encoding should +# take precedence and Content-Length should be ignored. +# The writedelay simulates the timing issue where chunks arrive in +# separate packets. + +# Server-side + + +writedelay: 500 + + +HTTP/1.1 200 OK +content-type: text/json +connection: keep-alive +content-length: 0 +transfer-encoding: chunked + +1a +random data in first chunk +1d + another data in second chunk +15 + third and last chunk +0 + + + + +# Client-side + + +http + + +HTTP POST response with both chunked and zero Content-Length + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -d "testdata" + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 8 +Content-Type: application/x-www-form-urlencoded + +testdata + + +HTTP/1.1 200 OK +content-type: text/json +connection: keep-alive +content-length: 0 +transfer-encoding: chunked + +random data in first chunk another data in second chunk third and last chunk + + + diff --git a/tests/data/test1678 b/tests/data/test1678 new file mode 100644 index 000000000000..c0cada0ba79f --- /dev/null +++ b/tests/data/test1678 @@ -0,0 +1,21 @@ + + + + +ssls-import + + + +# Client-side + + +SSLS-EXPORT + + +lib%TESTNUMBER + + +SSL session import tests + + + diff --git a/tests/data/test1686 b/tests/data/test1686 new file mode 100644 index 000000000000..2d419ad60834 --- /dev/null +++ b/tests/data/test1686 @@ -0,0 +1,84 @@ + + + + +HTTP +Digest + + + + + +HTTP/1.1 401 Authorization Required +Server: Apache/1.3.27 (Darwin) PHP/4.1.2 +WWW-Authenticate: Digest realm="my-backyard", nonce="314156295" +Content-Length: 26 + +This is not the real page + + +# This is supposed to be returned when the server gets a +# Authorization: Digest line passed-in from the client + +HTTP/1.1 200 OK +Server: Apache/1.3.27 (Darwin) PHP/4.1.2 +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 23 + +This IS the real page! + + + + + + +!SSPI +crypto +digest + + +http + + +HTTP Digest to different origins and switching credentials + + +lib%TESTNUMBER + + +%HOSTIP %HTTPPORT + + + + + +GET /api HTTP/1.1 +Host: first.test:%HTTPPORT +Accept: */* + +GET /api HTTP/1.1 +Host: first.test:%HTTPPORT +Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/api", response="4ecc00e567c37a9d537727890c2e5b32" +Accept: */* + +GET /hook HTTP/1.1 +Host: second.test:%HTTPPORT +Accept: */* + +GET /hook HTTP/1.1 +Host: second.test:%HTTPPORT +Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/hook", response="d3a7738fb6a23f5543fb8dacc0f0f253" +Accept: */* + +GET /hook HTTP/1.1 +Host: second.test:%HTTPPORT +Accept: */* + +GET /hook HTTP/1.1 +Host: second.test:%HTTPPORT +Authorization: Digest username="bob", realm="my-backyard", nonce="314156295", uri="/hook", response="777e68eddb77294d9cbd6134973cbbab" +Accept: */* + + + + diff --git a/tests/data/test1701 b/tests/data/test1701 deleted file mode 100644 index 50e782bed037..000000000000 --- a/tests/data/test1701 +++ /dev/null @@ -1,82 +0,0 @@ - - - - -HTTP -HTTP POST -HTTP/2 - - - -# Server-side - - -HTTP/1.1 200 OK -Date: Tue, 09 Nov 2010 14:49:00 GMT -Server: test-server/fake -Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT -ETag: "21025-dc7-39462498" -Accept-Ranges: bytes -Content-Length: 6 -Connection: close -Content-Type: text/html -Funny-head: yesyes - --foo- - - - -# Client-side - - -h2c - - -http/2 - - -HTTP/2 POST with Upgrade: - - -http://%HOSTIP:%HTTP2PORT/%TESTNUMBER --http2 -d "datatosend" - - - -# Verify data after the test has been "shot" - - -^X-Forwarded-Proto:.* -^Via:.* - - -POST /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTP2PORT -User-Agent: curl/%VERSION -Accept: */* -Content-Length: 10 -Content-Type: application/x-www-form-urlencoded - -datatosend - - -HTTP/1.1 101 Switching Protocols -Connection: Upgrade -Upgrade: h2c - -HTTP/2 200%SP -date: Tue, 09 Nov 2010 14:49:00 GMT -last-modified: Tue, 13 Jun 2000 12:10:00 GMT -etag: "21025-dc7-39462498" -accept-ranges: bytes -content-length: 6 -content-type: text/html -funny-head: yesyes -via: 1.1 nghttpx - --foo- - - -s/^server: nghttpx.*\r?\n// - - - diff --git a/tests/data/test1712 b/tests/data/test1712 index 5cdc642d0906..03fc9a729092 100644 --- a/tests/data/test1712 +++ b/tests/data/test1712 @@ -30,6 +30,9 @@ Funny-head: yesyes http + +COLUMNS=10000 + config file with argument using single quotes @@ -54,8 +57,7 @@ Content-Type: application/x-www-form-urlencoded 'arg-with-quote' -Warning: %LOGDIR/config:1 Option 'data' uses argument with leading single quote.%SP -It is probably a mistake. Consider double quotes. +Warning: %LOGDIR/config:1 Option 'data' uses argument with leading single quote. It is probably a mistake. Consider double quotes. diff --git a/tests/data/test1721 b/tests/data/test1721 new file mode 100644 index 000000000000..1b0e7a88f78d --- /dev/null +++ b/tests/data/test1721 @@ -0,0 +1,45 @@ + + + + +HTTP +HTTP GET + + + + + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Type: text/html + +-foo- + + + + + +http + + +Keep question mark for empty query + + +"http://%HOSTIP:%HTTPPORT/hello?" -w '%output{%LOGDIR/out%TESTNUMBER}%{url_effective}' + + + +# Verify data after the test has been "shot" + + +http://%HOSTIP:%HTTPPORT/hello? + + +GET /hello? HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1722 b/tests/data/test1722 new file mode 100644 index 000000000000..e51f26f4b432 --- /dev/null +++ b/tests/data/test1722 @@ -0,0 +1,61 @@ + + + + +HTTP +HTTP GET +chunked Transfer-Encoding + + +# Server-side + + +HTTP/1.1 200 funky chunky! +Server: fakeit/0.9 fakeitbad/1.0 +Transfer-Encoding: chunked, another +Connection: mooo + +40%CR +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%CR +30%CR +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb%CR +21;heresatest=moooo%CR +cccccccccccccccccccccccccccccccc +%CR +0%CR +%CR + + +HTTP/1.1 200 funky chunky! +Server: fakeit/0.9 fakeitbad/1.0 + + + +# Client-side + + +http + + +HTTP with chunked Transfer-Encoding not listed last + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +61 + + + + diff --git a/tests/data/test1723 b/tests/data/test1723 new file mode 100644 index 000000000000..ad93c3016883 --- /dev/null +++ b/tests/data/test1723 @@ -0,0 +1,61 @@ + + + + +HTTP +HTTP GET +chunked Transfer-Encoding + + +# Server-side + + +HTTP/1.1 200 funky chunky! +Server: fakeit/0.9 fakeitbad/1.0 +Transfer-Encoding: chunked, chunked, another +Connection: mooo + +40%CR +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%CR +30%CR +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb%CR +21;heresatest=moooo%CR +cccccccccccccccccccccccccccccccc +%CR +0%CR +%CR + + +HTTP/1.1 200 funky chunky! +Server: fakeit/0.9 fakeitbad/1.0 + + + +# Client-side + + +http + + +HTTP with two chunked Transfer-Encoding not listed last + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +61 + + + + diff --git a/tests/data/test1724 b/tests/data/test1724 new file mode 100644 index 000000000000..3cd328e39c39 --- /dev/null +++ b/tests/data/test1724 @@ -0,0 +1,53 @@ + + + + +IPFS + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 21 +Connection: close +Content-Type: text/plain +Funny-head: yesyes + +Hello curl from IPFS + + + +# Client-side + + +ipfs + + +http + + +IPFS with --proto-default HTTP + + +--ipfs-gateway http://%HOSTIP:%HTTPPORT ipfs://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u --proto-default http + + + +# Verify data after the test has been "shot" + + +GET /ipfs/bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1725 b/tests/data/test1725 new file mode 100644 index 000000000000..2a882c791d03 --- /dev/null +++ b/tests/data/test1725 @@ -0,0 +1,29 @@ + + + + +SCP +server key check + + + +# Client-side + + +scp + + +SCP incorrect host key with --proto-default SCP + + +--hostpubmd5 00000000000000000000000000000000 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: %HOSTIP:%SSHPORT%SCP_PWD/%LOGDIR/irrelevant-file --insecure --proto-default SCP + + + +# Verify data after the test has been "shot" + + +60 + + + diff --git a/tests/data/test1740 b/tests/data/test1740 new file mode 100644 index 000000000000..dda4b397755e --- /dev/null +++ b/tests/data/test1740 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP POST +--silent + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +# set the terminal wide to avoid word wrap in the message +COLUMNS=10000 + + +--silent suppresses Note: messages when verbose + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --silent -X POST -d foo -o %LOGDIR/out%TESTNUMBER -w '%{stderr}done\n' + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 3 +Content-Type: application/x-www-form-urlencoded + +foo + + +done + + + diff --git a/tests/data/test1741 b/tests/data/test1741 new file mode 100644 index 000000000000..e2e5cd838930 --- /dev/null +++ b/tests/data/test1741 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP POST + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +# set the terminal wide to avoid word wrap in the message +COLUMNS=10000 + + +Note: messages are shown when verbose without --silent + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -X POST -d foo -o %LOGDIR/out%TESTNUMBER --no-progress-meter -w '%{stderr}done\n' + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 3 +Content-Type: application/x-www-form-urlencoded + +foo + + +Note: Unnecessary use of -X or --request, POST is already inferred. +done + + + diff --git a/tests/data/test19 b/tests/data/test19 index 0c1b08b4c0b1..814163859c69 100644 --- a/tests/data/test19 +++ b/tests/data/test19 @@ -4,7 +4,6 @@ HTTP connect to non-listen -FAILURE # Server-side diff --git a/tests/data/test190 b/tests/data/test190 index 4b4da41b62e1..f59f871d6a36 100644 --- a/tests/data/test190 +++ b/tests/data/test190 @@ -4,7 +4,6 @@ FTP timeout -FAILURE DELAY diff --git a/tests/data/test1921 b/tests/data/test1921 new file mode 100644 index 000000000000..15a3fe1ae7b9 --- /dev/null +++ b/tests/data/test1921 @@ -0,0 +1,30 @@ + + + + +urlapi + + + +# Client-side + + + +Set a URL without scheme, then redirect with default scheme + + +lib%TESTNUMBER + + + +- + + + + + +URL: https://example.com/newpath + + + + diff --git a/tests/data/test1922 b/tests/data/test1922 new file mode 100644 index 000000000000..9c35ccff6f18 --- /dev/null +++ b/tests/data/test1922 @@ -0,0 +1,91 @@ + + + + +HTTP +HTTP proxy +HSTS +curl_easy_duphandle + + + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Type: text/plain +Content-Length: 5 +Strict-Transport-Security: max-age=31536000 + +Hello + + + + +HTTP/1.1 403 Forbidden +Content-Length: 0 +Connection: close + + + + + + +HSTS +https +Debug +proxy + + +http +http-proxy + + +CURL_HSTS_HTTP=yes + + +curl_easy_duphandle copies HSTS cache, auto upgrading HTTP to HTTPS. + + +lib%TESTNUMBER + + +- %HOSTIP %HTTPPORT %PROXYPORT + + + + +# First request: original handle GETs from the http server; the response +# carries Strict-Transport-Security, populating the live HSTS cache that +# the dup inherits. + +GET /%TESTNUMBER HTTP/1.1 +Host: hsts.example.com:%HTTPPORT +Accept: */* + + +# Second request: dup handle upgraded HTTP to HTTPS by copied HSTS cache, +# proxy receives CONNECT to port 443 proving the upgrade happened + +CONNECT hsts.example.com:443 HTTP/1.1 +Host: hsts.example.com:443 +Proxy-Connection: Keep-Alive + + + +First request: HSTS cache populated +Dup effective URL: https://hsts.example.com/%TESTNUMBER + +# CURLE_COULDNT_CONNECT (7) is intentional: The proxy rejects the CONNECT +# to port 443, collapsing the tunnel. All that is being validated is the +# CONNECT to port 443 itself. + +7 + + + diff --git a/tests/data/test1933 b/tests/data/test1933 index e1ef8b95ba13..cda12d19a121 100644 --- a/tests/data/test1933 +++ b/tests/data/test1933 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1934 b/tests/data/test1934 index 2128b299c2eb..9c52a0d0bddd 100644 --- a/tests/data/test1934 +++ b/tests/data/test1934 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1935 b/tests/data/test1935 index 158620a3bd29..f92c7e8ea2d3 100644 --- a/tests/data/test1935 +++ b/tests/data/test1935 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1936 b/tests/data/test1936 index 330fccc75650..e43eb0be6916 100644 --- a/tests/data/test1936 +++ b/tests/data/test1936 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1937 b/tests/data/test1937 index 19049410be98..2043053e59db 100644 --- a/tests/data/test1937 +++ b/tests/data/test1937 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1938 b/tests/data/test1938 index 91e27f876833..a2ea20fc9662 100644 --- a/tests/data/test1938 +++ b/tests/data/test1938 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1955 b/tests/data/test1955 index 7b84f746bd04..82349c25395b 100644 --- a/tests/data/test1955 +++ b/tests/data/test1955 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1956 b/tests/data/test1956 index 3e8a9f9ce449..f3ad8b859a14 100644 --- a/tests/data/test1956 +++ b/tests/data/test1956 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1957 b/tests/data/test1957 index 181eabb9f5af..77b48b238897 100644 --- a/tests/data/test1957 +++ b/tests/data/test1957 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1959 b/tests/data/test1959 index 7f0357da8f4b..27a778c305ac 100644 --- a/tests/data/test1959 +++ b/tests/data/test1959 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1961 b/tests/data/test1961 new file mode 100644 index 000000000000..0b44ca3c0edf --- /dev/null +++ b/tests/data/test1961 @@ -0,0 +1,18 @@ + + + + +unittest + + + +# Client-side + + +unittest + + +curlx_inet_ntop() and curlx_inet_pton() + + + diff --git a/tests/data/test1966 b/tests/data/test1966 index 49270d03a98e..914a1166e934 100644 --- a/tests/data/test1966 +++ b/tests/data/test1966 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP Digest auth +--resolve # Server-side diff --git a/tests/data/test1967 b/tests/data/test1967 new file mode 100644 index 000000000000..c012a70ba63c --- /dev/null +++ b/tests/data/test1967 @@ -0,0 +1,30 @@ + + + + +HTTP +urlapi + + + + + + +curl_url_set() a URL without guessing a scheme + + +lib%TESTNUMBER + + + +http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +URL http://a.b/x + + + diff --git a/tests/data/test1970 b/tests/data/test1970 index e697cabfeec6..8dae28c75339 100644 --- a/tests/data/test1970 +++ b/tests/data/test1970 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1971 b/tests/data/test1971 index bc44cd3eaa82..c303018c72df 100644 --- a/tests/data/test1971 +++ b/tests/data/test1971 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1972 b/tests/data/test1972 index 7de801da7e97..d128dccf452c 100644 --- a/tests/data/test1972 +++ b/tests/data/test1972 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1973 b/tests/data/test1973 index 896631f1514d..c2527df686f4 100644 --- a/tests/data/test1973 +++ b/tests/data/test1973 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1974 b/tests/data/test1974 index 6a99684d8855..20a10a87056d 100644 --- a/tests/data/test1974 +++ b/tests/data/test1974 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1975 b/tests/data/test1975 index a4d1a7f0f750..e5d6272eda0b 100644 --- a/tests/data/test1975 +++ b/tests/data/test1975 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1976 b/tests/data/test1976 index 5a0ff39dc086..3a09cc2ca31e 100644 --- a/tests/data/test1976 +++ b/tests/data/test1976 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 diff --git a/tests/data/test1978 b/tests/data/test1978 index 3ad4c670e2be..4113900fca53 100644 --- a/tests/data/test1978 +++ b/tests/data/test1978 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +aws-sigv4 @@ -69,6 +70,25 @@ header-no-value: header-some-no-value: header-some-no-value: value +PUT /%TESTNUMBER/testapi/test HTTP/1.1 +Host: 127.0.0.1:9000 +x-amz-meta-test: test2 +some-other-header: value +x-amz-meta-test: test1 +duplicate-header: duplicate +x-amz-meta-test: test3 +X-amz-meta-test2: test2 +x-amz-meta-blah: blah +x-Amz-meta-test2: test1 +x-amz-Meta-test2: test3 +curr-header-no-colon: value +next-header-no-colon: value +duplicate-header: duplicate +header-no-value: +header-no-value: +header-some-no-value: +header-some-no-value: value + diff --git a/tests/data/test1981 b/tests/data/test1981 index 1e2519a72a9b..e1fb24bf359c 100644 --- a/tests/data/test1981 +++ b/tests/data/test1981 @@ -38,9 +38,10 @@ Debug CURL_TIME=1754037103 +LC_TIME=C -http://%HOSTIP:%HTTPPORT/%TESTNUMBER --write-out='Time: %time{%d/%b/%Y %H:%M:%S.%f %z %Z}\n' -s -o %LOGDIR/dump +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --write-out='Time: %time{%d/%b/%Y %H:%M:%S.%f %z %Z%%s %s}\n' -s -o %LOGDIR/dump @@ -54,7 +55,7 @@ Accept: */* -Time: 01/Aug/2025 08:31:43.037103 +0000 UTC +Time: 01/Aug/2025 08:31:43.037103 +0000 UTC%s 1754037103 diff --git a/tests/data/test1985 b/tests/data/test1985 new file mode 100644 index 000000000000..78be4df0ea51 --- /dev/null +++ b/tests/data/test1985 @@ -0,0 +1,79 @@ + + + + +Digest + + + +# Server-side + + +HTTP/1.1 401 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Type: text/html +Content-Length: 0 +WWW-Authenticate: Digest realm="host-a-realm", nonce="host-a-nonce-0123456789", algorithm=MD5, qop="auth" + + + +HTTP/1.1 401 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Type: text/html +Content-Length: 0 +WWW-Authenticate: Digest realm="host-b-realm", nonce="2" + + + + +# Client-side + + +http + + +Digest response when not asked, then use Digest + + +!SSPI +crypto +digest + + +lib%TESTNUMBER + + + +http://firsthost:%HTTPPORT/%TESTNUMBER http://secondhost:%HTTPPORT/%TESTNUMBER0001 %HTTPPORT %HOSTIP + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: firsthost:%HTTPPORT +Accept: */* + +GET /%TESTNUMBER0001 HTTP/1.1 +Host: secondhost:%HTTPPORT +Accept: */* + +GET /19850001 HTTP/1.1 +Host: secondhost:%HTTPPORT +Authorization: Digest username="alice", realm="host-b-realm", nonce="2", uri="/19850001", response="b3186bc857ad5bb63947c39476d37290" +Accept: */* + +GET /19850001 HTTP/1.1 +Host: secondhost:%HTTPPORT +Authorization: Digest username="alice", realm="host-b-realm", nonce="2", uri="/19850001", response="b3186bc857ad5bb63947c39476d37290" +Accept: */* + + + +52 + + + diff --git a/tests/data/test20 b/tests/data/test20 index a851ba30d0a3..5dfe22dcd716 100644 --- a/tests/data/test20 +++ b/tests/data/test20 @@ -3,7 +3,6 @@ HTTP -FAILURE non-existing host diff --git a/tests/data/test2008 b/tests/data/test2008 index a24a01935639..9d1b14183391 100644 --- a/tests/data/test2008 +++ b/tests/data/test2008 @@ -5,6 +5,7 @@ HTTP -G --proto-default +--resolve diff --git a/tests/data/test201 b/tests/data/test201 index 7a1699d3e47f..ac1593c8e2c5 100644 --- a/tests/data/test201 +++ b/tests/data/test201 @@ -3,7 +3,6 @@ FILE -FAILURE diff --git a/tests/data/test2010 b/tests/data/test2010 index 443ae9d2f979..19a18920206b 100644 --- a/tests/data/test2010 +++ b/tests/data/test2010 @@ -35,7 +35,7 @@ http https -proxy credentials via options for two proxies, redirect from http to https +proxy creds via options for two proxies, redir from http to https diff --git a/tests/data/test2014 b/tests/data/test2014 new file mode 100644 index 000000000000..a7fb078db635 --- /dev/null +++ b/tests/data/test2014 @@ -0,0 +1,97 @@ + + + + +HTTP +HTTP PUT + + + +# Server-side + + +HTTP/1.1 200 OK swsbounce +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Accept-Ranges: bytes +Content-Length: 6 +Content-Type: text/html + +-foo- + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 20 +Content-Type: text/html + +the second response + + + +# Client-side + + +http + + +upload with glob, output name based on upload glob + + +-T '%LOGDIR/upload{%LThej%GT1,2}' http://%HOSTIP:%HTTPPORT/%TESTNUMBER --silent '--output=%LOGDIR/out-#%LThej%GT' + + + +first! + + + +second + + + + +# Verify data after the test has been "shot" + + +PUT /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 7 + +first! +PUT /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 7 + +second + + +%EMPTY + + + +HTTP/1.1 200 OK swsbounce +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Accept-Ranges: bytes +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 20 +Content-Type: text/html + +the second response + + + + diff --git a/tests/data/test2015 b/tests/data/test2015 new file mode 100644 index 000000000000..d0479c5b7bd6 --- /dev/null +++ b/tests/data/test2015 @@ -0,0 +1,92 @@ + + + + +HTTP +HTTP proxy +followlocation +cookies + + +# Server-side + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +contents + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +# Client-side + + +http + + +HTTP with cookie with with -b and redirect to new host + + +http://first.host.it.is/we/want/that/page/%TESTNUMBER -x %HOSTIP:%HTTPPORT -b "test=yes" --location + + +cookies +proxy + + + +# Verify data after the test has been "shot" + + +GET http://first.host.it.is/we/want/that/page/%TESTNUMBER HTTP/1.1 +Host: first.host.it.is +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive +Cookie: test=yes + +GET http://goto.second.host.now/%TESTNUMBER0002 HTTP/1.1 +Host: goto.second.host.now +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test2027 b/tests/data/test2027 index 2a5fa6e3db15..dfcefa302a16 100644 --- a/tests/data/test2027 +++ b/tests/data/test2027 @@ -16,7 +16,7 @@ Explanation for the duplicate 400 requests: libcurl does not detect that a given Digest password is wrong already on the first 401 response (as the data400 gives). libcurl will instead consider the -new response just as a duplicate and it sends another and detects the auth +new response as a duplicate and it sends another and detects the auth problem on the second 401 response! --> diff --git a/tests/data/test2030 b/tests/data/test2030 index e0d7ba551374..5027fd45da4b 100644 --- a/tests/data/test2030 +++ b/tests/data/test2030 @@ -21,7 +21,7 @@ Explanation for the duplicate 400 requests: libcurl does not detect that a given Digest password is wrong already on the first 401 response (as the data400 gives). libcurl will instead consider the -new response just as a duplicate and it sends another and detects the auth +new response as a duplicate and it sends another and detects the auth problem on the second 401 response! --> diff --git a/tests/data/test2036 b/tests/data/test2036 new file mode 100644 index 000000000000..b017a71abc8e --- /dev/null +++ b/tests/data/test2036 @@ -0,0 +1,26 @@ + + + + +--proto-default + + + +# Client-side + + +Attempt to set a default protocol with :// suffix + + +--proto-default https:// + + + +# Verify data after the test has been "shot" + +# CURLE_UNSUPPORTED_PROTOCOL is error code 1 + +1 + + + diff --git a/tests/data/test2039 b/tests/data/test2039 index bd9558a4bd67..6ef3b9d983d6 100644 --- a/tests/data/test2039 +++ b/tests/data/test2039 @@ -31,7 +31,7 @@ dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr ftp -FTP (optional .netrc with 'default' override; no user/pass) dir list PASV +FTP optional .netrc with 'default' override; no user/pass, dir list --netrc-optional --netrc-file %LOGDIR/netrc%TESTNUMBER ftp://%HOSTIP:%FTPPORT/ diff --git a/tests/data/test2045 b/tests/data/test2045 index 597deed7f66c..dd814abb6cb9 100644 --- a/tests/data/test2045 +++ b/tests/data/test2045 @@ -11,13 +11,13 @@ FTP # The purpose of this test is to make sure the --proto-default option works # properly. We specify a default protocol of FTP and if the option works properly -# curl will use the FTP protocol. If the option is broken however curl will use +# curl uses the FTP protocol. If the option is broken however curl uses # the HTTP protocol. # In the broken scenario curl would use HTTP to talk to our FTP server. We handle # that by replying with something that both protocols can understand. Our FTP # server allows a custom welcome message, so we use that feature to make an HTTP # reply that contains an FTP reply (think polyglot). In the case of FTP we expect -# curl will return CURLE_WEIRD_SERVER_REPLY so we test for that return code. +# curl to return CURLE_WEIRD_SERVER_REPLY so we test for that return code. REPLY welcome HTTP/1.1 200 OK\r\nContent-Length: 21\r\n\r\n500 Weird FTP Reply diff --git a/tests/data/test205 b/tests/data/test205 index 278562532357..3324d7d64f4f 100644 --- a/tests/data/test205 +++ b/tests/data/test205 @@ -3,7 +3,6 @@ FILE -FAILURE diff --git a/tests/data/test2050 b/tests/data/test2050 index ef20c69b828e..3c2a9b40f5f7 100644 --- a/tests/data/test2050 +++ b/tests/data/test2050 @@ -50,7 +50,8 @@ http-proxy -http://www.example.com.%TESTNUMBER/%TESTNUMBER --connect-to ::connect.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT +http://www.example.com.%TESTNUMBER/%TESTNUMBER --connect-to ::connect.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT --next +http://www.example.com.%TESTNUMBER:%HTTPPORT/%TESTNUMBER --connect-to ::www.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT proxy @@ -59,12 +60,18 @@ proxy # Verify data after the test has been "shot" - + CONNECT connect.example.com.%TESTNUMBER:%HTTPPORT HTTP/1.1 Host: connect.example.com.%TESTNUMBER:%HTTPPORT User-Agent: curl/%VERSION Proxy-Connection: Keep-Alive +GET http://www.example.com.%TESTNUMBER:%HTTPPORT/%TESTNUMBER HTTP/1.1 +Host: www.example.com.%TESTNUMBER:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + GET /%TESTNUMBER HTTP/1.1 diff --git a/tests/data/test2052 b/tests/data/test2052 index 85d3d0988d41..bd9a5eb9adfb 100644 --- a/tests/data/test2052 +++ b/tests/data/test2052 @@ -27,7 +27,7 @@ OK http ---connect-to: do not mix connections with and without a "connect to host" +--connect-to: do not mix connections with and w/o a "connect to host" diff --git a/tests/data/test2054 b/tests/data/test2054 index 83f203586dd9..245db971cfb5 100644 --- a/tests/data/test2054 +++ b/tests/data/test2054 @@ -26,7 +26,7 @@ OK http -Connect to specific host: use the first "connect-to" string that matches +Connect to specific host: use the first "connect-to" string diff --git a/tests/data/test2055 b/tests/data/test2055 index 608facb86e45..aad42d630236 100644 --- a/tests/data/test2055 +++ b/tests/data/test2055 @@ -48,24 +48,31 @@ http-proxy socks5 ---connect-to via SOCKS proxy and HTTP proxy (tunnel mode automatically) +--connect-to via SOCKS proxy and HTTP proxy (tunnel mode auto) proxy -http://www.example.com.%TESTNUMBER/%TESTNUMBER --connect-to ::connect.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT --preproxy socks5://%HOSTIP:%SOCKSPORT +http://www.example.com.%TESTNUMBER/%TESTNUMBER --connect-to ::connect.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT --preproxy socks5://%HOSTIP:%SOCKSPORT --next +http://www.example.com.%TESTNUMBER:%HTTPPORT/%TESTNUMBER --connect-to ::www.example.com.%TESTNUMBER:%HTTPPORT -x %HOSTIP:%PROXYPORT --preproxy socks5://%HOSTIP:%SOCKSPORT # Verify data after the test has been "shot" - + CONNECT connect.example.com.%TESTNUMBER:%HTTPPORT HTTP/1.1 Host: connect.example.com.%TESTNUMBER:%HTTPPORT User-Agent: curl/%VERSION Proxy-Connection: Keep-Alive +GET http://www.example.com.%TESTNUMBER:%HTTPPORT/%TESTNUMBER HTTP/1.1 +Host: www.example.com.%TESTNUMBER:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + GET /%TESTNUMBER HTTP/1.1 diff --git a/tests/data/test2057 b/tests/data/test2057 index 18ce33d947a4..d2ca3b79682a 100644 --- a/tests/data/test2057 +++ b/tests/data/test2057 @@ -5,45 +5,17 @@ HTTP HTTP GET HTTP Negotiate auth (stub ntlm) +SPNEGO NTLM disallowed # Server-side - - -HTTP/1.1 401 Authorization Required -Server: Microsoft-IIS/7.0 -Content-Type: text/html; charset=iso-8859-1 -WWW-Authenticate: Negotiate Qw== -Content-Length: 19 + +HTTP/1.1 200 OK swsclose +Content-Length: 23 -Still not yet sir! - - - -HTTP/1.1 200 Things are fine in server land -Server: Microsoft-IIS/7.0 -Content-Type: text/html; charset=iso-8859-1 -WWW-Authenticate: Negotiate RA== -Content-Length: 15 - -Nice auth sir! - - -HTTP/1.1 401 Authorization Required -Server: Microsoft-IIS/7.0 -Content-Type: text/html; charset=iso-8859-1 -WWW-Authenticate: Negotiate Qw== -Content-Length: 19 - -HTTP/1.1 200 Things are fine in server land -Server: Microsoft-IIS/7.0 -Content-Type: text/html; charset=iso-8859-1 -WWW-Authenticate: Negotiate RA== -Content-Length: 15 - -Nice auth sir! - +This IS the real page! + # Client-side @@ -52,7 +24,7 @@ Nice auth sir! http -HTTP Negotiate authentication (stub NTLM) +HTTP Negotiate authentication blocked for stub NTLM credentials GSS-API @@ -68,16 +40,15 @@ CURL_STUB_GSS_CREDS="NTLM_Alice" # Verify data after the test has been "shot" + +0 + +# NTLM is blocked within SPNEGO, so when only NTLM credentials are +# available, negotiate auth silently fails and the request is sent +# without any Authorization header. GET /%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Authorization: Negotiate %b64["NTLM_Alice":HTTP@127.0.0.1:2:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% -User-Agent: curl/%VERSION -Accept: */* - -GET /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Authorization: Negotiate %b64["NTLM_Alice":HTTP@127.0.0.1:3:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% User-Agent: curl/%VERSION Accept: */* diff --git a/tests/data/test2059 b/tests/data/test2059 index 4726c0a1e66f..71dc55f05963 100644 --- a/tests/data/test2059 +++ b/tests/data/test2059 @@ -70,7 +70,7 @@ proxy digest -HTTP Digest with PUT, resumed upload, modified method, SHA-256 and userhash +HTTP Digest PUT, resumed upload, modified method, SHA-256 and userhash http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET diff --git a/tests/data/test2064 b/tests/data/test2064 index d3897535c600..0ab4048dcc74 100644 --- a/tests/data/test2064 +++ b/tests/data/test2064 @@ -9,7 +9,7 @@ HTTP Digest auth # Server-side - + HTTP/1.1 401 Authorization Required Server: Apache/1.3.27 (Darwin) PHP/4.1.2 WWW-Authenticate: Digest realm="testrealm", nonce="2053604145", algorithm="SHA-256" diff --git a/tests/data/test2067 b/tests/data/test2067 index b6de16d0bd75..4b6030252c2f 100644 --- a/tests/data/test2067 +++ b/tests/data/test2067 @@ -55,7 +55,7 @@ crypto digest -HTTP POST --digest with SHA256 and user-specified Content-Length header +HTTP POST --digest with SHA256 and user-specified Content-Length # This test is to ensure 'Content-Length: 0' is sent while negotiating auth # even when there is a user-specified Content-Length header. diff --git a/tests/data/test207 b/tests/data/test207 index f1c0cf81fb63..8ac782f067bd 100644 --- a/tests/data/test207 +++ b/tests/data/test207 @@ -5,7 +5,6 @@ HTTP HTTP GET CURLE_PARTIAL_FILE -FAILURE chunked Transfer-Encoding diff --git a/tests/data/test2072 b/tests/data/test2072 index c7731a83e3c1..efad82012a8b 100644 --- a/tests/data/test2072 +++ b/tests/data/test2072 @@ -22,7 +22,7 @@ moo file -file:// with Unix path resolution behavior for the case of extra slashes +file:// with Unix path for the case of extra slashes file:////%FILE_PWD/%LOGDIR/test%TESTNUMBER.txt diff --git a/tests/data/test2074 b/tests/data/test2074 index c1571231a2df..0967031906e0 100644 --- a/tests/data/test2074 +++ b/tests/data/test2074 @@ -32,7 +32,7 @@ Funny-head: yesyes http -HTTP GET +HTTP with oauth2-bearer http://%HOSTIP:%HTTPPORT/%TESTNUMBER --oauth2-bearer mF_9.B5f-4.1JqM diff --git a/tests/data/test2087 b/tests/data/test2087 index c3a5901d172d..cfa51368406d 100644 --- a/tests/data/test2087 +++ b/tests/data/test2087 @@ -32,7 +32,7 @@ local-http https test-localhost.pem -simple HTTPS GET with base64-sha256 public key pinning (Schannel variant) +simple HTTPS GET with base64-sha256 public key pinning (Schannel) # This test is pointless if we are not using the Schannel backend diff --git a/tests/data/test323 b/tests/data/test2092 similarity index 57% rename from tests/data/test323 rename to tests/data/test2092 index e8f45c5ef8fe..6cbf8563fb7a 100644 --- a/tests/data/test323 +++ b/tests/data/test2092 @@ -2,33 +2,27 @@ -HTTPS -TLS-SRP -FAILURE +globbing # Client-side -https +http - -TLS-SRP - -TLS-SRP to non-TLS-SRP server +glob range that ends with 9223372036854775807 ---insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword badpass https://%HOSTIP:%HTTPSPORT/want/%TESTNUMBER +"%HOSTIP:%HTTPPORT/[0-1][9223372036854775806-9223372036854775807]/%TESTNUMBER" # Verify data after the test has been "shot" -35 +3 - diff --git a/tests/data/test2093 b/tests/data/test2093 new file mode 100644 index 000000000000..6c156140f5e8 --- /dev/null +++ b/tests/data/test2093 @@ -0,0 +1,50 @@ + + + + +HTTP +HTTP GET + + + +# Server-side + + +HTTP/1.1 200 OK swsclose +Content-Length: 9223372036854775807 +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +HTTP POST a few bytes with 2^63-1 bytes response + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -# -d "sending data" + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 12 +Content-Type: application/x-www-form-urlencoded + +sending data + + +18 + + + diff --git a/tests/data/test2094 b/tests/data/test2094 new file mode 100644 index 000000000000..57ded4456e7d --- /dev/null +++ b/tests/data/test2094 @@ -0,0 +1,68 @@ + + + + +HTTP +HTTP GET +HTTP Negotiate auth (stub krb5) +SPNEGO NTLM disallowed + + + +# Server-side + + +HTTP/1.1 200 Things are fine in server land +Server: Microsoft-IIS/7.0 +Content-Type: text/html; charset=iso-8859-1 +WWW-Authenticate: Negotiate RA== +Content-Length: 15 + +Nice auth sir! + + +HTTP/1.1 200 Things are fine in server land +Server: Microsoft-IIS/7.0 +Content-Type: text/html; charset=iso-8859-1 +WWW-Authenticate: Negotiate RA== +Content-Length: 15 + +Nice auth sir! + + + +# Client-side + + +http + + +SPNEGO with Kerberos still works when NTLM is blocked + + +GSS-API +Debug + + +CURL_STUB_GSS_CREDS="KRB5_Alice" + + +--negotiate http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +0 + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Negotiate %b64["KRB5_Alice":HTTP@127.0.0.1:1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test21 b/tests/data/test21 index af47cf914e03..8f3da43b8162 100644 --- a/tests/data/test21 +++ b/tests/data/test21 @@ -2,7 +2,6 @@ -FAILURE multiple HTTP requests diff --git a/tests/data/test2100 b/tests/data/test2100 index 8793c5786583..57dc8c6579c0 100644 --- a/tests/data/test2100 +++ b/tests/data/test2100 @@ -41,6 +41,7 @@ Funny-head: yesyes http +https # requires Debug so that it can use the DoH server without https @@ -54,8 +55,14 @@ IPv6 HTTP GET using DoH (with HTTPS RR) +# Make Happy Eyeballing wait longer on AAAA results to arrive +# On slow runs (valgrind) the connect otherwise might succeed before +# all DoH requests are sent off. + +CURL_DBG_HE_AAAA_AWAIT_MS=60000 + -http://foo.example.com:%HTTPPORT/%TESTNUMBER --doh-url http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 +https://foo.example.com:%HTTPSPORT/%TESTNUMBER --insecure --doh-insecure --doh-url https://%HOSTIP:%HTTPSPORT/%TESTNUMBER0001 @@ -71,43 +78,43 @@ s/com\x00\x00(\x1c|\x01)/com-00-00!/g; %if HTTPSRR POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message -Content-Length: 33 +Content-Length: 47 -%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPSPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 33 %hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message -Content-Length: 47 +Content-Length: 33 -%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%GET /%TESTNUMBER HTTP/1.1 -Host: foo.example.com:%HTTPPORT +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%GET /%TESTNUMBER HTTP/1.1 +Host: foo.example.com:%HTTPSPORT User-Agent: curl/%VERSION Accept: */* %else POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 33 %hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 33 %hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%GET /%TESTNUMBER HTTP/1.1 -Host: foo.example.com:%HTTPPORT +Host: foo.example.com:%HTTPSPORT User-Agent: curl/%VERSION Accept: */* diff --git a/tests/data/test2105 b/tests/data/test2105 new file mode 100644 index 000000000000..8bd26caff12d --- /dev/null +++ b/tests/data/test2105 @@ -0,0 +1,49 @@ + + + + +HTTP +HTTP GET + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/%CRfake +Content-Length: 6 +Funny-head: yesyes + +-foo- + + + +# Client-side + + +http + + +HTTP with spurious CR in received header + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +8 + + + diff --git a/tests/data/test2106 b/tests/data/test2106 new file mode 100644 index 000000000000..40d2dc8b62b6 --- /dev/null +++ b/tests/data/test2106 @@ -0,0 +1,53 @@ + + + + +HTTP +HTTP GET +chunked Transfer-Encoding + + + +# Server-side + + +HTTP/1.1 200 OK%CR +Server: test%CR +Transfer-Encoding: chunked%CR +Trailer: chunky-trailer%CR +%CR +6%CR +-foo-%CR +0%CR +chunky-trailer: he%hex[%00]hex%llo%CR +%CR + + + +# Client-side + + +http + + +HTTP chunked response with a nul byte in the trailer + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +8 + + + diff --git a/tests/data/test2107 b/tests/data/test2107 new file mode 100644 index 000000000000..8c4404410997 --- /dev/null +++ b/tests/data/test2107 @@ -0,0 +1,49 @@ + + + + +HTTP +HTTP CONNECT +HTTP proxy +proxytunnel + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 0 + + + +HTTP/1.1 200 OK%CR +X-Evil: he%hex[%00]hex%llo%CR +%CR + + + +# Client-side + + +http +http-proxy + + +proxy + + +HTTP CONNECT response with a nul byte in a header + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -p -x http://%HOSTIP:%PROXYPORT + + + +# Verify data after the test has been "shot" + + +8 + + + diff --git a/tests/data/test2108 b/tests/data/test2108 new file mode 100644 index 000000000000..481d09357e73 --- /dev/null +++ b/tests/data/test2108 @@ -0,0 +1,41 @@ + + + + +FTP + + +# Server-side + + +REPLY PASS 230 logged\x00 in + + + +# Client-side + + +ftp + + +FTP rejects a nul byte in a server response line + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER + + + + +# Verify data after the test has been "shot" + + +USER anonymous +PASS ftp@example.com + + +# 8 == CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2109 b/tests/data/test2109 new file mode 100644 index 000000000000..323ea144c4d2 --- /dev/null +++ b/tests/data/test2109 @@ -0,0 +1,50 @@ + + + + +MQTT +MQTT SUBSCRIBE + + + +# Server-side + + +hello + + + +# Client-side + + +mqtt + + +mqtt + + +MQTT rejects a control byte in the topic + + +mqtt://%HOSTIP:%MQTTPORT/aa%00bb + + + +# Verify data after the test has been "shot" + +# These are hexadecimal protocol dumps from the client +# Strip out the random part of the client id from the CONNECT message +# before comparison + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + +# the topic is decoded and the embedded nul rejected before SUBSCRIBE is sent + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 + + +3 + + + diff --git a/tests/data/test2110 b/tests/data/test2110 new file mode 100644 index 000000000000..6b48608c8be8 --- /dev/null +++ b/tests/data/test2110 @@ -0,0 +1,49 @@ + + + + +SMTP + + + +# Server-side + + + +# Client-side + + +smtp + + +SMTP --mail-from with embedded CRLF is rejected + + +From: different +To: another + +body + +# read the sender from a config file so the raw CR LF reaches curl intact +# regardless of how the platform passes command line arguments + +mail-from = "sender@example.com\r\nDATA" + + +smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com -K %LOGDIR/test%TESTNUMBER.config -T %LOGDIR/test%TESTNUMBER.eml + + + +# Verify data after the test has been "shot" + +# 43 - CURLE_BAD_FUNCTION_ARGUMENT + +43 + +# the injected DATA command must never reach the server + +EHLO %TESTNUMBER +QUIT + + + diff --git a/tests/data/test2113 b/tests/data/test2113 new file mode 100644 index 000000000000..1033c3d2b421 --- /dev/null +++ b/tests/data/test2113 @@ -0,0 +1,94 @@ + + + + +HTTP +HTTP proxy +HTTP Basic auth +HTTP proxy Basic auth +followlocation + + +# Server-side + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +contents + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +# Client-side + + +http + + +Authorization: with whitespace before colon and redir to new host + + +http://first.host.it.is/we/want/that/page/%TESTNUMBER -x %HOSTIP:%HTTPPORT -H "Authorization : s3cr3t" --proxy-user testing:this --location + + +proxy + + + +# Verify data after the test has been "shot" + + +GET http://first.host.it.is/we/want/that/page/%TESTNUMBER HTTP/1.1 +Host: first.host.it.is +Proxy-Authorization: Basic %b64[testing:this]b64% +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive +Authorization : s3cr3t + +GET http://goto.second.host.now/%TESTNUMBER0002 HTTP/1.1 +Host: goto.second.host.now +Proxy-Authorization: Basic %b64[testing:this]b64% +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test2114 b/tests/data/test2114 new file mode 100644 index 000000000000..eaeefa6a520b --- /dev/null +++ b/tests/data/test2114 @@ -0,0 +1,43 @@ + + + + +FTP + + + +# Server-side + + + +# Client-side + + +ftp + + +FTP username with embedded CRLF is rejected + +# read the credentials from a config file so the raw CR LF reaches curl intact +# regardless of how the platform passes command line arguments + +user = "anonymous\r\nSTOR: /%TESTNUMBER" + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER -K %LOGDIR/test%TESTNUMBER.config + + + +# Verify data after the test has been "shot" + +# 43 - CURLE_BAD_FUNCTION_ARGUMENT + +43 + +# the credentials are rejected before connecting so the injected STOR command +# must never reach the server + + + + + diff --git a/tests/data/test2115 b/tests/data/test2115 new file mode 100644 index 000000000000..d9c8b0513724 --- /dev/null +++ b/tests/data/test2115 @@ -0,0 +1,46 @@ + + + + +FTP +ACCT + + +# Server-side + + +REPLY PASS 332 please provide account name + + + +# Client-side + + +ftp + + +FTP --ftp-account with embedded CRLF is rejected + +# read the account from a config file so the raw CR LF reaches curl intact + +ftp-account = "one\r\nDELE %TESTNUMBER" + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER -K %LOGDIR/test%TESTNUMBER.config + + + +# Verify data after the test has been "shot" + +# 43 - CURLE_BAD_FUNCTION_ARGUMENT + +43 + +# the account is rejected before ACCT is built, so the injected DELE command +# must never reach the server + +USER anonymous +PASS ftp@example.com + + + diff --git a/tests/data/test2116 b/tests/data/test2116 new file mode 100644 index 000000000000..8ecd679b4a9e --- /dev/null +++ b/tests/data/test2116 @@ -0,0 +1,45 @@ + + + + +FTP +--ftp-alternative-to-user + + +# Server-side + + +REPLY USER 530 go away + + + +# Client-side + + +ftp + + +FTP --ftp-alternative-to-user with embedded CRLF is rejected + +# read the command from a config file so the raw CR LF reaches curl intact + +ftp-alternative-to-user = "USER me\r\nDELE %TESTNUMBER" + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER/ -K %LOGDIR/test%TESTNUMBER.config + + + +# Verify data after the test has been "shot" + +# 43 - CURLE_BAD_FUNCTION_ARGUMENT + +43 + +# the replacement command is rejected before it is sent, so the injected DELE +# command must never reach the server + +USER anonymous + + + diff --git a/tests/data/test2117 b/tests/data/test2117 new file mode 100644 index 000000000000..a5b800bc0e22 --- /dev/null +++ b/tests/data/test2117 @@ -0,0 +1,102 @@ + + + + +HTTP +HTTP GET +DOH +httpsrr + + + +# Server-side + + +# This is the DoH response for foo.example.com A 127.0.0.1. It will be sent +# for all DoH requests, so the AAAA and HTTPS resolves will fail with wrong +# data. But it does verify that the responses are acted upon. +# This requires that the test server is accessible at that address! + + +SFRUUC8xLjEgMjAwIE9LCkRhdGU6IFRodSwgMDkgTm92IDIwMTAgMTQ6NDk6MDAgR01UClNlcnZl +cjogdGVzdC1zZXJ2ZXIvZmFrZQpDb25uZWN0aW9uOiBjbG9zZQpDb250ZW50LVR5cGU6IGFwcGxp +Y2F0aW9uL2Rucy1tZXNzYWdlCkNvbnRlbnQtTGVuZ3RoOiA0OQoKAAABAAABAAEAAAAAA2Zvbwdl +eGFtcGxlA2NvbQAAAQABwAwAAQABAAAANwAEfwAAAQ== + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# Client-side + + +http +https + + +# requires Debug so that it can use the DoH server without https +# requires IPv6 so that we can assume and compare both DoH requests + + +Debug +DoH +IPv6 +ECH +HTTPSRR + + +HTTP GET using DoH with mandatory HTTPS-RR response + + +https://foo.example.com:%HTTPSPORT/%TESTNUMBER --insecure --ech true --connect-timeout 20 --doh-insecure --doh-url https://%HOSTIP:%HTTPSPORT/%TESTNUMBER0001 + + + +# Verify data after the test has been "shot" + + +# To make the test ignore the order of the two outgoing DoH requests, strip +# the family byte + + +s/com\x00\x00(\x1c|\x01)/com-00-00!/g; + + +POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 47 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPSPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%GET /%TESTNUMBER HTTP/1.1 +Host: foo.example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test2118 b/tests/data/test2118 new file mode 100644 index 000000000000..a3a18e750198 --- /dev/null +++ b/tests/data/test2118 @@ -0,0 +1,43 @@ + + + + +HTTPS +HTTP GET +PEM certificate + + + +# Server-side + + + +# Client-side + + +SSL +local-http +!wolfssl +!rustls + + +https test-localhost.nn.pem + + +lib%TESTNUMBER + + +VERIFYHOST with VERIFYPEER disabled rejects a mismatching certificate + + +https://localhost:%HTTPSPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +60 + + + diff --git a/tests/data/test2119 b/tests/data/test2119 new file mode 100644 index 000000000000..8f9c2ecaed25 --- /dev/null +++ b/tests/data/test2119 @@ -0,0 +1,54 @@ + + + + +HTTP +Transfer-Encoding + + + +# Server-side + + +HTTP/1.1 200 swsclose OK +Content-Length: 6 +Transfer-Encoding: identityx + +-foo- + + + +# Client-side + + +http + + +Reject unsolicited Transfer-Encoding token with an identity prefix + +# no --tr-encoding, so any transfer-coding other than chunked/identity is +# unsolicited and must be rejected + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -sS + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# CURLE_BAD_CONTENT_ENCODING is 61 + +61 + + +curl: (61) Unsolicited Transfer-Encoding (identityx) found + + + diff --git a/tests/data/test218 b/tests/data/test218 index 41bd83818ef4..14fc604830e5 100644 --- a/tests/data/test218 +++ b/tests/data/test218 @@ -31,7 +31,7 @@ HTTP PUT from a file but enforce chunked transfer-encoding -T %LOGDIR/file%TESTNUMBER -H "Transfer-Encoding: chunked" http://%HOSTIP:%HTTPPORT/%TESTNUMBER -just some tiny teeny contents +some tiny teeny test contents @@ -45,7 +45,7 @@ Accept: */*%CR Transfer-Encoding: chunked%CR %CR 1e%CR -just some tiny teeny contents +some tiny teeny test contents %CR 0%CR %CR diff --git a/tests/data/test2200 b/tests/data/test2200 index 95baca6bc098..bc1652eb2e98 100644 --- a/tests/data/test2200 +++ b/tests/data/test2200 @@ -28,7 +28,7 @@ mqtt mqtt -MQTT SUBSCRIBE with user and password +MQTT SUBSCRIBE with user and password, not authorized mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -u fakeuser:fakepasswd diff --git a/tests/data/test2203 b/tests/data/test2203 index 9d8c1da6793e..9dcfedadaf7d 100644 --- a/tests/data/test2203 +++ b/tests/data/test2203 @@ -28,7 +28,7 @@ mqtt mqtt -MQTT with error in CONNACK +MQTT with "no user or password" CONNACK mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER diff --git a/tests/data/test2206 b/tests/data/test2206 new file mode 100644 index 000000000000..5021c190f7f7 --- /dev/null +++ b/tests/data/test2206 @@ -0,0 +1,59 @@ + + + + +MQTT +MQTT SUBSCRIBE + + + +# Server-side + + +hello + + +# Send a PINGRESP (0xD0) with remaining_length=2 in place of the +# expected CONNACK. MQTT 3.1.1 s. 3.13.1 requires PINGRESP to have +# remaining_length == 0. curl must reject this with +# CURLE_WEIRD_SERVER_REPLY rather than dispatching to the CONNACK +# handler. + +PINGRESP-as-CONNACK TRUE + + + +# Client-side + + +mqtt + + +mqtt + + +MQTT reject PINGRESP with nonzero remaining_length in place of CONNACK + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + +# Strip out the random part of the client id from the CONNECT message +# before comparison + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server PINGRESP-as-CONNACK 2 d0020000 + + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2207 b/tests/data/test2207 new file mode 100644 index 000000000000..61b423059e84 --- /dev/null +++ b/tests/data/test2207 @@ -0,0 +1,59 @@ + + + + +MQTT +MQTT SUBSCRIBE + + + +# Server-side + + +hello + + +# Send a DISCONNECT with remaining_length=2 after the PUBLISH. +# MQTT 3.1.1 s. 3.14.1 requires DISCONNECT to have remaining_length == 0. +# curl must reject this with CURLE_WEIRD_SERVER_REPLY. + +DISCONNECT-malformed TRUE + + + +# Client-side + + +mqtt + + +mqtt + + +MQTT reject DISCONNECT with nonzero remaining_length + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232303700 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232303768656c6c6f0a +server DISCONNECT-malformed 2 e0020000 + + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2208 b/tests/data/test2208 new file mode 100644 index 000000000000..354d1a2a7e5d --- /dev/null +++ b/tests/data/test2208 @@ -0,0 +1,90 @@ + + + + +HTTP +HTTP proxy +--location +HTTP Basic auth + + + +# Server-side + + +HTTP/1.1 301 redirect +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close +Content-Type: text/html +Location: http://@firsthost.com:9999/a/path/%TESTNUMBER0002 + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 4 +Connection: close +Content-Type: text/html + +hey + + + +HTTP/1.1 301 redirect +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close +Content-Type: text/html +Location: http://@firsthost.com:9999/a/path/%TESTNUMBER0002 + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 4 +Connection: close +Content-Type: text/html + +hey + + + + +# Client-side + + +proxy + + +http + + +HTTP auth on redirect with empty URL userinfo + + +-x http://%HOSTIP:%HTTPPORT http://firsthost.com -L -u joe:secret + + + +# Verify data after the test has been "shot" + + +GET http://firsthost.com/ HTTP/1.1 +Host: firsthost.com +Authorization: Basic %b64[joe:secret]b64% +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://firsthost.com:9999/a/path/%TESTNUMBER0002 HTTP/1.1 +Host: firsthost.com:9999 +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test221 b/tests/data/test221 index 5a7d082e7954..03f6d0505537 100644 --- a/tests/data/test221 +++ b/tests/data/test221 @@ -5,7 +5,6 @@ HTTP HTTP GET compressed -FAILURE # Server-side diff --git a/tests/data/test223 b/tests/data/test223 index 9912d3d47185..660c85da129c 100644 --- a/tests/data/test223 +++ b/tests/data/test223 @@ -5,7 +5,6 @@ HTTP HTTP GET compressed -FAILURE # Server-side diff --git a/tests/data/test225 b/tests/data/test225 index 1ce22088ee0d..403bd2fec2f1 100644 --- a/tests/data/test225 +++ b/tests/data/test225 @@ -3,7 +3,6 @@ FTP -FAILURE # Client-side diff --git a/tests/data/test226 b/tests/data/test226 index 1872981d644f..dbf6d1af38f0 100644 --- a/tests/data/test226 +++ b/tests/data/test226 @@ -3,7 +3,6 @@ FTP -FAILURE diff --git a/tests/data/test229 b/tests/data/test229 index b90214ff5107..bd911db9ee95 100644 --- a/tests/data/test229 +++ b/tests/data/test229 @@ -4,7 +4,6 @@ FTP ACCT -FAILURE # Server-side diff --git a/tests/data/test23 b/tests/data/test23 index 1771fe9e1583..8126025b9063 100644 --- a/tests/data/test23 +++ b/tests/data/test23 @@ -3,7 +3,6 @@ unsupported scheme -FAILURE # Server-side diff --git a/tests/data/test2302 b/tests/data/test2302 index 397350dc99ae..396ba16a7ecc 100644 --- a/tests/data/test2302 +++ b/tests/data/test2302 @@ -6,7 +6,7 @@ WebSockets -# Sends a PING + a 5 byte hello TEXT +# Sends a PING + a 5-byte hello TEXT HTTP/1.1 101 Switching to WebSockets diff --git a/tests/data/test2304 b/tests/data/test2304 index 26bbda187e6a..aab6dc822b48 100644 --- a/tests/data/test2304 +++ b/tests/data/test2304 @@ -62,7 +62,7 @@ Connection: Upgrade # This test used to check that "connection closed" was output, but -# that is flaky since the outgoing PING just before might fail already +# that is flaky since the outgoing PING before might fail already # and then the test exists before the output gets to be written diff --git a/tests/data/test2305 b/tests/data/test2305 new file mode 100644 index 000000000000..61a2d4401a7d --- /dev/null +++ b/tests/data/test2305 @@ -0,0 +1,61 @@ + + + + +HTTP +HTTP GET +compressed + + +# Server-side + + +HTTP/1.1 200 OK +Date: Mon, 29 Nov 2004 21:56:53 GMT +Server: test-server/fake +Content-Type: text/plain; charset=UTF-8 +Content-Encoding: gzip +Content-Length: 54 + +%hex[%1f%8b%08%00%00%00%00%00%02%13%f3%48%cd%c9%c9%d7%51%00%00%05%6f%57%de%07%00%00%00%1f%8b%08%00%00%00%00%00%02%13%0b%cf%2f%ca%49%51%e4%02%00%dd%d1%ca%53%07%00%00%00]hex% + + + + +# Client-side + + +libz + + +http + + +HTTP GET gzip compressed content with two concatenated gzip members + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --compressed --silent --show-error + + + +# Verify data after the test has been "shot" + + +s/^Accept-Encoding: [a-zA-Z, ]*/Accept-Encoding: xxx/ + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Accept-Encoding: xxx + + + +23 + + +curl: (23) Multi-member gzip response not supported + + + diff --git a/tests/data/test2310 b/tests/data/test2310 new file mode 100644 index 000000000000..0446c374a0ef --- /dev/null +++ b/tests/data/test2310 @@ -0,0 +1,50 @@ + + + + +variables + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: something-close, close-something, close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# Client-side + + +http + + +variable decode and trim + + +--variable 'VAR=IA==' --expand-url 'http://%HOSTIP:%HTTPPORT/{{VAR:64dec:trim}}' + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test2311 b/tests/data/test2311 new file mode 100644 index 000000000000..1d43a3292956 --- /dev/null +++ b/tests/data/test2311 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP GET +HTTP proxy +cookies + + + +# Server-side + + +HTTP/1.1 200 OK +Server: test-server/fake +Content-Length: 21 + +This server says moo + + + +# Client-side + + +http + + +Cookie from file: control octet rejected, prefixes case sensitive + + +http://example.fake/%TESTNUMBER -b %LOGDIR/injar%TESTNUMBER -x %HOSTIP:%HTTPPORT + + +example.fake FALSE / FALSE 0 clean good +example.fake FALSE / FALSE 0 bad %hex[ba%07d]hex% +example.fake FALSE / FALSE 0 __secure-x yes +example.fake FALSE / FALSE 0 __Secure-y no + + +cookies +proxy + + + +# Verify data after the test has been "shot" + + +GET http://example.fake/%TESTNUMBER HTTP/1.1 +Host: example.fake +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive +Cookie: __secure-x=yes; clean=good + + + + diff --git a/tests/data/test2318 b/tests/data/test2318 new file mode 100644 index 000000000000..ab778b52cb65 --- /dev/null +++ b/tests/data/test2318 @@ -0,0 +1,65 @@ + + + + +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 6 +Set-Cookie: sid=DOMAIN_SECRET; Domain=github.io; Path=/ + +-foo- + + + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +# Client-side + + +PSL +cookies + + +http + + +Get cookies from PSL domain then use *.domain + + +--resolve github.io:%HTTPPORT:%HOSTIP --resolve attacker.github.io:%HTTPPORT:%HOSTIP -b "" http://github.io:%HTTPPORT/%TESTNUMBER http://attacker.github.io:%HTTPPORT/%TESTNUMBER0002 http://github.io:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: github.io:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: attacker.github.io:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER HTTP/1.1 +Host: github.io:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: sid=DOMAIN_SECRET + + + + diff --git a/tests/data/test234 b/tests/data/test234 index 0672a25bde44..aa13dccc7aba 100644 --- a/tests/data/test234 +++ b/tests/data/test234 @@ -63,7 +63,7 @@ contents http -HTTP, proxy, site+proxy auth and Location: to new host location-trusted +HTTP, proxy, site+proxy auth and Location: --location-trusted http://first.host.it.is/we/want/that/page/%TESTNUMBER -x %HOSTIP:%HTTPPORT --user iam:myself --proxy-user testing:this --location-trusted diff --git a/tests/data/test2349 b/tests/data/test2349 new file mode 100644 index 000000000000..a09a98cdf443 --- /dev/null +++ b/tests/data/test2349 @@ -0,0 +1,63 @@ + + + + +HTTP +HTTP GET + + + + + +HTTP/1.1 407 NoNoNo +Transfer-Encoding: chunked +Trailer: proxy-trailer + +4 +aaaa +0 +proxy-trailer: 12345 + + + + +# Client-side + + +http +http-proxy + + +proxy +Debug + + +HTTP CONNECT -D file + + +CURL_DBG_SUPPRESS_CONNECT_HDS=1 + + +http://%HOSTIP:%HTTPPORT/ --proxy %HOSTIP:%PROXYPORT --proxytunnel -sS -D %LOGDIR/heads%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +7 + + +CONNECT %HOSTIP:%HTTPPORT HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + +# Header file is empty, as CONNECT headers were suppress by DEBUG env var + + + + + diff --git a/tests/data/test2397 b/tests/data/test2397 new file mode 100644 index 000000000000..1a0996febb38 --- /dev/null +++ b/tests/data/test2397 @@ -0,0 +1,55 @@ + + + + +CURLINFO_REFERER +CURLOPT_REFERER + + + + + +HTTP/1.1 301 redir away +Content-Length: 0 +Location: /%TESTNUMBER0002 + + + +HTTP/1.1 200 OK +Content-Length: 4 + +hej + + + + + +http + + + +lib%TESTNUMBER + + + +Get CURLINFO_REFERER set CURLOPT_REFERER + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Referer: http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + diff --git a/tests/data/test2405 b/tests/data/test2405 index 0d64ec458a5f..6ccb6ba394d4 100644 --- a/tests/data/test2405 +++ b/tests/data/test2405 @@ -4,7 +4,6 @@ multi HTTP -flaky diff --git a/tests/data/test2407 b/tests/data/test2407 index 33555a85028f..b9f17bb22fe1 100644 --- a/tests/data/test2407 +++ b/tests/data/test2407 @@ -5,7 +5,6 @@ multi HTTP HTTP/2 -flaky diff --git a/tests/data/test2408 b/tests/data/test2408 new file mode 100644 index 000000000000..edc83d48a4ec --- /dev/null +++ b/tests/data/test2408 @@ -0,0 +1,92 @@ + + + + +HTTP +HTTP GET +globbing +{} list + + +# Server-side + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +moo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +foo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +hoo + + + +# Client-side + + +http + + +multiple requests using named {} globs in URL + + +"%HOSTIP:%HTTPPORT/{%LTtest%GT%TESTNUMBER,%TESTNUMBER0002,%TESTNUMBER0003}" -o "%LOGDIR/dump-#%LTtest%GT" + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0003 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +moo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +foo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +hoo + + + + diff --git a/tests/data/test2409 b/tests/data/test2409 new file mode 100644 index 000000000000..4c9e9c57a175 --- /dev/null +++ b/tests/data/test2409 @@ -0,0 +1,92 @@ + + + + +HTTP +HTTP GET +globbing +{} list + + +# Server-side + + +HTTP/1.1 200 swsbounce +Funny-head: yesyes +Content-Length: 4 + +moo + + +HTTP/1.1 200 swsbounce +Funny-head: yesyes +Content-Length: 4 + +foo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +hoo + + + +# Client-side + + +http + + +multiple requests using named [] globs in URL + + +"%HOSTIP:%HTTPPORT/hello[%LTtest%GT7-9]" -o "%LOGDIR/dump-#%LTtest%GT" + + + +# Verify data after the test has been "shot" + + +GET /hello7 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /hello8 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /hello9 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + +HTTP/1.1 200 swsbounce +Funny-head: yesyes +Content-Length: 4 + +moo + + +HTTP/1.1 200 swsbounce +Funny-head: yesyes +Content-Length: 4 + +foo + + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 4 + +hoo + + + + diff --git a/tests/data/test2410 b/tests/data/test2410 new file mode 100644 index 000000000000..ce83263e532d --- /dev/null +++ b/tests/data/test2410 @@ -0,0 +1,34 @@ + + + + +HTTP +HTTP GET +globbing +{} list + + +# Server-side + +# Client-side + + +duplicate named glob + + +"https://dummy.example/{%LTtest%GTA,B}{%LTtest%GTC,D}" -o "%LOGDIR/dump" + + + +# Verify data after the test has been "shot" + + +curl: (3) Duplicate glob name in position 40: +https://dummy.example/{%LTtest%GTA,B}{%LTtest%GTC,D} + ^ + + +3 + + + diff --git a/tests/data/test2411 b/tests/data/test2411 new file mode 100644 index 000000000000..5f45ac6ad8cf --- /dev/null +++ b/tests/data/test2411 @@ -0,0 +1,37 @@ + + + + +HTTP +HTTP GET +globbing +{} list + + +# Server-side + +# Client-side + + +http + + +reference a named glob not set + + +"%HOSTIP:%HTTPPORT/{%LTtest%GTA,B}{%LTmoo%GTC,D}" -o "somewhere/#%LTfoo%GT" + + + +# Verify data after the test has been "shot" + + +curl: (43) no glob exists with this name in position 16: +somewhere/#%LTfoo%GT + ^ + + +43 + + + diff --git a/tests/data/test2412 b/tests/data/test2412 new file mode 100644 index 000000000000..e0320e2ce4b6 --- /dev/null +++ b/tests/data/test2412 @@ -0,0 +1,50 @@ + + + + +multi + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6007 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- +%repeat[1000 x foobar]% + + + +# Client-side + + +wakeup + + +http + + +lib%TESTNUMBER + + +checking curl_multi_fdset on nothing to do + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/data/test2413 b/tests/data/test2413 new file mode 100644 index 000000000000..0b0e8a08aacc --- /dev/null +++ b/tests/data/test2413 @@ -0,0 +1,19 @@ + + + + +unittest +Curl_peer + + + +# Client-side + + +unittest + + +Curl_peer unit tests + + + diff --git a/tests/data/test324 b/tests/data/test2414 similarity index 54% rename from tests/data/test324 rename to tests/data/test2414 index a91301fe1d8a..197beffe8f9c 100644 --- a/tests/data/test324 +++ b/tests/data/test2414 @@ -2,33 +2,29 @@ -HTTPS -TLS-SRP -FAILURE +multi +wakeup # Client-side - -httptls+srp - -TLS-SRP +wakeup + + + +lib%TESTNUMBER + -TLS-SRP with server cert checking +wakeup, perform and poll needs to exit - # no --insecure ---tlsauthtype SRP --tlsuser jsmith --tlspassword abc https://%HOSTIP:%HTTPTLSPORT/want/fails + # Verify data after the test has been "shot" - -60 - - diff --git a/tests/data/test2500 b/tests/data/test2500 index 2478245a8cc9..2abed30416f2 100644 --- a/tests/data/test2500 +++ b/tests/data/test2500 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP/3 +--resolve diff --git a/tests/data/test2503 b/tests/data/test2503 index 5ade56188553..934798d33627 100644 --- a/tests/data/test2503 +++ b/tests/data/test2503 @@ -5,6 +5,7 @@ HTTP HTTP/3 HTTPS +--resolve -w %header diff --git a/tests/data/test2504 b/tests/data/test2504 index 8cec1c8210f2..7bb4a81ed7e6 100644 --- a/tests/data/test2504 +++ b/tests/data/test2504 @@ -34,6 +34,9 @@ custom Host with cookie, handle reuse, no custom Host: http://%HOSTIP:%HTTPPORT + +cookies + # Verify data after the test has been "shot" diff --git a/tests/data/test256 b/tests/data/test256 index 3bf44c5770f9..cc4d7e209dc4 100644 --- a/tests/data/test256 +++ b/tests/data/test256 @@ -6,7 +6,6 @@ HTTP HTTP GET HTTP proxy Resume -FAILURE # Server-side diff --git a/tests/data/test258 b/tests/data/test258 index 25a2a8f596f1..7471c7072ef7 100644 --- a/tests/data/test258 +++ b/tests/data/test258 @@ -64,7 +64,7 @@ proxy digest -HTTP POST multipart without Expect: header using proxy anyauth (Digest) +HTTP multipart POST without Expect: using proxy anyauth (Digest) -x http://%HOSTIP:%HTTPPORT http://remotehost:54321/we/want/%TESTNUMBER -F name=daniel -F tool=curl -F file=@%LOGDIR/test%TESTNUMBER.txt -H "Expect:" -U uuuser:pppassword --proxy-anyauth diff --git a/tests/data/test2606 b/tests/data/test2606 new file mode 100644 index 000000000000..1ce80013a717 --- /dev/null +++ b/tests/data/test2606 @@ -0,0 +1,18 @@ + + + + +unittest + + + +# Client-side + + +unittest + + +Curl_conn_adjust_pollset unit test + + + diff --git a/tests/data/test2713 b/tests/data/test2713 index 6ec722ceae0c..6c50bc72fe06 100644 --- a/tests/data/test2713 +++ b/tests/data/test2713 @@ -34,7 +34,7 @@ upgrade # Full list of frames: see 'verify.stdout' below -# A 126 byte long PING +# A 126-byte-long PING HTTP/1.1 101 Switching to WebSockets Server: server/%TESTNUMBER diff --git a/tests/data/test2714 b/tests/data/test2714 index 60c5c6daf739..654004966f31 100644 --- a/tests/data/test2714 +++ b/tests/data/test2714 @@ -34,7 +34,7 @@ upgrade # Full list of frames: see 'verify.stdout' below -# A 126 byte long PONG +# A 126-byte-long PONG HTTP/1.1 101 Switching to WebSockets Server: server/%TESTNUMBER diff --git a/tests/data/test2715 b/tests/data/test2715 index 68ea8647abe6..4cf4febb3532 100644 --- a/tests/data/test2715 +++ b/tests/data/test2715 @@ -34,7 +34,7 @@ upgrade # Full list of frames: see 'verify.stdout' below -# A 126 byte long CLOSE +# A 126-byte-long CLOSE HTTP/1.1 101 Switching to WebSockets Server: server/%TESTNUMBER diff --git a/tests/data/test283 b/tests/data/test283 index 3ca5bd1e0d62..051e58c87c8b 100644 --- a/tests/data/test283 +++ b/tests/data/test283 @@ -4,7 +4,6 @@ TFTP TFTP RRQ -FAILURE diff --git a/tests/data/test284 b/tests/data/test284 index 469172a5dc96..aa48f4388cfa 100644 --- a/tests/data/test284 +++ b/tests/data/test284 @@ -11,7 +11,7 @@ TFTP RRQ A chunk of data which exactly fits into -a 512 byte TFTP block, testing a boundary +a 512-byte TFTP block, testing a boundary condition in the TFTP receive code. 789ABCDEF 123456789ABCDEF @@ -47,7 +47,7 @@ condition in the TFTP receive code. tftp -TFTP retrieve of boundary case 512 byte file +TFTP retrieve of boundary case 512-byte file tftp://%HOSTIP:%TFTPPORT//%TESTNUMBER diff --git a/tests/data/test286 b/tests/data/test286 index 9902d160b835..cc9fd7c8f62e 100644 --- a/tests/data/test286 +++ b/tests/data/test286 @@ -13,14 +13,14 @@ TFTP WRQ tftp -TFTP send of boundary case 512 byte file +TFTP send of boundary case 512-byte file -T %LOGDIR/test%TESTNUMBER.txt tftp://%HOSTIP:%TFTPPORT// A chunk of data which exactly fits into -a 512 byte TFTP block, testing a boundary +a 512-byte TFTP block, testing a boundary condition in the TFTP transmit code. 89ABCDEF 123456789ABCDEF @@ -54,7 +54,7 @@ condition in the TFTP transmit code. A chunk of data which exactly fits into -a 512 byte TFTP block, testing a boundary +a 512-byte TFTP block, testing a boundary condition in the TFTP transmit code. 89ABCDEF 123456789ABCDEF diff --git a/tests/data/test2885 b/tests/data/test2885 new file mode 100644 index 000000000000..456224fdc51b --- /dev/null +++ b/tests/data/test2885 @@ -0,0 +1,52 @@ + + + + +HTTP +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Set-Cookie: thisis=SECRET;%TABSecure +Set-Cookie: also=notab; Secure + + + + + + +cookies + + +https + + +Cookies with TAB before 'secure' + + +https://cookie.example:%HTTPSPORT/%TESTNUMBER -c %LOGDIR/cookies.txt --resolve cookie.example:%HTTPSPORT:%HOSTIP --insecure + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: cookie.example:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +cookie.example%TABFALSE%TAB/%TABTRUE%TAB0%TABalso%TABnotab +cookie.example%TABFALSE%TAB/%TABTRUE%TAB0%TABthisis%TABSECRET + + + diff --git a/tests/data/test289 b/tests/data/test289 index cf8464b1b629..4253c1b77604 100644 --- a/tests/data/test289 +++ b/tests/data/test289 @@ -5,7 +5,6 @@ FTP STOR Resume -FAILURE diff --git a/tests/data/test29 b/tests/data/test29 index 122cfd23b73f..df67f850197f 100644 --- a/tests/data/test29 +++ b/tests/data/test29 @@ -5,7 +5,6 @@ HTTP HTTP GET timeout -FAILURE # Server-side diff --git a/tests/data/test293 b/tests/data/test293 index cc6cccb41ee1..c6f69f2e0105 100644 --- a/tests/data/test293 +++ b/tests/data/test293 @@ -5,7 +5,6 @@ HTTP HTTP GET --max-filesize -FAILURE diff --git a/tests/data/test295 b/tests/data/test295 index 1eb14475fb98..e7fbf6745c4b 100644 --- a/tests/data/test295 +++ b/tests/data/test295 @@ -6,7 +6,6 @@ FTP PASV LIST ACCT -FAILURE # Server-side diff --git a/tests/data/test30 b/tests/data/test30 index c8984105f83f..c0b5d60d431d 100644 --- a/tests/data/test30 +++ b/tests/data/test30 @@ -4,7 +4,6 @@ HTTP HTTP GET -FAILURE # Server-side diff --git a/tests/data/test3002 b/tests/data/test3002 index 8c0f063ece3c..7aa195444ad8 100644 --- a/tests/data/test3002 +++ b/tests/data/test3002 @@ -16,7 +16,7 @@ SMTP smtp -SMTP multiple and invalid (first) --mail-rcpt and --mail-rcpt-allowfails +SMTP invalid (first) --mail-rcpt and --mail-rcpt-allowfails From: different diff --git a/tests/data/test3003 b/tests/data/test3003 index cbeb24316344..66e7627290cd 100644 --- a/tests/data/test3003 +++ b/tests/data/test3003 @@ -16,7 +16,7 @@ SMTP smtp -SMTP multiple and invalid (last) --mail-rcpt and --mail-rcpt-allowfails +SMTP invalid (last) --mail-rcpt and --mail-rcpt-allowfails From: different diff --git a/tests/data/test3004 b/tests/data/test3004 index 732c47835241..ac348339ad90 100644 --- a/tests/data/test3004 +++ b/tests/data/test3004 @@ -16,7 +16,7 @@ SMTP smtp -SMTP multiple and invalid (middle) --mail-rcpt and --mail-rcpt-allowfails +SMTP invalid (middle) --mail-rcpt and --mail-rcpt-allowfails From: different diff --git a/tests/data/test3005 b/tests/data/test3005 index 6799b3136db6..7bdb503e3f83 100644 --- a/tests/data/test3005 +++ b/tests/data/test3005 @@ -16,7 +16,7 @@ SMTP smtp -SMTP multiple invalid (all but one) --mail-rcpt and --mail-rcpt-allowfails +SMTP invalid (all but one) --mail-rcpt and --mail-rcpt-allowfails From: different diff --git a/tests/data/test3006 b/tests/data/test3006 index c1583f639765..518f328e290b 100644 --- a/tests/data/test3006 +++ b/tests/data/test3006 @@ -16,7 +16,7 @@ SMTP smtp -SMTP with multiple invalid (all) --mail-rcpt and --mail-rcpt-allowfails +SMTP invalid (all) --mail-rcpt and --mail-rcpt-allowfails From: different diff --git a/tests/data/test3016 b/tests/data/test3016 index 2d530f55c026..c710e3976283 100644 --- a/tests/data/test3016 +++ b/tests/data/test3016 @@ -2,8 +2,6 @@ -HTTP -HTTP GET FILE @@ -14,7 +12,7 @@ FILE file -GET a directory using file:// +Get current directory using file:// diff --git a/tests/data/test302 b/tests/data/test302 index 1bae0c76410f..3c6eb09851eb 100644 --- a/tests/data/test302 +++ b/tests/data/test302 @@ -6,7 +6,6 @@ HTTPS HTTP GET HTTP CONNECT HTTP proxy -FAILURE diff --git a/tests/data/test3021 b/tests/data/test3021 index b7fe479cb2bc..1aa973a68843 100644 --- a/tests/data/test3021 +++ b/tests/data/test3021 @@ -17,10 +17,6 @@ test # Client-side -# so far only the libssh2 backend supports SHA256 - -libssh2 - sftp diff --git a/tests/data/test3022 b/tests/data/test3022 index 057242b0f513..6d692a817ef3 100644 --- a/tests/data/test3022 +++ b/tests/data/test3022 @@ -17,10 +17,6 @@ test # Client-side -# so far only the libssh2 backend supports SHA256 - -libssh2 - scp diff --git a/tests/data/test3023 b/tests/data/test3023 index e6ffc6a00559..7aed3bd4f9f0 100644 --- a/tests/data/test3023 +++ b/tests/data/test3023 @@ -31,7 +31,7 @@ local-http https test-localhost-san-first.pem -HTTPS localhost, first subaltname matches, CN does not match (Schannel) +HTTPS localhost, first SAN matches, CN does not match (Schannel) # This test is pointless if we are not using the Schannel backend diff --git a/tests/data/test303 b/tests/data/test303 index 44060b496e79..51dc471e61a1 100644 --- a/tests/data/test303 +++ b/tests/data/test303 @@ -5,7 +5,6 @@ HTTPS HTTP GET timeout -FAILURE diff --git a/tests/data/test3031 b/tests/data/test3031 index bf41ac78dbf7..68ffb466919d 100644 --- a/tests/data/test3031 +++ b/tests/data/test3031 @@ -32,7 +32,7 @@ http http ---output-dir with --create-dirs +--dump-header with --create-dirs http://%HOSTIP:%HTTPPORT/this/is/the/%TESTNUMBER --dump-header %PWD/%LOGDIR/tmp/out.txt --create-dirs diff --git a/tests/data/test305 b/tests/data/test305 index 57d33938e118..1acefe29be4c 100644 --- a/tests/data/test305 +++ b/tests/data/test305 @@ -4,7 +4,6 @@ HTTPS HTTP GET -FAILURE diff --git a/tests/data/test306 b/tests/data/test306 index 5ef7a59010bc..b0974a77a36f 100644 --- a/tests/data/test306 +++ b/tests/data/test306 @@ -10,7 +10,7 @@ HTTP GET # Server-side -No headers at all, just data swsclose +No headers at all, data swsclose Let's get diff --git a/tests/data/test308 b/tests/data/test308 index 60274f489d75..593976b4c239 100644 --- a/tests/data/test308 +++ b/tests/data/test308 @@ -4,7 +4,6 @@ HTTPS HTTP GET -FAILURE diff --git a/tests/data/test31 b/tests/data/test31 index 92cc58ca2efa..c885b7c9faed 100644 --- a/tests/data/test31 +++ b/tests/data/test31 @@ -6,6 +6,7 @@ HTTP HTTP GET cookies cookiejar +--resolve # Server-side diff --git a/tests/data/test310 b/tests/data/test310 index 558f137d8ac4..12a12a4f1503 100644 --- a/tests/data/test310 +++ b/tests/data/test310 @@ -31,7 +31,7 @@ local-http https test-localhost.pem -simple HTTPS GET +HTTPS GET with specified CA cert bundle -4 --cacert %CERTDIR/certs/test-ca.crt https://localhost:%HTTPSPORT/%TESTNUMBER diff --git a/tests/data/test3106 b/tests/data/test3106 new file mode 100644 index 000000000000..971107e0fa87 --- /dev/null +++ b/tests/data/test3106 @@ -0,0 +1,77 @@ + + + + +HTTP +HTTPS +HTTP proxy +HTTP Basic auth +followlocation + + + +# Server-side + + +HTTP/1.1 200 OK + + + + +HTTP/1.1 302 Found +Location: http://example.com:%HTTPSPORT/%TESTNUMBER0002 +Content-Length: 0 + + + + +HTTP/1.1 200 OK +Content-Length: 2 + +OK + + + +# Client-side + + +SSL +proxy + + +https +http-proxy + + +HTTPS to HTTP redirect on same host and port without auth + + +--insecure --location --user user:secret --proxy %HOSTIP:%PROXYPORT https://example.com:%HTTPSPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +CONNECT example.com:%HTTPSPORT HTTP/1.1 +Host: example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +GET http://example.com:%HTTPSPORT/%TESTNUMBER0002 HTTP/1.1 +Host: example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + +GET /%TESTNUMBER HTTP/1.1 +Host: example.com:%HTTPSPORT +Authorization: Basic %b64[user:secret]b64% +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test311 b/tests/data/test311 index 292aa4d859c9..848cbedb45de 100644 --- a/tests/data/test311 +++ b/tests/data/test311 @@ -17,6 +17,7 @@ PEM certificate SSL local-http +!wolfssl-5.9.2 https test-localhost0h.pem diff --git a/tests/data/test315 b/tests/data/test315 index 62499204a2ef..31d812cd05f9 100644 --- a/tests/data/test315 +++ b/tests/data/test315 @@ -5,7 +5,6 @@ HTTP HTTP GET compressed -FAILURE # Server-side diff --git a/tests/data/test316 b/tests/data/test316 index 0780acce1ae5..1adf9996047f 100644 --- a/tests/data/test316 +++ b/tests/data/test316 @@ -46,7 +46,7 @@ brotli http -HTTP GET brotli compressed content of size more than CURL_MAX_WRITE_SIZE +HTTP brotli compressed content of size more than CURL_MAX_WRITE_SIZE http://%HOSTIP:%HTTPPORT/%TESTNUMBER --compressed diff --git a/tests/data/test320 b/tests/data/test320 index b9a8cce9a51d..645299d5dea0 100644 --- a/tests/data/test320 +++ b/tests/data/test320 @@ -2,54 +2,58 @@ -HTTPS -HTTP GET -TLS-SRP +HTTP +cookies # Server-side - -%includetext %SRCDIR/data/data%TESTNUMBER.html% + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: something-close, close-something, close +Content-Type: text/html +Funny-head: yesyes + +-foo- # Client-side - -httptls+srp - -TLS-SRP +cookies +PSL + +http + -simple TLS-SRP HTTPS GET, check user in response +Load cookies from file for localhost ---insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword abc -A curl-test-suite https://%HOSTIP:%HTTPTLSPORT +http://localhost:%HTTPPORT/%TESTNUMBER -b %LOGDIR/cookies.txt --resolve localhost:%HTTPPORT:%HOSTIP + +localhost%TABTRUE%TAB/%TABFALSE%TAB0%TABgood%TABallowed + # Verify data after the test has been "shot" - -HTTP/1.0 200 OK -Content-type: text/html - -FINE -User-Agent: curl-test-suite + +GET /%TESTNUMBER HTTP/1.1 +Host: localhost:%HTTPPORT +User-Agent: curl/%VERSION Accept: */* +Cookie: good=allowed - - -s/^%LTp%GTConnected as user 'jsmith'.*/FINE/ -s/Protocol version:.*[0-9]// -s/GNUTLS/GnuTLS/ -s/(AES[-_])\d\d\d([-_]CBC)/$1NNN$2/ -s/^%LT.*\n// -s/^\n// - + - diff --git a/tests/data/test3201 b/tests/data/test3201 index 7e051a216729..cf98f4f0a394 100644 --- a/tests/data/test3201 +++ b/tests/data/test3201 @@ -33,7 +33,7 @@ Funny-head: barkbark http -HTTP GET when PROXY Protocol enabled and spoofed client IP +HTTP GET when PROXY Protocol enabled and spoofed client IPv4 http://%HOSTIP:%HTTPPORT/%TESTNUMBER --haproxy-clientip "192.168.1.1" -H "Testno: %TESTNUMBER" diff --git a/tests/data/test3203 b/tests/data/test3203 index 6a548140604a..8b52cf41ccc6 100644 --- a/tests/data/test3203 +++ b/tests/data/test3203 @@ -2,8 +2,6 @@ -HTTP -HTTP GET FILE @@ -14,18 +12,20 @@ FILE file -GET a directory using file:// +Get a directory using file:// - - -!win32 - file://localhost%FILE_PWD/%LOGDIR/test%TESTNUMBER.dir/ +%if Unicode + +Contents of file are irrelevant + +%else Contents of file are irrelevant +%endif # Verify data after the test has been "shot" @@ -34,7 +34,11 @@ Contents of file are irrelevant 0 +%if Unicode +%hex[%E6%BC%A2%E5%AD%97%2D%C3%A4]hex%.txt +%else dir-listing-test.txt +%endif diff --git a/tests/data/test3207 b/tests/data/test3207 index 521a1047f06c..ba15b1a47975 100644 --- a/tests/data/test3207 +++ b/tests/data/test3207 @@ -32,7 +32,7 @@ OpenSSL https -concurrent HTTPS GET using shared ssl session cache +concurrent HTTPS GET using shared SSL session cache lib%TESTNUMBER diff --git a/tests/data/test321 b/tests/data/test321 index cdc18028b9a0..47aa48d66ee8 100644 --- a/tests/data/test321 +++ b/tests/data/test321 @@ -2,33 +2,49 @@ -HTTPS -TLS-SRP -FAILURE +etags +# Server-side + + +HTTP/1.1 200 OK +ETag: "remote" +Content-Length: 0 + + + + # Client-side -httptls+srp +http - -TLS-SRP - -TLS-SRP with bad username and password +--etag-save to stdout append to file - ---insecure --tlsauthtype SRP --tlsuser baduser --tlspassword badpass https://%HOSTIP:%HTTPTLSPORT + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --etag-save - >> %LOGDIR/out%TESTNUMBER + + +initial content + # Verify data after the test has been "shot" - -35 - - + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + +initial content +"remote" + + diff --git a/tests/data/test3211 b/tests/data/test3211 index 826c961a4d63..1c2643abc143 100644 --- a/tests/data/test3211 +++ b/tests/data/test3211 @@ -13,7 +13,7 @@ uint_bset unittest -uint_bset unit tests +uint_bset and uint_hashset unit tests diff --git a/tests/data/test3212 b/tests/data/test3212 index 826c961a4d63..3c5cd37e7f51 100644 --- a/tests/data/test3212 +++ b/tests/data/test3212 @@ -3,7 +3,7 @@ unittest -uint_bset +uint32_tbl @@ -13,7 +13,7 @@ uint_bset unittest -uint_bset unit tests +uint32_tbl unit tests diff --git a/tests/data/test3217 b/tests/data/test3217 index df7c890eba72..4749779b6753 100644 --- a/tests/data/test3217 +++ b/tests/data/test3217 @@ -5,7 +5,6 @@ FTP PASV RETR -FAILURE # Server-side diff --git a/tests/data/test3218 b/tests/data/test3218 index 0c98b202eca5..a56b9a2b98e6 100644 --- a/tests/data/test3218 +++ b/tests/data/test3218 @@ -5,7 +5,6 @@ FTP PASV RETR -FAILURE # Server-side diff --git a/tests/data/test322 b/tests/data/test322 index c0581088ae6c..3f2ec9126144 100644 --- a/tests/data/test322 +++ b/tests/data/test322 @@ -2,33 +2,34 @@ -HTTPS -TLS-SRP -FAILURE +--proto -# Client-side - -httptls+srp - -TLS-SRP +http -TLS-SRP with bad password +--proto with multiple modifiers ---insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword badpass https://%HOSTIP:%HTTPTLSPORT +http://localhost/wont-get-this --proto +-http -# Verify data after the test has been "shot" -35 +2 + +curl: unrecognized protocol '-http' +curl: option --proto: is badly used here +%if manual +curl: try 'curl --help' or 'curl --manual' for more information +%else +curl: try 'curl --help' for more information +%endif + - diff --git a/tests/data/test3220 b/tests/data/test3220 index ba8dec8f923f..ca8bfaa134b0 100644 --- a/tests/data/test3220 +++ b/tests/data/test3220 @@ -33,7 +33,7 @@ Funny-head: barkbark http -HTTP GET when PROXY Protocol enabled and spoofed client IP +HTTP GET when PROXY Protocol enabled and spoofed client IPv6 http://%HOSTIP:%HTTPPORT/%TESTNUMBER --haproxy-clientip "2a04:4e42::347" -H "Testno: %TESTNUMBER" diff --git a/tests/data/test3221 b/tests/data/test3221 new file mode 100644 index 000000000000..321213ab0919 --- /dev/null +++ b/tests/data/test3221 @@ -0,0 +1,74 @@ + + + + +HTTP +HTTP GET +digest + + + +# Server-side + + +HTTP/1.1 401 Authorization Required +WWW-Authenticate: Digest realm="testrealm%0a%0d", nonce="1053604145" +Content-Length: 4 + +hej + + + +HTTP/1.1 200 OK +Content-Length: 23 + +This IS the real page! + + + +HTTP/1.1 401 Authorization Required +WWW-Authenticate: Digest realm="testrealm%0a%0d", nonce="1053604145" +Content-Length: 4 + +HTTP/1.1 200 OK +Content-Length: 23 + +This IS the real page! + + + +# Client-side + + +http + + +!SSPI +crypto +digest + + +HTTP Digest with CRLF in username + + +http://hello%0a%0d:there@%HOSTIP:%HTTPPORT/ --digest + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Digest username="hello%0A%0D", realm="testrealm%0a%0d", nonce="1053604145", uri="/", response="64e5ae1b90f05309847ac483c1094284" +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test3222 b/tests/data/test3222 new file mode 100644 index 000000000000..cf6caa157f80 --- /dev/null +++ b/tests/data/test3222 @@ -0,0 +1,57 @@ + + + + +HTTP +aws-sigv4 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + +# Client-side + + +http + + +Debug +aws + + +aws-sigv4 with CRLF in username + + +"http://user%0d%0a:secret@fake.fake.fake:8000/" --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: fake.fake.fake:8000 +Authorization: AWS4-HMAC-SHA256 Credential=user%0D%0A/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=e5747e9555c0e96f1067cc4bf9f6055e72a185178e5dd0c2909279ec1d66360b +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test3223 b/tests/data/test3223 new file mode 100644 index 000000000000..5748d6d07aa3 --- /dev/null +++ b/tests/data/test3223 @@ -0,0 +1,67 @@ + + +# A partial/aborted HTTP/1.1 response marks the transfer premature. +# multi_conn_should_close() then closes non-multiplexed connections so they +# cannot be reused by a later transfer in the same process. + + +HTTP +HTTP GET +CURLE_PARTIAL_FILE +connection reuse +--next + + + + +HTTP/1.1 200 OK swsclose +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 100 +Content-Type: text/plain + +0123456789 + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + +connection-monitor + + + + +http + + +HTTP connection not reused after partial/aborted response + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/3223-1 -w "connects=%{num_connects}/" --next http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 -o %LOGDIR/3223-2 -w "connects=%{num_connects}/" + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +[DISCONNECT] +GET /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +[DISCONNECT] + +# Second transfer must open a new connection (cannot reuse after premature). + +connects=1/connects=1/ + + + diff --git a/tests/data/test3224 b/tests/data/test3224 new file mode 100644 index 000000000000..4e62303aeb95 --- /dev/null +++ b/tests/data/test3224 @@ -0,0 +1,69 @@ + + +# HTTP has PROTOPT_CREDSPERREQUEST: Basic credentials are attached to the +# request, not to the connection for reuse matching. Different -u values +# therefore still reuse the idle connection (see also test1134). Protocols +# without CREDSPERREQUEST (and NTLM/Negotiate mid-handshake) refuse reuse. +# This test pins the HTTP Basic behavior with an explicit num_connects check. + + +HTTP +HTTP GET +HTTP Basic auth +connection reuse +--next + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +A1 + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +B2 + + +connection-monitor + + + + +http + + +HTTP connection reused across different Basic auth credentials + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u user1:password1 -o %LOGDIR/3224-1 -w "connects=%{num_connects}/" --next http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 -u user2:password2 -o %LOGDIR/3224-2 -w "connects=%{num_connects}/" + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[user1:password1]b64% +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[user2:password2]b64% +User-Agent: curl/%VERSION +Accept: */* + +[DISCONNECT] + + +connects=1/connects=0/ + + + diff --git a/tests/data/test3225 b/tests/data/test3225 new file mode 100644 index 000000000000..a853f40fc86e --- /dev/null +++ b/tests/data/test3225 @@ -0,0 +1,69 @@ + + +# url_match_destination() / Curl_peer_same_destination() require matching +# origin hostname and port. Different Host names that resolve to the same +# address must not share a connection. Both connections may stay in the +# pool until process exit, so the server may see two DISCONNECT events only +# at the end; num_connects proves a new connection was opened. + + +HTTP +HTTP GET +--resolve +connection reuse +--next + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +H1 + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +H2 + + +connection-monitor + + + + +http + + +HTTP connection not reused when target host changes + + +http://host-a.example.test:%HTTPPORT/%TESTNUMBER --resolve host-a.example.test:%HTTPPORT:%HOSTIP -o %LOGDIR/3225-1 -w "connects=%{num_connects}/" --next http://host-b.example.test:%HTTPPORT/%TESTNUMBER0001 --resolve host-b.example.test:%HTTPPORT:%HOSTIP -o %LOGDIR/3225-2 -w "connects=%{num_connects}/" + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: host-a.example.test:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0001 HTTP/1.1 +Host: host-b.example.test:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +[DISCONNECT] +[DISCONNECT] + +# Same IP via --resolve, different Host names => new connection each time. + +connects=1/connects=1/ + + + diff --git a/tests/data/test3226 b/tests/data/test3226 new file mode 100644 index 000000000000..92fa2421ef89 --- /dev/null +++ b/tests/data/test3226 @@ -0,0 +1,63 @@ + + +# Positive control: same host reuses the idle connection +# (second transfer reports num_connects=0). + + +HTTP +HTTP GET +connection reuse +--next + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Content-Type: text/plain + +OK + + +connection-monitor + + + + +http + + +HTTP connection is reused for same host + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/3226-1 -w "connects=%{num_connects}/" --next http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 -o %LOGDIR/3226-2 -w "connects=%{num_connects}/" + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +[DISCONNECT] + + +connects=1/connects=0/ + + + diff --git a/tests/data/test3227 b/tests/data/test3227 new file mode 100644 index 000000000000..cd9d74b30f8d --- /dev/null +++ b/tests/data/test3227 @@ -0,0 +1,20 @@ + + + + +unittest +mime +SMTP + + + +# Client-side + + +unittest + + +reject CR and LF in mail mime part name and filename + + + diff --git a/tests/data/test3228 b/tests/data/test3228 new file mode 100644 index 000000000000..c0ad8ab5ebad --- /dev/null +++ b/tests/data/test3228 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP GET +HTTP added headers + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close + +-foo- + + + +# Client-side + + +http + + +Send custom request headers larger than the response header limit + + +header "X-Large: %repeat[307200 x x]%" +header "Connection: %repeat[307200 x x]%" +header "%repeat[307201 x X]%;" + + +-K - http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +X-Large: %repeat[307200 x x]% +%repeat[307201 x X]%: +Connection: %repeat[307200 x x]% + + + +Maximum allocated: 4000000 + + + diff --git a/tests/data/test3229 b/tests/data/test3229 new file mode 100644 index 000000000000..02964053f353 --- /dev/null +++ b/tests/data/test3229 @@ -0,0 +1,37 @@ + + + + +HTTP +HTTP GET +HTTP added headers + + + +# Client-side + + +http + + +Reject a custom request header larger than the request buffer + + +header "X-Large: %repeat[1048576 x x]%" + + +-K - http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + +# 100 == CURLE_TOO_LARGE + +100 + + +Maximum allocated: 6000000 + + + diff --git a/tests/data/test3302 b/tests/data/test3302 new file mode 100644 index 000000000000..6dc504f6cf96 --- /dev/null +++ b/tests/data/test3302 @@ -0,0 +1,19 @@ + + + + +unittest +CURLOPT_GSSAPI_DELEGATION + + + +# Client-side + + +unittest + + +CURLOPT_GSSAPI_DELEGATION stores flags in data->set + + + diff --git a/tests/data/test3303 b/tests/data/test3303 new file mode 100644 index 000000000000..0bd9e8a7f043 --- /dev/null +++ b/tests/data/test3303 @@ -0,0 +1,20 @@ + + + + +unittest +TLS +mTLS + + + +# Client-side + + +unittest + + +conn-reuse distinguishes mTLS key, cert_type, key_type and key_passwd + + + diff --git a/tests/data/test3304 b/tests/data/test3304 new file mode 100644 index 000000000000..738fd5664f6c --- /dev/null +++ b/tests/data/test3304 @@ -0,0 +1,20 @@ + + + + +unittest +TLS +mTLS + + + +# Client-side + + +unittest + + +TLS session cache peer key uses mTLS key, key_type and cert_type + + + diff --git a/tests/data/test3305 b/tests/data/test3305 new file mode 100644 index 000000000000..7a6d63beeeca --- /dev/null +++ b/tests/data/test3305 @@ -0,0 +1,84 @@ + + + + +HTTP +cookies +--resolve + + + +# Server-side + + +HTTP/1.1 301 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 6 +Set-Cookie: this=secret; domain=example.com; secure; path=/ +Set-Cookie: that=secret; domain=www.example.com; secure; path=/ +Set-Cookie: second=fine; + +-foo- + + +# The cookie 'this' should not be accepted since it would be the same as already +# set with a 'secure' flag. +# The cookie 'second' is however not secure so it is fair game to override + +HTTP/1.1 301 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 6 +Set-Cookie: this=open; path=/ +Set-Cookie: that=open; path=/; domain=example.com +Set-Cookie: second=override + +-foo- + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 + +-foo- + + + +# Client-side + + +http +https + + +same-name cookie over HTTPS and HTTP with different domains + + +https://www.example.com:%HTTPSPORT/ http://www.example.com:%HTTPPORT/%TESTNUMBER0002 https://www.example.com:%HTTPSPORT/%TESTNUMBER0003 --insecure -c %LOGDIR/cookie%TESTNUMBER --resolve www.example.com:%HTTPSPORT:%HOSTIP --resolve www.example.com:%HTTPPORT:%HOSTIP + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: www.example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: www.example.com:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: second=fine + +GET /%TESTNUMBER0003 HTTP/1.1 +Host: www.example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: second=override; that=secret; this=secret + + + + diff --git a/tests/data/test3306 b/tests/data/test3306 new file mode 100644 index 000000000000..fe2082f6cf5a --- /dev/null +++ b/tests/data/test3306 @@ -0,0 +1,23 @@ + + + + +unittest +threads + + + +# Client-side + + +unittest +Debug + + +CURL_DBG_THRDPOOL_FAIL_STARTS=5 + + +thrdqueue retries failed thread starts on receive + + + diff --git a/tests/data/test332 b/tests/data/test332 index b7a7e6000df3..e968c26fe4f8 100644 --- a/tests/data/test332 +++ b/tests/data/test332 @@ -4,7 +4,6 @@ TFTP TFTP RRQ -FAILURE diff --git a/tests/data/test3400 b/tests/data/test3400 new file mode 100644 index 000000000000..12d014bffd21 --- /dev/null +++ b/tests/data/test3400 @@ -0,0 +1,19 @@ + + + + +unittest +capsule + + + + + +unittest + + +capsule protocol encode and decode unit tests + + + + diff --git a/tests/data/test3401 b/tests/data/test3401 new file mode 100644 index 000000000000..0c88738ec206 --- /dev/null +++ b/tests/data/test3401 @@ -0,0 +1,55 @@ + + + + +HTTP +HTTPS proxy +cookies +Secure + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 4 + +foo + + + +# Client-side + + +http +https-proxy + + +HTTPS-proxy +cookies + + +HTTP via HTTPS proxy does not send Secure cookies + + +-x https://%HOSTIP:%HTTPSPROXYPORT --proxy-insecure -b %LOGDIR/jar%TESTNUMBER.txt http://test.example/%TESTNUMBER + + +# Netscape HTTP Cookie File +test.example FALSE / TRUE 9999999999 session secret + + + +# Verify data after the test has been "shot" + + +GET http://test.example/%TESTNUMBER HTTP/1.1 +Host: test.example +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test341 b/tests/data/test341 index 8aea4fbb7ce4..0d204a4f1310 100644 --- a/tests/data/test341 +++ b/tests/data/test341 @@ -35,7 +35,7 @@ chunky-trailer: header data http -A non existing file with --etag-compare is just a blank +A non existing file with --etag-compare is a blank http://%HOSTIP:%HTTPPORT/%TESTNUMBER --etag-compare %LOGDIR/etag%TESTNUMBER diff --git a/tests/data/test345 b/tests/data/test345 index d87cf0696c33..2836773889a6 100644 --- a/tests/data/test345 +++ b/tests/data/test345 @@ -30,7 +30,7 @@ Funny-head: yesyes http -Both --etag-compare and -save store new Etag using one pre-existing file +Both --etag-compare and -save store new Etag in pre-existing file "21025-dc7-39462498" diff --git a/tests/data/test357 b/tests/data/test357 index a763fd22556d..c6b11bd83c75 100644 --- a/tests/data/test357 +++ b/tests/data/test357 @@ -48,6 +48,9 @@ no-expect http + +!torture + HTTP PUT with Expect: 100-continue and 417 response diff --git a/tests/data/test358 b/tests/data/test358 index ac7f3d57e421..99035cc049f8 100644 --- a/tests/data/test358 +++ b/tests/data/test358 @@ -36,7 +36,7 @@ http http/2 -HTTPS GET translated by alt-svc lookup to HTTP/2 GET +HTTP GET translated by alt-svc lookup to HTTP/2 GET # make Debug-curl accept Alt-Svc over plain HTTP diff --git a/tests/data/test36 b/tests/data/test36 index ea97f6160b99..a99e560bdcde 100644 --- a/tests/data/test36 +++ b/tests/data/test36 @@ -5,7 +5,6 @@ HTTP HTTP GET chunked Transfer-Encoding -FAILURE # Server-side diff --git a/tests/data/test37 b/tests/data/test37 index cf455095475e..46f994f1e787 100644 --- a/tests/data/test37 +++ b/tests/data/test37 @@ -4,7 +4,6 @@ HTTP HTTP GET -FAILURE # Server-side diff --git a/tests/data/test38 b/tests/data/test38 index 842b0e535e7d..05afab2829f8 100644 --- a/tests/data/test38 +++ b/tests/data/test38 @@ -5,7 +5,6 @@ HTTP HTTP GET Resume -FAILURE # Server-side diff --git a/tests/data/test387 b/tests/data/test387 index ebdd4b5d116f..5872cd692e11 100644 --- a/tests/data/test387 +++ b/tests/data/test387 @@ -51,7 +51,7 @@ Connection: TE 61 -curl: (61) Reject response due to more than 5 content encodings +curl: (61) Reject response exceeding limit of 5 transfer encodings diff --git a/tests/data/test388 b/tests/data/test388 index 7994eaf8ab05..b9f33e2d2575 100644 --- a/tests/data/test388 +++ b/tests/data/test388 @@ -56,7 +56,7 @@ Content-Length: 26 This is not the real page -# The second request to the 1002 section will bounce this one back instead +# The second request to the 1002 section bounces this one back instead # thanks to the swsbounce keyword up there HTTP/1.1 200 OK diff --git a/tests/data/test390 b/tests/data/test390 index 7a5faaf8361b..0489d9be8662 100644 --- a/tests/data/test390 +++ b/tests/data/test390 @@ -3,6 +3,7 @@ HTTP +FILE FTP parallel diff --git a/tests/data/test393 b/tests/data/test393 index fbf07d7c601d..3353da688a37 100644 --- a/tests/data/test393 +++ b/tests/data/test393 @@ -5,7 +5,6 @@ HTTP HTTP GET --max-filesize -FAILURE diff --git a/tests/data/test394 b/tests/data/test394 index 3a51d5a9c594..c3cbc691eb3c 100644 --- a/tests/data/test394 +++ b/tests/data/test394 @@ -4,7 +4,6 @@ HTTP HTTP GET -FAILURE diff --git a/tests/data/test399 b/tests/data/test399 index cc4d0fc47428..6984fbd85f3c 100644 --- a/tests/data/test399 +++ b/tests/data/test399 @@ -12,7 +12,7 @@ URL http -65536 bytes long hostname in URL +65536-byte-long hostname in URL url = http://%repeat[65536 x a]%/399 diff --git a/tests/data/test402 b/tests/data/test402 index 5226d6c731b2..ab036b27d645 100644 --- a/tests/data/test402 +++ b/tests/data/test402 @@ -4,7 +4,6 @@ FTP FTPS -FAILURE diff --git a/tests/data/test403 b/tests/data/test403 index bbf162d22c71..6bf16a62305d 100644 --- a/tests/data/test403 +++ b/tests/data/test403 @@ -7,7 +7,6 @@ FTPS PASV LIST CCC -FAILURE # Server-side diff --git a/tests/data/test404 b/tests/data/test404 index 96f3eaa9c09e..1e69b881f7ea 100644 --- a/tests/data/test404 +++ b/tests/data/test404 @@ -4,7 +4,6 @@ FTP FTPS -FAILURE diff --git a/tests/data/test405 b/tests/data/test405 index 73a6dcf430ad..e67dcf16d4b0 100644 --- a/tests/data/test405 +++ b/tests/data/test405 @@ -4,7 +4,6 @@ FTP FTPS -FAILURE diff --git a/tests/data/test409 b/tests/data/test409 index 5660b5d6ca84..f9a7131ce23c 100644 --- a/tests/data/test409 +++ b/tests/data/test409 @@ -2,57 +2,59 @@ -FTP -FTPS -EPSV -STOR +HTTP +cookies +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6 +Connection: something-close, close-something, close +Content-Type: text/html +Funny-head: yesyes + +-foo- + + + # Client-side -SSL +cookies +PSL -ftps +http -FTPS PASV upload file +Load cookies from file where some are PSL domains - -data - to - see -that FTP -works - so does it? - ---insecure --ftp-ssl-control ftps://%HOSTIP:%FTPSPORT/%TESTNUMBER -T %LOGDIR/test%TESTNUMBER.txt +http://foo.co.uk:%HTTPPORT/%TESTNUMBER -b %LOGDIR/cookies.txt --resolve foo.co.uk:%HTTPPORT:%HOSTIP + +.co.uk%TABTRUE%TAB/%TABFALSE%TAB0%TABbad%TABnot-allowed +foo.co.uk%TABTRUE%TAB/%TABFALSE%TAB0%TABgood%TABallowed + # Verify data after the test has been "shot" - -data - to - see -that FTP -works - so does it? - - -USER anonymous -PASS ftp@example.com -PBSZ 0 -PROT C -PWD -EPSV -TYPE I -STOR %TESTNUMBER -QUIT + +GET /%TESTNUMBER HTTP/1.1 +Host: foo.co.uk:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: good=allowed + diff --git a/tests/data/test41 b/tests/data/test41 index efeb58ec9b65..0259f25bb94f 100644 --- a/tests/data/test41 +++ b/tests/data/test41 @@ -4,7 +4,6 @@ HTTP HTTP FORMPOST -FAILURE # Server-side diff --git a/tests/data/test418 b/tests/data/test418 index ccda8912298d..cdd25f4ba9bc 100644 --- a/tests/data/test418 +++ b/tests/data/test418 @@ -59,7 +59,7 @@ Connection: TE 61 -curl: (61) Reject response due to more than 5 content encodings +curl: (61) Reject response exceeding limit of 5 transfer encodings diff --git a/tests/data/test419 b/tests/data/test419 index 51c5a16a0810..04a6a63d9f89 100644 --- a/tests/data/test419 +++ b/tests/data/test419 @@ -3,7 +3,6 @@ --dump-header -FAILURE diff --git a/tests/data/test433 b/tests/data/test433 index 85cf29ee4967..8a3cee80a1d7 100644 --- a/tests/data/test433 +++ b/tests/data/test433 @@ -31,14 +31,12 @@ http XDG_CONFIG_HOME=%PWD/%LOGDIR HOME CURL_HOME +# set the terminal wide to avoid word wrap in the message +COLUMNS=10000 Verify XDG_CONFIG_HOME use to find curlrc -# set the terminal wide to avoid word wrap in the message - -COLUMNS=300 - %HOSTIP:%HTTPPORT/%TESTNUMBER --no-progress-meter diff --git a/tests/data/test459 b/tests/data/test459 index 91f2d67bca18..f5649922cd86 100644 --- a/tests/data/test459 +++ b/tests/data/test459 @@ -30,6 +30,9 @@ Funny-head: yesyes http + +COLUMNS=10000 + config file with argument using whitespace missing quotes @@ -54,8 +57,7 @@ Content-Type: application/x-www-form-urlencoded arg -Warning: %LOGDIR/config:1 Option 'data' uses argument with unquoted whitespace.%SP -Warning: This may cause side-effects. Consider double quotes. +Warning: %LOGDIR/config:1 Option 'data' uses argument with unquoted whitespace. This may cause side-effects. Consider double quotes. diff --git a/tests/data/test467 b/tests/data/test467 index 799e1609f670..ba4673920331 100644 --- a/tests/data/test467 +++ b/tests/data/test467 @@ -9,7 +9,7 @@ cmdline # Client-side -use a bad short option letter that does not exist (after one does exist) +non-existing short option letter (after one that exists) # the second option is outside the normal accepted range diff --git a/tests/data/test472 b/tests/data/test472 index dd426b9cecf1..f2e96717f78d 100644 --- a/tests/data/test472 +++ b/tests/data/test472 @@ -36,7 +36,7 @@ Unicode aws -aws-sigv4 with query +aws-sigv4 with query using unicode "http://fake.fake.fake:8000/%TESTNUMBER/a=%hex[%e3%81%82]hex%" -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test485 b/tests/data/test485 index f530d8389e5f..8f934a83be57 100644 --- a/tests/data/test485 +++ b/tests/data/test485 @@ -14,7 +14,7 @@ etag # Client-side -Use --etag-compare and -save with more than one URL, URLs specified first +--etag-compare and -save with more than one URL, URLs specified first http://example.com/%TESTNUMBER http://example.net/fooo --etag-save %LOGDIR/etag%TESTNUMBER diff --git a/tests/data/test5000 b/tests/data/test5000 new file mode 100644 index 000000000000..3f2a98280206 --- /dev/null +++ b/tests/data/test5000 @@ -0,0 +1,57 @@ + + + + +HTTP +CURLOPT_HTTPSIG_ALGORITHM +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Type: text/html +Content-Length: 0 + + + + +# Client-side + + +http + +# this relies on the Debug feature which allows tests to set the time + +Debug +httpsig + + + +httpsig: basic GET with Ed25519 + + +lib%TESTNUMBER + + + +http://example.com:9000/%TESTNUMBER/resource example.com:9000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:9000 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:Olp3ugYSVwc47PTNBMrkV2P+8p5w2VPFzURxNaG44UA2On6OFG2FzFLEl8QEN1khiOdRNlFDouZsOwpJoPHvBA==: +Accept: */* + + + + diff --git a/tests/data/test5001 b/tests/data/test5001 new file mode 100644 index 000000000000..72ae3e07cffb --- /dev/null +++ b/tests/data/test5001 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: CLI GET with query + + +"http://example.com:8000/%TESTNUMBER/resource?action=read" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource?action=read HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:RQniOeqmwdRzGvoDIMJ8XJha75evJWgqo5/66EeuJeEGczZtnP2U/F52Lzd/y7Vd1DCb8oUcCKHrKi2VJI7lBA==: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5002 b/tests/data/test5002 new file mode 100644 index 000000000000..e09762243be4 --- /dev/null +++ b/tests/data/test5002 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 +hmac-sha256 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: CLI GET with HMAC-SHA256 + + +"http://example.com:7000/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key @%SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" --connect-to example.com:7000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:7000 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="shared-key-1";alg="hmac-sha256" +Signature: sig1=:jvajJheXE4H/TqWfAn8m0d0Rx0XWYKADN/1P0qn+FLw=: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5003 b/tests/data/test5003 new file mode 100644 index 000000000000..3520feab40fa --- /dev/null +++ b/tests/data/test5003 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 +httpsig-headers + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: custom --httpsig-headers + + +"http://example.com:6000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority" --connect-to example.com:6000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:6000 +Signature-Input: sig1=("@method" "@authority");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:q+8VHrGBOhkJm9d9dxKv3BfhyVWKIFiYXqdLZVx8yGrTzZO6Q6zJicFFuLvwJex6DS+Pw6YEMQzmtyj7dKOCCw==: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5004 b/tests/data/test5004 new file mode 100644 index 000000000000..33822b6b6d65 --- /dev/null +++ b/tests/data/test5004 @@ -0,0 +1,66 @@ + + + + +HTTP +CURLOPT_HTTPSIG_ALGORITHM +RFC9421 +ed25519 +B.2.6 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Type: text/html +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + + +httpsig: Ed25519 POST with headers (RFC test vector) + + +lib%TESTNUMBER + + + +CURL_HTTPSIG_CREATED=1618884473 + + + +http://example.com/%TESTNUMBER/foo example.com:80:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER/foo HTTP/1.1 +Host: example.com +Signature-Input: sig1=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:6QriGerNuao/A02UMre01lQOwlVQ0L9Cx3WJPQonQpUdKxSNg/XXXqjKdcC9PELzmAKJ10loLIq10yYLTzmzCA==: +Accept: */* +Date: Tue, 20 Apr 2021 02:07:55 GMT +Content-Type: application/json +Content-Length: 18 + +{"hello": "world"} + + + diff --git a/tests/data/test5005 b/tests/data/test5005 new file mode 100644 index 000000000000..210eac29dbb4 --- /dev/null +++ b/tests/data/test5005 @@ -0,0 +1,56 @@ + + + + +HTTP +httpsig +RFC9421 +header-signing + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: sign with a regular HTTP header + + +"http://example.com:5000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority content-type:" -H "Content-Type: application/json" --connect-to example.com:5000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5000 +Signature-Input: sig1=("@method" "@authority" "content-type");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:lyo8O1BjxYmPJchwmIO6ZwOEycijlfsLsqVpVVMiuPFAWrZ7zm0eoIF0X0wET8bsO9jiDRWjDx0b0dPK7FHPCA==: +User-Agent: curl/%VERSION +Accept: */* +Content-Type: application/json + + + + diff --git a/tests/data/test5006 b/tests/data/test5006 new file mode 100644 index 000000000000..602404e2a4f3 --- /dev/null +++ b/tests/data/test5006 @@ -0,0 +1,56 @@ + + + + +HTTP +httpsig +RFC9421 +case-normalization + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: component name lowercasing + + +"http://example.com:5100/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method Content-Type:" -H "Content-Type: text/plain" --connect-to example.com:5100:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5100 +Signature-Input: sig1=("@method" "content-type");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:tu3WEN25K59evFpiHY5GZurVRtbaex5QM1W9b+P1rPCgNDsEEOepNZap/+nm48QZSensuc4tOpy1gbvAj25gAg==: +User-Agent: curl/%VERSION +Accept: */* +Content-Type: text/plain + + + + diff --git a/tests/data/test5007 b/tests/data/test5007 new file mode 100644 index 000000000000..2ea42a9ffc4a --- /dev/null +++ b/tests/data/test5007 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP POST +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: POST method + + +"http://example.com:5200/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -d "postbody" --connect-to example.com:5200:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +POST /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5200 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:fqvzBFSBFu9oys7DawypZIrt2VS+nOnN1TQ7aTLcPUbEv7Zqm4leYd0q5r9FwW+kvabAjMj8J8N8F7FmPpIWCA==: +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 8 +Content-Type: application/x-www-form-urlencoded + +postbody + + + diff --git a/tests/data/test5008 b/tests/data/test5008 new file mode 100644 index 000000000000..bbb7e1370e7e --- /dev/null +++ b/tests/data/test5008 @@ -0,0 +1,46 @@ + + + + +HTTP +httpsig +RFC9421 +error + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: error - missing --httpsig-key + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +2 + + + diff --git a/tests/data/test5009 b/tests/data/test5009 new file mode 100644 index 000000000000..2b5771c00486 --- /dev/null +++ b/tests/data/test5009 @@ -0,0 +1,46 @@ + + + + +HTTP +httpsig +RFC9421 +error + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: error - unsupported algorithm + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "rsa-pss-sha512" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +2 + + + diff --git a/tests/data/test5010 b/tests/data/test5010 new file mode 100644 index 000000000000..6d6293e140a0 --- /dev/null +++ b/tests/data/test5010 @@ -0,0 +1,46 @@ + + + + +HTTP +httpsig +RFC9421 +error + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: error - non-existent key file + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @/nonexistent/key.hex --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +26 + + + diff --git a/tests/data/test5011 b/tests/data/test5011 new file mode 100644 index 000000000000..d0a5004d22dc --- /dev/null +++ b/tests/data/test5011 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 +query-special-chars + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: query string with special characters + + +"http://example.com:5300/%TESTNUMBER/resource?name=me%AMPnoval%AMPaim=b%25ad" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:5300:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource?name=me%AMPnoval%AMPaim=b%25ad HTTP/1.1 +Host: example.com:5300 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:ZFaJcDMTJEBbr90c0FaqupdJYDhNSz1i/eVCpMTDJ+1phLql2U4ROLiO26SGL6D0Nd5rr/3VXbDcWJ2vnV6IBA==: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5012 b/tests/data/test5012 new file mode 100644 index 000000000000..528e70838350 --- /dev/null +++ b/tests/data/test5012 @@ -0,0 +1,58 @@ + + + + +HTTP +httpsig +RFC9421 +default-port + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: default port omitted from @authority + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5013 b/tests/data/test5013 new file mode 100644 index 000000000000..7e2844e2015d --- /dev/null +++ b/tests/data/test5013 @@ -0,0 +1,58 @@ + + + + +HTTP +httpsig +RFC9421 +non-default-port + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: non-default port in @authority + + +"http://example.com:9000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:9000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:9000 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5014 b/tests/data/test5014 new file mode 100644 index 000000000000..0be72c8927c7 --- /dev/null +++ b/tests/data/test5014 @@ -0,0 +1,58 @@ + + + + +HTTP +httpsig +RFC9421 +empty-query + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: @query explicitly requested, no query in URL + + +"http://example.com:5400/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority query" --connect-to example.com:5400:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5400 +Signature-Input: sig1=("@method" "@authority" "@query");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5015 b/tests/data/test5015 new file mode 100644 index 000000000000..e8739e013209 --- /dev/null +++ b/tests/data/test5015 @@ -0,0 +1,62 @@ + + + + +HTTP +HTTP POST +httpsig +RFC9421 +hmac-sha256 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: POST with HMAC-SHA256 + + +"http://example.com:5500/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key @%SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" -d "postbody" --connect-to example.com:5500:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +POST /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5500 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="shared-key-1";alg="hmac-sha256" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 8 +Content-Type: application/x-www-form-urlencoded + +postbody + + + diff --git a/tests/data/test5016 b/tests/data/test5016 new file mode 100644 index 000000000000..fba1ae0156ef --- /dev/null +++ b/tests/data/test5016 @@ -0,0 +1,60 @@ + + + + +HTTP +httpsig +RFC9421 +hmac-sha256 +header-signing + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: HMAC-SHA256 with custom headers + + +"http://example.com:5600/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key @%SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" --httpsig-headers "method authority content-type:" -H "Content-Type: application/json" --connect-to example.com:5600:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5600 +Signature-Input: sig1=("@method" "@authority" "content-type");created=0;keyid="shared-key-1";alg="hmac-sha256" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* +Content-Type: application/json + + + + diff --git a/tests/data/test5017 b/tests/data/test5017 new file mode 100644 index 000000000000..f734593cab5b --- /dev/null +++ b/tests/data/test5017 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 +preexisting-signature + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: user-supplied Signature header skips signing + + +"http://example.com:5700/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -H "Signature: preexisting" -H "Signature-Input: preexisting" --connect-to example.com:5700:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5700 +User-Agent: curl/%VERSION +Accept: */* +Signature: preexisting +Signature-Input: preexisting + + + + diff --git a/tests/data/test5018 b/tests/data/test5018 new file mode 100644 index 000000000000..365a85fb2b23 --- /dev/null +++ b/tests/data/test5018 @@ -0,0 +1,63 @@ + + + + +HTTP +HTTP PUT +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +putdata + + +httpsig: PUT upload + + +"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T log/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +PUT /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:5800 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 8 + +putdata + + + diff --git a/tests/data/test5019 b/tests/data/test5019 new file mode 100644 index 000000000000..d958836d3327 --- /dev/null +++ b/tests/data/test5019 @@ -0,0 +1,46 @@ + + + + +HTTP +httpsig +RFC9421 +error + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: error - duplicate component + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --httpsig-headers "method method" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +43 + + + diff --git a/tests/data/test5020 b/tests/data/test5020 new file mode 100644 index 000000000000..33b1c2b6e9dd --- /dev/null +++ b/tests/data/test5020 @@ -0,0 +1,46 @@ + + + + +HTTP +httpsig +RFC9421 +error + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 + + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: error - rejects legacy '@' component syntax + + +"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --httpsig-headers "@method authority" --connect-to example.com::%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +43 + + + diff --git a/tests/data/test5021 b/tests/data/test5021 new file mode 100644 index 000000000000..cc96dfc50b80 --- /dev/null +++ b/tests/data/test5021 @@ -0,0 +1,57 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: default algorithm (ed25519) when --httpsig-algo omitted + + +"http://example.com:6100/%TESTNUMBER/resource" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:6100:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/ + + +GET /%TESTNUMBER/resource HTTP/1.1 +Host: example.com:6100 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519" +Signature: sig1=:STRIPPED: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5022 b/tests/data/test5022 new file mode 100644 index 000000000000..40699719376b --- /dev/null +++ b/tests/data/test5022 @@ -0,0 +1,55 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: pass key directly + + +"http://example.com:8000/5001/resource?action=read" --httpsig-algo "ed25519" --variable key@%SRCDIR/data/data-httpsig-ed25519.key --expand-httpsig-key '{{key:trim}}' --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /5001/resource?action=read HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:RQniOeqmwdRzGvoDIMJ8XJha75evJWgqo5/66EeuJeEGczZtnP2U/F52Lzd/y7Vd1DCb8oUcCKHrKi2VJI7lBA==: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5023 b/tests/data/test5023 new file mode 100644 index 000000000000..d706ec70c01d --- /dev/null +++ b/tests/data/test5023 @@ -0,0 +1,66 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 301 OK +Content-Length: 0 +Location: http://example.org:9000/%TESTNUMBER0002 + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: cross-origin redirect + + +"http://example.com:8000/%TESTNUMBER/resource?action=read" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT --connect-to example.org:9000:%HOSTIP:%HTTPPORT --location + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource?action=read HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:faTRQjDfWWm39STQkXyafoAp6ee2FCPh2KMefXOB51WAvoOdc6/Uwp9LW4zNWI7r+ivTKY7oSDb5kqcPJ/aSAQ==: +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: example.org:9000 +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5024 b/tests/data/test5024 new file mode 100644 index 000000000000..eefbe9564e96 --- /dev/null +++ b/tests/data/test5024 @@ -0,0 +1,68 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 301 OK +Content-Length: 0 +Location: /%TESTNUMBER0002 + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: same-origin redirect + + +"http://example.com:8000/%TESTNUMBER/resource?action=read" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT --location + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource?action=read HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:pxRrwEySHVgwXOI93y5KxKytfoXU6leQpC7i82Uodh8+oI7ycqCogD4PUU1p0RUAzoqeT2RHVWIN7Qg3k4A9DQ==: +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:QVx0WyxuyHqb3j0YihCLaWYdZOySKoIezj6FmTzsJprSqgm6yUsFbcWek4r6pALF1/jKGwNB1Rbyv3DE/pn/BA==: +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5025 b/tests/data/test5025 new file mode 100644 index 000000000000..0e45de0d818f --- /dev/null +++ b/tests/data/test5025 @@ -0,0 +1,66 @@ + + + + +HTTP +httpsig +RFC9421 + + + +# Server-side + + +HTTP/1.1 301 OK +Content-Length: 0 +Location: http://bob:alice@example.org:9000/%TESTNUMBER0002 + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +Content-Length: 6 +Connection: close +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +httpsig + + +httpsig: cross-origin + user auth redirect + + +"http://example.com:8000/%TESTNUMBER/resource?action=read" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT --connect-to example.org:9000:%HOSTIP:%HTTPPORT --location + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/resource?action=read HTTP/1.1 +Host: example.com:8000 +Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519" +Signature: sig1=:OUXXFiQlk/CEUksEkxvuivcFqWxL3xoPIZhB9FKTrx4MbhQqCRYMT0CQCUA9wCeRGkzmpGComBk5oPwKZr+MDg==: +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: example.org:9000 +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test5026 b/tests/data/test5026 new file mode 100644 index 000000000000..72dfc77f6140 --- /dev/null +++ b/tests/data/test5026 @@ -0,0 +1,94 @@ + + + + +HTTP +HTTP proxy +followlocation +aws-sigv4 + + +# Server-side + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +contents + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +# Client-side + + +http + + +AWS SigV4 redirect to a different host + + +http://first.host.it.is/we/want/that/page/%TESTNUMBER -x %HOSTIP:%HTTPPORT -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --location + + +Debug +aws +proxy + + + +# Verify data after the test has been "shot" + + +GET http://first.host.it.is/we/want/that/page/%TESTNUMBER HTTP/1.1 +Host: first.host.it.is +Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=5524f6f960590dc88a36551f11e407031a373e9e2caa3d3f7a3d9280cae367e2 +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://goto.second.host.now/%TESTNUMBER0002 HTTP/1.1 +Host: goto.second.host.now +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test5027 b/tests/data/test5027 new file mode 100644 index 000000000000..5ac74a08112e --- /dev/null +++ b/tests/data/test5027 @@ -0,0 +1,96 @@ + + + + +HTTP +HTTP proxy +followlocation +aws-sigv4 + + +# Server-side + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +contents + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +HTTP/1.1 302 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Location: http://goto.second.host.now/%TESTNUMBER0002 +Content-Length: 8 +Connection: close + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + + +# Client-side + + +http + + +AWS SigV4 trusted redirect to a different host + + +http://first.host.it.is/we/want/that/page/%TESTNUMBER -x %HOSTIP:%HTTPPORT -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --location-trusted + + +Debug +aws +proxy + + + +# Verify data after the test has been "shot" + + +GET http://first.host.it.is/we/want/that/page/%TESTNUMBER HTTP/1.1 +Host: first.host.it.is +Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=c540681cd0bba0cd9f6983a4529a45545ca4693192b65f60d8288ea617670f55 +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://goto.second.host.now/%TESTNUMBER0002 HTTP/1.1 +Host: goto.second.host.now +Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=f73dad70768d102fbfc307cedf6c77a5ef2ce9da51989647b6240e903340dd26 +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test5028 b/tests/data/test5028 new file mode 100644 index 000000000000..03b721234a77 --- /dev/null +++ b/tests/data/test5028 @@ -0,0 +1,43 @@ + + + + +GOPHER +header dump + + + +# Server-side + + +hello + + + +# Client-side + + +gopher + + +Gopher header dump + + +Debug + + +# Previous versions of the Gopher implementation could partially send the +# CRLF. Try to trigger that bug, which hangs the connection. +CURL_SMALLSENDS=1 + + +gopher://%HOSTIP:%GOPHERPORT/1/%TESTNUMBER -D %LOGDIR/heads%TESTNUMBER -m 5 + + + + + +/%TESTNUMBER + + + diff --git a/tests/data/test504 b/tests/data/test504 index 457dfb6d3848..c4670ee1b174 100644 --- a/tests/data/test504 +++ b/tests/data/test504 @@ -6,7 +6,6 @@ HTTP HTTP GET HTTP proxy multi -FAILURE connect to non-listen diff --git a/tests/data/test507 b/tests/data/test507 index c06743f30c9d..5915ef70422b 100644 --- a/tests/data/test507 +++ b/tests/data/test507 @@ -4,7 +4,6 @@ HTTP multi -FAILURE non-existing host diff --git a/tests/data/test527 b/tests/data/test527 index bcc0d8781844..735bd20e8528 100644 --- a/tests/data/test527 +++ b/tests/data/test527 @@ -31,7 +31,7 @@ ftp lib526 -FTP RETR same file using different handles but same connection +FTP RETR same file using different handles and closed connections ftp://%HOSTIP:%FTPPORT/path/%TESTNUMBER diff --git a/tests/data/test529 b/tests/data/test529 index 473bf165f3ac..52495ce63c6e 100644 --- a/tests/data/test529 +++ b/tests/data/test529 @@ -24,7 +24,7 @@ ftp lib525 -FTP PORT upload using multi interface (weird cleanup function sequence) +FTP PORT upload using multi interface (weird cleanup function seq) ftp://%HOSTIP:%FTPPORT/path/%TESTNUMBER %LOGDIR/upload%TESTNUMBER diff --git a/tests/data/test536 b/tests/data/test536 index b03bc52318f1..e0bcc906f7de 100644 --- a/tests/data/test536 +++ b/tests/data/test536 @@ -43,7 +43,7 @@ CURLINFO_USED_PROXY # 1 - the non-proxy using URL # 2 - the CURLOPT_RESOLVE string to change IP for the name -http://%HOSTIP:%HTTPPORT goingdirect.com:%HTTPPORT goingdirect.com:%HTTPPORT:%HOSTIP +http://%HOSTIP:%HTTPPORT goingdirect.test:%HTTPPORT goingdirect.test:%HTTPPORT:%HOSTIP proxy @@ -53,13 +53,13 @@ proxy # Verify data after the test has been "shot" -GET http://usingproxy.com/ HTTP/1.1 -Host: usingproxy.com +GET http://usingproxy.test/ HTTP/1.1 +Host: usingproxy.test Accept: */* Proxy-Connection: Keep-Alive GET / HTTP/1.1 -Host: goingdirect.com:%HTTPPORT +Host: goingdirect.test:%HTTPPORT Accept: */* diff --git a/tests/data/test538 b/tests/data/test538 index 2ee171b5b1a0..69e6fa931c35 100644 --- a/tests/data/test538 +++ b/tests/data/test538 @@ -3,7 +3,6 @@ FTP -FAILURE multi diff --git a/tests/data/test546 b/tests/data/test546 index 06bc27501d79..97459e9e3b52 100644 --- a/tests/data/test546 +++ b/tests/data/test546 @@ -36,7 +36,7 @@ ftp lib533 -FTP RETR a non-existing file then a found one using the multi interface +FTP RETR non-existing file then a found one using multi ftp://%HOSTIP:%FTPPORT/path/%TESTNUMBER ftp://%HOSTIP:%FTPPORT/path/%TESTNUMBER diff --git a/tests/data/test550 b/tests/data/test550 index c3b74717851f..7ef61f89a431 100644 --- a/tests/data/test550 +++ b/tests/data/test550 @@ -37,7 +37,7 @@ proxy lib549 -FTP RETR over proxy with CURLOPT_PROXY_TRANSFER_MODE and ASCII transfer +FTP RETR over proxy with CURLOPT_PROXY_TRANSFER_MODE using ASCII # first URL then proxy diff --git a/tests/data/test557 b/tests/data/test557 index 47242b1487c8..df859e97c7c6 100644 --- a/tests/data/test557 +++ b/tests/data/test557 @@ -35,6 +35,8 @@ All curl_mprintf() curl_off_t tests OK! All curl_mprintf() strings tests OK! All float strings tests OK! All curl_mprintf() octal and hexadecimal tests OK! +testing a string and -443 is super fun and 9876543 +All v-functions test OK! diff --git a/tests/data/test565 b/tests/data/test565 index 432e4177e6f0..6975239b77de 100644 --- a/tests/data/test565 +++ b/tests/data/test565 @@ -68,7 +68,7 @@ lib510 -send HTTP POST using read callback, chunked transfer-encoding and Digest +HTTP POST using read callback, chunked transfer-encoding and Digest http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test574 b/tests/data/test574 index 6681bfaa254c..38dfb7ab800d 100644 --- a/tests/data/test574 +++ b/tests/data/test574 @@ -22,7 +22,7 @@ ftp lib%TESTNUMBER -FTP wildcard download - changed fnmatch, 2x perform (Unix LIST response) +FTP wildcard download - changed fnmatch, 2x perform (Unix LIST) ftp://%HOSTIP:%FTPPORT/fully_simulated/UNIX/*.txt diff --git a/tests/data/test583 b/tests/data/test583 index 9463cd887fc5..8428d31395c9 100644 --- a/tests/data/test583 +++ b/tests/data/test583 @@ -25,7 +25,7 @@ lib%TESTNUMBER SFTP with multi interface, remove handle early -# The command here uses 'localhost' just to make sure that curl_multi_perform +# The command here uses 'localhost' to make sure that curl_multi_perform # does not reach too far in the first invoke. When using c-ares at least, the # name resolve causes it to return rather quickly and thus we could trigger # the problem we are looking to verify. diff --git a/tests/data/test594 b/tests/data/test594 index 98db071f2649..aaa007cbde8a 100644 --- a/tests/data/test594 +++ b/tests/data/test594 @@ -12,7 +12,6 @@ multi EPRT refused NODATACONN timeout -FAILURE diff --git a/tests/data/test604 b/tests/data/test604 index e6d6371945d5..c901cb6fbf2e 100644 --- a/tests/data/test604 +++ b/tests/data/test604 @@ -3,7 +3,6 @@ SFTP -FAILURE diff --git a/tests/data/test605 b/tests/data/test605 index ea7af94af466..ba708afeffbb 100644 --- a/tests/data/test605 +++ b/tests/data/test605 @@ -3,7 +3,6 @@ SCP -FAILURE diff --git a/tests/data/test606 b/tests/data/test606 index 48286e63e6c9..95e9f3d6b3cf 100644 --- a/tests/data/test606 +++ b/tests/data/test606 @@ -3,7 +3,6 @@ SFTP -FAILURE diff --git a/tests/data/test607 b/tests/data/test607 index 92ffe48e5596..960cb0b2aaaa 100644 --- a/tests/data/test607 +++ b/tests/data/test607 @@ -3,7 +3,6 @@ SCP -FAILURE diff --git a/tests/data/test608 b/tests/data/test608 index 5ac7a67d96c0..9e35595e763f 100644 --- a/tests/data/test608 +++ b/tests/data/test608 @@ -20,7 +20,7 @@ Test file for rename test sftp -SFTP post-quote rename +SFTP post-quote rename a file --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: -Q "-rename %SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt %SFTP_PWD/%LOGDIR/file%TESTNUMBER-renamed.txt" sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt --insecure diff --git a/tests/data/test609 b/tests/data/test609 index efd8496f3264..c5c7335f735d 100644 --- a/tests/data/test609 +++ b/tests/data/test609 @@ -4,7 +4,6 @@ SFTP post-quote -FAILURE diff --git a/tests/data/test611 b/tests/data/test611 index c64d5ba94de1..3de6009e5230 100644 --- a/tests/data/test611 +++ b/tests/data/test611 @@ -23,7 +23,7 @@ sftp %PERL %SRCDIR/libtest/test610.pl mkdir %PWD/%LOGDIR/test%TESTNUMBER.dir -SFTP post-quote rename +SFTP post-quote rename a directory --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: -Q "-rename %SFTP_PWD/%LOGDIR/test%TESTNUMBER.dir %SFTP_PWD/%LOGDIR/test%TESTNUMBER.new" sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt --insecure diff --git a/tests/data/test615 b/tests/data/test615 index 82307392ba65..8e2a6760b736 100644 --- a/tests/data/test615 +++ b/tests/data/test615 @@ -4,7 +4,6 @@ SFTP SFTP put -FAILURE diff --git a/tests/data/test620 b/tests/data/test620 index 0136b6e2bcc3..8ed5784e2d9d 100644 --- a/tests/data/test620 +++ b/tests/data/test620 @@ -3,7 +3,6 @@ SFTP -FAILURE diff --git a/tests/data/test621 b/tests/data/test621 index 33290e9953de..d15974cbd626 100644 --- a/tests/data/test621 +++ b/tests/data/test621 @@ -3,7 +3,6 @@ SCP -FAILURE diff --git a/tests/data/test622 b/tests/data/test622 index 97d0075b971d..33a73dee0315 100644 --- a/tests/data/test622 +++ b/tests/data/test622 @@ -4,7 +4,6 @@ SFTP SFTP put -FAILURE diff --git a/tests/data/test623 b/tests/data/test623 index 94f0ddc55b61..cbafe3811e51 100644 --- a/tests/data/test623 +++ b/tests/data/test623 @@ -4,7 +4,6 @@ SCP SCP upload -FAILURE diff --git a/tests/data/test626 b/tests/data/test626 index 3ded7bf87467..2ec6f5d48b14 100644 --- a/tests/data/test626 +++ b/tests/data/test626 @@ -4,7 +4,6 @@ SFTP pre-quote -FAILURE diff --git a/tests/data/test628 b/tests/data/test628 index 5394d00c6df7..c343108e2dbc 100644 --- a/tests/data/test628 +++ b/tests/data/test628 @@ -3,7 +3,6 @@ SFTP -FAILURE diff --git a/tests/data/test629 b/tests/data/test629 index 0c9e32ae946f..2869517e8f9e 100644 --- a/tests/data/test629 +++ b/tests/data/test629 @@ -3,7 +3,6 @@ SCP -FAILURE diff --git a/tests/data/test630 b/tests/data/test630 index 8d5c1f8e2971..e5e2fd8fa4d3 100644 --- a/tests/data/test630 +++ b/tests/data/test630 @@ -3,7 +3,6 @@ SFTP -FAILURE server key check diff --git a/tests/data/test631 b/tests/data/test631 index 33e7fff9b5d5..813c008e5537 100644 --- a/tests/data/test631 +++ b/tests/data/test631 @@ -3,7 +3,6 @@ SCP -FAILURE server key check diff --git a/tests/data/test632 b/tests/data/test632 index 0520bcea0bb6..1c67dff5d801 100644 --- a/tests/data/test632 +++ b/tests/data/test632 @@ -3,7 +3,6 @@ SFTP -FAILURE server key check diff --git a/tests/data/test639 b/tests/data/test639 index e87b2c931d24..529e70515a78 100644 --- a/tests/data/test639 +++ b/tests/data/test639 @@ -25,7 +25,7 @@ sftp %PERL %SRCDIR/libtest/test610.pl mkdir %PWD/%LOGDIR/test%TESTNUMBER.dir -SFTP post-quote rename * asterisk accept-fail +SFTP post-quote rename error with accept-fail --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: -Q "-*rename %SFTP_PWD/%LOGDIR/test%TESTNUMBER-not-exists-dir %SFTP_PWD/%LOGDIR/test%TESTNUMBER.new" sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt --insecure diff --git a/tests/data/test644 b/tests/data/test644 index e2199510be7a..acf5e08aab2d 100644 --- a/tests/data/test644 +++ b/tests/data/test644 @@ -76,9 +76,14 @@ Accept: */* +%if win32 +[ZoneTransfer] +HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%else user.creator => curl user.mime_type => text/html user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%endif diff --git a/tests/data/test656 b/tests/data/test656 index 8f586b739d13..4e1599baccf9 100644 --- a/tests/data/test656 +++ b/tests/data/test656 @@ -3,7 +3,6 @@ SFTP -FAILURE diff --git a/tests/data/test679 b/tests/data/test679 index 478e2d25796a..6b995e3d1039 100644 --- a/tests/data/test679 +++ b/tests/data/test679 @@ -30,13 +30,13 @@ Funny-head: yesyes http -netrc with quoted password +netrc with quoted username and password --netrc-optional --netrc-file %LOGDIR/netrc%TESTNUMBER http://%HOSTIP:%HTTPPORT/ -machine %HOSTIP login user1 password "with spaces and \"\n\r\t\a" +machine %HOSTIP login "user one" password "with spaces and \"\n\r\t\a" @@ -45,7 +45,7 @@ machine %HOSTIP login user1 password "with spaces and \"\n\r\t\a" GET / HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Authorization: Basic %b64[user1:with%20spaces%20and%20"%0a%0d%09a]b64% +Authorization: Basic %b64[user%20one:with%20spaces%20and%20"%0a%0d%09a]b64% User-Agent: curl/%VERSION Accept: */* diff --git a/tests/data/test687 b/tests/data/test687 index fd3052da60c9..045348b301f3 100644 --- a/tests/data/test687 +++ b/tests/data/test687 @@ -53,9 +53,14 @@ Accept: */* +%if win32 +[ZoneTransfer] +HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%else user.creator => curl user.mime_type => fake/data user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%endif diff --git a/tests/data/test688 b/tests/data/test688 index 9de1bab933dc..32ed0e9f30ce 100644 --- a/tests/data/test688 +++ b/tests/data/test688 @@ -36,10 +36,10 @@ xattr CURL_FAKE_XATTR=1 -basic --xattr with -O +basic --xattr with (uppercase) -O ---xattr -O --output-dir %LOGDIR http://%HOSTIP:%HTTPPORT/%TESTNUMBER +--xattr -O --output-dir %LOGDIR http://%HOSTIP:%HTTPPORT/%TESTNUMBER --referer https://referrer.invalid/ @@ -50,12 +50,20 @@ GET /%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT User-Agent: curl/%VERSION Accept: */* +Referer: https://referrer.invalid/ +%if win32 +[ZoneTransfer] +ReferrerUrl=https://referrer.invalid/ +HostUrl=http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%else user.creator => curl +user.xdg.referrer.url => https://referrer.invalid/ user.mime_type => fake/data user.xdg.origin.url => http://%HOSTIP:%HTTPPORT/%TESTNUMBER +%endif diff --git a/tests/data/test7 b/tests/data/test7 index 0f00d8009762..ccdac1927380 100644 --- a/tests/data/test7 +++ b/tests/data/test7 @@ -35,6 +35,9 @@ HTTP with cookie parser and header recording http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER -b none -D %LOGDIR/heads%TESTNUMBER.txt + +cookies + # Verify data after the test has been "shot" diff --git a/tests/data/test702 b/tests/data/test702 index 7b899884c5c0..ea565d4be54f 100644 --- a/tests/data/test702 +++ b/tests/data/test702 @@ -6,7 +6,6 @@ HTTP SOCKS4 connect to non-listen -FAILURE # Server-side diff --git a/tests/data/test703 b/tests/data/test703 index e3ce17e142f2..197b0da2b4ab 100644 --- a/tests/data/test703 +++ b/tests/data/test703 @@ -6,7 +6,6 @@ HTTP SOCKS5 connect to non-listen -FAILURE # Server-side diff --git a/tests/data/test704 b/tests/data/test704 index 0decb7cddff4..5ef11ea6f098 100644 --- a/tests/data/test704 +++ b/tests/data/test704 @@ -6,7 +6,6 @@ HTTP SOCKS4 connect to non-listen -FAILURE # Server-side diff --git a/tests/data/test705 b/tests/data/test705 index b2d1642ba9c0..c86289be6756 100644 --- a/tests/data/test705 +++ b/tests/data/test705 @@ -6,7 +6,6 @@ HTTP SOCKS5 connect to non-listen -FAILURE # Server-side diff --git a/tests/data/test708 b/tests/data/test708 index 8c52751423f7..e96900b52bd4 100644 --- a/tests/data/test708 +++ b/tests/data/test708 @@ -39,7 +39,7 @@ socks4 all_proxy=socks4://%HOSTIP:%SOCKSPORT -HTTP GET via SOCKS4 proxy +HTTP GET via SOCKS4 all_proxy http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test716 b/tests/data/test716 index 6f692fbd0918..266826c8d5b1 100644 --- a/tests/data/test716 +++ b/tests/data/test716 @@ -30,7 +30,7 @@ SOCKS5 proxy with too long username # it should never connect to the target server -http://hohoho.example.com:99/%TESTNUMBER -x socks5://%repeat[256 x A]%:b@%HOSTIP:%SOCKSPORT +http://localhost:99/%TESTNUMBER -x socks5://%repeat[256 x A]%:b@%HOSTIP:%SOCKSPORT diff --git a/tests/data/test73 b/tests/data/test73 index b9864e83f7ee..4a4ba413f3ae 100644 --- a/tests/data/test73 +++ b/tests/data/test73 @@ -27,7 +27,7 @@ boo http -HTTP, receive cookies when using custom Host:, domain using only two dots +HTTP cookies when using custom Host:, domain using only two dots http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER -c %LOGDIR/jar%TESTNUMBER.txt -H "Host: host.NOT_DISCLOSED.se" diff --git a/tests/data/test739 b/tests/data/test739 index 06b73f9027a4..bf7dc42ce459 100644 --- a/tests/data/test739 +++ b/tests/data/test739 @@ -19,7 +19,7 @@ ipfs http -IPNS path and query args for gateway and IPFS URL (malformed gateway URL) +IPNS path and query args for gw and IPFS URL (malformed gw URL) --ipfs-gateway "http://%HOSTIP:%HTTPPORT/some/path?biz=baz" "ipns://fancy.tld/a/b?foo=bar%AMPaaa=bbb" diff --git a/tests/data/test741 b/tests/data/test741 index 0c4e0e1a5c9d..2c118b657d79 100644 --- a/tests/data/test741 +++ b/tests/data/test741 @@ -22,7 +22,7 @@ http HOME=%PWD/%LOGDIR -IPFS malformed gw URL from multiline gateway file, first line no url +IPFS malformed gw URL from multiline gateway file, first line no URL ipfs://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u diff --git a/tests/data/test743 b/tests/data/test743 index cf6eb8da8d96..8627c36c107c 100644 --- a/tests/data/test743 +++ b/tests/data/test743 @@ -31,7 +31,7 @@ Funny-head: yesyes http ---config with a 127 byte line +--config with a 127-byte line -A pointless diff --git a/tests/data/test744 b/tests/data/test744 index 7db113b03675..06f99e83792c 100644 --- a/tests/data/test744 +++ b/tests/data/test744 @@ -45,7 +45,7 @@ http http-proxy ---netrc-file with a 127 byte line +--netrc-file with a 127-byte line machine foo.host login foo password baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar diff --git a/tests/data/test75 b/tests/data/test75 index 40c0d3b4dea4..b76cefbf77d9 100644 --- a/tests/data/test75 +++ b/tests/data/test75 @@ -5,7 +5,6 @@ HTTP HTTP GET globbing -FAILURE # Server-side @@ -31,7 +30,7 @@ HTTP, urlglob retrieval with bad range 3 -curl: (3) bad range in URL position 47: +curl: (3) bad range in position 47: http://a-site-never-accessed.example.org/[2-1] ^ diff --git a/tests/data/test750 b/tests/data/test750 index 8e4d220c7480..03352ece66b9 100644 --- a/tests/data/test750 +++ b/tests/data/test750 @@ -32,7 +32,7 @@ http proxy -HTTP CONNECT with proxy returning just HTML and closing +HTTP CONNECT with proxy returning HTML and closing http://test.example --proxy http://%HOSTIP:%HTTPPORT --proxytunnel -sS diff --git a/tests/data/test758 b/tests/data/test758 index df39300fe2ae..08f024fecd61 100644 --- a/tests/data/test758 +++ b/tests/data/test758 @@ -39,7 +39,7 @@ https lib%TESTNUMBER -multi_socket interface transfer with callbacks returning error +HTTPS multi_socket interface transfer with callbacks returning error https://localhost:%HTTPSPORT/file%TESTNUMBER diff --git a/tests/data/test759 b/tests/data/test759 index 9c67e30f2d94..7ad896bbccce 100644 --- a/tests/data/test759 +++ b/tests/data/test759 @@ -18,7 +18,7 @@ glob '{,' # Verify data after the test has been "shot" -# curl: (3) unmatched brace in URL position 1: +# curl: (3) unmatched brace in position 1: 3 diff --git a/tests/data/test761 b/tests/data/test761 index eec55e297fca..2772a8f78fa8 100644 --- a/tests/data/test761 +++ b/tests/data/test761 @@ -22,8 +22,8 @@ http://testingthis/%repeat[201 x {a}b]% 3 -curl: (3) too many {} sets in URL position 403: -http://testingthis/%repeat[113 x {a}b]%{a +curl: (3) too many {} sets in position 403: +http://testingthis/%repeat[114 x {a}b]%{a diff --git a/tests/data/test779 b/tests/data/test779 index 16aebce880c4..42ee36363439 100644 --- a/tests/data/test779 +++ b/tests/data/test779 @@ -6,6 +6,7 @@ HTTP IMAP oauth2-bearer followlocation +--resolve # Server-side diff --git a/tests/data/test795 b/tests/data/test795 index 859040414379..a0d168d5ffb2 100644 --- a/tests/data/test795 +++ b/tests/data/test795 @@ -5,6 +5,7 @@ HTTP IMAP followlocation +--resolve # Server-side diff --git a/tests/data/test798 b/tests/data/test798 index c69461ead714..8208a2c923d7 100644 --- a/tests/data/test798 +++ b/tests/data/test798 @@ -36,9 +36,10 @@ http HTTP cookies in a folded header -http://localhost:%HTTPPORT/we/want/%TESTNUMBER -b none -c %LOGDIR/jar%TESTNUMBER.txt +-4 http://localhost:%HTTPPORT/we/want/%TESTNUMBER -b none -c %LOGDIR/jar%TESTNUMBER.txt +PSL cookies local-http @@ -58,7 +59,7 @@ Accept: */* # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -#HttpOnly_.localhost TRUE /p4 TRUE 0 flavor tasty +#HttpOnly_localhost FALSE /p4 TRUE 0 flavor tasty diff --git a/tests/data/test800 b/tests/data/test800 index 512f9bbcae2a..2c4d97088efa 100644 --- a/tests/data/test800 +++ b/tests/data/test800 @@ -27,7 +27,7 @@ body imap -IMAP FETCH message +IMAP FETCH message with MAILINDEX 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=1' -u '"user:sec"ret{' diff --git a/tests/data/test803 b/tests/data/test803 index 20f84f83d7a8..6c4c67697cf1 100644 --- a/tests/data/test803 +++ b/tests/data/test803 @@ -6,7 +6,6 @@ IMAP Clear Text SELECT UIDVALIDITY -FAILURE diff --git a/tests/data/test830 b/tests/data/test830 index 755629bc4b6f..36ab0a31ecba 100644 --- a/tests/data/test830 +++ b/tests/data/test830 @@ -44,7 +44,7 @@ IMAP CRAM-MD5 graceful cancellation 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE CRAM-MD5 diff --git a/tests/data/test831 b/tests/data/test831 index be101931d7d9..436f98fd10fc 100644 --- a/tests/data/test831 +++ b/tests/data/test831 @@ -45,7 +45,7 @@ IMAP NTLM graceful cancellation 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE NTLM diff --git a/tests/data/test832 b/tests/data/test832 index 2b455901dc63..bcc74657248e 100644 --- a/tests/data/test832 +++ b/tests/data/test832 @@ -46,7 +46,7 @@ IMAP DIGEST-MD5 graceful cancellation 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE DIGEST-MD5 diff --git a/tests/data/test838 b/tests/data/test838 index b28f7da3226e..f365ad678121 100644 --- a/tests/data/test838 +++ b/tests/data/test838 @@ -14,7 +14,7 @@ RFC4422 AUTH EXTERNAL REPLY AUTHENTICATE + -REPLY = A002 OK AUTHENTICATE completed +REPLY A002 OK AUTHENTICATE completed From: me@somewhere @@ -45,7 +45,7 @@ IMAP external authentication without credentials A001 CAPABILITY A002 AUTHENTICATE EXTERNAL -= + A003 SELECT %TESTNUMBER A004 FETCH 1 BODY[] A005 LOGOUT diff --git a/tests/data/test844 b/tests/data/test844 index 6761a3aa9404..7237827d9872 100644 --- a/tests/data/test844 +++ b/tests/data/test844 @@ -39,7 +39,7 @@ IMAP OAuth 2.0 (OAUTHBEARER) failure as continuation 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE OAUTHBEARER diff --git a/tests/data/test845 b/tests/data/test845 index 09068062872f..39f2c46e6488 100644 --- a/tests/data/test845 +++ b/tests/data/test845 @@ -27,7 +27,7 @@ REPLY AQ== A002 NO Authentication failed imap -IMAP OAuth 2.0 (OAUTHBEARER) failure as continuation with initial response +IMAP OAuth failure as continuation with initial response 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=1' -u user --oauth2-bearer mF_9.B5f-4.1JqM @@ -41,7 +41,7 @@ IMAP OAuth 2.0 (OAUTHBEARER) failure as continuation with initial response 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE OAUTHBEARER %b64[n,a=user,%01host=127.0.0.1%01port=%IMAPPORT%01auth=Bearer mF_9.B5f-4.1JqM%01%01]b64% diff --git a/tests/data/test847 b/tests/data/test847 index e175ba106218..8ee2a22b38c3 100644 --- a/tests/data/test847 +++ b/tests/data/test847 @@ -27,7 +27,7 @@ body imap -IMAP FETCH message +IMAP FETCH message with UID 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;UID=1' -u '"user:sec"ret{' diff --git a/tests/data/test849 b/tests/data/test849 index c48f4447fa42..390c33132697 100644 --- a/tests/data/test849 +++ b/tests/data/test849 @@ -38,7 +38,7 @@ IMAP plain auth with alt authorization identity (Not authorized) 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "LOGOUT" +# transfer and such a connection does not get a "LOGOUT" A001 CAPABILITY A002 AUTHENTICATE PLAIN diff --git a/tests/data/test852 b/tests/data/test852 index 43779dcdc898..64e2ad33ada2 100644 --- a/tests/data/test852 +++ b/tests/data/test852 @@ -5,7 +5,6 @@ POP3 Clear Text LIST -FAILURE diff --git a/tests/data/test855 b/tests/data/test855 index 966eeaa8226a..3e01b3221670 100644 --- a/tests/data/test855 +++ b/tests/data/test855 @@ -5,7 +5,6 @@ POP3 Clear Text RETR -FAILURE diff --git a/tests/data/test856 b/tests/data/test856 index 1bd80741937c..553907a1147e 100644 --- a/tests/data/test856 +++ b/tests/data/test856 @@ -4,7 +4,6 @@ POP3 Clear Text -FAILURE @@ -35,7 +34,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user:wrong 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA USER user diff --git a/tests/data/test87 b/tests/data/test87 index 620ae384886c..8477513f0ad5 100644 --- a/tests/data/test87 +++ b/tests/data/test87 @@ -6,7 +6,6 @@ HTTP HTTP GET globbing [] range -FAILURE # Server-side diff --git a/tests/data/test876 b/tests/data/test876 index ffc280281b16..3a814687291d 100644 --- a/tests/data/test876 +++ b/tests/data/test876 @@ -45,7 +45,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user:secret 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH CRAM-MD5 diff --git a/tests/data/test877 b/tests/data/test877 index 8433b8118bbe..8f359ff8ec03 100644 --- a/tests/data/test877 +++ b/tests/data/test877 @@ -46,7 +46,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u testuser:testpass 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH NTLM diff --git a/tests/data/test878 b/tests/data/test878 index 23f074d53fa1..588a22b5026c 100644 --- a/tests/data/test878 +++ b/tests/data/test878 @@ -47,7 +47,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user:secret 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH DIGEST-MD5 diff --git a/tests/data/test884 b/tests/data/test884 index 1a49a6a5a551..577c7376e63a 100644 --- a/tests/data/test884 +++ b/tests/data/test884 @@ -16,7 +16,7 @@ RFC5034 AUTH EXTERNAL REPLY AUTH + -REPLY = +OK Login successful +REPLY +OK Login successful From: me@somewhere @@ -47,7 +47,7 @@ POP3 external authentication without credentials CAPA AUTH EXTERNAL -= + RETR %TESTNUMBER QUIT diff --git a/tests/data/test889 b/tests/data/test889 index bd5615bd2ba5..8a808beda5a6 100644 --- a/tests/data/test889 +++ b/tests/data/test889 @@ -42,7 +42,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user --oauth2-bearer mF_9.B5f-4.1JqM 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH OAUTHBEARER diff --git a/tests/data/test890 b/tests/data/test890 index 4aec72a3848e..68d36f0c50b0 100644 --- a/tests/data/test890 +++ b/tests/data/test890 @@ -28,7 +28,7 @@ REPLY AQ== -ERR Authentication failed pop3 -POP3 OAuth 2.0 (OAUTHBEARER) failure as continuation with initial response +POP3 OAuth failure as continuation with initial response pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user --oauth2-bearer mF_9.B5f-4.1JqM --sasl-ir @@ -42,7 +42,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u user --oauth2-bearer mF_9.B5f-4.1JqM --s 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH OAUTHBEARER %b64[n,a=user,%01host=127.0.0.1%01port=%POP3PORT%01auth=Bearer mF_9.B5f-4.1JqM%01%01]b64% diff --git a/tests/data/test893 b/tests/data/test893 index 1ba48a23a117..2e2ed5215276 100644 --- a/tests/data/test893 +++ b/tests/data/test893 @@ -40,7 +40,7 @@ pop3://%HOSTIP:%POP3PORT/%TESTNUMBER -u kurt:xipj3plmq --sasl-authzid ursel 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" CAPA AUTH PLAIN diff --git a/tests/data/test91 b/tests/data/test91 index 004b8cb4709d..07d4e92adf44 100644 --- a/tests/data/test91 +++ b/tests/data/test91 @@ -82,7 +82,7 @@ SSL http -HTTP with NTLM/Negotiate/Basic, anyauth and user with domain, with size 0 +HTTP NTLM/Negotiate/Basic, anyauth and user with domain, with size 0 http://%HOSTIP:%HTTPPORT/%TESTNUMBER --anyauth -u mydomain\\myself:secret diff --git a/tests/data/test932 b/tests/data/test932 index e039cda6370c..ec002ae314c6 100644 --- a/tests/data/test932 +++ b/tests/data/test932 @@ -44,7 +44,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH CRAM-MD5 diff --git a/tests/data/test933 b/tests/data/test933 index 7fce345e02a8..0c4a8c42cd7e 100644 --- a/tests/data/test933 +++ b/tests/data/test933 @@ -45,7 +45,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH NTLM diff --git a/tests/data/test934 b/tests/data/test934 index 3b9ea78090d0..c440eb4974bc 100644 --- a/tests/data/test934 +++ b/tests/data/test934 @@ -46,7 +46,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH DIGEST-MD5 diff --git a/tests/data/test94 b/tests/data/test94 index bca0a8e1cc71..30c370ef9fe5 100644 --- a/tests/data/test94 +++ b/tests/data/test94 @@ -6,7 +6,6 @@ HTTPS HTTP GET HTTP CONNECT HTTP proxy -FAILURE # Server-side diff --git a/tests/data/test943 b/tests/data/test943 index a9a28d6bd99b..102df73610de 100644 --- a/tests/data/test943 +++ b/tests/data/test943 @@ -15,7 +15,7 @@ RFC4954 AUTH EXTERNAL REPLY AUTH 334 EXTERNAL supported -REPLY = 235 Authenticated +REPLY 235 Authenticated @@ -40,7 +40,7 @@ mail body EHLO %TESTNUMBER AUTH EXTERNAL -= + MAIL FROM:%LTsender@example.com%GT RCPT TO:%LTrecipient@example.com%GT DATA diff --git a/tests/data/test948 b/tests/data/test948 index 98c86a78c6ab..6955f993a90c 100644 --- a/tests/data/test948 +++ b/tests/data/test948 @@ -45,7 +45,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH OAUTHBEARER diff --git a/tests/data/test949 b/tests/data/test949 index 954664ad2961..95d049282359 100644 --- a/tests/data/test949 +++ b/tests/data/test949 @@ -27,7 +27,7 @@ REPLY AQ== 535 Username and Password not accepted. Learn more at\r\n535 http://s smtp -SMTP OAuth 2.0 (OAUTHBEARER) failure as continuation with initial response +SMTP OAuth failure as continuation with initial response mail body @@ -44,7 +44,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH OAUTHBEARER %b64[n,a=user,%01host=127.0.0.1%01port=%SMTPPORT%01auth=Bearer mF_9.B5f-4.1JqM%01%01]b64% diff --git a/tests/data/test950 b/tests/data/test950 index 3ea2cb7b51a5..6d6fcc303389 100644 --- a/tests/data/test950 +++ b/tests/data/test950 @@ -24,7 +24,7 @@ smtp SMTP VRFY with custom request -# the custom request just does it lowercase to remain the same command +# the custom request does it lowercase to remain the same command smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient --request "vrfy" diff --git a/tests/data/test954 b/tests/data/test954 index 29f85fb1055d..1d39ff9f1059 100644 --- a/tests/data/test954 +++ b/tests/data/test954 @@ -42,7 +42,7 @@ smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-fr 67 # The multi interface considers a broken "CONNECT" as a prematurely broken -# transfer and such a connection will not get a "QUIT" +# transfer and such a connection does not get a "QUIT" EHLO %TESTNUMBER AUTH PLAIN diff --git a/tests/data/test956 b/tests/data/test956 index 5ee26997b214..1e31ad163b68 100644 --- a/tests/data/test956 +++ b/tests/data/test956 @@ -23,7 +23,7 @@ codeset-utf8 LC_ALL=C.UTF-8 -SMTP without SMTPUTF8 support - UTF-8 based recipient (local part only) +SMTP without SMTPUTF8 support - UTF-8 based recipient (local part) From: different diff --git a/tests/data/test958 b/tests/data/test958 index 9ee2bb681ed3..ff9ffe35aa7a 100644 --- a/tests/data/test958 +++ b/tests/data/test958 @@ -24,7 +24,7 @@ codeset-utf8 LC_ALL=C.UTF-8 -SMTP external VRFY without SMTPUTF8 - UTF-8 recipient (local part only) +SMTP external VRFY without SMTPUTF8 - UTF-8 recipient (local part) smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt Anv%hex[%c3%a4]hex%ndaren@example.com diff --git a/tests/data/test962 b/tests/data/test962 index 07bb95905c70..a3fb2c99a8e6 100644 --- a/tests/data/test962 +++ b/tests/data/test962 @@ -25,7 +25,7 @@ codeset-utf8 LC_ALL=C.UTF-8 -SMTP without SMTPUTF8 support - UTF-8 based sender (host part only) +SMTP without SMTPUTF8 support - UTF-8 based sender, with IDN From: different diff --git a/tests/data/test964 b/tests/data/test964 index 3465d20211d3..4c001a082e97 100644 --- a/tests/data/test964 +++ b/tests/data/test964 @@ -26,7 +26,7 @@ codeset-utf8 LC_ALL=C.UTF-8 -SMTP external VRFY without SMTPUTF8 (IDN) - UTF-8 recipient (host part) +SMTP external VRFY w/o SMTPUTF8 (IDN) - UTF-8 recipient (host part) smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt user@%hex[%c3%a5%c3%a4%c3%b6]hex%.se diff --git a/tests/data/test99 b/tests/data/test99 index 6a4f27677496..4582eba4065d 100644 --- a/tests/data/test99 +++ b/tests/data/test99 @@ -6,7 +6,6 @@ HTTP HTTP GET Resume Largefile -FAILURE # Server-side diff --git a/tests/devtest.pl b/tests/devtest.pl index ed9958907445..322b6904b6da 100755 --- a/tests/devtest.pl +++ b/tests/devtest.pl @@ -69,7 +69,7 @@ BEGIN # This function is currently required to be here by servers.pm # This is copied from runtests.pl # -my $uname_release = `uname -r`; +my $uname_release = qx(uname -r); my $is_wsl = $uname_release =~ /Microsoft$/; sub logmsg { for(@_) { @@ -94,7 +94,7 @@ sub parseprotocols { # Generate a "proto-ipv6" version of each protocol to match the # IPv6 name and a "proto-unix" to match the variant which # uses Unix domain sockets. This works even if support is not - # compiled in because the test will fail. + # compiled in because the test fails. push @protocols, map(("$_-ipv6", "$_-unix"), @protocols); # 'http-proxy' is used in test cases to do CONNECT through @@ -108,7 +108,7 @@ sub parseprotocols { # Initialize @protocols from the curl binary under test # sub init_protocols { - for (`$CURL -V 2>$dev_null`) { + for(qx($CURL -V 2>$dev_null)) { if(m/^Protocols: (.*)$/) { parseprotocols($1); } diff --git a/tests/dictserver.py b/tests/dictserver.py index b4e1afd78b4b..bff6e80157e4 100755 --- a/tests/dictserver.py +++ b/tests/dictserver.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -26,8 +25,6 @@ # """DICT server.""" -from __future__ import absolute_import, division, print_function, unicode_literals - import argparse import logging import os @@ -97,12 +94,12 @@ def handle(self): response_data = "No matches" # Send back a failure to find. - response = "552 {0}\n".format(response_data) + response = f"552 {response_data}\n" log.debug("[DICT] Responding with %r", response) self.request.sendall(response.encode("utf-8")) - except IOError: - log.exception("[DICT] IOError hit during request") + except OSError: + log.exception("[DICT] OSError hit during request") def get_options(): diff --git a/tests/ech_combos.py b/tests/ech_combos.py index 8c100c2bb734..90db92ca91d0 100755 --- a/tests/ech_combos.py +++ b/tests/ech_combos.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -26,7 +25,7 @@ # # Python3 program to print all combination of size r in an array of size n. # This is used to generate test lines in tests/ech_test.sh. -# This will be discarded in the process of moving from experimental, +# This is discarded in the process of moving from experimental, # but is worth preserving for the moment in case of changes to the # ECH command line args @@ -45,12 +44,12 @@ def CombinationRepetitionUtil(chosen, arr, badarr, index, if chosen[j] in badarr: res = 0 j = j - 1 - print("cli_test $turl 1", res, end = " ") + print("cli_test $turl 1", res, end=" ") # print combination but eliminating any runs of # two identical params for j in range(r): if j != 0 and chosen[j] != chosen[j-1]: - print(chosen[j], end = " ") + print(chosen[j], end=" ") print() return @@ -65,7 +64,7 @@ def CombinationRepetitionUtil(chosen, arr, badarr, index, chosen[index] = arr[start] # Current is excluded, replace it - # with next (Note that i+1 is passed, + # with next (Note that i + 1 is passed, # but index is not changed) CombinationRepetitionUtil(chosen, arr, badarr, index + 1, r, start, end) @@ -89,8 +88,8 @@ def CombinationRepetition(arr, badarr, n, r): # Driver code -badarr = [ '--ech grease', '--ech false', '--ech ecl:$badecl', '--ech pn:$badpn' ] -goodarr = [ '--ech hard', '--ech true', '--ech ecl:$goodecl', '--ech pn:$goodpn' ] +badarr = ['--ech grease', '--ech false', '--ech ecl:$badecl', '--ech pn:$badpn'] +goodarr = ['--ech hard', '--ech true', '--ech ecl:$goodecl', '--ech pn:$goodpn'] arr = badarr + goodarr r = 8 n = len(arr) - 1 diff --git a/tests/ech_tests.sh b/tests/ech_tests.sh index 3944793b702b..72a2d454e49c 100755 --- a/tests/ech_tests.sh +++ b/tests/ech_tests.sh @@ -31,7 +31,7 @@ # TODO: Translate this into something that approximates a valid curl test:-) # Should be useful though even before such translation and a pile less work # to do this than that. The pile of work required would include making an -# ECH-enabled server and a DoH server. For now, this is just run manually. +# ECH-enabled server and a DoH server. For now, this is run manually. # # set -x @@ -211,7 +211,7 @@ have_portsblocked="no" NOW=$(whenisitagain) BINNAME=$(basename "$0" .sh) if [ ! -d "$LTOP" ]; then - mkdir -p "$LTOP" + mkdir -p -- "$LTOP" fi if [ ! -d "$LTOP" ]; then echo "Cannot see $LTOP for logs - exiting" @@ -223,7 +223,7 @@ echo "-----" > "$logfile" echo "Running $0 at $NOW" >> "$logfile" echo "Running $0 at $NOW" -# check we have the binaries needed and which TLS library we will be using +# check we have the binaries needed and which TLS library we are using if [ -f "$OSSL"/libssl.so ]; then have_ossl="yes" fi @@ -253,9 +253,9 @@ fi wolf_cnt=$($CURL "${CURL_PARAMS[@]}" -V 2> /dev/null | grep -c wolfSSL) if ((wolf_cnt == 1)); then using_wolf="yes" - # for some reason curl+wolfSSL dislikes certs that are ok - # for browsers, so we will test using "insecure" mode (-k) - # but that is ok here as we are only interested in ECH testing + # for some reason curl + wolfSSL dislikes certs that are ok + # for browsers, so we test using "insecure" mode (-k) + # but that is OK here as we are only interested in ECH testing CURL_PARAMS+=(-k) fi # check if we have dig and it knows https or not @@ -474,7 +474,7 @@ done # Check various command line options, if we are good so far if [[ "$using_ossl" == "yes" && "$allgood" == "yes" ]]; then - # use this test URL as it will tell us if things worked + # use this test URL as it tells us if things worked turl="https://defo.ie/ech-check.php" echo "cli_test with $turl" echo "cli_test with $turl" >> "$logfile" @@ -498,7 +498,7 @@ if [[ "$using_ossl" == "yes" && "$allgood" == "yes" ]]; then # ecl:ecl can be correct, incorrect or missing # ech:pn can be correct, incorrect or missing # in all cases the "last" argument provided should "win" - # but only one of hard, true, grease or false will apply + # but only one of hard, true, grease or false applies turl="https://defo.ie/ech-check.php" echconfiglist=$(get_ech_configlist defo.ie) goodecl=$echconfiglist @@ -790,7 +790,7 @@ if [[ "$using_ossl" == "yes" && "$allgood" == "yes" ]]; then turl="https://tcd.ie" echo "cli_test with $turl" echo "cli_test with $turl" >> "$logfile" - # the params below do not matter much here as we will fail anyway + # the params below do not matter much here as we fail anyway echconfiglist=$(get_ech_configlist defo.ie) goodecl=$echconfiglist badecl="$goodecl" @@ -1083,14 +1083,14 @@ else echo "NOT all good, log in $logfile" fi -# send a mail to root (will be forwarded) but just once every 24 hours +# send a mail to root (it is forwarded) but only once every 24 hours # 'cause we only really need "new" news itsnews="yes" age_of_news=0 if [ -f "$LTOP"/bad_runs ]; then age_of_news=$(fileage "$LTOP"/bad_runs) # only consider news "new" if we have not mailed today - if ((age_of_news < 24*3600)); then + if ((age_of_news < 24 * 3600)); then itsnews="no" fi fi diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl index 32080b21f6f6..23278e6de665 100755 --- a/tests/ftpserver.pl +++ b/tests/ftpserver.pl @@ -106,7 +106,7 @@ BEGIN #********************************************************************** # global vars used for filenames # -my $PORTFILE="ftpserver.port"; # server port filename +my $PORTFILE = "ftpserver.port"; # server port filename my $portfile; # server port file path my $pidfile; # server pid filename my $mainsockf_pidfile; # pid file for primary connection sockfilt process @@ -171,7 +171,7 @@ BEGIN # $ftptargetdir is keeping the fake "name" of LIST directory. # my $ftplistparserstate; -my $ftptargetdir=""; +my $ftptargetdir = ""; #********************************************************************** # global variables used when running an FTP server to keep state info @@ -180,9 +180,9 @@ BEGIN # that they are closely related and relationship is a bit awkward. # my $datasockf_state = 'STOPPED'; # see datasockf_state() sub -my $datasockf_mode = 'none'; # ['none','active','passive'] -my $datasockf_runs = 'no'; # ['no','yes'] -my $datasockf_conn = 'no'; # ['no','yes'] +my $datasockf_mode = 'none'; # ['none', 'active', 'passive'] +my $datasockf_runs = 'no'; # ['no', 'yes'] +my $datasockf_conn = 'no'; # ['no', 'yes'] #********************************************************************** # global vars used for signal handling @@ -196,9 +196,9 @@ BEGIN my $POP3_TIMESTAMP = "<1972.987654321\@curl>"; #********************************************************************** -# exit_signal_handler will be triggered to indicate that the program +# exit_signal_handler is triggered to indicate that the program # should finish its execution in a controlled way as soon as possible. -# For now, program will also terminate from within this handler. +# For now, program also terminates from within this handler. # sub exit_signal_handler { my $signame = shift; @@ -215,7 +215,7 @@ sub exit_signal_handler { sub ftpmsg { # append to the server.input file - open(my $input, ">>", "$logdir/server$idstr.input") || + open(my $input, ">>", "$logdir/server$idstr.input") or logmsg "failed to open $logdir/server$idstr.input\n"; print $input @_; @@ -227,9 +227,9 @@ sub ftpmsg { } #********************************************************************** -# eXsysread is a wrapper around perl's sysread() function. This will -# repeat the call to sysread() until it has actually read the complete -# number of requested bytes or an unrecoverable condition occurs. +# eXsysread is a wrapper around perl's sysread() function. This repeats +# the call to sysread() until it has actually read the complete number +# of requested bytes or an unrecoverable condition occurs. # On success returns a positive value, the number of bytes requested. # On failure or timeout returns zero. # @@ -330,7 +330,7 @@ sub read_mainsockf { $timeout = $sockfilt_timeout + ($nbytes >> 12); } if(eXsysread($FH, $scalar, $nbytes, $timeout) != $nbytes) { - my ($fcaller, $lcaller) = (caller)[1,2]; + my ($fcaller, $lcaller) = (caller)[1, 2]; logmsg "Error: read_mainsockf() failure at $fcaller " . "line $lcaller. Due to eXsysread() failure\n"; return 0; @@ -354,7 +354,7 @@ sub read_datasockf { $timeout = $sockfilt_timeout + ($nbytes >> 12); } if(eXsysread($FH, $scalar, $nbytes, $timeout) != $nbytes) { - my ($fcaller, $lcaller) = (caller)[1,2]; + my ($fcaller, $lcaller) = (caller)[1, 2]; logmsg "Error: read_datasockf() failure at $fcaller " . "line $lcaller. Due to eXsysread() failure\n"; return 0; @@ -373,7 +373,7 @@ sub sysread_or_die { $result = sysread($$FH, $$scalar, $length); if(not defined $result) { - ($fcaller, $lcaller) = (caller)[1,2]; + ($fcaller, $lcaller) = (caller)[1, 2]; logmsg "Failed to read input\n"; logmsg "Error: $srvrname server, sysread error: $!\n"; logmsg "Exited from sysread_or_die() at $fcaller " . @@ -388,7 +388,7 @@ sub sysread_or_die { exit; } elsif($result == 0) { - ($fcaller, $lcaller) = (caller)[1,2]; + ($fcaller, $lcaller) = (caller)[1, 2]; logmsg "Failed to read input\n"; logmsg "Error: $srvrname server, read zero\n"; logmsg "Exited from sysread_or_die() at $fcaller " . @@ -520,7 +520,7 @@ sub senddata { } else { # pause between each byte - for (split(//,$l)) { + for(split(//, $l)) { sockfiltsecondary $_; Time::HiRes::sleep($datadelay); } @@ -531,7 +531,7 @@ sub senddata { #********************************************************************** # protocolsetup initializes the 'displaytext' and 'commandfunc' hashes # for the given protocol. References to protocol command callbacks are -# stored in 'commandfunc' hash, and text which will be returned to the +# stored in 'commandfunc' hash, and text which is returned to the # client before the command callback runs is stored in 'displaytext'. # sub protocolsetup { @@ -562,8 +562,8 @@ sub protocolsetup { 'LIST' => '150 here comes a directory', 'NLST' => '150 here comes a directory', 'CWD' => '250 CWD command successful.', - 'SYST' => '215 UNIX Type: L8', # just fake something - 'QUIT' => '221 bye bye baby', # just reply something + 'SYST' => '215 UNIX Type: L8', # fake something + 'QUIT' => '221 bye bye baby', # reply something 'MKD' => '257 Created your requested directory', 'REST' => '350 Yeah yeah we set it there for you', 'DELE' => '200 OK OK OK whatever you say', @@ -712,7 +712,7 @@ sub disc_handshake { } sub close_dataconn { - my ($closed)=@_; # non-zero if already disconnected + my ($closed) = @_; # non-zero if already disconnected my $datapid = processexists($datasockf_pidfile); @@ -940,12 +940,12 @@ sub DATA_smtp { logmsg "Store test number $testno in $filename\n"; - open(my $file, ">", $filename) || + open(my $file, ">", $filename) or return 0; # failed to open output my $line; - my $ulsize=0; - my $disc=0; + my $ulsize = 0; + my $disc = 0; my $raw; while(5 == (sysread \*SFREAD, $line, 5)) { if($line eq "DATA\n") { @@ -978,7 +978,7 @@ sub DATA_smtp { } elsif($line eq "DISC\n") { # disconnect! - $disc=1; + $disc = 1; printf SFWRITE "ACKD\n"; last; } @@ -1143,7 +1143,7 @@ sub QUIT_smtp { my $selected; # Any IMAP parameter can come in escaped and in double quotes. -# This function is dumb (so far) and just removes the quotes if present. +# This function is dumb (so far) and removes the quotes if present. sub fix_imap_params { foreach (@_) { $_ = $1 if /^"(.*)"$/; @@ -1252,7 +1252,7 @@ sub FETCH_imap { @data = getreplydata($selected); } - for (@data) { + for(@data) { $size += length($_); } @@ -1290,7 +1290,7 @@ sub APPEND_imap { logmsg "Store test number $testno in $filename\n"; - open(my $file, ">", $filename) || + open(my $file, ">", $filename) or return 0; # failed to open output my $received = 0; @@ -2018,7 +2018,7 @@ sub QUIT_pop3 { ################ ################ FTP commands ################ -my $rest=0; +my $rest = 0; sub REST_ftp { $rest = $_[0]; logmsg "Set REST position to $rest\n" @@ -2135,7 +2135,7 @@ sub LIST_ftp { } sub NLST_ftp { - my @ftpdir=("file", "with space", "fake", "..", " ..", "funny", "README"); + my @ftpdir = ("file", "with space", "fake", "..", " ..", "funny", "README"); if($datasockf_conn eq 'no') { if($nodataconn425) { @@ -2242,7 +2242,7 @@ sub SIZE_ftp { } } else { - $size=0; + $size = 0; @data = getpart("reply", "data$testpart"); for(@data) { $size += length($_); @@ -2320,7 +2320,7 @@ sub RETR_ftp { my @data = getpart("reply", "data$testpart"); - my $size=0; + my $size = 0; for(@data) { $size += length($_); } @@ -2344,7 +2344,7 @@ sub RETR_ftp { senddata $send; } close_dataconn(0); - $retrweirdo=0; # switch off the weirdo again! + $retrweirdo = 0; # switch off the weirdo again! } else { my $sz = "($size bytes)"; @@ -2372,7 +2372,7 @@ sub RETR_ftp { } sub STOR_ftp { - my $testno=$_[0]; + my $testno = $_[0]; my $filename = "$logdir/upload.$testno"; @@ -2399,12 +2399,12 @@ sub STOR_ftp { sendcontrol "125 Gimme gimme gimme!\r\n"; - open(my $file, ">", $filename) || + open(my $file, ">", $filename) or return 0; # failed to open output my $line; - my $ulsize=0; - my $disc=0; + my $ulsize = 0; + my $disc = 0; while(5 == (sysread DREAD, $line, 5)) { if($line eq "DATA\n") { my $i; @@ -2425,7 +2425,7 @@ sub STOR_ftp { } elsif($line eq "DISC\n") { # disconnect! - $disc=1; + $disc = 1; printf DWRITE "ACKD\n"; last; } @@ -2454,7 +2454,7 @@ sub STOR_ftp { } sub PASV_ftp { - my ($arg, $cmd)=@_; + my ($arg, $cmd) = @_; my $pasvport; # kill previous data connection sockfilt when alive @@ -2561,21 +2561,21 @@ sub PASV_ftp { if($cmd ne "EPSV") { # PASV reply - my $p=$listenaddr; + my $p = $listenaddr; $p =~ s/\./,/g; if($pasvbadip) { - $p="1,2,3,4"; + $p = "1,2,3,4"; } sendcontrol sprintf("227 Entering Passive Mode ($p,%d,%d)\r\n", - int($pasvport/256), int($pasvport%256)); + int($pasvport / 256), int($pasvport % 256)); } else { # EPSV reply sendcontrol sprintf("229 Entering Passive Mode (|||%d|)\r\n", $pasvport); } - logmsg "Client has been notified that DATA conn ". - "will be accepted on port $pasvport\n"; + logmsg "Client has been notified that upcoming DATA connection ". + "is awaited on port $pasvport\n"; if($nodataconn) { my $str = nodataconn_str(); @@ -2765,7 +2765,7 @@ sub datasockf_state { } elsif($state eq 'PASSIVE_NODATACONN') { # Data sockfilter bound port without listening, - # client will not be able to establish data connection. + # client is unable to establish data connection. $datasockf_state = $state; $datasockf_mode = 'passive'; $datasockf_runs = 'yes'; @@ -2826,23 +2826,23 @@ sub customize { %customcount = (); # %delayreply = (); # - open(my $custom, "<", "$logdir/$SERVERCMD") || + open(my $custom, "<", "$logdir/$SERVERCMD") or return 1; logmsg "FTPD: Getting commands from $logdir/$SERVERCMD\n"; while(<$custom>) { if($_ =~ /REPLY \"([A-Z]+ [A-Za-z0-9+-\/=\*. ]+)\" (.*)/) { - $fulltextreply{$1}=eval "qq{$2}"; + $fulltextreply{$1} = eval "qq{$2}"; logmsg "FTPD: set custom reply for $1\n"; } elsif($_ =~ /REPLY(LF|) ([A-Za-z0-9+\/=\*]*) (.*)/) { - $commandreply{$2}=eval "qq{$3}"; + $commandreply{$2} = eval "qq{$3}"; if($1 ne "LF") { - $commandreply{$2}.="\r\n"; + $commandreply{$2} .= "\r\n"; } else { - $commandreply{$2}.="\n"; + $commandreply{$2} .= "\n"; } if($2 eq "") { logmsg "FTPD: set custom reply for empty command\n"; @@ -2854,11 +2854,11 @@ sub customize { elsif($_ =~ /COUNT ([A-Z]+) (.*)/) { # we blank the custom reply for this command when having # been used this number of times - $customcount{$1}=$2; + $customcount{$1} = $2; logmsg "FTPD: blank custom reply for $1 command after $2 uses\n"; } elsif($_ =~ /DELAY ([A-Z]+) (\d*)/) { - $delayreply{$1}=$2; + $delayreply{$1} = $2; logmsg "FTPD: delay reply for $1 with $2 seconds\n"; } elsif($_ =~ /POSTFETCH (.*)/) { @@ -2866,56 +2866,56 @@ sub customize { $postfetch = $1; } elsif($_ =~ /SLOWDOWNDATA/) { - $ctrldelay=0; - $datadelay=0.005; + $ctrldelay = 0; + $datadelay = 0.005; logmsg "FTPD: send response data with 5ms delay per byte\n"; } elsif($_ =~ /SLOWDOWN/) { - $ctrldelay=0.005; - $datadelay=0.005; + $ctrldelay = 0.005; + $datadelay = 0.005; logmsg "FTPD: send response with 5ms delay between each byte\n"; } elsif($_ =~ /RETRWEIRDO/) { logmsg "FTPD: instructed to use RETRWEIRDO\n"; - $retrweirdo=1; + $retrweirdo = 1; } elsif($_ =~ /RETRNOSIZE/) { logmsg "FTPD: instructed to use RETRNOSIZE\n"; - $retrnosize=1; + $retrnosize = 1; } elsif($_ =~ /RETRSIZE (\d+)/) { - $retrsize= $1; + $retrsize = $1; logmsg "FTPD: instructed to use RETRSIZE = $1\n"; } elsif($_ =~ /PASVBADIP/) { logmsg "FTPD: instructed to use PASVBADIP\n"; - $pasvbadip=1; + $pasvbadip = 1; } elsif($_ =~ /NODATACONN425/) { # applies to both active and passive FTP modes logmsg "FTPD: instructed to use NODATACONN425\n"; - $nodataconn425=1; - $nodataconn=1; + $nodataconn425 = 1; + $nodataconn = 1; } elsif($_ =~ /NODATACONN421/) { # applies to both active and passive FTP modes logmsg "FTPD: instructed to use NODATACONN421\n"; - $nodataconn421=1; - $nodataconn=1; + $nodataconn421 = 1; + $nodataconn = 1; } elsif($_ =~ /NODATACONN150/) { # applies to both active and passive FTP modes logmsg "FTPD: instructed to use NODATACONN150\n"; - $nodataconn150=1; - $nodataconn=1; + $nodataconn150 = 1; + $nodataconn = 1; } elsif($_ =~ /NODATACONN/) { # applies to both active and passive FTP modes logmsg "FTPD: instructed to use NODATACONN\n"; - $nodataconn=1; + $nodataconn = 1; } elsif($_ =~ /^STOR (.*)/) { - $storeresp=$1; + $storeresp = $1; logmsg "FTPD: instructed to use respond to STOR with '$storeresp'\n"; } elsif($_ =~ /CAPA (.*)/) { @@ -3142,7 +3142,8 @@ sub customize { $| = 1; &customize(); # read test control instructions - loadtest("$logdir/test$testno"); + # force a reload, a test retry may have regenerated the file + loadtest("$logdir/test$testno", 0, 1); my $welcome = $commandreply{"welcome"}; if(!$welcome) { @@ -3150,7 +3151,7 @@ sub customize { } else { # clear it after use - $commandreply{"welcome"}=""; + $commandreply{"welcome"} = ""; if($welcome !~ /\r\n\z/) { $welcome .= "\r\n"; } @@ -3217,21 +3218,21 @@ sub customize { # IMAP is different with its identifier first on the command line if(($full =~ /^([^ ]+) ([^ ]+) (.*)/) || ($full =~ /^([^ ]+) ([^ ]+)/)) { - $cmdid=$1; # set the global variable - $FTPCMD=$2; - $FTPARG=$3; + $cmdid = $1; # set the global variable + $FTPCMD = $2; + $FTPARG = $3; } # IMAP authentication cancellation elsif($full =~ /^\*$/) { # Command id has already been set - $FTPCMD="*"; - $FTPARG=""; + $FTPCMD = "*"; + $FTPARG = ""; } # IMAP long "commands" are base64 authentication data elsif($full =~ /^[A-Z0-9+\/]*={0,2}$/i) { # Command id has already been set - $FTPCMD=$full; - $FTPARG=""; + $FTPCMD = $full; + $FTPARG = ""; } else { sendcontrol "$full BAD Command\r\n"; @@ -3239,19 +3240,19 @@ sub customize { } } elsif($full =~ /^([A-Z]{3,4})(\s(.*))?$/i) { - $FTPCMD=$1; - $FTPARG=$3; + $FTPCMD = $1; + $FTPARG = $3; } elsif($proto eq "pop3") { # POP3 authentication cancellation if($full =~ /^\*$/) { - $FTPCMD="*"; - $FTPARG=""; + $FTPCMD = "*"; + $FTPARG = ""; } # POP3 long "commands" are base64 authentication data elsif($full =~ /^[A-Z0-9+\/]*={0,2}$/i) { - $FTPCMD=$full; - $FTPARG=""; + $FTPCMD = $full; + $FTPARG = ""; } else { sendcontrol "-ERR Unrecognized command\r\n"; @@ -3261,13 +3262,13 @@ sub customize { elsif($proto eq "smtp") { # SMTP authentication cancellation if($full =~ /^\*$/) { - $FTPCMD="*"; - $FTPARG=""; + $FTPCMD = "*"; + $FTPARG = ""; } # SMTP long "commands" are base64 authentication data elsif($full =~ /^[A-Z0-9+\/]{0,512}={0,2}$/i) { - $FTPCMD=$full; - $FTPARG=""; + $FTPCMD = $full; + $FTPARG = ""; } else { sendcontrol "500 Unrecognized command\r\n"; @@ -3289,7 +3290,7 @@ sub customize { my $delay = $delayreply{$FTPCMD}; if($delay) { - # just go sleep this many seconds! + # go sleep this many seconds! logmsg("Sleep for $delay seconds\n"); my $twentieths = $delay * 20; while($twentieths--) { @@ -3312,7 +3313,7 @@ sub customize { if($text && ($text ne "")) { if($customcount{$FTPCMD} && (!--$customcount{$FTPCMD})) { # used enough times so blank the custom command reply - $commandreply{$FTPCMD}=""; + $commandreply{$FTPCMD} = ""; } sendcontrol $text; diff --git a/tests/getpart.pm b/tests/getpart.pm index 5fa4b1e62b34..7bdf9817460d 100644 --- a/tests/getpart.pm +++ b/tests/getpart.pm @@ -44,17 +44,17 @@ BEGIN { ); } -use Memoize; +use Memoize qw(memoize flush_cache); my @xml; # test data file contents my $xmlfile; # test data filename -my $warning=0; -my $trace=0; +my $warning = 0; +my $trace = 0; # Normalize the part function arguments for proper caching. This includes the # filename in the arguments since that is an implied parameter that affects the -# return value. Any error messages will only be displayed the first time, but +# return value. Any error messages are only displayed the first time, but # those are disabled by default anyway, so should never been seen outside # development. sub normalize_part { @@ -66,12 +66,12 @@ sub testcaseattr { my %hash; for(@xml) { if(($_ =~ /^ *\]*)/)) { - my $attr=$1; + my $attr = $1; while($attr =~ s/ *([^=]*)= *(\"([^\"]*)\"|\'([^\']*)\')//) { - my ($var, $cont)=($1, $2); + my ($var, $cont) = ($1, $2); $cont =~ s/^\"(.*)\"$/$1/; $cont =~ s/^\'(.*)\'$/$1/; - $hash{$var}=$cont; + $hash{$var} = $cont; } } } @@ -82,10 +82,10 @@ sub getpartattr { # if $part is undefined (ie only one argument) then # return the attributes of the section - my ($section, $part)=@_; + my ($section, $part) = @_; my %hash; - my $inside=0; + my $inside = 0; # print "Section: $section, part: $part\n"; @@ -98,13 +98,13 @@ sub getpartattr { !(defined($part))) ) { $inside++; - my $attr=$1; + my $attr = $1; while($attr =~ s/ *([^=]*)= *(\"([^\"]*)\"|\'([^\']*)\')//) { - my ($var, $cont)=($1, $2); + my ($var, $cont) = ($1, $2); $cont =~ s/^\"(.*)\"$/$1/; $cont =~ s/^\'(.*)\'$/$1/; - $hash{$var}=$cont; + $hash{$var} = $cont; } last; } @@ -121,10 +121,10 @@ sub getpartattr { memoize('getpartattr', NORMALIZER => 'normalize_part'); # cache each result sub getpart { - my ($section, $part)=@_; + my ($section, $part) = @_; my @this; - my $inside=0; + my $inside = 0; my $line; for(@xml) { @@ -176,7 +176,7 @@ sub getpart { memoize('getpart', NORMALIZER => 'normalize_part'); # cache each result sub partexists { - my ($section, $part)=@_; + my ($section, $part) = @_; my $inside = 0; @@ -195,13 +195,13 @@ sub partexists { } # The code currently never calls this more than once per part per file, so -# caching a result that will never be used again just slows things down. +# caching a result that is never used again only slows things down. # memoize('partexists', NORMALIZER => 'normalize_part'); # cache each result sub loadtest { - my ($file, $original)=@_; + my ($file, $original, $force) = @_; - if(defined $xmlfile && $file eq $xmlfile) { + if(!$force && defined $xmlfile && $file eq $xmlfile) { # This test is already loaded return } @@ -209,6 +209,11 @@ sub loadtest { undef @xml; $xmlfile = ""; + # flush results cached from an earlier load, the file may have been + # regenerated since with new variable substitutions + flush_cache('getpart'); + flush_cache('getpartattr'); + if(open(my $xmlh, "<", $file)) { if($original) { binmode $xmlh, ':crlf'; @@ -288,7 +293,7 @@ sub checktest { # write the test to the given file sub savetest { - my ($file)=@_; + my ($file) = @_; if(open(my $xmlh, ">", $file)) { binmode $xmlh; # for crapage systems, use binary @@ -328,7 +333,7 @@ sub striparray { # pass array *REFERENCES* ! # sub compareparts { - my ($firstref, $secondref)=@_; + my ($firstref, $secondref) = @_; # we cannot compare arrays index per index since with data chunks, # they may not be "evenly" distributed @@ -384,21 +389,21 @@ sub compareparts { # Write a given array to the specified file # sub writearray { - my ($filename, $arrayref)=@_; + my ($filename, $arrayref) = @_; - open(my $temp, ">", $filename) || die "Failure writing file"; - binmode($temp,":raw"); # Cygwin fix + open(my $temp, ">", $filename) or die "Failure writing file"; + binmode($temp, ":raw"); # Cygwin fix for(@$arrayref) { print $temp $_; } - close($temp) || die "Failure writing file"; + close($temp) or die "Failure writing file"; } # # Load a specified file and return it as an array # sub loadarray { - my ($filename)=@_; + my ($filename) = @_; my @array; if(open(my $temp, "<", $filename)) { diff --git a/tests/globalconfig.pm b/tests/globalconfig.pm index d99d4306cf2b..9b3d1bf76c76 100644 --- a/tests/globalconfig.pm +++ b/tests/globalconfig.pm @@ -90,54 +90,54 @@ use File::Spec; # # config variables overridden by command-line options -our $verbose; # 1 to show verbose test output -our $torture; # 1 to enable torture testing -our $proxy_address; # external HTTP proxy address -our $listonly; # only list the tests -our $buildinfo; # dump buildinfo.txt -our $run_duphandle; # run curl with --test-duphandle to verify handle duplication -our $run_event_based; # run curl with --test-event to test the event API -our $automakestyle; # use automake-like test status output format -our $anyway; # continue anyway, even if a test fail -our $CURLVERSION=""; # curl's reported version number -our $CURLVERNUM=""; # curl's reported version number (without -DEV) -our $randseed = 0; # random number seed -our $maxtime; # curl command timeout override -our $mintotal; # minimum number of tests to run +our $verbose; # 1 to show verbose test output +our $torture; # 1 to enable torture testing +our $proxy_address; # external HTTP proxy address +our $listonly; # only list the tests +our $buildinfo; # dump buildinfo.txt +our $run_duphandle; # run curl with --test-duphandle to verify handle duplication +our $run_event_based; # run curl with --test-event to test the event API +our $automakestyle; # use automake-like test status output format +our $anyway; # continue anyway, even if a test fail +our $CURLVERSION = ""; # curl's reported version number +our $CURLVERNUM = ""; # curl's reported version number (without -DEV) +our $randseed = 0; # random number seed +our $maxtime; # curl command timeout override +our $mintotal; # minimum number of tests to run # paths our $pwd = getcwd(); # current working directory our $srcdir = $ENV{'srcdir'} || '.'; # root of the test source code -our $perlcmd=shell_quote($^X); -our $perl="$perlcmd -I. " . shell_quote("-I$srcdir"); # invoke perl like this -our $LOGDIR="log"; # root of the log directory; this will be different for - # each runner in multiprocess mode -our $LIBDIR=dirsepadd("./libtest/" . ($ENV{'CURL_DIRSUFFIX'} || '')); -our $UNITDIR=dirsepadd("./unit/" . ($ENV{'CURL_DIRSUFFIX'} || '')); -our $TUNITDIR=dirsepadd("./tunit/" . ($ENV{'CURL_DIRSUFFIX'} || '')); -our $SRVDIR=dirsepadd("./server/" . ($ENV{'CURL_DIRSUFFIX'} || '')); -our $TESTDIR="$srcdir/data"; -our $CURL=dirsepadd("../src/" . ($ENV{'CURL_DIRSUFFIX'} || '')) . +our $perlcmd = shell_quote($^X); +our $perl = "$perlcmd -I. " . shell_quote("-I$srcdir"); # invoke perl like this +our $LOGDIR = "log"; # root of the log directory; this is different for + # each runner in multiprocess mode +our $LIBDIR = dirsepadd("./libtest/" . ($ENV{'CURL_DIRSUFFIX'} || '')); +our $UNITDIR = dirsepadd("./unit/" . ($ENV{'CURL_DIRSUFFIX'} || '')); +our $TUNITDIR = dirsepadd("./tunit/" . ($ENV{'CURL_DIRSUFFIX'} || '')); +our $SRVDIR = dirsepadd("./server/" . ($ENV{'CURL_DIRSUFFIX'} || '')); +our $TESTDIR = "$srcdir/data"; +our $CURL = dirsepadd("../src/" . ($ENV{'CURL_DIRSUFFIX'} || '')) . "curl".exe_ext('TOOL'); # what curl binary to run on the tests -our $CURLINFO=dirsepadd("../src/" . ($ENV{'CURL_DIRSUFFIX'} || '')) . +our $CURLINFO = dirsepadd("../src/" . ($ENV{'CURL_DIRSUFFIX'} || '')) . "curlinfo".exe_ext('TOOL'); # what curlinfo binary to run on the tests -our $VCURL=$CURL; # what curl binary to use to verify the servers with - # VCURL is handy to set to the system one when the one you - # just built hangs or crashes and thus prevent verification +our $VCURL = $CURL; # what curl binary to use to verify the servers with + # VCURL is handy to set to the system one when the one you + # built hangs or crashes and thus prevent verification # the path to the script that analyzes the memory debug output file -our $memanalyze="$perl " . shell_quote("$srcdir/memanalyze.pl"); +our $memanalyze = "$perl " . shell_quote("$srcdir/memanalyze.pl"); our $valgrind; # path to valgrind, or empty if disabled our $dev_null = File::Spec->devnull(); # null device path, eg: /dev/null # paths in $LOGDIR -our $LOCKDIR = "lock"; # root of the server directory with lock files -our $PIDDIR = "server"; # root of the server directory with PID files -our $SERVERIN="server.input"; # what curl sent the server -our $PROXYIN="proxy.input"; # what curl sent the proxy -our $MEMDUMP="memdump"; # file that the memory debugging creates -our $SERVERCMD="server.cmd"; # copy server instructions here -our $DNSCMD="dnsd.cmd"; # write DNS instructions here +our $LOCKDIR = "lock"; # root of the server directory with lock files +our $PIDDIR = "server"; # root of the server directory with PID files +our $SERVERIN = "server.input"; # what curl sent the server +our $PROXYIN = "proxy.input"; # what curl sent the proxy +our $MEMDUMP = "memdump"; # file that the memory debugging creates +our $SERVERCMD = "server.cmd"; # copy server instructions here +our $DNSCMD = "dnsd.cmd"; # write DNS instructions here # other config variables our @protocols; # array of lowercase supported protocol servers diff --git a/tests/http-server.pl b/tests/http-server.pl index 006b6f381781..14c66a8b42a6 100755 --- a/tests/http-server.pl +++ b/tests/http-server.pl @@ -40,7 +40,7 @@ BEGIN ); my $verbose = 0; # set to 1 for debugging -my $port = 8990; # just a default +my $port = 8990; # a default my $unix_socket; # location to place a listening Unix socket my $ipvnum = 4; # default IP version of http server my $idnum = 1; # default http server instance number diff --git a/tests/http/CMakeLists.txt b/tests/http/CMakeLists.txt index 3373f8af2b39..9801d51907c5 100644 --- a/tests/http/CMakeLists.txt +++ b/tests/http/CMakeLists.txt @@ -28,6 +28,12 @@ if(NOT CADDY) endif() mark_as_advanced(CADDY) +find_program(H2O "h2o") # /usr/local/bin/h2o +if(NOT H2O) + set(H2O "") +endif() +mark_as_advanced(H2O) + find_program(VSFTPD "vsftpd") # /usr/sbin/vsftpd if(NOT VSFTPD) set(VSFTPD "") diff --git a/tests/http/Makefile.am b/tests/http/Makefile.am index f4dc92f61b06..4f2fad7d02d7 100644 --- a/tests/http/Makefile.am +++ b/tests/http/Makefile.am @@ -31,13 +31,15 @@ TESTENV = \ testenv/dnsd.py \ testenv/dante.py \ testenv/env.py \ + testenv/h2o.py \ testenv/httpd.py \ testenv/mod_curltest/mod_curltest.c \ testenv/nghttpx.py \ testenv/ports.py \ testenv/sshd.py \ testenv/vsftpd.py \ - testenv/ws_echo_server.py + testenv/ws_echo_server.py \ + testenv/ws_4frames_server.py EXTRA_DIST = \ CMakeLists.txt \ @@ -72,10 +74,11 @@ EXTRA_DIST = \ test_40_socks.py \ test_50_scp.py \ test_51_sftp.py \ + test_60_h3_proxy.py \ $(TESTENV) clean-local: - rm -rf *.pyc __pycache__ + rm -rf ./*.pyc __pycache__ rm -rf gen check: libtests diff --git a/tests/http/config.ini.in b/tests/http/config.ini.in index 78808e966db9..daf9869b7cda 100644 --- a/tests/http/config.ini.in +++ b/tests/http/config.ini.in @@ -44,3 +44,6 @@ danted = @DANTED@ [sshd] sshd = @SSHD@ sftpd = @SFTPD@ + +[h2o] +h2o = @H2O@ diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 08da73ac0fdf..31a3d55b6487 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -1,4 +1,4 @@ -#*************************************************************************** +# *************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -31,9 +31,10 @@ import pytest from testenv.env import EnvConfig -sys.path.append(os.path.join(os.path.dirname(__file__), '.')) +sys.path.append(os.path.join(os.path.dirname(__file__), ".")) from testenv import Env, Httpd, Nghttpx, NghttpxFwd, NghttpxQuic, Sshd +from testenv.h2o import H2oProxy, H2oServer log = logging.getLogger(__name__) @@ -42,51 +43,47 @@ def pytest_report_header(config): # Env inits its base properties only once, we can report them here env = Env() report = [ - f'Testing curl {env.curl_version()}', - f' platform: {platform.platform()}', - f' curl: Version: {env.curl_version_string()}', - f' curl: Features: {env.curl_features_string()}', - f' curl: Protocols: {env.curl_protocols_string()}', - f' httpd: {env.httpd_version()}', - f' httpd-proxy: {env.httpd_version()}' + f"Testing curl {env.curl_version()}", + f" platform: {platform.platform()}", + f" curl: Version: {env.curl_version_string()}", + f" curl: Features: {env.curl_features_string()}", + f" curl: Protocols: {env.curl_protocols_string()}", + f" httpd: {env.httpd_version()}", + f" httpd-proxy: {env.httpd_version()}", ] if env.have_h3(): - report.extend([ - f' nghttpx: {env.nghttpx_version()}' - ]) + report.extend([f" nghttpx: {env.nghttpx_version()}"]) + if env.have_h2o(): + report.extend([f" h2o: {env.h2o_version()}"]) if env.has_caddy(): - report.extend([ - f' Caddy: {env.caddy_version()}' - ]) + report.extend([f" Caddy: {env.caddy_version()}"]) if env.has_vsftpd(): - report.extend([ - f' VsFTPD: {env.vsftpd_version()}' - ]) - buildinfo_fn = os.path.join(env.build_dir, 'buildinfo.txt') + report.extend([f" VsFTPD: {env.vsftpd_version()}"]) + buildinfo_fn = os.path.join(env.build_dir, "buildinfo.txt") if os.path.exists(buildinfo_fn): - with open(buildinfo_fn, 'r') as file_in: + with open(buildinfo_fn, "r") as file_in: for line in file_in: line = line.strip() - if line and not line.startswith('#'): + if line and not line.startswith("#"): report.extend([line]) - return '\n'.join(report) + return "\n".join(report) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def env_config(pytestconfig, testrun_uid, worker_id) -> EnvConfig: - return EnvConfig(pytestconfig=pytestconfig, - testrun_uid=testrun_uid, - worker_id=worker_id) + return EnvConfig( + pytestconfig=pytestconfig, testrun_uid=testrun_uid, worker_id=worker_id + ) -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def env(pytestconfig, env_config) -> Env: env = Env(pytestconfig=pytestconfig, env_config=env_config) level = logging.DEBUG if env.verbose > 0 else logging.INFO - logging.getLogger('').setLevel(level=level) - if not env.curl_has_protocol('http'): + logging.getLogger("").setLevel(level=level) + if not env.curl_has_protocol("http"): pytest.skip("curl built without HTTP support") - if not env.curl_has_protocol('https'): + if not env.curl_has_protocol("https"): pytest.skip("curl built without HTTPS support") if env.setup_incomplete(): pytest.skip(env.incomplete_reason()) @@ -95,23 +92,21 @@ def env(pytestconfig, env_config) -> Env: return env -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def httpd(env) -> Generator[Httpd, None, None]: httpd = Httpd(env=env) if not httpd.exists(): - pytest.skip(f'httpd not found: {env.httpd}') + pytest.skip(f"httpd not found: {env.httpd}") httpd.clear_logs() assert httpd.initial_start() yield httpd httpd.stop() -@pytest.fixture(scope='session') -def nghttpx(env, httpd) -> Generator[Union[Nghttpx,bool], None, None]: +@pytest.fixture(scope="session") +def nghttpx(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: nghttpx = NghttpxQuic(env=env) - if nghttpx.exists(): - if not nghttpx.supports_h3() and env.have_h3_curl(): - log.warning('nghttpx does not support QUIC, but curl does') + if nghttpx.exists() and nghttpx.supports_h3() and env.have_h3_curl(): nghttpx.clear_logs() assert nghttpx.initial_start() yield nghttpx @@ -120,8 +115,8 @@ def nghttpx(env, httpd) -> Generator[Union[Nghttpx,bool], None, None]: yield False -@pytest.fixture(scope='session') -def nghttpx_fwd(env, httpd) -> Generator[Union[Nghttpx,bool], None, None]: +@pytest.fixture(scope="session") +def nghttpx_fwd(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: nghttpx = NghttpxFwd(env=env) if nghttpx.exists(): nghttpx.clear_logs() @@ -132,37 +127,67 @@ def nghttpx_fwd(env, httpd) -> Generator[Union[Nghttpx,bool], None, None]: yield False -@pytest.fixture(scope='session') -def sshd(env: Env) -> Generator[Union[Sshd,bool], None, None]: +@pytest.fixture(scope="session") +def sshd(env: Env) -> Generator[Union[Sshd, bool], None, None]: if env.has_sshd(): sshd = Sshd(env=env) - assert sshd.initial_start(), f'{sshd.dump_log()}' + assert sshd.initial_start(), f"{sshd.dump_log()}" yield sshd sshd.stop() else: yield False -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def configures_httpd(env, httpd) -> Generator[bool, None, None]: # include this fixture as test parameter if the test configures httpd itself yield True -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def configures_nghttpx(env, httpd) -> Generator[bool, None, None]: # include this fixture as test parameter if the test configures nghttpx itself yield True -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True, scope="function") def server_reset(request, env, httpd, nghttpx): # make sure httpd is in default configuration when a test starts - if 'configures_httpd' not in request.node._fixtureinfo.argnames: + if "configures_httpd" not in request.node._fixtureinfo.argnames: httpd.reset_config() httpd.reload_if_config_changed() - if env.have_h3() and \ - 'nghttpx' in request.node._fixtureinfo.argnames and \ - 'configures_nghttpx' not in request.node._fixtureinfo.argnames: + if ( + env.have_h3() + and "nghttpx" in request.node._fixtureinfo.argnames + and "configures_nghttpx" not in request.node._fixtureinfo.argnames + ): nghttpx.reset_config() nghttpx.reload_if_config_changed() + + +@pytest.fixture(scope="session") +def h2o_server(env) -> Generator[Union[H2oServer, bool], None, None]: + h2o = H2oServer(env=env) + if env.have_h2o(): + h2o.clear_logs() + if not h2o.initial_start(): + h2o_logs = "\n".join(h2o.dump_logs()) + pytest.skip(f"h2o server failed to start\n{h2o_logs}") + yield h2o + h2o.kill() + else: + yield False + + +@pytest.fixture(scope="session") +def h2o_proxy(env) -> Generator[Union[H2oProxy, bool], None, None]: + h2o = H2oProxy(env=env) + if env.have_h2o(): + h2o.clear_logs() + if not h2o.initial_start(): + h2o_logs = "\n".join(h2o.dump_logs()) + pytest.skip(f"h2o proxy failed to start\n{h2o_logs}") + yield h2o + h2o.kill() + else: + yield False diff --git a/tests/http/requirements.txt b/tests/http/requirements.txt index feb0fedf81a2..cb577aaa4ed6 100644 --- a/tests/http/requirements.txt +++ b/tests/http/requirements.txt @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: curl -cryptography==46.0.7 -filelock==3.25.2 +cryptography==50.0.0 +filelock==3.32.4 psutil==7.2.2 -pytest==9.0.3 +pytest==9.1.1 pytest-xdist==3.8.0 -websockets==16.0 +websockets==17.0.1 diff --git a/tests/http/scorecard.py b/tests/http/scorecard.py old mode 100644 new mode 100755 index 205b556f61e2..55497cb90f24 --- a/tests/http/scorecard.py +++ b/tests/http/scorecard.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -40,6 +39,7 @@ Dante, Env, ExecResult, + H2oServer, Httpd, NghttpxQuic, RunProfile, @@ -55,14 +55,14 @@ class ScoreCardError(Exception): class Card: @classmethod def fmt_ms(cls, tval): - return f'{int(tval*1000)} ms' if tval >= 0 else '--' + return f'{int(tval * 1000)} ms' if tval >= 0 else '--' @classmethod def fmt_size(cls, val): - if val >= (1024*1024*1024): - return f'{val / (1024*1024*1024):0.000f}GB' + if val >= (1024 * 1024 * 1024): + return f'{val / (1024 * 1024 * 1024):0.000f}GB' if val >= (1024 * 1024): - return f'{val / (1024*1024):0.000f}MB' + return f'{val / (1024 * 1024):0.000f}MB' if val >= 1024: return f'{val / 1024:0.000f}KB' return f'{val:0.000f}B' @@ -71,8 +71,10 @@ def fmt_size(cls, val): def fmt_mbs(cls, val): if val is None or val < 0: return '--' - if val >= (1024*1024): - return f'{val/(1024*1024):.3g} MB/s' + if val >= (1024 * 1024 * 1024): + return f'{val / (1024 * 1024 * 1024):.3g} GB/s' + if val >= (1024 * 1024): + return f'{val / (1024 * 1024):.3g} MB/s' if val >= 1024: return f'{val / 1024:.3g} KB/s' return f'{val:.3g} B/s' @@ -81,10 +83,10 @@ def fmt_mbs(cls, val): def fmt_speed(cls, val): if val is None or val < 0: return '--' - if val >= (10*1024*1024): - return f'{(val/(1024*1024)):.3f} MB/s' - if val >= (10*1024): - return f'{val/1024:.3f} KB/s' + if val >= (10 * 1024 * 1024): + return f'{(val / (1024 * 1024)):.3f} MB/s' + if val >= (10 * 1024): + return f'{val / 1024:.3f} KB/s' return f'{val:.3f} B/s' @classmethod @@ -92,10 +94,10 @@ def fmt_speed_result(cls, val, limit): if val is None or val < 0: return '--' pct = ((val / limit) * 100) - 100 - if val >= (10*1024*1024): - return f'{(val/(1024*1024)):.3f} MB/s, {pct:+.1f}%' - if val >= (10*1024): - return f'{val/1024:.3f} KB/s, {pct:+.1f}%' + if val >= (10 * 1024 * 1024): + return f'{(val / (1024 * 1024)):.3f} MB/s, {pct:+.1f}%' + if val >= (10 * 1024): + return f'{val / 1024:.3f} KB/s, {pct:+.1f}%' return f'{val:.3f} B/s, {pct:+.1f}%' @classmethod @@ -145,7 +147,7 @@ def mk_reqs_cell(cls, samples, profiles, errors): def parse_size(cls, s): m = re.match(r'(\d+)(mb|kb|gb)?', s, re.IGNORECASE) if m is None: - raise Exception(f'unrecognized size: {s}') + raise ScoreCardError(f'unrecognized size: {s}') size = int(m.group(1)) if not m.group(2): pass @@ -194,7 +196,7 @@ def print_score_table(cls, score): errors = [] col_has_stats = [] for idx, col in enumerate(cols): - cellw = max([len(r[idx]["sval"]) for r in rows]) + cellw = max(len(r[idx]["sval"]) for r in rows) colw.append(max(cellw, len(col))) col_has_stats.append(False) for row in rows: @@ -208,20 +210,20 @@ def print_score_table(cls, score): print(f' {col:>{colw[idx]}} {"[cpu/rss]":<{statw}}', end='') else: print(f' {col:>{colw[idx]}}', end='') - print('') + print() for row in rows: for idx, cell in enumerate(row): print(f' {cell["sval"]:>{colw[idx]}}', end='') if col_has_stats[idx]: if 'stats' in cell: s = f'[{cell["stats"]["cpu"]:>.1f}%' \ - f'/{Card.fmt_size(cell["stats"]["rss"])}]' + f'/{Card.fmt_size(cell["stats"]["rss-max"])}]' else: s = '' print(f' {s:<{statw}}', end='') if 'errors' in cell: errors.extend(cell['errors']) - print('') + print() if len(errors): print(f'Errors: {errors}') @@ -240,6 +242,7 @@ def __init__(self, env: Env, with_flame: bool = False, socks_args: Optional[List[str]] = None, limit_rate: Optional[str] = None, + http_plain: bool = False, suppress_cl: bool = False): self.verbose = verbose self.env = env @@ -254,21 +257,23 @@ def __init__(self, env: Env, self._socks_args = socks_args self._limit_rate_num = 0 self._limit_rate = limit_rate + self._http_plain = http_plain + self._scheme = 'http' if http_plain else 'https' if self._limit_rate: m = re.match(r'(\d+(\.\d+)?)([gmkb])?', self._limit_rate.lower()) if not m: - raise Exception(f'unrecognised limit-rate: {self._limit_rate}') + raise ScoreCardError(f'unrecognised limit-rate: {self._limit_rate}') self._limit_rate_num = float(m.group(1)) if m.group(3) == 'g': - self._limit_rate_num *= 1024*1024*1024 + self._limit_rate_num *= 1024 * 1024 * 1024 elif m.group(3) == 'm': - self._limit_rate_num *= 1024*1024 + self._limit_rate_num *= 1024 * 1024 elif m.group(3) == 'k': self._limit_rate_num *= 1024 elif m.group(3) == 'b': pass else: - raise Exception(f'unrecognised limit-rate: {self._limit_rate}') + raise ScoreCardError(f'unrecognised limit-rate: {self._limit_rate}') self.suppress_cl = suppress_cl def info(self, msg): @@ -300,7 +305,7 @@ def handshakes(self) -> Dict[str, Any]: curl = self.mk_curl_client() args = [ '--http3-only' if self.protocol == 'h3' else '--http2', - f'--{ipv}', f'https://{authority}/' + f'--{ipv}', f'{self._scheme}://{authority}/' ] r = curl.run_direct(args=args, with_stats=True) if r.exit_code == 0 and len(r.stats) == 1: @@ -335,7 +340,7 @@ def setup_resources(self, server_docs: str, self._make_docs_file(docs_dir=server_docs, fname=fname, fsize=fsize) self._make_docs_file(docs_dir=server_docs, - fname='reqs10.data', fsize=10*1024) + fname='reqs10.data', fsize=10 * 1024) def _check_downloads(self, r: ExecResult, count: int): error = '' @@ -364,11 +369,11 @@ def dl_single(self, url: str, nsamples: int = 1): if err: errors.append(err) elif self._limit_rate: - total_speed = sum([s['speed_download'] for s in r.stats]) + total_speed = sum(s['speed_download'] for s in r.stats) samples.append(total_speed / len(r.stats)) profiles.append(r.profile) else: - total_size = sum([s['size_download'] for s in r.stats]) + total_size = sum(s['size_download'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) if self._limit_rate: @@ -392,11 +397,11 @@ def dl_serial(self, url: str, count: int, nsamples: int = 1): if err: errors.append(err) elif self._limit_rate: - total_speed = sum([s['speed_download'] for s in r.stats]) + total_speed = sum(s['speed_download'] for s in r.stats) samples.append(total_speed / len(r.stats)) profiles.append(r.profile) else: - total_size = sum([s['size_download'] for s in r.stats]) + total_size = sum(s['size_download'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) if self._limit_rate: @@ -425,11 +430,11 @@ def dl_parallel(self, url: str, count: int, nsamples: int = 1): if err: errors.append(err) elif self._limit_rate: - total_speed = sum([s['speed_download'] for s in r.stats]) + total_speed = sum(s['speed_download'] for s in r.stats) samples.append(total_speed / len(r.stats)) profiles.append(r.profile) else: - total_size = sum([s['size_download'] for s in r.stats]) + total_size = sum(s['size_download'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) if self._limit_rate: @@ -440,7 +445,7 @@ def downloads(self, count: int, fsizes: List[int], meta: Dict[str, Any]) -> Dict nsamples = meta['samples'] max_parallel = self._download_parallel if self._download_parallel > 0 else count cols = ['size'] - if not self._download_parallel: + if not self._download_parallel or count == 1: cols.append('single') if count > 1: cols.append(f'serial({count})') @@ -456,7 +461,7 @@ def downloads(self, count: int, fsizes: List[int], meta: Dict[str, Any]) -> Dict 'sval': Card.fmt_size(fsize) }] self.info(f'{row[0]["sval"]} downloads...') - url = f'https://{self.env.domain1}:{self.server_port}/score{row[0]["sval"]}.data' + url = f'{self._scheme}://{self.env.domain1}:{self.server_port}/score{row[0]["sval"]}.data' if 'single' in cols: row.append(self.dl_single(url=url, nsamples=nsamples)) if count > 1: @@ -468,7 +473,7 @@ def downloads(self, count: int, fsizes: List[int], meta: Dict[str, Any]) -> Dict if self._limit_rate: title = f'Download Speed ({self.protocol}), limit={Card.fmt_speed(self._limit_rate_num)}, from {meta["server"]}' else: - title = f'Downloads ({self.protocol})from {meta["server"]}' + title = f'Downloads ({self.protocol}) from {meta["server"]}' if self._socks_args: title += f' via {self._socks_args}' return { @@ -508,7 +513,7 @@ def ul_single(self, url: str, fpath: str, nsamples: int = 1): if err: errors.append(err) else: - total_size = sum([s['size_upload'] for s in r.stats]) + total_size = sum(s['size_upload'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) return Card.mk_mbs_cell(samples, profiles, errors) @@ -528,7 +533,7 @@ def ul_serial(self, url: str, fpath: str, count: int, nsamples: int = 1): if err: errors.append(err) else: - total_size = sum([s['size_upload'] for s in r.stats]) + total_size = sum(s['size_upload'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) return Card.mk_mbs_cell(samples, profiles, errors) @@ -537,7 +542,7 @@ def ul_parallel(self, url: str, fpath: str, count: int, nsamples: int = 1): samples = [] errors = [] profiles = [] - max_parallel = self._download_parallel if self._download_parallel > 0 else count + max_parallel = self._upload_parallel if self._upload_parallel > 0 else count url = f'{url}?id=[0-{count - 1}]' self.info('parallel...') for _ in range(nsamples): @@ -553,7 +558,7 @@ def ul_parallel(self, url: str, fpath: str, count: int, nsamples: int = 1): if err: errors.append(err) else: - total_size = sum([s['size_upload'] for s in r.stats]) + total_size = sum(s['size_upload'] for s in r.stats) samples.append(total_size / r.duration.total_seconds()) profiles.append(r.profile) return Card.mk_mbs_cell(samples, profiles, errors) @@ -578,7 +583,7 @@ def uploads(self, count: int, fsizes: List[int], meta: Dict[str, Any]) -> Dict[s 'sval': Card.fmt_size(fsize) }] self.info(f'{row[0]["sval"]} uploads...') - url = f'https://{self.env.domain1}:{self.server_port}/curltest/put' + url = f'{self._scheme}://{self.env.domain1}:{self.server_port}/curltest/put' fname = f'upload{row[0]["sval"]}.data' fpath = self._make_docs_file(docs_dir=self.env.gen_dir, fname=fname, fsize=fsize) @@ -635,19 +640,21 @@ def do_requests(self, url: str, count: int, max_parallel: int = 1, nsamples: int return Card.mk_reqs_cell(samples, profiles, errors) def requests(self, count: int, meta: Dict[str, Any]) -> Dict[str, Any]: - url = f'https://{self.env.domain1}:{self.server_port}/reqs10.data' - fsize = 10*1024 + url = f'{self._scheme}://{self.env.domain1}:{self.server_port}/reqs10.data' + fsize = 10 * 1024 cols = ['size', 'total'] rows = [] mparallel = meta['request_parallels'] cols.extend([f'{mp} max' for mp in mparallel]) - row = [{ - 'val': fsize, - 'sval': Card.fmt_size(fsize) - },{ - 'val': count, - 'sval': f'{count}', - }] + row = [ + { + 'val': fsize, + 'sval': Card.fmt_size(fsize) + }, { + 'val': count, + 'sval': f'{count}', + } + ] self.info('requests, max parallel...') row.extend([self.do_requests(url=url, count=count, max_parallel=mp, nsamples=meta["samples"]) @@ -686,7 +693,7 @@ def score(self, 'os': self.env.curl_os(), 'server': self.server_descr, 'samples': nsamples, - 'date': f'{datetime.datetime.now(tz=datetime.timezone.utc).isoformat()}', + 'date': f'{datetime.datetime.now(datetime.timezone.utc).isoformat()}', } } if self._limit_rate: @@ -771,22 +778,24 @@ def run_score(args, protocol): uploads = None requests = args.requests - test_httpd = protocol != 'h3' - test_caddy = protocol == 'h3' - if args.caddy or args.httpd: - test_caddy = args.caddy - test_httpd = args.httpd - rv = 0 env = Env() env.setup() env.test_timeout = None + test_httpd = protocol != 'h3' + test_h2o = protocol == 'h3' and env.have_h2o() + test_caddy = protocol == 'h3' and not test_h2o + if args.caddy or args.httpd or args.h2o: + test_caddy = args.caddy + test_httpd = args.httpd + test_h2o = args.h2o + sockd = None socks_args = None if args.socks4 and args.socks5: raise ScoreCardError('unable to run --socks4 and --socks5 together') - elif args.socks4 or args.socks5: + if args.socks4 or args.socks5: sockd = Dante(env=env) if sockd: assert sockd.initial_start() @@ -798,6 +807,7 @@ def run_score(args, protocol): httpd = None nghttpx = None caddy = None + h2o = None try: cards = [] @@ -839,7 +849,28 @@ def run_score(args, protocol): server_port = env.h3_port else: server_descr = f'httpd/{env.httpd_version()}' - server_port = env.https_port + server_port = env.http_port if args.http_plain else env.https_port + card = ScoreRunner(env=env, + protocol=protocol, + server_descr=server_descr, + server_port=server_port, + verbose=args.verbose, curl_verbose=args.curl_verbose, + download_parallel=args.download_parallel, + upload_parallel=args.upload_parallel, + with_flame=args.flame, + socks_args=socks_args, + limit_rate=args.limit_rate, + http_plain=args.http_plain) + card.setup_resources(server_docs, downloads) + cards.append(card) + + if test_h2o: + h2o = H2oServer(env=env) + h2o.clear_logs() + assert h2o.initial_start() + server_descr = f'H2o/{env.h2o_version()}' + server_port = h2o.port + server_docs = h2o.docs_dir card = ScoreRunner(env=env, protocol=protocol, server_descr=server_descr, @@ -915,6 +946,8 @@ def run_score(args, protocol): caddy.stop() if nghttpx: nghttpx.stop(wait_dead=False) + if h2o: + h2o.stop() if httpd: httpd.stop() if sockd: @@ -945,6 +978,8 @@ def main(): default=1, help="how many sample runs to make") parser.add_argument("--httpd", action='store_true', default=False, help="evaluate httpd server only") + parser.add_argument("--h2o", action='store_true', default=False, + help="evaluate h2o server only") parser.add_argument("--caddy", action='store_true', default=False, help="evaluate caddy server only") parser.add_argument("--curl-verbose", action='store_true', @@ -958,9 +993,11 @@ def main(): parser.add_argument("--remote", action='store', type=str, default=None, help="score against the remote server at :") parser.add_argument("--flame", action='store_true', - default = False, help="produce a flame graph on curl") + default=False, help="produce a flame graph on curl") parser.add_argument("--limit-rate", action='store', type=str, default=None, help="use curl's --limit-rate") + parser.add_argument("--http-plain", action='store_true', + default=False, help="run http: test instead of https:") parser.add_argument("-H", "--handshakes", action='store_true', default=False, help="evaluate handshakes only") diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py index 86f7ad191f18..3b7719fcce26 100644 --- a/tests/http/test_01_basic.py +++ b/tests/http/test_01_basic.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -134,7 +132,7 @@ def test_01_09_h2_prior_knowledge(self, env: Env, httpd): r = curl.http_get(url=url, extra_args=['--http2-prior-knowledge']) r.check_exit_code(0) assert len(r.responses) == 1, f'{r.responses}' - assert r.response['status'] == 200, f'{r.responsw}' + assert r.response['status'] == 200, f'{r.response}' assert r.response['protocol'] == 'HTTP/2', f'{r.response}' assert r.json['server'] == env.domain1 diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index dc51c25f52dd..473502809b46 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -43,12 +41,12 @@ class TestDownload: def _class_scope(self, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) - env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024) - env.make_data_file(indir=indir, fname="data-10m", fsize=10*1024*1024) - env.make_data_file(indir=indir, fname="data-50m", fsize=50*1024*1024) - env.make_data_gzipbomb(indir=indir, fname="bomb-100m.txt", fsize=100*1024*1024) + env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=indir, fname="data-1m", fsize=1024 * 1024) + env.make_data_file(indir=indir, fname="data-10m", fsize=10 * 1024 * 1024) + env.make_data_file(indir=indir, fname="data-50m", fsize=50 * 1024 * 1024) + env.make_data_gzipbomb(indir=indir, fname="bomb-100m.txt", fsize=100 * 1024 * 1024) # download 1 file @pytest.mark.parametrize("proto", Env.http_protos()) @@ -141,11 +139,11 @@ def test_02_07_download_reuse(self, env: Env, httpd, nghttpx, proto): urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]' r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True, extra_args=[ - '--parallel', '--parallel-max', '200' - ]) + '--parallel', '--parallel-max', '200' + ]) r.check_response(http_status=200, count=count) # should have used at most 2 connections only (test servers allow 100 req/conn) - # it may be just 1 on slow systems where request are answered faster than + # it may be 1 on slow systems where request are answered faster than # curl can exhaust the capacity or if curl runs with address-sanitizer speed assert r.total_connects <= 2, "h2 should use fewer connections here" @@ -157,8 +155,8 @@ def test_02_07b_download_reuse(self, env: Env, httpd, nghttpx, proto): urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]' r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True, extra_args=[ - '--parallel' - ]) + '--parallel' + ]) r.check_response(count=count, http_status=200) # http/1.1 should have used count connections assert r.total_connects == count, "http/1.1 should use this many connections" @@ -275,7 +273,7 @@ def test_02_20_h2_small_frames(self, env: Env, httpd, configures_httpd): self.check_downloads(curl, srcfile, count) # download serial via lib client, pause/resume at different offsets - @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000]) + @pytest.mark.parametrize("pause_offset", [0, 10 * 1024, 100 * 1023, 640000]) @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_21_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset): count = 2 @@ -293,7 +291,7 @@ def test_02_21_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset): self.check_downloads(client, srcfile, count) # download via lib client, several at a time, pause/resume - @pytest.mark.parametrize("pause_offset", [100*1023]) + @pytest.mark.parametrize("pause_offset", [100 * 1023]) @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_22_lib_parallel_resume(self, env: Env, httpd, nghttpx, proto, pause_offset): count = 2 @@ -417,7 +415,7 @@ def test_02_25_h2_upgrade_x(self, env: Env, httpd): assert r.exit_code == 0, f'{client.dump_logs()}' # Special client that tests TLS session reuse in parallel transfers - # TODO: just uses a single connection for h2/h3. Not sure how to prevent that + # TODO: uses a single connection for h2/h3. Not sure how to prevent that @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_26_session_shared_reuse(self, env: Env, proto, httpd, nghttpx): url = f'https://{env.authority_for(env.domain1, proto)}/data-100k' @@ -473,15 +471,17 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) assert False, f'download {dfile} differs:\n{diff}' # download via lib client, 1 at a time, pause/resume at different offsets - @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000]) + @pytest.mark.parametrize("pause_offset", [0, 10 * 1024, 100 * 1023, 640000]) @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_29_h2_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset): count = 2 @@ -526,7 +526,7 @@ def test_02_31_parallel_upgrade(self, env: Env, httpd, nghttpx): assert r.total_connects <= 3, r.dump_logs() # nghttpx is the only server we have that supports TLS early data - @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx") + @pytest.mark.skipif(condition=not Env.have_h3_server(), reason="no nghttpx with QUIC") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") @pytest.mark.parametrize("proto", Env.http_protos()) @@ -535,12 +535,6 @@ def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto): pytest.skip('TLS earlydata not implemented') if proto == 'h3' and not env.curl_can_h3_early_data(): pytest.skip("h3 early data not supported") - if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run: - pytest.skip('failing on macOS CI runners') - if proto == 'h3' and env.curl_uses_lib('wolfssl'): - pytest.skip('h3 wolfssl early data failing') - if proto == 'h3' and env.curl_uses_lib('gnutls'): - pytest.skip('h3 gnutls early data failing') count = 2 docname = 'data-10k' # we want this test to always connect to nghttpx, since it is @@ -571,7 +565,7 @@ def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto): if m: earlydata[int(m.group(1))] = int(m.group(2)) continue - if re.match(r'\[1-1] \* SSL reusing session.*', line): + if re.match(r'\[1-1] \* (\[1-1] )?SSL reusing session.*', line): reused_session = True assert reused_session, 'session was not reused for 2nd transfer' assert earlydata[0] == 0, f'{earlydata}' @@ -584,7 +578,8 @@ def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto): @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) @pytest.mark.parametrize("max_host_conns", [0, 1, 5]) - def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_conns): + @pytest.mark.parametrize("share_connect", [False, True]) + def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_conns, share_connect): if not env.curl_is_debug(): pytest.skip('only works for curl debug builds') if not env.curl_is_verbose(): @@ -599,6 +594,7 @@ def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_co client = LocalClient(name='cli_hx_download', env=env, run_env=run_env) if not client.exists(): pytest.skip(f'example client not built: {client.name}') + extra_args = ['-S'] if share_connect else [] # share connections r = client.run(args=[ '-n', f'{count}', '-m', f'{max_parallel}', @@ -606,8 +602,7 @@ def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_co '-x', # always use a fresh connection '-M', str(max_host_conns), # limit conns per host '-r', f'{env.domain1}:{port}:127.0.0.1', - '-V', proto, url - ]) + ] + extra_args + ['-V', proto, url]) r.check_exit_code(0) srcfile = os.path.join(httpd.docs_dir, docname) self.check_downloads(client, srcfile, count) @@ -623,7 +618,8 @@ def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_co @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) @pytest.mark.parametrize("max_total_conns", [0, 1, 5]) - def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_conns): + @pytest.mark.parametrize("share_connect", [False, True]) + def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_conns, share_connect): if not env.curl_is_debug(): pytest.skip('only works for curl debug builds') if not env.curl_is_verbose(): @@ -638,6 +634,7 @@ def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_ client = LocalClient(name='cli_hx_download', env=env, run_env=run_env) if not client.exists(): pytest.skip(f'example client not built: {client.name}') + extra_args = ['-S'] if share_connect else [] # share connections r = client.run(args=[ '-n', f'{count}', '-m', f'{max_parallel}', @@ -645,8 +642,7 @@ def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_ '-x', # always use a fresh connection '-T', str(max_total_conns), # limit total connections '-r', f'{env.domain1}:{port}:127.0.0.1', - '-V', proto, url - ]) + ] + extra_args + ['-V', proto, url]) r.check_exit_code(0) srcfile = os.path.join(httpd.docs_dir, docname) self.check_downloads(client, srcfile, count) @@ -688,7 +684,7 @@ def test_02_35_pause_bomb(self, env: Env, httpd, nghttpx, proto): # download with looong urls @pytest.mark.parametrize("proto", Env.http_protos()) - @pytest.mark.parametrize("url_junk", [1024, 16*1024, 32*1024, 64*1024, 80*1024, 96*1024]) + @pytest.mark.parametrize("url_junk", [1024, 16 * 1024, 32 * 1024, 64 * 1024, 80 * 1024, 96 * 1024]) def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): if proto == 'h3' and env.curl_uses_lib('quiche'): pytest.skip("quiche fails from 16k onwards") @@ -699,11 +695,7 @@ def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): if url_junk <= 1024: r.check_exit_code(0) r.check_response(http_status=200) - elif url_junk <= 16*1024: - r.check_exit_code(0) - # server replies with 414, Request URL too long - r.check_response(http_status=414) - elif url_junk <= 32*1024: + elif url_junk <= 32 * 1024: r.check_exit_code(0) # server replies with 414, Request URL too long r.check_response(http_status=414) @@ -716,11 +708,11 @@ def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): # h2 is unable to send such large headers (frame limits) r.check_exit_code(55) elif proto == 'h3': - if url_junk <= 64*1024: - r.check_exit_code(0) - # nghttpx reports 431 Request Header Field too Large + # nghttpx reports 431 Request Header Field too Large + # or destroys the connection with internal error + # ERR_QPACK_HEADER_TOO_LARGE, + # depending on nghttp3 version and payload size + assert r.exit_code in [0, 56], f'expected exit code 0 or 56, '\ + f'got {r.exit_code}\n{r.dump_logs()}' + if r.exit_code == 0: r.check_response(http_status=431) - else: - # nghttpx destroys the connection with internal error - # ERR_QPACK_HEADER_TOO_LARGE - r.check_exit_code(56) diff --git a/tests/http/test_03_goaway.py b/tests/http/test_03_goaway.py index 4c57bf2715d0..36febdbd18a5 100644 --- a/tests/http/test_03_goaway.py +++ b/tests/http/test_03_goaway.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | diff --git a/tests/http/test_04_stuttered.py b/tests/http/test_04_stuttered.py index 1c578e2e9e77..3ad25d93ae43 100644 --- a/tests/http/test_04_stuttered.py +++ b/tests/http/test_04_stuttered.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | diff --git a/tests/http/test_05_errors.py b/tests/http/test_05_errors.py index a78b9c37463b..768416eea331 100644 --- a/tests/http/test_05_errors.py +++ b/tests/http/test_05_errors.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -24,6 +22,7 @@ # ########################################################################### # +import contextlib import logging import os import socket @@ -43,7 +42,7 @@ class TestErrors: @pytest.mark.parametrize("proto", Env.http_protos()) def test_05_01_partial_1(self, env: Env, httpd, nghttpx, proto): if proto == 'h3' and env.curl_uses_lib('quiche') and \ - not env.curl_lib_version_at_least('quiche', '0.24.8'): + not env.curl_lib_version_at_least('quiche', '0.29.0'): pytest.skip("quiche issue #2277 not fixed") count = 1 curl = CurlClient(env=env) @@ -64,7 +63,7 @@ def test_05_01_partial_1(self, env: Env, httpd, nghttpx, proto): @pytest.mark.parametrize("proto", Env.http_mplx_protos()) def test_05_02_partial_20(self, env: Env, httpd, nghttpx, proto): if proto == 'h3' and env.curl_uses_lib('quiche') and \ - not env.curl_lib_version_at_least('quiche', '0.24.8'): + not env.curl_lib_version_at_least('quiche', '0.29.0'): pytest.skip("quiche issue #2277 not fixed") count = 20 curl = CurlClient(env=env) @@ -100,7 +99,7 @@ def test_05_03_required(self, env: Env, httpd, nghttpx): assert r.stats[0]['http_version'] == '1.1', r.dump_logs() # On the URL used here, Apache is doing an "unclean" TLS shutdown, - # meaning it sends no shutdown notice and just closes TCP. + # meaning it sends no shutdown notice and closes TCP. # The HTTP response delivers a body without Content-Length. We expect: # - http/1.0 to fail since it relies on a clean connection close to # detect the end of the body @@ -192,12 +191,11 @@ def test_05_09_handshake_eof(self, env: Env): # accept one connection and immediately close it def accept_and_close(): - try: + # ignore expected socket error + with contextlib.suppress(OSError): conn, _ = server.accept() conn.recv(1) # wait for ClientHello conn.close() - except Exception: - pass t = threading.Thread(target=accept_and_close) t.start() diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index fb9df11d2f76..b72a89d75e4c 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -103,13 +101,22 @@ def test_06_12_stats_fail_tcp(self, env: Env, httpd, nghttpx): # check timers when trying 3 unresponsive addresses @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), - reason='curl lacks ipv6 support') + reason='curl lacks IPv6 support') @pytest.mark.skipif(condition=not Env.curl_has_feature('AsynchDNS'), reason='curl lacks async DNS support') @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") def test_06_13_timers(self, env: Env): curl = CurlClient(env=env) - # ipv6 0100::/64 is supposed to go into the void (rfc6666) + # IPv6 0100::/64 is supposed to go into the void (rfc6666), but in + # some implementations, this is broken. Try to detect this + r = curl.http_download(urls=['https://xxx.invalid/'], extra_args=[ + '--resolve', 'xxx.invalid:443:0100::1', + '--connect-timeout', '0.5', + ]) + # this should error with CURLE_OPERATION_TIMEDOUT + if r.exit_code != 28: + pytest.skip('system does not blackhole 0100::/64') + r = curl.http_download(urls=['https://xxx.invalid/'], extra_args=[ '--resolve', 'xxx.invalid:443:0100::1,0100::2,0100::3', '--connect-timeout', '1', @@ -120,7 +127,7 @@ def test_06_13_timers(self, env: Env): assert r.stats[0]['time_connect'] == 0 # no one connected # check that we indeed started attempts on all 3 addresses tcp_attempts = [line for line in r.trace_lines - if re.match(r'.*Trying \[100::[123]]:443', line)] + if re.match(r'.*Trying \[100::[123]]:443', line)] assert len(tcp_attempts) == 3, f'fond: {"".join(tcp_attempts)}\n{r.dump_logs()}' # if the 0100::/64 really goes into the void, we should see 2 HAPPY_EYEBALLS # timeouts being set here @@ -133,7 +140,7 @@ def test_06_13_timers(self, env: Env): # no immediately failed attempts, as should be he_timers_set = [line for line in r.trace_lines if re.match(r'.*\[TIMER] \[HAPPY_EYEBALLS] set for', line)] - assert len(he_timers_set) == 2, f'found: {"".join(he_timers_set)}\n{r.dump_logs()}' + assert len(he_timers_set) >= 2, f'found: {"".join(he_timers_set)}\n{r.dump_logs()}' # download using HTTP/3 on missing server with alt-svc pointing there @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") @@ -214,3 +221,16 @@ def test_06_24_h3_altsvc_h2_used(self, env: Env, httpd, nghttpx): r.check_exit_code(0) r.check_response(count=1, http_status=200) assert r.stats[0]['http_version'] == '2' + + # h3 download using --connect-to IPv6 address + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") + def test_06_25_h3_connect_to(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env, force_resolv=False) + urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json' + r = curl.http_download(urls=[urln], extra_args=[ + '--http3-only', '--connect-to', + f'{env.authority_for(env.domain1, "h3")}:[::1]:{env.https_port}' + ]) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '3' diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index 49f5213aa152..8187e0a99bdd 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,6 +27,7 @@ import logging import os import re +import string import sys from typing import List, Union @@ -42,22 +41,23 @@ class TestUpload: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd, nghttpx): - env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63*1024) - env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64*1024) - env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=env.gen_dir, fname="data-1m+", fsize=(1024*1024)+1) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-1m+", fsize=(1024 * 1024) + 1) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # upload small data, check that this is what was echoed @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, proto): - data = '0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto) r.check_stats(count=1, http_status=200, exitcode=0) - respdata = open(curl.response_file(0)).readlines() + with open(curl.response_file(0)) as fr: + respdata = fr.readlines() assert respdata == [data] # upload large data, check that this is what was echoed @@ -68,21 +68,23 @@ def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) r.check_stats(count=1, http_status=200, exitcode=0) - indata = open(fdata).readlines() - respdata = open(curl.response_file(0)).readlines() + with open(fdata) as fi, open(curl.response_file(0)) as fr: + indata = fi.readlines() + respdata = fr.readlines() assert respdata == indata # upload data sequentially, check that they were echoed @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, proto): count = 20 - data = '0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto) r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == [data] # upload data parallel, check that they were echoed @@ -90,14 +92,15 @@ def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, proto): def test_07_11_upload_parallel(self, env: Env, httpd, nghttpx, proto): # limit since we use a separate connection in h1 count = 20 - data = '0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=['--parallel']) r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == [data] # upload large data sequentially, check that this is what was echoed @@ -109,10 +112,12 @@ def test_07_12_upload_seq_large(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) r.check_response(count=count, http_status=200) - indata = open(fdata).readlines() + with open(fdata) as fi: + indata = fi.readlines() r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == indata # upload very large data sequentially, check that this is what was echoed @@ -124,9 +129,11 @@ def test_07_13_upload_seq_large(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) r.check_stats(count=count, http_status=200, exitcode=0) - indata = open(fdata).readlines() + with open(fdata) as fi: + indata = fi.readlines() for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == indata # upload from stdin, issue #14870 @@ -141,13 +148,14 @@ def test_07_14_upload_stdin(self, env: Env, httpd, nghttpx, proto, indata): r = curl.http_put(urls=[url], data=indata, alpn_proto=proto) r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == [f'{len(indata)}'] @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_15_hx_put(self, env: Env, httpd, nghttpx, proto): count = 2 - upload_size = 128*1024 + upload_size = 128 * 1024 url = f'https://localhost:{env.https_port}/curltest/put' client = LocalClient(name='cli_hx_upload', env=env) if not client.exists(): @@ -161,7 +169,7 @@ def test_07_15_hx_put(self, env: Env, httpd, nghttpx, proto): @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_16_hx_put_reuse(self, env: Env, httpd, nghttpx, proto): count = 2 - upload_size = 128*1024 + upload_size = 128 * 1024 url = f'https://localhost:{env.https_port}/curltest/put' client = LocalClient(name='cli_hx_upload', env=env) if not client.exists(): @@ -175,7 +183,7 @@ def test_07_16_hx_put_reuse(self, env: Env, httpd, nghttpx, proto): @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_17_hx_post_reuse(self, env: Env, httpd, nghttpx, proto): count = 2 - upload_size = 128*1024 + upload_size = 128 * 1024 url = f'https://localhost:{env.https_port}/curltest/echo' client = LocalClient(name='cli_hx_upload', env=env) if not client.exists(): @@ -191,14 +199,15 @@ def test_07_17_hx_post_reuse(self, env: Env, httpd, nghttpx, proto): def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, proto): # limit since we use a separate connection in h1 count = 10 - data = '0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=['--parallel']) r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == [data] # upload large data parallel, check that this is what was echoed @@ -243,7 +252,8 @@ def test_07_30_put_100k(self, env: Env, httpd, nghttpx, proto): exp_data = [f'{os.path.getsize(fdata)}'] r.check_response(count=count, http_status=200) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == exp_data # PUT 10m @@ -259,7 +269,8 @@ def test_07_31_put_10m(self, env: Env, httpd, nghttpx, proto): exp_data = [f'{os.path.getsize(fdata)}'] r.check_response(count=count, http_status=200) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == exp_data # issue #10591 @@ -351,8 +362,9 @@ def test_07_35_h1_h2_upgrade_upload(self, env: Env, httpd, nghttpx): r.check_response(count=1, http_status=200) # apache does not Upgrade on request with a body assert r.stats[0]['http_version'] == '1.1', f'{r}' - indata = open(fdata).readlines() - respdata = open(curl.response_file(0)).readlines() + with open(fdata) as fi, open(curl.response_file(0)) as fr: + indata = fi.readlines() + respdata = fr.readlines() assert respdata == indata # upload to a 301,302,303 response @@ -361,14 +373,15 @@ def test_07_35_h1_h2_upgrade_upload(self, env: Env, httpd, nghttpx): def test_07_36_upload_30x(self, env: Env, httpd, nghttpx, redir, proto): if proto == 'h3' and env.curl_uses_ossl_quic(): pytest.skip("OpenSSL's own QUIC is flaky here") - data = '0123456789' * 10 + data = string.digits * 10 curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo{redir}?id=[0-0]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ '-L', '--trace-config', 'http/2,http/3' ]) r.check_response(count=1, http_status=200) - respdata = open(curl.response_file(0)).readlines() + with open(curl.response_file(0)) as fr: + respdata = fr.readlines() assert respdata == [] # was transformed to a GET # upload to a 307 response @@ -376,14 +389,15 @@ def test_07_36_upload_30x(self, env: Env, httpd, nghttpx, redir, proto): def test_07_37_upload_307(self, env: Env, httpd, nghttpx, proto): if proto == 'h3' and env.curl_uses_ossl_quic(): pytest.skip("OpenSSL's own QUIC is flaky here") - data = '0123456789' * 10 + data = string.digits * 10 curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo307?id=[0-0]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ '-L', '--trace-config', 'http/2,http/3' ]) r.check_response(count=1, http_status=200) - respdata = open(curl.response_file(0)).readlines() + with open(curl.response_file(0)) as fr: + respdata = fr.readlines() assert respdata == [data] # was POST again # POST form data, yet another code path in transfer @@ -406,8 +420,9 @@ def test_07_39_post_urlenc_small(self, env: Env, httpd, nghttpx, proto): '--trace-config', 'http/2,http/3' ]) r.check_stats(count=1, http_status=200, exitcode=0) - indata = open(fdata).readlines() - respdata = open(curl.response_file(0)).readlines() + with open(fdata) as fi, open(curl.response_file(0)) as fr: + indata = fi.readlines() + respdata = fr.readlines() assert respdata == indata # POST data urlencoded, large enough to be sent separate from request headers @@ -420,8 +435,9 @@ def test_07_40_post_urlenc_large(self, env: Env, httpd, nghttpx, proto): '--trace-config', 'http/2,http/3' ]) r.check_stats(count=1, http_status=200, exitcode=0) - indata = open(fdata).readlines() - respdata = open(curl.response_file(0)).readlines() + with open(fdata) as fi, open(curl.response_file(0)) as fr: + indata = fi.readlines() + respdata = fr.readlines() assert respdata == indata # POST data urlencoded, small enough to be sent with request headers @@ -442,8 +458,9 @@ def test_07_41_post_urlenc_small(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args) r.check_stats(count=1, http_status=200, exitcode=0) - indata = open(fdata).readlines() - respdata = open(curl.response_file(0)).readlines() + with open(fdata) as fi, open(curl.response_file(0)) as fr: + indata = fi.readlines() + respdata = fr.readlines() assert respdata == indata def check_download(self, r: ExecResult, count: int, srcfile: Union[str, os.PathLike], curl: CurlClient): @@ -451,8 +468,10 @@ def check_download(self, r: ExecResult, count: int, srcfile: Union[str, os.PathL dfile = curl.download_file(i) assert os.path.exists(dfile), f'download {dfile} missing\n{r.dump_logs()}' if not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -519,7 +538,7 @@ def test_07_43_upload_denied(self, env: Env, httpd, nghttpx, proto): @pytest.mark.parametrize("httpcode", [301, 302, 307, 308]) def test_07_44_put_redir(self, env: Env, httpd, nghttpx, proto, httpcode): count = 1 - upload_size = 128*1024 + upload_size = 128 * 1024 url = f'https://localhost:{env.https_port}/curltest/put-redir-{httpcode}' client = LocalClient(name='cli_hx_upload', env=env) if not client.exists(): @@ -529,7 +548,7 @@ def test_07_44_put_redir(self, env: Env, httpd, nghttpx, proto, httpcode): ]) r.check_exit_code(0) results = [int(m.group(1)) for line in r.trace_lines - if (m := re.match(r'.* FINISHED, result=(\d+), response=(\d+)', line))] + if (m := re.match(r'.* FINISHED, result=(\d+), response=(\d+)', line))] httpcodes = [int(m.group(2)) for line in r.trace_lines if (m := re.match(r'.* FINISHED, result=(\d+), response=(\d+)', line))] if httpcode == 308: @@ -548,8 +567,8 @@ def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]' r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, with_headers=True, extra_args=[ - '--limit-rate', f'{speed_limit}' - ]) + '--limit-rate', f'{speed_limit}' + ]) r.check_response(count=count, http_status=200) assert r.responses[0]['header']['received-length'] == f'{up_len}', f'{r.responses[0]}' up_speed = r.stats[0]['speed_upload'] @@ -565,8 +584,8 @@ def test_07_51_echo_speed_limit(self, env: Env, httpd, nghttpx, proto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, with_headers=True, extra_args=[ - '--limit-rate', f'{speed_limit}' - ]) + '--limit-rate', f'{speed_limit}' + ]) r.check_response(count=count, http_status=200) up_speed = r.stats[0]['speed_upload'] assert up_speed <= (speed_limit * 1.1), f'{r.stats[0]}' @@ -636,13 +655,13 @@ def test_07_63_upload_exp100_paused(self, env: Env, httpd, nghttpx, proto): r.check_exit_code(0) # nghttpx is the only server we have that supports TLS early data - @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx") + @pytest.mark.skipif(condition=not Env.have_h3_server(), reason="no QUIC in nghttpx") @pytest.mark.parametrize("proto,upload_size", [ pytest.param('http/1.1', 100, id='h1-small-body'), - pytest.param('http/1.1', 10*1024, id='h1-medium-body'), - pytest.param('http/1.1', 32*1024, id='h1-limited-body'), - pytest.param('h2', 10*1024, id='h2-medium-body'), - pytest.param('h2', 32*1024, id='h2-limited-body'), + pytest.param('http/1.1', 10 * 1024, id='h1-medium-body'), + pytest.param('http/1.1', 32 * 1024, id='h1-limited-body'), + pytest.param('h2', 10 * 1024, id='h2-medium-body'), + pytest.param('h2', 32 * 1024, id='h2-limited-body'), pytest.param('h3', 1024, id='h3-small-body'), pytest.param('h3', 1024 * 1024, id='h3-limited-body'), ]) @@ -699,8 +718,9 @@ def check_downloads(self, client, r, source: List[str], count: int, dfile = client.download_file(i) assert os.path.exists(dfile), f'download {dfile} missing\n{r.dump_logs()}' if complete: - diff = "".join(difflib.unified_diff(a=source, - b=open(dfile).readlines(), + with open(dfile) as fb: + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=source, b=b, fromfile='-', tofile=dfile, n=1)) diff --git a/tests/http/test_08_caddy.py b/tests/http/test_08_caddy.py index c7dd25a8eb5d..467f1cc8308c 100644 --- a/tests/http/test_08_caddy.py +++ b/tests/http/test_08_caddy.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,6 +27,7 @@ import logging import os import re +import string import pytest from testenv import Caddy, CurlClient, Env, LocalClient @@ -59,12 +58,12 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, caddy): - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10*1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024*1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5*1024*1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10*1024*1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100*1024*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10 * 1024) + self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024 * 1024) + self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5 * 1024 * 1024) + self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10 * 1024 * 1024) + self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100 * 1024 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file @pytest.mark.parametrize("proto", Env.http_protos()) @@ -144,14 +143,15 @@ def test_08_05_download_1mb_parallel(self, env: Env, caddy: Caddy, proto): def test_08_06_post_parallel(self, env: Env, httpd, caddy, proto): # limit since we use a separate connection in h1 count = 20 - data = '0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.domain2}:{caddy.port}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=['--parallel']) r.check_stats(count=count, http_status=200, exitcode=0) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == [data] # put large file, check that they length were echoed @@ -166,7 +166,8 @@ def test_08_07_put_large(self, env: Env, httpd, caddy, proto): exp_data = [f'{os.path.getsize(fdata)}'] r.check_response(count=count, http_status=200) for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == exp_data @pytest.mark.parametrize("proto", Env.http_protos()) @@ -210,8 +211,10 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) diff --git a/tests/http/test_09_push.py b/tests/http/test_09_push.py index 8f4714be0708..7fb37b679df3 100644 --- a/tests/http/test_09_push.py +++ b/tests/http/test_09_push.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -40,9 +38,9 @@ def _class_scope(self, env, httpd): push_dir = os.path.join(httpd.docs_dir, 'push') if not os.path.exists(push_dir): os.makedirs(push_dir) - env.make_data_file(indir=push_dir, fname="data1", fsize=1*1024) - env.make_data_file(indir=push_dir, fname="data2", fsize=1*1024) - env.make_data_file(indir=push_dir, fname="data3", fsize=1*1024) + env.make_data_file(indir=push_dir, fname="data1", fsize=1 * 1024) + env.make_data_file(indir=push_dir, fname="data2", fsize=1 * 1024) + env.make_data_file(indir=push_dir, fname="data3", fsize=1 * 1024) def httpd_configure(self, env, httpd): httpd.set_extra_config(env.domain1, [ diff --git a/tests/http/test_10_proxy.py b/tests/http/test_10_proxy.py index b1840b484d7a..bd07841a68c0 100644 --- a/tests/http/test_10_proxy.py +++ b/tests/http/test_10_proxy.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -45,11 +43,11 @@ def _class_scope(self, env, httpd, nghttpx_fwd): os.makedirs(push_dir) if env.have_nghttpx(): nghttpx_fwd.start_if_needed() - env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) indir = httpd.docs_dir - env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024) + env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=indir, fname="data-1m", fsize=1024 * 1024) def get_tunnel_proto_used(self, r: ExecResult): for line in r.trace_lines: @@ -57,7 +55,6 @@ def get_tunnel_proto_used(self, r: ExecResult): if m: return m.group(1) assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}' - return None # download via http: proxy (no tunnel) def test_10_01_proxy_http(self, env: Env, httpd): @@ -85,15 +82,16 @@ def test_10_02_proxys_down(self, env: Env, httpd, proto): # upload via https: with proto (no tunnel) @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL") @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) - @pytest.mark.parametrize("fname, fcount", [ - ['data.json', 5], - ['data-100k', 5], - ['data-1m', 2] + @pytest.mark.parametrize("fname, fcount, with_alpn", [ + ['data.json', 5, False], + ['data.json', 5, True], + ['data-100k', 5, True], + ['data-1m', 2, True] ]) @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") def test_10_02_proxys_up(self, env: Env, httpd, nghttpx, proto, - fname, fcount): + fname, fcount, with_alpn): if proto == 'h2' and not env.curl_uses_lib('nghttp2'): pytest.skip('only supported with nghttp2') count = fcount @@ -101,13 +99,17 @@ def test_10_02_proxys_up(self, env: Env, httpd, nghttpx, proto, curl = CurlClient(env=env) url = f'http://localhost:{env.http_port}/curltest/echo?id=[0-{count-1}]' xargs = curl.get_proxy_args(proto=proto) + if not with_alpn: + xargs.append('--no-alpn') r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto, extra_args=xargs) r.check_response(count=count, http_status=200, protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1') - indata = open(srcfile).readlines() + with open(srcfile) as fi: + indata = fi.readlines() for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == indata # download http: via http: proxytunnel @@ -229,9 +231,11 @@ def test_10_08_upload_seq_large(self, env: Env, httpd, nghttpx, proto, assert self.get_tunnel_proto_used(r) == tunnel r.check_response(count=count, http_status=200) assert r.total_connects == 1, r.dump_logs() - indata = open(srcfile).readlines() + with open(srcfile) as fi: + indata = fi.readlines() for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == indata, f'response {i} differs' @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL") @@ -386,9 +390,9 @@ def test_10_14_proxys_ip_addr(self, env: Env, httpd, proto): r.check_response(count=1, http_status=200, protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1') - # download via http: ipv4 proxy (no tunnel) using IP address, IPv6 only + # download via http: IPv4 proxy (no tunnel) using IP address, IPv6 only @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), - reason='no ipv6 support') + reason='no IPv6 support') def test_10_15_proxy_ip_addr(self, env: Env, httpd): proto = 'http/1.1' curl = CurlClient(env=env, force_resolv=False) @@ -397,12 +401,12 @@ def test_10_15_proxy_ip_addr(self, env: Env, httpd): xargs.append('-6') r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, extra_args=xargs) - r.check_exit_code(0), f'{r}' + r.check_exit_code(0) r.check_response(count=1, http_status=200, protocol='HTTP/1.1') - # download via http: ipv6 proxy (no tunnel) using IP address, IPv4 only + # download via http: IPv6 proxy (no tunnel) using IP address, IPv4 only @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), - reason='no ipv6 support') + reason='no IPv6 support') def test_10_16_proxy_ip_addr(self, env: Env, httpd): proto = 'http/1.1' curl = CurlClient(env=env, force_resolv=False) @@ -411,5 +415,15 @@ def test_10_16_proxy_ip_addr(self, env: Env, httpd): xargs.append('-4') r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, extra_args=xargs) - r.check_exit_code(0), f'{r}' + r.check_exit_code(0) r.check_response(count=1, http_status=200, protocol='HTTP/1.1') + + # download via http: proxy (no tunnel), check connection reuse + def test_10_17_proxy_http(self, env: Env, httpd): + curl = CurlClient(env=env) + url1 = f'http://localhost:{env.http_port}/data.json' + url2 = f'http://127.0.0.1:{env.http_port}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto='http/1.1', with_stats=True, + extra_args=curl.get_proxy_args(proxys=False)) + r.check_response(count=2, http_status=200) + assert r.total_connects == 1, r.dump_logs() diff --git a/tests/http/test_11_unix.py b/tests/http/test_11_unix.py index fe99512a0473..9321d7141402 100644 --- a/tests/http/test_11_unix.py +++ b/tests/http/test_11_unix.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -70,21 +68,19 @@ def stop(self): def _process(self): while self._done is False: try: - c, client_address = self._socket.accept() + c, _client_address = self._socket.accept() try: c.recv(16) - c.sendall("""HTTP/1.1 200 Ok + c.sendall(b"""HTTP/1.1 200 Ok Server: UdsFaker Content-Type: application/json Content-Length: 19 -{ "host": "faked" }""".encode()) +{ "host": "faked" }""") finally: c.close() - except ConnectionAbortedError: - self._done = True - except OSError: + except (ConnectionAbortedError, OSError): self._done = True @@ -136,3 +132,16 @@ def test_11_03_unix_connect_quic(self, env: Env, httpd, uds_faker): r.check_response(exitcode=96, http_status=None) assert r.stats[0]['remote_port'] == -1, f'{r.dump_logs()}' assert r.stats[0]['local_port'] == -1, f'{r.dump_logs()}' + + # download http: via Unix socket, ignore proxy args + def test_11_04_unix_connect_http(self, env: Env, httpd, uds_faker): + curl = CurlClient(env=env) + url = f'http://{env.domain1}:{env.http_port}/data.json' + xargs = curl.get_proxy_args(proto='http/1.1', use_ip=True, proxys=False) + xargs.extend([ + '--unix-socket', uds_faker.path, + ]) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) + r.check_response(count=1, http_status=200) + assert r.stats[0]['remote_port'] == -1, f'{r.dump_logs()}' + assert r.stats[0]['local_port'] == -1, f'{r.dump_logs()}' diff --git a/tests/http/test_12_reuse.py b/tests/http/test_12_reuse.py index 8ff062d6dc26..b7a2513ef0fb 100644 --- a/tests/http/test_12_reuse.py +++ b/tests/http/test_12_reuse.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -71,3 +69,71 @@ def test_12_02_h1_conn_timeout(self, env: Env, httpd, configures_httpd, nghttpx, r.check_response(count=count, http_status=200) # Connections time out on server before we send another request, assert r.total_connects == count + + # After a partial/aborted HTTP/1.1 response the connection must not be + # reused (multi_conn_should_close with premature on non-multiplexed conn). + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_12_03_no_reuse_after_partial(self, env: Env, httpd, nghttpx, proto): + curl = CurlClient(env=env) + auth = env.authority_for(env.domain1, proto) + # Server promises more bytes than it sends, then resets. + partial = f'https://{auth}/curltest/tweak?id=0&chunks=1&chunk_size=100&body_error=reset' + ok = f'https://{auth}/data.json' + r = curl.http_download(urls=[partial, ok], alpn_proto=proto, extra_args=[ + '--retry', '0', + ]) + # First transfer fails (partial/reset); second succeeds on a new connection. + assert len(r.stats) == 2, r.dump_logs() + assert r.stats[0]['exitcode'] != 0, r.dump_logs() + assert r.stats[1].get('http_code') == 200, r.dump_logs() + # Both transfers must open their own connection. + assert r.stats[0]['num_connects'] == 1, r.dump_logs() + assert r.stats[1]['num_connects'] == 1, r.dump_logs() + assert r.total_connects == 2, r.dump_logs() + + # HTTP uses PROTOPT_CREDSPERREQUEST: Basic credentials are per request, so + # different -u values still reuse the idle connection (unlike NTLM/Negotiate + # which bind credentials onto the connection). + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_12_04_reuse_different_basic_credentials(self, env: Env, httpd, nghttpx, proto): + curl = CurlClient(env=env) + url1 = f'https://{env.authority_for(env.domain1, proto)}/data.json?cred=1' + url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json?cred=2' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto, url_options={ + url1: ['-u', 'user1:password1'], + url2: ['-u', 'user2:password2'], + }) + assert len(r.stats) == 2, r.dump_logs() + assert r.stats[0].get('http_code') == 200, r.dump_logs() + assert r.stats[1].get('http_code') == 200, r.dump_logs() + assert r.stats[0]['num_connects'] == 1, r.dump_logs() + assert r.stats[1]['num_connects'] == 0, r.dump_logs() + assert r.total_connects == 1, r.dump_logs() + + # Different target hostnames must not reuse even if they resolve to the + # same address (Curl_peer_same_destination matches hostname + port). + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_12_05_no_reuse_different_host(self, env: Env, httpd, nghttpx, proto): + curl = CurlClient(env=env) + # domain1 and domain2 are both served by the same httpd instance. + url1 = f'https://{env.authority_for(env.domain1, proto)}/data.json' + url2 = f'https://{env.authority_for(env.domain2, proto)}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto) + r.check_response(count=2, http_status=200) + assert r.stats[0]['num_connects'] == 1, r.dump_logs() + assert r.stats[1]['num_connects'] == 1, r.dump_logs() + assert r.total_connects == 2, r.dump_logs() + + # Positive control: same host reuses one connection. + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_12_06_reuse_same_host(self, env: Env, httpd, nghttpx, proto): + curl = CurlClient(env=env) + url1 = f'https://{env.authority_for(env.domain1, proto)}/data.json?a=1' + url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json?a=2' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto) + assert len(r.stats) == 2, r.dump_logs() + assert r.stats[0].get('http_code') == 200, r.dump_logs() + assert r.stats[1].get('http_code') == 200, r.dump_logs() + assert r.stats[0]['num_connects'] == 1, r.dump_logs() + assert r.stats[1]['num_connects'] == 0, r.dump_logs() + assert r.total_connects == 1, r.dump_logs() diff --git a/tests/http/test_13_proxy_auth.py b/tests/http/test_13_proxy_auth.py index d74a9f280b76..4e5d45e91882 100644 --- a/tests/http/test_13_proxy_auth.py +++ b/tests/http/test_13_proxy_auth.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -48,7 +46,6 @@ def get_tunnel_proto_used(self, r: ExecResult): if m: return m.group(1) assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}' - return None # download via http: proxy (no tunnel), no auth def test_13_01_proxy_no_auth(self, env: Env, httpd, configures_httpd): @@ -176,9 +173,9 @@ def test_13_10_tunnels_mixed_auth(self, env: Env, httpd, configures_httpd): url2 = f'http://localhost:{env.http_port}/data.json?2' url3 = f'http://localhost:{env.http_port}/data.json?3' xargs1 = curl.get_proxy_args(proxys=False, tunnel=True) - xargs1.extend(['--proxy-user', 'proxy:proxy']) # good auth + xargs1.extend(['--proxy-user', 'proxy:proxy']) # good auth xargs2 = curl.get_proxy_args(proxys=False, tunnel=True) - xargs2.extend(['--proxy-user', 'ungood:ungood']) # bad auth + xargs2.extend(['--proxy-user', 'ungood:ungood']) # bad auth xargs3 = curl.get_proxy_args(proxys=False, tunnel=True) # no auth r = curl.http_download(urls=[url1, url2, url3], alpn_proto='http/1.1', with_stats=True, diff --git a/tests/http/test_14_auth.py b/tests/http/test_14_auth.py index 288e7b5d28de..ab941b6efa2d 100644 --- a/tests/http/test_14_auth.py +++ b/tests/http/test_14_auth.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -26,6 +24,7 @@ # import logging import os +import string import pytest from testenv import CurlClient, Env @@ -37,7 +36,7 @@ class TestAuth: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd, nghttpx): - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file, not authenticated @pytest.mark.parametrize("proto", Env.http_protos()) @@ -64,7 +63,7 @@ def test_14_02_digest_get_auth(self, env: Env, httpd, nghttpx, proto): def test_14_03_digest_put_auth(self, env: Env, httpd, nghttpx, proto): if not env.curl_has_feature('digest'): pytest.skip("curl built without digest") - data='0123456789' + data = string.digits curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ @@ -77,7 +76,7 @@ def test_14_03_digest_put_auth(self, env: Env, httpd, nghttpx, proto): def test_14_04_digest_large_pw(self, env: Env, httpd, nghttpx, proto): if not env.curl_has_feature('digest'): pytest.skip("curl built without digest") - data='0123456789' + data = string.digits password = 'x' * 65535 curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' @@ -92,10 +91,10 @@ def test_14_04_digest_large_pw(self, env: Env, httpd, nghttpx, proto): # PUT data, basic auth large pw @pytest.mark.parametrize("proto", Env.http_mplx_protos()) def test_14_05_basic_large_pw(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and not env.curl_uses_lib('ngtcp2'): + if proto == 'h3' and env.curl_uses_lib('quiche'): # See pytest.skip("quiche has problems with large requests") - # just large enough that nghttp2 will submit + # large enough that nghttp2 will submit password = 'x' * (47 * 1024) fdata = os.path.join(env.gen_dir, 'data-10m') curl = CurlClient(env=env) @@ -104,9 +103,13 @@ def test_14_05_basic_large_pw(self, env: Env, httpd, nghttpx, proto): '--basic', '--user', f'test:{password}', '--trace-config', 'http/2,http/3' ]) - # but apache either denies on length limit or gives a 400 - r.check_exit_code(0) - assert r.stats[0]['http_code'] in [400, 431] + if proto == 'h3' and r.exit_code != 0: + # nghttpx violently closes the connection now + assert r.exit_code in [55, 56, 95], f'{r.dump_logs()}' + else: + # but apache either denies on length limit or gives a 400 + r.check_exit_code(0) + assert r.stats[0]['http_code'] in [400, 431] # PUT data, basic auth with very large pw @pytest.mark.parametrize("proto", Env.http_mplx_protos()) diff --git a/tests/http/test_15_tracing.py b/tests/http/test_15_tracing.py index d1bcc2227316..d2e987f8679b 100644 --- a/tests/http/test_15_tracing.py +++ b/tests/http/test_15_tracing.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -55,7 +53,7 @@ def test_15_02_trace_ids(self, env: Env, httpd): '-v', '--trace-config', 'ids' ]) r.check_response(http_status=200) - for line in r.trace_lines: + for line in r.trace_lines: m = re.match(r'^\[0-[0x]] .+', line) if m is None: assert False, f'no match: {line}' @@ -68,7 +66,7 @@ def test_15_03_trace_ids_time(self, env: Env, httpd): '-v', '--trace-config', 'ids,time' ]) r.check_response(http_status=200) - for line in r.trace_lines: + for line in r.trace_lines: m = re.match(r'^([0-9:.]+) \[0-[0x]] .+', line) if m is None: assert False, f'no match: {line}' @@ -84,7 +82,7 @@ def test_15_04_trace_all(self, env: Env, httpd): ]) r.check_response(http_status=200) found_tcp = False - for line in r.trace_lines: + for line in r.trace_lines: m = re.match(r'^([0-9:.]+) \[0-[0x]] .+', line) if m is None: assert False, f'no match: {line}' @@ -102,7 +100,7 @@ def test_15_05_trace_all(self, env: Env, httpd): ]) r.check_response(http_status=200) found_tcp = False - for line in r.trace_lines: + for line in r.trace_lines: m = re.match(r'^\[0-[0x]] .+', line) if m is None: assert False, f'no match: {line}' diff --git a/tests/http/test_16_info.py b/tests/http/test_16_info.py index 5be5e31b92ca..0d184931e208 100644 --- a/tests/http/test_16_info.py +++ b/tests/http/test_16_info.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -38,10 +36,10 @@ class TestInfo: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd): indir = httpd.docs_dir - env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024) - env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024) + env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=indir, fname="data-1m", fsize=1024 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100 * 1024) # download plain file @pytest.mark.parametrize("proto", Env.http_protos()) diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index b2339dab5165..70af06eff9a0 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -28,6 +26,8 @@ import logging import os import re +from dataclasses import dataclass +from typing import ClassVar, Dict, List import pytest from testenv import CurlClient, Env, LocalClient @@ -35,15 +35,16 @@ log = logging.getLogger(__name__) +@dataclass(frozen=True) class TLSDefs: - TLS_VERSIONS = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'] - TLS_VERSION_IDS = { + TLS_VERSIONS: ClassVar[List[str]] = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'] + TLS_VERSION_IDS: ClassVar[Dict[str, int]] = { 'TLSv1': 0x301, 'TLSv1.1': 0x302, 'TLSv1.2': 0x303, 'TLSv1.3': 0x304 } - CURL_ARG_MIN_VERSION_ID = { + CURL_ARG_MIN_VERSION_ID: ClassVar[Dict[str, int]] = { 'none': 0x0, 'tlsv1': 0x301, 'tlsv1.0': 0x301, @@ -51,7 +52,7 @@ class TLSDefs: 'tlsv1.2': 0x303, 'tlsv1.3': 0x304, } - CURL_ARG_MAX_VERSION_ID = { + CURL_ARG_MAX_VERSION_ID: ClassVar[Dict[str, int]] = { 'none': 0x0, '1.0': 0x301, '1.1': 0x302, @@ -64,7 +65,7 @@ class TestSSLUse: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd, nghttpx): - env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10*1024) + env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10 * 1024) def test_17_01_sslinfo_plain(self, env: Env, httpd): proto = 'http/1.1' @@ -82,9 +83,8 @@ def test_17_02_sslinfo_reconnect(self, env: Env, tls_max, httpd): count = 3 exp_resumed = 'Resumed' xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}'] - if env.curl_uses_lib('libressl'): - if tls_max == '1.3': - exp_resumed = 'Initial' # 1.2 works in LibreSSL, but 1.3 does not, TODO + if env.curl_uses_lib('libressl') and tls_max == '1.3': + exp_resumed = 'Initial' # 1.2 works in LibreSSL, but 1.3 does not, TODO if env.curl_uses_lib('rustls-ffi'): exp_resumed = 'Initial' # Rustls does not support sessions, TODO if env.curl_uses_lib('mbedtls') and tls_max == '1.3' and \ @@ -127,7 +127,7 @@ def test_17_03_trailing_dot(self, env: Env, proto, httpd, nghttpx): # the SNI the server received is without trailing dot assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}' - # use hostname with double trailing dot, verify handshake + # use hostname with double trailing dot @pytest.mark.parametrize("proto", Env.http_protos()) def test_17_04_double_dot(self, env: Env, proto, httpd, nghttpx): curl = CurlClient(env=env) @@ -142,10 +142,8 @@ def test_17_04_double_dot(self, env: Env, proto, httpd, nghttpx): if proto != 'h3': # we proxy h3 assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}' assert False, f'should not have succeeded: {r.json}' - # 7 - Rustls rejects a servername with .. during setup - # 35 - LibreSSL rejects setting an SNI name with trailing dot - # 60 - peer name matching failed against certificate - assert r.exit_code in [7, 35, 60], f'{r}' + # 3 - not allowed in the URL + assert r.exit_code == 3, f'{r}' # use ip address for connect @pytest.mark.parametrize("proto", Env.http_protos()) @@ -224,7 +222,7 @@ def gen_test_17_07_list(): ['AES256ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384'], False], ['CHACHA20ish', ['ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True], ['AES256ish+CHACHA20ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384', - 'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True], + 'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True], ] ret = [] for tls_id, tls_proto in { @@ -245,10 +243,10 @@ def test_17_07_ssl_ciphers(self, env: Env, httpd, configures_httpd, succeed13, succeed12): # to test setting cipher suites, the AES 256 ciphers are disabled in the test server httpd.set_extra_config('base', [ - 'SSLCipherSuite SSL' - ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256' + 'SSLCipherSuite SSL' + + ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256' + ':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305', - 'SSLCipherSuite TLSv1.3' + 'SSLCipherSuite TLSv1.3' + ' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256', f'SSLProtocol {tls_proto}' ]) @@ -269,9 +267,9 @@ def test_17_07_ssl_ciphers(self, env: Env, httpd, configures_httpd, elif env.curl_uses_lib('schannel'): # not in CI, so untested if ciphers12 is not None: pytest.skip('Schannel does not support setting TLSv1.2 ciphers by name') - elif env.curl_uses_lib('mbedtls') and not env.curl_lib_version_at_least('mbedtls', '3.6.0'): - if tls_proto == 'TLSv1.3': - pytest.skip('mbedTLS < 3.6.0 does not support TLSv1.3') + elif (env.curl_uses_lib('mbedtls') and not env.curl_lib_version_at_least('mbedtls', '3.6.0') + and tls_proto == 'TLSv1.3'): + pytest.skip('mbedTLS < 3.6.0 does not support TLSv1.3') # test extra_args = ['--tls13-ciphers', ':'.join(ciphers13)] if ciphers13 else [] extra_args += ['--ciphers', ':'.join(ciphers12)] if ciphers12 else [] @@ -321,13 +319,12 @@ def test_17_09_ssl_min_max(self, env: Env, httpd, configures_httpd, server_tls, ]) httpd.reload_if_config_changed() # curl's TLS backend supported version - if env.curl_uses_lib('gnutls') or \ - env.curl_uses_lib('quiche') or \ - env.curl_uses_lib('aws-lc') or \ - env.curl_uses_lib('boringssl'): - curl_supported = [0x301, 0x302, 0x303, 0x304] - elif env.curl_uses_lib('openssl') and \ - env.curl_lib_version_before('openssl', '3.0.0'): + if (env.curl_uses_lib('gnutls') or + env.curl_uses_lib('quiche') or + env.curl_uses_lib('aws-lc') or + env.curl_uses_lib('boringssl') or + (env.curl_uses_lib('openssl') and + env.curl_lib_version_before('openssl', '3.0.0'))): curl_supported = [0x301, 0x302, 0x303, 0x304] else: # most SSL backends dropped support for TLSv1.0, TLSv1.1 curl_supported = [0x303, 0x304] @@ -523,14 +520,14 @@ def test_17_17_h1_ignore_ciphers13(self, env: Env, httpd): pytest.param("-MAC-ALL:+AEAD", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-MAC-only-AEAD'), pytest.param("-GROUP-ALL:+GROUP-X25519", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-group-only-X25519'), pytest.param("-GROUP-ALL:+GROUP-SECP192R1", "", [], False, id='group-only-SECP192R1'), - ]) + ]) def test_17_18_gnutls_priority(self, env: Env, httpd, configures_httpd, priority, tls_proto, ciphers, success): # to test setting cipher suites, the AES 256 ciphers are disabled in the test server httpd.set_extra_config('base', [ - 'SSLCipherSuite SSL' - ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256' + 'SSLCipherSuite SSL' + + ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256' + ':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305', - 'SSLCipherSuite TLSv1.3' + 'SSLCipherSuite TLSv1.3' + ' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256', ]) httpd.reload_if_config_changed() diff --git a/tests/http/test_18_methods.py b/tests/http/test_18_methods.py index 006347092833..452b422c6b06 100644 --- a/tests/http/test_18_methods.py +++ b/tests/http/test_18_methods.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -37,9 +35,9 @@ class TestMethods: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd, nghttpx): indir = httpd.docs_dir - env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024) + env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=indir, fname="data-1m", fsize=1024 * 1024) # download 1 file @pytest.mark.parametrize("proto", Env.http_protos()) diff --git a/tests/http/test_19_shutdown.py b/tests/http/test_19_shutdown.py index d72ed5a2ece2..388d3955dabe 100644 --- a/tests/http/test_19_shutdown.py +++ b/tests/http/test_19_shutdown.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -39,9 +37,9 @@ class TestShutdown: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd): indir = httpd.docs_dir - env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024) - env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024) + env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) + env.make_data_file(indir=indir, fname="data-1m", fsize=1024 * 1024) # check with `tcpdump` that we see curl TCP RST packets @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @@ -185,7 +183,8 @@ def test_19_06_check_shutdown(self, env: Env, httpd, nghttpx, proto): # run connection pressure, many small transfers, not reusing connections, # limited total @pytest.mark.parametrize("proto", ['http/1.1']) - def test_19_07_shutdown_by_curl(self, env: Env, httpd, proto): + @pytest.mark.parametrize("share_connect", [False, True]) + def test_19_07_shutdown_by_curl(self, env: Env, httpd, proto, share_connect): if not env.curl_is_debug(): pytest.skip('only works for curl debug builds') count = 500 @@ -197,17 +196,50 @@ def test_19_07_shutdown_by_curl(self, env: Env, httpd, proto): }) if not client.exists(): pytest.skip(f'example client not built: {client.name}') + extra_args = ['-S'] if share_connect else [] # share connections r = client.run(args=[ '-n', f'{count}', # that many transfers '-C', env.ca.cert_file, '-f', # forbid conn reuse '-m', '10', # max parallel '-T', '5', # max total conns at a time - '-V', proto, - url - ]) + ] + extra_args + ['-V', proto, url]) r.check_exit_code(0) shutdowns = [line for line in r.trace_lines if re.match(r'.*SHUTDOWN] shutdown, done=1', line)] # we see less clean shutdowns as total limit forces early closes assert len(shutdowns) < count, f'{shutdowns}' + + # Strictly event-based transfers, no connection reuse. Connections + # whose graceful shutdown cannot finish right away must have their + # socket stay registered with the application's socket callback, + # or they never make progress and leak until multi cleanup. + @pytest.mark.parametrize("proto", ['http/1.1']) + def test_19_08_event_shutdown_watched(self, env: Env, httpd, proto): + if not env.curl_is_debug(): + pytest.skip('only works for curl debug builds') + count = 5 + docname = 'data.json' + url = f'https://localhost:{env.https_port}/{docname}' + client = LocalClient(name='cli_ev_download', env=env, run_env={ + # make socket receives block often, so the TLS shutdown + # cannot finish on its first attempt + 'CURL_DBG_SOCK_RBLOCK': '90', + 'CURL_DEBUG': 'ssl,multi' + }) + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + r = client.run(args=[ + '-n', f'{count}', '-C', env.ca.cert_file, url + ]) + r.check_exit_code(0) + m = None + for line in r.stderr.splitlines(): + m = re.match(r'.*\[ev] final: watched=(\d+) socks_left=(\d+)', line) + if m: + break + assert m, f'no client event summary found: {r.stderr}' + # shutdown sockets were watched after transfers finished and + # all shutdowns finished within the event loop + assert int(m.group(1)) > 0, f'{r.stderr}' + assert int(m.group(2)) == 0, f'{r.stderr}' diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index fdc9df6eb7d2..c859279c0ee6 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -24,13 +22,17 @@ # ########################################################################### # +import base64 +import hashlib import logging import os +import re import shutil import socket import subprocess +import threading import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict import pytest @@ -40,19 +42,25 @@ log = logging.getLogger(__name__) -@pytest.mark.skipif(condition=not Env.curl_has_protocol('ws'), - reason='curl lacks ws protocol support') -class TestWebsockets: +class WsServer: - PORT_SPECS = { - 'ws': socket.SOCK_STREAM, - } + def __init__(self, name, env, cmd): + self.name = name + self.env = env + self.run_dir = os.path.join(env.gen_dir, self.name) + self.err_file = os.path.join(self.run_dir, 'stderr') + self._rmrf(self.run_dir) + self._mkpath(self.run_dir) + self.cmd = cmd + self.wsproc = None + self.cerr = None + self.port = 0 def check_alive(self, env, port, timeout=Env.SERVER_TIMEOUT): curl = CurlClient(env=env) url = f'http://localhost:{port}/' - end = datetime.now() + timedelta(seconds=timeout) - while datetime.now() < end: + end = datetime.now(timezone.utc) + timedelta(seconds=timeout) + while datetime.now(timezone.utc) < end: r = curl.http_download(urls=[url]) if r.exit_code == 0: return True @@ -67,42 +75,62 @@ def _rmrf(self, path): if os.path.exists(path): shutil.rmtree(path) - @pytest.fixture(autouse=True, scope='class') - def ws_echo(self, env): - self.run_dir = os.path.join(env.gen_dir, 'ws_echo_server') - err_file = os.path.join(self.run_dir, 'stderr') - self._rmrf(self.run_dir) - self._mkpath(self.run_dir) - self.cmd = os.path.join(env.project_dir, - 'tests/http/testenv/ws_echo_server.py') - self.wsproc = None - self.cerr = None + def startup(self): def startup(ports: Dict[str, int]) -> bool: - wargs = [self.cmd, '--port', str(ports['ws'])] + self.port = ports[self.name] + wargs = [self.cmd, '--port', str(self.port)] log.info(f'start_ {wargs}') self.wsproc = subprocess.Popen(args=wargs, cwd=self.run_dir, stderr=self.cerr, stdout=self.cerr) - if self.check_alive(env, ports['ws']): - env.update_ports(ports) + if self.check_alive(self.env, self.port): + self.env.update_ports(ports) return True log.error(f'not alive {wargs}') self.wsproc.terminate() self.wsproc = None return False - with open(err_file, 'w') as self.cerr: - assert alloc_ports_and_do(TestWebsockets.PORT_SPECS, startup, - env.gen_root, max_tries=3) - assert self.wsproc - yield - self.wsproc.terminate() + self.cerr = open(self.err_file, 'w') # noqa: SIM115 + port_spec = { + self.name: socket.SOCK_STREAM + } + assert alloc_ports_and_do(port_spec, startup, + self.env.gen_root, max_tries=3) + assert self.wsproc + + def shutdown(self): + self.wsproc.terminate() + self.cerr.close() + + +@pytest.mark.skipif(condition=not Env.curl_has_protocol('ws'), + reason='curl lacks ws protocol support') +class TestWebsockets: + + @pytest.fixture(autouse=True, scope='class') + def ws_echo(self, env): + cmd = os.path.join(env.project_dir, + 'tests/http/testenv/ws_echo_server.py') + server = WsServer('ws_echo', env, cmd) + server.startup() + yield server + server.shutdown() + + @pytest.fixture(autouse=True, scope='class') + def ws_4frames(self, env): + cmd = os.path.join(env.project_dir, + 'tests/http/testenv/ws_4frames_server.py') + server = WsServer('ws_4frames', env, cmd) + server.startup() + yield server + server.shutdown() def test_20_01_basic(self, env: Env, ws_echo): curl = CurlClient(env=env) - url = f'http://localhost:{env.ws_port}/' + url = f'http://localhost:{ws_echo.port}/' r = curl.http_download(urls=[url]) r.check_response(http_status=426) @@ -111,7 +139,7 @@ def test_20_02_pingpong_small(self, env: Env, ws_echo): client = LocalClient(env=env, name='cli_ws_pingpong') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[url, payload]) r.check_exit_code(0) @@ -121,7 +149,7 @@ def test_20_03_pingpong_too_large(self, env: Env, ws_echo): client = LocalClient(env=env, name='cli_ws_pingpong') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[url, payload]) r.check_exit_code(100) # CURLE_TOO_LARGE @@ -133,7 +161,7 @@ def test_20_04_data_small(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[f'-{model}', '-m', str(1), '-M', str(10), url]) r.check_exit_code(0) @@ -145,7 +173,7 @@ def test_20_05_data_med(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[f'-{model}', '-m', str(120), '-M', str(130), url]) r.check_exit_code(0) @@ -157,7 +185,7 @@ def test_20_06_data_large(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[f'-{model}', '-m', str(65535 - 5), '-M', str(65535 + 5), url]) r.check_exit_code(0) @@ -171,12 +199,12 @@ def test_20_07_data_large_small_recv(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data', run_env=run_env) if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' r = client.run(args=[f'-{model}', '-m', str(65535 - 5), '-M', str(65535 + 5), url]) r.check_exit_code(0) # Send large frames and simulate send blocking on 8192 bytes chunks - # Simlates error reported in #15865 + # Simulates error reported in #15865 @pytest.mark.parametrize("model", [ pytest.param(1, id='multi_perform'), pytest.param(2, id='curl_ws_send+recv'), @@ -187,7 +215,7 @@ def test_20_08_data_very_large(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data', run_env=run_env) if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' count = 10 large = 20000 r = client.run(args=[f'-{model}', '-c', str(count), '-m', str(large), url]) @@ -201,8 +229,131 @@ def test_20_09_data_empty(self, env: Env, ws_echo, model): client = LocalClient(env=env, name='cli_ws_data') if not client.exists(): pytest.skip(f'example client not built: {client.name}') - url = f'ws://localhost:{env.ws_port}/' + url = f'ws://localhost:{ws_echo.port}/' count = 10 large = 0 r = client.run(args=[f'-{model}', '-c', str(count), '-m', str(large), url]) r.check_exit_code(0) + + # use ws:// URL with HTTP proxy, check that it tunnels automatically + def test_20_10_proxy_http(self, env: Env, httpd, ws_echo): + curl = CurlClient(env=env) + url = f'ws://127.0.0.1:{ws_echo.port}/' + xargs = curl.get_proxy_args(proxys=False) + xargs.extend([ + '--max-time', '2' + ]) + r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=xargs) + # The CONNECT through the proxy fails as it does not allow it + r.check_exit_code(7) # CURLE_COULDNT_CONNECT + assert r.stats[0]['http_connect'] == 403, f'{r}' + + def test_20_11_crazy_pings(self, env: Env): + st = {} + send_rounds = 1 + + def srv(): + try: + with socket.socket() as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("127.0.0.1", 0)) + s.listen(1) + st["p"] = s.getsockname()[1] + + c, _ = s.accept() + c.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4096) + c.settimeout(Env.SERVER_TIMEOUT) + req = b"" + while b"\r\n\r\n" not in req: + req += c.recv(4096) + + k = re.search(rb"(?im)^Sec-WebSocket-Key:\s*(\S+)", req).group(1) + a = base64.b64encode( + hashlib.sha1(k + b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest() + ).decode() + c.sendall( + ( + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {a}\r\n\r\n" + ).encode() + ) + + f = b"\x89\x00" * 65536 # PING frames, many + try: + for _ in range(send_rounds): + c.sendall(f) + f = b"\x88\x00" # CLOSE frame + c.sendall(f) + except OSError: + # Client may close/reset while we intentionally flood frames. + # Send errors are expected here, ignore them. + pass + time.sleep(1) + c.close() + except OSError as e: + st["err"] = e + + run_env = os.environ.copy() + if 'CURL_DEBUG' in run_env: + del run_env['CURL_DEBUG'] + curl = CurlClient(env=env, run_env=run_env) + send_rounds = 2 + threading.Thread(target=srv, daemon=True).start() + while "p" not in st and "err" not in st: + time.sleep(0.01) + assert "err" not in st, f'ws-ping server failed to start: {st["err"]}' + + url = f'ws://127.0.0.1:{st["p"]}/' + r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + with_profile=True) + assert r.exit_code in [55, 56], f'{r.dump_logs()}' # SEND/RECV_ERROR + assert r.profile, f'{r}' + rss1 = r.profile.stats['rss-max'] / (1024 * 1024) + + st.clear() + send_rounds = 10 + threading.Thread(target=srv, daemon=True).start() + while "p" not in st and "err" not in st: + time.sleep(0.01) + assert "err" not in st, f'ws-ping server failed to start: {st["err"]}' + + url = f'ws://127.0.0.1:{st["p"]}/' + r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + with_profile=True) + assert r.exit_code in [55, 56], f'{r.dump_logs()}' # SEND/RECV_ERROR + assert r.profile, f'{r}' + rss2 = r.profile.stats['rss-max'] / (1024 * 1024) + assert (rss1 * 1.2) > rss2, 'bad memory increase' + + # test small frames delivery when pausing + def test_20_12_pause_frames_small(self, env: Env, ws_4frames): + payload = 127 * "x" + client = LocalClient(env=env, name='cli_ws_pause') + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + url = f'ws://localhost:{ws_4frames.port}/small' + r = client.run(args=[url, payload]) + r.check_exit_code(0) + + # test small frames delivery when pausing + def test_20_13_pause_frames_large(self, env: Env, ws_4frames): + payload = 127 * "x" + client = LocalClient(env=env, name='cli_ws_pause') + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + url = f'ws://localhost:{ws_4frames.port}/large' + r = client.run(args=[url, payload]) + r.check_exit_code(0) + + # test handling of write callback errors + def test_20_14_write_err(self, env: Env, ws_4frames): + payload = 127 * "x" + client = LocalClient(env=env, name='cli_ws_write_err') + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + url = f'ws://localhost:{ws_4frames.port}/small' + r = client.run(args=[url, payload]) + r.check_exit_code(0) diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index b55f066f2d25..28634f14614e 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -26,11 +24,12 @@ # import logging import os +import re from datetime import timedelta from typing import Generator import pytest -from testenv import CurlClient, Env, LocalClient, Dnsd +from testenv import CurlClient, Dnsd, Env, LocalClient log = logging.getLogger(__name__) @@ -51,7 +50,7 @@ def _class_scope(self, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) - # use .invalid host name that should never resolv + # use .invalid hostname that should never resolv def test_21_01_resolv_invalid_one(self, env: Env, httpd, nghttpx): count = 1 run_env = os.environ.copy() @@ -62,33 +61,33 @@ def test_21_01_resolv_invalid_one(self, env: Env, httpd, nghttpx): r.check_exit_code(6) r.check_stats(count=count, http_status=0, exitcode=6) - # use .invalid host name, one after the other + # use .invalid hostname, one after the other @pytest.mark.parametrize("delay_ms", [1, 50]) def test_21_02_resolv_invalid_serial(self, env: Env, delay_ms, httpd, nghttpx): count = 10 run_env = os.environ.copy() run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' curl = CurlClient(env=env, run_env=run_env, force_resolv=False) - urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] + urls = [f'https://test-{i}.http.curl.invalid/' for i in range(count)] r = curl.http_download(urls=urls, with_stats=True) r.check_exit_code(6) r.check_stats(count=count, http_status=0, exitcode=6) - # use .invalid host name, parallel + # use .invalid hostname, parallel @pytest.mark.parametrize("delay_ms", [1, 50]) def test_21_03_resolv_invalid_parallel(self, env: Env, delay_ms, httpd, nghttpx): count = 20 run_env = os.environ.copy() run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' curl = CurlClient(env=env, run_env=run_env, force_resolv=False) - urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] + urls = [f'https://test-{i}.http.curl.invalid/' for i in range(count)] r = curl.http_download(urls=urls, with_stats=True, extra_args=[ '--parallel' ]) r.check_exit_code(6) r.check_stats(count=count, http_status=0, exitcode=6) - # resolve first url with ipv6 only and fail that, resolve second + # resolve first URL with IPv6 only and fail that, resolve second # with ipv*, should succeed. def test_21_04_resolv_inv_v6(self, env: Env, httpd): count = 2 @@ -100,7 +99,7 @@ def test_21_04_resolv_inv_v6(self, env: Env, httpd): pytest.skip(f'example client not built: {client.name}') dfiles = [client.download_file(i) for i in range(count)] self._clean_files(dfiles) - # let the first URL resolve via ipv6 only, which we force to fail + # let the first URL resolve via IPv6 only, which we force to fail r = client.run(args=[ '-n', f'{count}', '-6', '-C', env.ca.cert_file, url ]) @@ -108,7 +107,7 @@ def test_21_04_resolv_inv_v6(self, env: Env, httpd): assert not os.path.exists(dfiles[0]) assert os.path.exists(dfiles[1]) - # use .invalid host name, parallel, single resolve thread + # use .invalid hostname, parallel, single resolve thread @pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver") def test_21_05_resolv_single_thread(self, env: Env, httpd, nghttpx): count = 10 @@ -117,7 +116,7 @@ def test_21_05_resolv_single_thread(self, env: Env, httpd, nghttpx): run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' run_env['CURL_DBG_RESOLV_MAX_THREADS'] = '1' curl = CurlClient(env=env, run_env=run_env, force_resolv=False) - urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] + urls = [f'https://test-{i}.http.curl.invalid/' for i in range(count)] r = curl.http_download(urls=urls, with_stats=True, extra_args=[ '--parallel', '-6' ]) @@ -125,77 +124,208 @@ def test_21_05_resolv_single_thread(self, env: Env, httpd, nghttpx): r.check_stats(count=count, http_status=0, exitcode=6) assert r.duration > timedelta(milliseconds=count * delay_ms), f'{r}' + def dns_settings(self, dns_method, dnsd, path="/"): + xargs = [] + run_env = os.environ.copy() + run_env['CURL_DEBUG'] = 'all' + if dns_method == 'DoH': + if not Env.curl_can_doh(): + pytest.skip(reason="curl built without DoH") + xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}{path}'] + else: + if not Env.curl_override_dns(): + pytest.skip(reason="no DNS override") + run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + run_env['CURL_QUICK_EXIT'] = '1' + return run_env, xargs + # dnsd with no answers - @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") - def test_21_06_dnsd_empty(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_06_dnsd_empty(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers() - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) - url = f'https://test-dnsd.http.curl.invalid/' - r = curl.http_download(urls=[url], with_stats=True) + url = 'https://test-dnsd.http.curl.invalid/' + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(6) # could not resolve host r.check_stats(count=1, http_status=0, exitcode=6) # dnsd with one answer for A - @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") - def test_21_07_dnsd_a(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_07_dnsd_a(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_a=['127.0.0.1']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['remote_ip'] == '127.0.0.1' # dnsd with one answer for AAAA - @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") - def test_21_08_dnsd_aaaa(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_08_dnsd_aaaa(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_aaaa=['[::1]']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_QUICK_EXIT'] = '1' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['remote_ip'] == '::1' # dnsd with one answer for A, delayed one for AAAA - @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") - def test_21_09_dnsd_a_delay(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_09_dnsd_a_delay(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'], delay_aaaa_ms=env.test_timeout * 1000) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_QUICK_EXIT'] = '1' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['remote_ip'] == '127.0.0.1' # dnsd with one answer for AAAA, delayed one for A - @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") - def test_21_10_dnsd_aaaa_delay(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_10_dnsd_aaaa_delay(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'], delay_a_ms=env.test_timeout * 1000) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_QUICK_EXIT'] = '1' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['remote_ip'] == '::1' + # transient resolve failures must not be cached as negative + # entries: a second lookup of the same name tries again + @pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver") + def test_21_11_resolv_transient_uncached(self, env: Env, httpd, nghttpx): + count = 2 + delay_ms = 250 + run_env = os.environ.copy() + run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-again.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + # not cached as negative: the second transfer resolved again + if env.curl_is_verbose(): + assert not [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + assert r.stats[1]['time_total'] > (delay_ms / 2) / 1000.0, f'{r.stats[1]}' + + # a negative resolve answer is cached: a second lookup of the same + # name fails right away from the cache + @pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver") + def test_21_12_resolv_negative_cached(self, env: Env, httpd, nghttpx): + count = 2 + delay_ms = 250 + run_env = os.environ.copy() + run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' + run_env['CURL_DBG_RESOLV_FAIL_NEGATIVE'] = '1' + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-nxdomain.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + # the second transfer failed right away from the cache entry + if env.curl_is_verbose(): + assert [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + assert r.stats[1]['time_total'] < (delay_ms / 2) / 1000.0, f'{r.stats[1]}' + + # dnsd giving NXDOMAIN for all families: the negative answer is + # cached and a second lookup of the same name uses the cache + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_13_dnsd_nxdomain_cached(self, env: Env, httpd, dnsd, dns_method): + count = 2 + dnsd.set_answers(rcode_a=3, rcode_aaaa=3) + run_env, xargs = self.dns_settings(dns_method, dnsd) + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-nx.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + if env.curl_is_verbose(): + assert [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + + # dnsd failing one family with SERVFAIL: not an authoritative + # negative answer, a second lookup of the same name tries again + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_21_14_dnsd_servfail_uncached(self, env: Env, httpd, dnsd, dns_method): + count = 2 + dnsd.set_answers(rcode_a=2, rcode_aaaa=3) + run_env, xargs = self.dns_settings(dns_method, dnsd) + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-sf.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + if env.curl_is_verbose(): + assert not [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + + # a resolve gets processed even when the first resolver thread + # starts fail, e.g. when the system temporarily refuses to spawn + # threads. The transfer must fail on the (debug-forced) lookup + # failure well before the resolve timeout. + @pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver") + def test_21_15_resolv_thread_start_fails(self, env: Env, httpd, nghttpx): + run_env = os.environ.copy() + run_env['CURL_DBG_THRDPOOL_FAIL_STARTS'] = '3' + run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = '10' + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + url = 'https://test-1.http.curl.invalid/' + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--connect-timeout', '20' + ]) + r.check_exit_code(6) + r.check_stats(count=1, http_status=0, exitcode=6) + assert r.duration < timedelta(seconds=20), f'{r}' + + # dnsd with one answer for AAAA, delayed one for A + @pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") + @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") + @pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver") + def test_21_16_dnsd_link_local(self, env: Env, httpd, dnsd): + dnsd.set_answers(addr_aaaa=['[fe80::1]']) + run_env = os.environ.copy() + run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + run_env['CURL_QUICK_EXIT'] = '1' + run_env['CURL_DEBUG'] = 'dns' + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--connect-timeout', '1' + ]) + # should fail with CURLE_OPERATION_TIMEOUT or COULDNT_CONNECT + assert r.exit_code in [7, 28], f'{r.dump_logs()}' + af_unspec_resolves = [ + line for line in r.trace_lines + if re.match(r'.* \[DNS] re-queueing query .+ for AF_UNSPEC resolve', line) + ] + assert len(af_unspec_resolves) == 1, f'{r.dump_logs()}' + aaaa_resolves = [line for line in r.trace_lines if + re.match(r'.* \* IPv6: fe80::1', line)] + assert len(aaaa_resolves) == 1, f'{r.dump_logs()}' + + # dnsd+DoH, handling HTTP response failure + def test_21_17_dnsd_http_fails(self, env: Env, httpd, dnsd): + count = 2 + dnsd.set_answers(rcode_a=2, rcode_aaaa=3) + run_env, xargs = self.dns_settings('DoH', dnsd, path='/notfound') + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-sf.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + if env.curl_is_verbose(): + assert not [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + def _clean_files(self, files): for file in files: if os.path.exists(file): diff --git a/tests/http/test_22_httpsrr.py b/tests/http/test_22_httpsrr.py index 61f5e2e2f73f..63bb2c029ed9 100644 --- a/tests/http/test_22_httpsrr.py +++ b/tests/http/test_22_httpsrr.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,13 +27,12 @@ from typing import Generator import pytest -from testenv import CurlClient, Env, Dnsd +from testenv import CurlClient, Dnsd, Env log = logging.getLogger(__name__) @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") -@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override") @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPSRR'), reason="no HTTPSRR support") class TestHTTPSRR: @@ -46,86 +43,103 @@ def dnsd(self, env: Env) -> Generator[Dnsd, None, None]: yield dnsd dnsd.stop() - # dnsd a HTTPS-RR that prefers HTTP/1.1. - def test_22_01_httpsrr_h1(self, env: Env, httpd, dnsd): - dnsd.set_answers(addr_a=['127.0.0.1'], - https=['10 . alpn=http/1.1']) + def dns_settings(self, dns_method, dnsd): run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' run_env['CURL_QUICK_EXIT'] = '1' run_env['CURL_DEBUG'] = 'dns,https-connect' + run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' + xargs = [] + if dns_method == 'DoH': + if not Env.curl_can_doh(): + pytest.skip(reason="curl built without DoH") + xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}/'] + else: + if not Env.curl_override_dns(): + pytest.skip(reason="no DNS override") + run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + return run_env, xargs + + # dnsd a HTTPS-RR that prefers HTTP/1.1. + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_01_httpsrr_h1(self, env: Env, httpd, dnsd, dns_method): + dnsd.set_answers(addr_a=['127.0.0.1'], + https=['10 . alpn=http/1.1']) + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '1.1', f'{r}' # dnsd a HTTPS-RR that prefers HTTP/2, this overrides the --http3 option. @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") - def test_22_02_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_02_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], https=['10 . alpn=h2']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' - run_env['CURL_QUICK_EXIT'] = '1' - run_env['CURL_DEBUG'] = 'dns,https-connect' + run_env, xargs = self.dns_settings(dns_method, dnsd) + xargs.append('--http3') curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True, extra_args=[ - '--http3' - ]) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '2', f'{r}' # dnsd a HTTPS-RR that prefers HTTP/3. @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") - def test_22_03_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_03_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], https=['10 . alpn=h3,h2']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' - run_env['CURL_QUICK_EXIT'] = '1' - run_env['CURL_DEBUG'] = 'dns,https-connect' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '3', f'{r}' # dnsd a HTTPS-RR that prefers HTTP/1.1 for another target, so ignored. - def test_22_04_httpsrr_wrong_target(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_04_httpsrr_wrong_target(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], https=['10 another alpn=http/1.1']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' - run_env['CURL_QUICK_EXIT'] = '1' - run_env['CURL_DEBUG'] = 'dns,https-connect' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '2', f'{r}' # dnsd a HTTPS-RR with no-default-alpn, ignored by curl for now - def test_22_05_httpsrr_no_default_alpn(self, env: Env, httpd, dnsd): + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_05_httpsrr_no_default_alpn(self, env: Env, httpd, dnsd, dns_method): dnsd.set_answers(addr_a=['127.0.0.1'], https=['10 . no-default-alpn alpn=http/1.1']) - run_env = os.environ.copy() - run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' - run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' - run_env['CURL_QUICK_EXIT'] = '1' - run_env['CURL_DEBUG'] = 'dns,https-connect' + run_env, xargs = self.dns_settings(dns_method, dnsd) curl = CurlClient(env=env, run_env=run_env, force_resolv=False) url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json' - r = curl.http_download(urls=[url], with_stats=True) + r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs) r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '2', f'{r}' + + # download https: via https: proxytunnel + @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'), + reason='curl lacks HTTPS-proxy support') + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) + def test_22_06_httpsrr_proxy(self, env: Env, httpd, nghttpx_fwd, dnsd, dns_method): + dnsd.set_answers(addr_a=['127.0.0.1'], + https=['10 . alpn=http/1.1']) + run_env, xargs = self.dns_settings(dns_method, dnsd) + curl = CurlClient(env=env, run_env=run_env) + url = f'https://localhost:{env.https_port}/data.json' + xargs.extend(curl.get_proxy_args(tunnel=True)) + r = curl.http_download(urls=[url], alpn_proto='h2', with_stats=True, + extra_args=xargs) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '1.1', f'{r}' diff --git a/tests/http/test_30_vsftpd.py b/tests/http/test_30_vsftpd.py index 5991c4555343..860c38d5d3f4 100644 --- a/tests/http/test_30_vsftpd.py +++ b/tests/http/test_30_vsftpd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -64,20 +62,21 @@ def _class_scope(self, env, vsftpd): os.makedirs(vsftpd.docs_dir) self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-0k', fsize=0) self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10*1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024*1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10*1024*1024) + self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10 * 1024) + self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024 * 1024) + self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-0k", fsize=0) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) - env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100*1024) - env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024*1024) + env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) + env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) def test_30_01_list_dir(self, env: Env, vsftpd: VsFTPD): curl = CurlClient(env=env) url = f'ftp://{env.ftp_domain}:{vsftpd.port}/' r = curl.ftp_get(urls=[url], with_stats=True) r.check_stats(count=1, http_status=226) - lines = open(os.path.join(curl.run_dir, 'download_#1.data')).readlines() + with open(os.path.join(curl.run_dir, 'download_#1.data')) as fd: + lines = fd.readlines() assert len(lines) == 5, f'list: {lines}' r.check_stats_timelines() @@ -148,7 +147,7 @@ def _rmf(self, path): @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_30_06_shutdownh_download(self, env: Env, vsftpd: VsFTPD): + def test_30_06_shutdown_download(self, env: Env, vsftpd: VsFTPD): docname = 'data-1k' curl = CurlClient(env=env) count = 1 @@ -160,13 +159,13 @@ def test_30_06_shutdownh_download(self, env: Env, vsftpd: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpd.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' # check with `tcpdump` if curl causes any TCP RST packets @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_30_07_shutdownh_upload(self, env: Env, vsftpd: VsFTPD): + def test_30_07_shutdown_upload(self, env: Env, vsftpd: VsFTPD): docname = 'upload-1k' curl = CurlClient(env=env) srcfile = os.path.join(env.gen_dir, docname) @@ -181,7 +180,7 @@ def test_30_07_shutdownh_upload(self, env: Env, vsftpd: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpd.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' def test_30_08_active_download(self, env: Env, vsftpd: VsFTPD): docname = 'data-10k' @@ -251,8 +250,10 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -264,8 +265,10 @@ def check_upload(self, env, vsftpd: VsFTPD, docname, binary=True): assert os.path.exists(srcfile) assert os.path.exists(dstfile) if not filecmp.cmp(srcfile, dstfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dstfile).readlines(), + with open(srcfile) as fa, open(dstfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dstfile, n=1)) diff --git a/tests/http/test_31_vsftpds.py b/tests/http/test_31_vsftpds.py index 0f8f23e9b6fa..debb8c9b5bc1 100644 --- a/tests/http/test_31_vsftpds.py +++ b/tests/http/test_31_vsftpds.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -37,6 +35,10 @@ @pytest.mark.skipif(condition=not Env.has_vsftpd(), reason="missing vsftpd") +@pytest.mark.skipif(condition=Env.curl_uses_lib('rustls-ffi'), + reason="rustls does not support TLS session reuse") +@pytest.mark.skipif(condition=Env.curl_uses_lib('libressl'), + reason="libressl fails on TLS session reuse") class TestVsFTPD: SUPPORTS_SSL = True @@ -70,19 +72,20 @@ def _class_scope(self, env, vsftpds): if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10*1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024*1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10*1024*1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) - env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100*1024) - env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024*1024) + env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) + env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) def test_31_01_list_dir(self, env: Env, vsftpds: VsFTPD): curl = CurlClient(env=env) url = f'ftp://{env.ftp_domain}:{vsftpds.port}/' r = curl.ftp_ssl_get(urls=[url], with_stats=True) r.check_stats(count=1, http_status=226) - lines = open(os.path.join(curl.run_dir, 'download_#1.data')).readlines() + with open(os.path.join(curl.run_dir, 'download_#1.data')) as fd: + lines = fd.readlines() assert len(lines) == 4, f'list: {lines}' r.check_stats_timelines() @@ -100,31 +103,39 @@ def test_31_02_download_1(self, env: Env, vsftpds: VsFTPD, docname): self.check_downloads(curl, srcfile, count) r.check_stats_timelines() - @pytest.mark.parametrize("docname", [ - 'data-1k', 'data-1m', 'data-10m' + @pytest.mark.parametrize("docname,count,secure", [ + ['data-1k', 10, True], + ['data-1m', 5, True], + ['data-1m', 5, False], + ['data-10m', 2, True] ]) - def test_31_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname): + def test_31_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname, count, secure): curl = CurlClient(env=env) srcfile = os.path.join(vsftpds.docs_dir, f'{docname}') - count = 10 url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]' - r = curl.ftp_ssl_get(urls=[url], with_stats=True) + xargs = [] + if not secure: + xargs.append('--insecure') + r = curl.ftp_ssl_get(urls=[url], with_stats=True, extra_args=xargs) r.check_stats(count=count, http_status=226) self.check_downloads(curl, srcfile, count) assert r.total_connects == count + 1, 'should reuse the control conn' r.check_stats_timelines() - @pytest.mark.parametrize("docname", [ - 'data-1k', 'data-1m', 'data-10m' + @pytest.mark.parametrize("docname,count,secure", [ + ['data-1k', 10, True], + ['data-1m', 5, True], + ['data-1m', 5, False], + ['data-10m', 2, True] ]) - def test_31_04_download_10_parallel(self, env: Env, vsftpds: VsFTPD, docname): + def test_31_04_download_10_parallel(self, env: Env, vsftpds: VsFTPD, docname, count, secure): curl = CurlClient(env=env) srcfile = os.path.join(vsftpds.docs_dir, f'{docname}') - count = 10 url = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]' - r = curl.ftp_ssl_get(urls=[url], with_stats=True, extra_args=[ - '--parallel' - ]) + xargs = ['--parallel'] + if not secure: + xargs.append('--insecure') + r = curl.ftp_ssl_get(urls=[url], with_stats=True, extra_args=xargs) r.check_stats(count=count, http_status=226) self.check_downloads(curl, srcfile, count) assert r.total_connects > count + 1, 'should have used several control conns' @@ -153,7 +164,7 @@ def _rmf(self, path): @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_31_06_shutdownh_download(self, env: Env, vsftpds: VsFTPD): + def test_31_06_shutdown_download(self, env: Env, vsftpds: VsFTPD): docname = 'data-1k' curl = CurlClient(env=env) count = 1 @@ -164,13 +175,13 @@ def test_31_06_shutdownh_download(self, env: Env, vsftpds: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpds.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' # check with `tcpdump` if curl causes any TCP RST packets @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_31_07_shutdownh_upload(self, env: Env, vsftpds: VsFTPD): + def test_31_07_shutdown_upload(self, env: Env, vsftpds: VsFTPD): docname = 'upload-1k' curl = CurlClient(env=env) srcfile = os.path.join(env.gen_dir, docname) @@ -184,14 +195,14 @@ def test_31_07_shutdownh_upload(self, env: Env, vsftpds: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpds.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' def test_31_08_upload_ascii(self, env: Env, vsftpds: VsFTPD): docname = 'upload-ascii' line_length = 21 srcfile = os.path.join(env.gen_dir, docname) dstfile = os.path.join(vsftpds.docs_dir, docname) - env.make_data_file(indir=env.gen_dir, fname=docname, fsize=100*1024, + env.make_data_file(indir=env.gen_dir, fname=docname, fsize=100 * 1024, line_length=line_length) srcsize = os.path.getsize(srcfile) self._rmf(dstfile) @@ -203,7 +214,8 @@ def test_31_08_upload_ascii(self, env: Env, vsftpds: VsFTPD): r.check_stats(count=count, http_status=226) # expect the uploaded file to be number of converted newlines larger dstsize = os.path.getsize(dstfile) - newlines = len(open(srcfile).readlines()) + with open(srcfile) as fd: + newlines = len(fd.readlines()) assert (srcsize + newlines) == dstsize, \ f'expected source with {newlines} lines to be that much larger,'\ f'instead srcsize={srcsize}, upload size={dstsize}, diff={dstsize-srcsize}' @@ -248,7 +260,8 @@ def test_31_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): r = curl.ftp_ssl_upload(urls=[url], updata=indata, with_stats=True) r.check_stats(count=count, http_status=226) assert os.path.exists(dstfile) - destdata = open(dstfile).readlines() + with open(dstfile) as fd: + destdata = fd.readlines() expdata = [indata] if len(indata) else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' @@ -270,14 +283,40 @@ def test_31_12_upload_eprt(self, env: Env, vsftpds: VsFTPD): dstfile = os.path.join(vsftpds.docs_dir, docname) assert os.path.exists(dstfile), f'{r.dump_logs()}' + # connection reuse with STARTTLS required + # 1st download without STARTTLS, 2nd with --ssl-reqd + @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") + def test_31_13_starttls_reuse(self, env: Env, vsftpds: VsFTPD): + run_env = os.environ.copy() + run_env['CURL_DBG_NO_USE_SSL_ON_FIRST'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url1 = f'ftp://{env.ftp_domain}:{vsftpds.port}/data-1k' + url2 = f'ftp://{env.ftp_domain}:{vsftpds.port}/data-10k' + r = curl.run_direct(with_stats=True, args=[ + '-svv', '--resolve', f'{env.ftp_domain}:{vsftpds.port}:127.0.0.1', + '--cacert', env.ca.cert_file, + url1, '--out-null', + url2, '--out-null', '--ssl-reqd' + ]) + r.check_exit_code(0) + r.check_stats(count=2, http_status=226) + # expect 4 connections to have been made: + # 1. 1st CONTROL without STARTTLS + # 2. 1st DATA for download + # 3. 2nd CONTROL with STARTTLS (not reuse of 1) + # 4. 2nd DATA for download + assert r.total_connects == 4, f'{r.dump_logs()}' + def check_downloads(self, client, srcfile: str, count: int, complete: bool = True): for i in range(count): dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -289,8 +328,10 @@ def check_upload(self, env, vsftpd: VsFTPD, docname): assert os.path.exists(srcfile) assert os.path.exists(dstfile) if not filecmp.cmp(srcfile, dstfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dstfile).readlines(), + with open(srcfile) as fa, open(dstfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dstfile, n=1)) diff --git a/tests/http/test_32_ftps_vsftpd.py b/tests/http/test_32_ftps_vsftpd.py index 5433081d9822..6db7fb1eaa0f 100644 --- a/tests/http/test_32_ftps_vsftpd.py +++ b/tests/http/test_32_ftps_vsftpd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -37,6 +35,10 @@ @pytest.mark.skipif(condition=not Env.has_vsftpd(), reason="missing vsftpd") +@pytest.mark.skipif(condition=Env.curl_uses_lib('rustls-ffi'), + reason="rustls does not support TLS session reuse") +@pytest.mark.skipif(condition=Env.curl_uses_lib('libressl'), + reason="libressl fails on TLS session reuse") class TestFtpsVsFTPD: SUPPORTS_SSL = True @@ -70,19 +72,20 @@ def _class_scope(self, env, vsftpds): if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10*1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024*1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10*1024*1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) - env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100*1024) - env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024*1024) + env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) + env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) def test_32_01_list_dir(self, env: Env, vsftpds: VsFTPD): curl = CurlClient(env=env) url = f'ftps://{env.ftp_domain}:{vsftpds.port}/' r = curl.ftp_get(urls=[url], with_stats=True) r.check_stats(count=1, http_status=226) - lines = open(os.path.join(curl.run_dir, 'download_#1.data')).readlines() + with open(os.path.join(curl.run_dir, 'download_#1.data')) as fd: + lines = fd.readlines() assert len(lines) == 4, f'list: {lines}' r.check_stats_timelines() @@ -100,15 +103,20 @@ def test_32_02_download_1(self, env: Env, vsftpds: VsFTPD, docname): self.check_downloads(curl, srcfile, count) r.check_stats_timelines() - @pytest.mark.parametrize("docname", [ - 'data-1k', 'data-1m', 'data-10m' + @pytest.mark.parametrize("docname,count,secure", [ + ['data-1k', 10, True], + ['data-1m', 5, True], + ['data-1m', 5, False], + ['data-10m', 2, True] ]) - def test_32_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname): + def test_32_03_download_10_serial(self, env: Env, vsftpds: VsFTPD, docname, count, secure): curl = CurlClient(env=env) srcfile = os.path.join(vsftpds.docs_dir, f'{docname}') - count = 10 url = f'ftps://{env.ftp_domain}:{vsftpds.port}/{docname}?[0-{count-1}]' - r = curl.ftp_get(urls=[url], with_stats=True) + xargs = [] + if not secure: + xargs.append('--insecure') + r = curl.ftp_get(urls=[url], with_stats=True, extra_args=xargs) r.check_stats(count=count, http_status=226) self.check_downloads(curl, srcfile, count) assert r.total_connects == count + 1, 'should reuse the control conn' @@ -120,7 +128,7 @@ def test_32_03b_ftp_compat_ftps(self, env: Env, vsftpds: VsFTPD): curl = CurlClient(env=env) docname = 'data-1k' count = 2 - url1= f'ftps://{env.ftp_domain}:{vsftpds.port}/{docname}' + url1 = f'ftps://{env.ftp_domain}:{vsftpds.port}/{docname}' url2 = f'ftp://{env.ftp_domain}:{vsftpds.port}/{docname}' r = curl.ftp_get(urls=[url1, url2], with_stats=True) r.check_stats(count=count, http_status=226) @@ -166,7 +174,7 @@ def _rmf(self, path): @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_32_06_shutdownh_download(self, env: Env, vsftpds: VsFTPD): + def test_32_06_shutdown_download(self, env: Env, vsftpds: VsFTPD): docname = 'data-1k' curl = CurlClient(env=env) count = 1 @@ -177,13 +185,13 @@ def test_32_06_shutdownh_download(self, env: Env, vsftpds: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpds.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' # check with `tcpdump` if curl causes any TCP RST packets @pytest.mark.skipif(condition=not Env.tcpdump(), reason="tcpdump not available") @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") - def test_32_07_shutdownh_upload(self, env: Env, vsftpds: VsFTPD): + def test_32_07_shutdown_upload(self, env: Env, vsftpds: VsFTPD): docname = 'upload-1k' curl = CurlClient(env=env) srcfile = os.path.join(env.gen_dir, docname) @@ -197,14 +205,14 @@ def test_32_07_shutdownh_upload(self, env: Env, vsftpds: VsFTPD): # look only at ports from DATA connection. data_ports = vsftpds.get_data_ports(r) assert len(data_ports), f'unable to find FTP data port connected to\n{r.dump_logs()}' - assert len(r.tcpdump.get_rsts(ports=data_ports)) == 0, 'Unexpected TCP RST packets' + assert len(r.tcpdump.get_rsts(port_pairs=data_ports)) == 0, 'Unexpected TCP RST packets' def test_32_08_upload_ascii(self, env: Env, vsftpds: VsFTPD): docname = 'upload-ascii' line_length = 21 srcfile = os.path.join(env.gen_dir, docname) dstfile = os.path.join(vsftpds.docs_dir, docname) - env.make_data_file(indir=env.gen_dir, fname=docname, fsize=100*1024, + env.make_data_file(indir=env.gen_dir, fname=docname, fsize=100 * 1024, line_length=line_length) srcsize = os.path.getsize(srcfile) self._rmf(dstfile) @@ -216,7 +224,8 @@ def test_32_08_upload_ascii(self, env: Env, vsftpds: VsFTPD): r.check_stats(count=count, http_status=226) # expect the uploaded file to be number of converted newlines larger dstsize = os.path.getsize(dstfile) - newlines = len(open(srcfile).readlines()) + with open(srcfile) as fd: + newlines = len(fd.readlines()) assert (srcsize + newlines) == dstsize, \ f'expected source with {newlines} lines to be that much larger,'\ f'instead srcsize={srcsize}, upload size={dstsize}, diff={dstsize-srcsize}' @@ -261,7 +270,8 @@ def test_32_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): r = curl.ftp_upload(urls=[url], updata=indata, with_stats=True) r.check_stats(count=count, http_status=226) assert os.path.exists(dstfile) - destdata = open(dstfile).readlines() + with open(dstfile) as fd: + destdata = fd.readlines() expdata = [indata] if len(indata) else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' @@ -289,8 +299,10 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -302,8 +314,10 @@ def check_upload(self, env, vsftpd: VsFTPD, docname): assert os.path.exists(srcfile) assert os.path.exists(dstfile) if not filecmp.cmp(srcfile, dstfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dstfile).readlines(), + with open(srcfile) as fa, open(dstfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dstfile, n=1)) diff --git a/tests/http/test_40_socks.py b/tests/http/test_40_socks.py index 0e1d117399c4..186b75e43072 100644 --- a/tests/http/test_40_socks.py +++ b/tests/http/test_40_socks.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -49,8 +47,8 @@ def danted(self, env: Env) -> Generator[Dante, None, None]: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, httpd): indir = httpd.docs_dir - env.make_data_file(indir=indir, fname="data-10m", fsize=10*1024*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=indir, fname="data-10m", fsize=10 * 1024 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) @pytest.mark.parametrize("sproto", ['socks4', 'socks5']) def test_40_01_socks_http(self, env: Env, sproto, danted: Dante, httpd): @@ -74,6 +72,24 @@ def test_40_02_socks_https(self, env: Env, sproto, proto, danted: Dante, httpd): else: r.check_response(http_status=200) + # download via socks to https: proxy (no tunnel) + @pytest.mark.parametrize("sproto", ['socks4', 'socks5']) + @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) + def test_40_02b_socks_https_proxy(self, env: Env, sproto, proto, danted: Dante, httpd): + if proto == 'h2' and not env.curl_uses_lib('nghttp2'): + pytest.skip('only supported with nghttp2') + curl = CurlClient(env=env, socks_args=[ + f'--{sproto}', f'127.0.0.1:{danted.port}' + ]) + url = f'http://localhost:{env.http_port}/data.json' + xargs = curl.get_proxy_args(proto=proto, tunnel=False) + r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True, + extra_args=xargs) + r.check_response(http_status=200) + exp_http_version = '2' if proto == 'h2' else '1.1' + assert r.stats[0]['proxy_used'] == 1, f'{r}' + assert r.stats[0]['http_version'] == exp_http_version, f'{r}' + @pytest.mark.parametrize("sproto", ['socks4', 'socks5']) @pytest.mark.parametrize("proto", Env.http_h1_h2_protos()) def test_40_03_dl_serial(self, env: Env, httpd, danted, proto, sproto): @@ -96,7 +112,9 @@ def test_40_04_ul_serial(self, env: Env, httpd, danted, proto, sproto): url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) r.check_stats(count=count, http_status=200, exitcode=0) - indata = open(fdata).readlines() + with open(fdata) as fi: + indata = fi.readlines() for i in range(count): - respdata = open(curl.response_file(i)).readlines() + with open(curl.response_file(i)) as fr: + respdata = fr.readlines() assert respdata == indata diff --git a/tests/http/test_50_scp.py b/tests/http/test_50_scp.py index 409378a11ee7..13c36acd6571 100644 --- a/tests/http/test_50_scp.py +++ b/tests/http/test_50_scp.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -41,10 +39,10 @@ class TestScp: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, sshd): - env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10*1024*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) def test_50_01_insecure(self, env: Env, sshd: Sshd): curl = CurlClient(env=env) @@ -191,8 +189,10 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -202,8 +202,10 @@ def check_upload(self, sshd: Sshd, srcfile, destfile, binary=True): assert os.path.exists(srcfile) assert os.path.exists(destfile) if not filecmp.cmp(srcfile, destfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(destfile).readlines(), + with open(srcfile) as fa, open(destfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=destfile, n=1)) diff --git a/tests/http/test_51_sftp.py b/tests/http/test_51_sftp.py index 052c7ef46980..6caaad9d25ba 100644 --- a/tests/http/test_51_sftp.py +++ b/tests/http/test_51_sftp.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -41,10 +39,10 @@ class TestSftp: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env, sshd): - env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10*1024*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10*1024) - env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) + env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) + env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) def test_51_01_insecure(self, env: Env, sshd: Sshd): curl = CurlClient(env=env) @@ -191,8 +189,10 @@ def check_downloads(self, client, srcfile: str, count: int, dfile = client.download_file(i) assert os.path.exists(dfile) if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(dfile).readlines(), + with open(srcfile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=dfile, n=1)) @@ -202,8 +202,10 @@ def check_upload(self, sshd: Sshd, srcfile, destfile, binary=True): assert os.path.exists(srcfile) assert os.path.exists(destfile) if not filecmp.cmp(srcfile, destfile, shallow=False): - diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), - b=open(destfile).readlines(), + with open(srcfile) as fa, open(destfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, fromfile=srcfile, tofile=destfile, n=1)) diff --git a/tests/http/test_60_h3_proxy.py b/tests/http/test_60_h3_proxy.py new file mode 100644 index 000000000000..650644eb0d5a --- /dev/null +++ b/tests/http/test_60_h3_proxy.py @@ -0,0 +1,624 @@ +# *************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +# +import os +import subprocess +import time + +import pytest +from testenv import CurlClient, Env + +MARK_NEEDS_HTTPS_PROXY = pytest.mark.skipif( + condition=not Env.curl_has_feature("HTTPS-proxy"), + reason="curl lacks HTTPS-proxy support" +) +MARK_NEEDS_HTTP3 = pytest.mark.skipif( + condition=not Env.curl_has_feature("HTTP3"), reason="curl lacks HTTP/3 support" +) +MARK_NEEDS_PROXY_HTTP3 = pytest.mark.skipif( + condition=not Env.curl_has_feature("proxy-HTTP3"), + reason="curl lacks experimental HTTP/3 proxy support" +) +MARK_NEEDS_NGHTTP3 = pytest.mark.skipif( + condition=not Env.curl_uses_lib("nghttp3"), reason="only supported with nghttp3" +) +MARK_NEEDS_NGHTTP2 = pytest.mark.skipif( + condition=not Env.curl_uses_lib("nghttp2"), reason="only supported with nghttp2" +) +MARK_NEEDS_H2O = pytest.mark.skipif( + condition=not Env.have_h2o(), reason="no h2o available" +) +MARK_NEEDS_NGHTTPX = pytest.mark.skipif( + condition=not Env.have_nghttpx(), reason="no nghttpx available" +) + +UNSUPPORTED_OPT_MSG = "does not support this" +H2O_HELLO_MSG = '"message": "Hello from h2o HTTP/3 server"' + + +def _require_available(**items): + missing = [name for name, value in items.items() if not value] + if missing: + pytest.skip(f"{' or '.join(missing)} not available") + + +def _download_path(curl: CurlClient) -> str: + return os.path.join(curl.run_dir, "download_#1.data") + + +def _check_download_message(curl: CurlClient, expected: str): + dpath = _download_path(curl) + assert os.path.exists(dpath), f"Download file not found: {dpath}" + with open(dpath, "r") as fd: + content = fd.read() + assert expected in content, f"Unexpected response content: {content}" + + +def _check_download_size(curl: CurlClient, expected_size: int): + dpath = _download_path(curl) + assert os.path.exists(dpath), f"Download file not found: {dpath}" + actual = os.path.getsize(dpath) + assert actual == expected_size, f"expected {expected_size}B download, got {actual}B" + + +def _nghttpx_proxy_args( + env: Env, + nghttpx, + nghttpx_fwd, + proxy_proto: str, + tunnel: bool, +): + port = env.pts_port(proxy_proto) + domain = env.proxy_domain + xxarg = None + if proxy_proto == "h3": + port = nghttpx.port + domain = env.domain1 + xxarg = "--proxy-http3" + elif proxy_proto == "h2": + xxarg = "--proxy-http2" + + xargs = [ + "--proxy", f"https://{domain}:{port}/", + "--resolve", f"{domain}:{port}:127.0.0.1", + "--proxy-cacert", env.ca.cert_file + ] + if xxarg: + xargs.append(xxarg) + if tunnel: + xargs.append("--proxytunnel") + return xargs + + +def _h2o_proxy_args( + env: Env, + h2o_proxy, + proxy_proto: str, + tunnel: bool, +): + pport = env.pts_port(proxy_proto, use_h2o=True) + xargs = [ + "--proxy", f"https://{env.proxy_domain}:{pport}/", + "--resolve", f"{env.proxy_domain}:{pport}:127.0.0.1", + "--proxy-cacert", env.ca.cert_file, + "--cacert", env.ca.cert_file, + ] + if proxy_proto == "h2": + xargs.append("--proxy-http2") + elif proxy_proto == "h3": + xargs.append("--proxy-http3") + + if tunnel: + xargs.append("--proxytunnel") + + return xargs + + +@MARK_NEEDS_HTTPS_PROXY +@MARK_NEEDS_HTTP3 +@MARK_NEEDS_NGHTTP3 +class TestH3Proxy: + + # Success matrix for HTTP/3 proxy CONNECT / CONNECT-UDP. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + @pytest.mark.parametrize( + ["alpn_proto", "proxy_proto"], + [ + pytest.param("http/1.1", "h3", id="h1_over_h3_proxytunnel"), + pytest.param( + "h2", + "h3", + marks=MARK_NEEDS_NGHTTP2, + id="h2_over_h3_proxytunnel", + ), + pytest.param("h3", "h3", id="h3_over_h3_proxytunnel"), + pytest.param( + "h3", + "h2", + marks=MARK_NEEDS_NGHTTP2, + id="h3_over_h2_proxytunnel", + ), + pytest.param("h3", "http/1.1", id="h3_over_h1_proxytunnel"), + ], + ) + def test_60_01_connect_tunnel( + self, + env: Env, + h2o_server, + h2o_proxy, + alpn_proto, + proxy_proto, + ): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/data.json" + proxy_args = _h2o_proxy_args( + env, h2o_proxy, proxy_proto, tunnel=True + ) + + r = curl.http_download( + urls=[url], alpn_proto=alpn_proto, with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + _check_download_message(curl, H2O_HELLO_MSG) + + # Failure matrix when proxy side does not support requested mode. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_NGHTTPX + @pytest.mark.parametrize( + ["alpn_proto", "proxy_proto", "exp_err"], + [ + pytest.param( + "http/1.1", + "h3", + "could not connect to server", + id="fail_h1_over_h3_proxytunnel", + ), + pytest.param( + "h2", + "h3", + "could not connect to server", + marks=MARK_NEEDS_NGHTTP2, + id="fail_h2_over_h3_proxytunnel", + ), + pytest.param( + "h3", + "h3", + "could not connect to server", + id="fail_h3_over_h3_proxytunnel", + ), + #pytest.param( + # "h3", + # "h2", + # "proxy closed connection", + # marks=MARK_NEEDS_NGHTTP2, + # id="fail_h3_over_h2_proxytunnel", + #), + pytest.param( + "h3", + "http/1.1", + "connect-udp tunnel failed", + id="fail_h3_over_h1_proxytunnel", + ), + ], + ) + def test_60_02_connect_tunnel_fail( + self, + env: Env, + httpd, + nghttpx, + nghttpx_fwd, + h2o_proxy, + alpn_proto, + proxy_proto, + exp_err, + ): + _require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd, + h2o_proxy=h2o_proxy) + + curl = CurlClient(env=env) + url = f"https://localhost:{env.https_port}/data.json" + proxy_args = _nghttpx_proxy_args( + env, nghttpx, nghttpx_fwd, proxy_proto, tunnel=True + ) + r = curl.http_download( + urls=[url], alpn_proto=alpn_proto, with_stats=True, extra_args=proxy_args + ) + assert r.exit_code != 0, f"Expected failure but curl succeeded: {r.dump_logs()}" + assert exp_err in r.stderr.lower(), ( + f"Expected protocol/proxy error but got: {r.dump_logs()}" + ) + + # Behavior checks for tunnel vs non-tunnel proxy mode selection. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_NGHTTPX + @pytest.mark.parametrize( + ["proxy_proto"], + [ + #pytest.param("h3", id="proxy_h3"), + pytest.param("h2", marks=MARK_NEEDS_NGHTTP2, id="proxy_h2"), + pytest.param("http/1.1", id="proxy_h1"), + ], + ) + def test_60_03_h3_target_auto_connect_udp( + self, env: Env, httpd, nghttpx, nghttpx_fwd, proxy_proto + ): + _require_available( + httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd + ) + + curl = CurlClient(env=env) + url = f"https://localhost:{httpd.ports['https']}/data.json" + proxy_args = _nghttpx_proxy_args( + env, nghttpx, nghttpx_fwd, proxy_proto, tunnel=False + ) + r = curl.http_download( + urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args + ) + + # An HTTP/3 target auto-triggers CONNECT-UDP even without --proxytunnel, + # as HTTPS targets auto-trigger CONNECT. nghttpx does not support + # CONNECT-UDP so this fails, which confirms auto-CONNECT-UDP is active. + assert r.exit_code != 0, ( + "expected failure: h3 target auto-triggers CONNECT-UDP " + "which nghttpx does not support" + ) + assert "connect-udp" in r.stderr.lower(), ( + f"expected CONNECT-UDP attempt in output, got: {r.dump_logs()}" + ) + + # Guard checks for unsupported HTTP/3 proxy options. + + @pytest.mark.skipif( + condition=Env.curl_has_feature("proxy-HTTP3"), reason="curl has h3 proxy support" + ) + def test_60_04_guard_proxy_http3_unsupported(self, env: Env, httpd): + curl = CurlClient(env=env) + url = f"https://localhost:{httpd.ports['https']}/data.json" + proxy_args = [ + "--proxy", + "https://127.0.0.1:1/", + "--proxy-http3", + "--proxytunnel", + "--cacert", + env.ca.cert_file, + ] + + r = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_exit_code(2) + assert UNSUPPORTED_OPT_MSG in r.stderr.lower(), ( + f"Expected unsupported option failure but got: {r.stderr}" + ) + + # Robustness checks for shutdown and proxy loss during transfer. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_05_graceful_shutdown(self, env: Env, h2o_server, h2o_proxy): + if not env.curl_is_debug(): + pytest.skip("needs debug curl for shutdown trace lines") + if not env.curl_is_verbose(): + pytest.skip("needs verbose-strings curl build") + + curl = CurlClient(env=env, run_env={"CURL_DEBUG": "all"}) + url = f"https://localhost:{h2o_server.port}/data.json" + proxy_args = curl.get_proxy_args(proto="h3", tunnel=True) + proxy_args.extend(["--cacert", env.ca.cert_file, "--insecure"]) + + r = curl.http_download( + urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + + shutdown_lines = [ + line + for line in r.trace_lines + if ("start shutdown(" in line.lower()) + or ("shutdown completely sent off" in line.lower()) + ] + assert shutdown_lines, f"No shutdown trace lines found:\n{r.stderr}" + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_06_proxy_drop_mid_transfer(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + + env.make_data_file(indir=h2o_server.docs_dir, fname="proxy-drop-20m", fsize=20 * 1024 * 1024) + proxy_port = h2o_proxy.port + url = f"https://localhost:{h2o_server.port}/proxy-drop-20m" + out_path = os.path.join(env.gen_dir, "proxy-drop.out") + if os.path.exists(out_path): + os.remove(out_path) + args = [ + env.curl, + "--http1.1", + "--proxy", f"https://{env.proxy_domain}:{proxy_port}/", + "--resolve", f"{env.proxy_domain}:{proxy_port}:127.0.0.1", + "--proxy-http3", + "--proxytunnel", + "--proxy-cacert", env.ca.cert_file, + "--cacert", env.ca.cert_file, + "--limit-rate", "10k", + "--max-time", "20", + "-o", out_path, + "-v", + url, + ] + + proc = None + try: + proc = subprocess.Popen( + args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + while not os.path.exists(out_path): + time.sleep(0.1) + assert h2o_proxy.kill(), "failed to stop h2o proxy" + _, stderr = proc.communicate(timeout=30) + assert proc.returncode != 0, ( + "curl should fail when proxy is terminated mid-transfer" + ) + assert proc.returncode == 56, f'{stderr}' + finally: + if proc and (proc.poll() is None): + proc.kill() + proc.wait(timeout=5) + assert h2o_proxy.start(), "failed to restart h2o proxy" + + # Large file transfers and multiplexing through HTTP/3 proxy. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_07_large_download(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + env.make_data_file(indir=h2o_server.docs_dir, fname="download-10m", fsize=10 * 1024 * 1024) + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/download-10m" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + r = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + _check_download_size(curl, 10 * 1024 * 1024) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_08_large_upload(self, env: Env, httpd, h2o_server, h2o_proxy): + _require_available(h2o_proxy=h2o_proxy) + env.make_data_file(indir=env.gen_dir, fname="upload-2m", fsize=2 * 1024 * 1024) + fdata = os.path.join(env.gen_dir, "upload-2m") + curl = CurlClient(env=env) + url = f"https://localhost:{httpd.ports['https']}/curltest/echo?id=[0-0]" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + r = curl.http_upload( + urls=[url], + data=f"@{fdata}", + alpn_proto="http/1.1", + with_stats=True, + extra_args=proxy_args, + ) + r.check_response(count=1, http_status=200) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024) + count = 5 + curl = CurlClient(env=env) + urln = f"https://localhost:{h2o_server.port}/download-1m?[0-{count - 1}]" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + proxy_args.extend(["--parallel", "--parallel-max", f"{count}"]) + r = curl.http_download( + urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=count, http_status=200) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_10_proxy_basic_auth(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/data.json" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + proxy_args.extend(["--proxy-user", "testuser:testpass"]) + r = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + _check_download_message(curl, H2O_HELLO_MSG) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_11_connection_reuse(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + curl = CurlClient(env=env) + urln = f"https://localhost:{h2o_server.port}/data.json?[0-2]" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + r = curl.http_download( + urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=3, http_status=200) + assert r.total_connects <= 3, ( + f"expected proxy connection reuse, got {r.total_connects} connects" + ) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + @pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'), + reason='curl lacks SSL session export support') + def test_60_12_quic_session_resumption(self, env: Env, h2o_server, h2o_proxy): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/data.json" + xargs = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + session_file = os.path.join(env.gen_dir, 'test_60_12.sessions') + if os.path.exists(session_file): + os.remove(session_file) + xargs.extend(['--ssl-sessions', session_file]) + # First request establishes QUIC session + r1 = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs + ) + r1.check_response(count=1, http_status=200) + xargs.extend(['--trace-config', 'ssls']) + r2 = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs + ) + r2.check_response(count=1, http_status=200) + reuses = [line for line in r2.trace_lines if '[SSLS] took session for proxy.http.curl.se' in line] + assert len(reuses), f'{r2.dump_logs()}' + + # CONNECT-UDP tunnel payload size and capsule-protocol tests. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + @pytest.mark.parametrize( + "fname,fsize", + [ + ("download-1400", 1400), + ("download-1m", 1 * 1024 * 1024), + ("download-10m", 10 * 1024 * 1024), + ], + ) + def test_60_13_udp_tunnel_payload_sizes( + self, env: Env, h2o_server, h2o_proxy, fname, fsize + ): + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + env.make_data_file(indir=h2o_server.docs_dir, fname=fname, fsize=fsize) + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/{fname}" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + r = curl.http_download( + urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + _check_download_size(curl, fsize) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_NGHTTPX + def test_60_14_udp_tunnel_capsule_absent( + self, env: Env, httpd, nghttpx, nghttpx_fwd + ): + _require_available( + httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd + ) + curl = CurlClient(env=env) + url = f"https://localhost:{httpd.ports['https']}/data.json" + proxy_args = _nghttpx_proxy_args( + env, nghttpx, nghttpx_fwd, "h3", tunnel=True + ) + r = curl.http_download( + urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args + ) + assert r.exit_code != 0, ( + "expected failure: nghttpx does not support CONNECT-UDP / Capsule-Protocol" + ) + + # Timeout and protocol-mismatch edge cases. + + #@MARK_NEEDS_PROXY_HTTP3 + #@MARK_NEEDS_H2O + #def test_60_15_connect_timeout(self, env: Env, h2o_proxy): + # _require_available(h2o_proxy=h2o_proxy) + # curl = CurlClient(env=env, timeout=15) + # url = f"https://localhost:{h2o_proxy.port}/data.json" + # # IPv6 0100::/64 is supposed to go into the void (rfc6666) + # xargs = [ + # '--proxy', 'https://xxx.invalid/', + # '--resolve', 'xxx.invalid:443:0100::1,0100::2,0100::3', + # '--proxy-http3', '--proxytunnel', + # '--connect-timeout', '1', + # ] + # r = curl.http_download( + # urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs + # ) + # r.check_exit_code(28) # CURLE_OPERATION_TIMEDOUT + # assert r.duration.total_seconds() < 10, ( + # f"timeout not respected: took {r.duration.total_seconds():.1f}s" + # ) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + @MARK_NEEDS_NGHTTP2 + def test_60_16_h2_uses_connect_tcp_not_udp(self, env: Env, httpd, h2o_proxy): + _require_available(httpd=httpd, h2o_proxy=h2o_proxy) + curl = CurlClient(env=env) + url = f"https://localhost:{env.https_port}/data.json" + proxy_args = curl.get_proxy_args("h3", tunnel=True) + # h2 inner traffic always uses CONNECT (TCP), never CONNECT-UDP, + # even through an HTTP/3 proxy with --proxytunnel. h2o supports + # CONNECT TCP tunneling, so this request succeeds. + r = curl.http_download( + urls=[url], alpn_proto="h2", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + + # Verify that happy eyeballs is active for HTTP/3 proxy connections. + # + # With the H3-PROXY filter sitting above HAPPY-EYEBALLS -> UDP, address + # family selection to the proxy is done by happy eyeballs. + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + def test_60_17_happy_eyeballs_filter_present(self, env: Env, h2o_server, h2o_proxy): + """Verbose trace confirms HAPPY-EYEBALLS filter is in the H3 proxy chain.""" + if not env.curl_is_debug(): + pytest.skip("needs debug curl for filter trace") + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + curl = CurlClient(env=env, run_env={"CURL_DEBUG": "HAPPY-EYEBALLS,H3-PROXY"}) + url = f"https://localhost:{h2o_server.port}/data.json" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + r = curl.http_download( + urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + ) + r.check_response(count=1, http_status=200) + assert "happy-eyeballs" in r.stderr.lower(), ( + f"expected HAPPY-EYEBALLS trace for H3 proxy, got: {r.stderr}" + ) + + @MARK_NEEDS_PROXY_HTTP3 + @MARK_NEEDS_H2O + @MARK_NEEDS_NGHTTP2 + def test_60_18_happy_eyeballs_ipv4_all_proto(self, env: Env, h2o_server, h2o_proxy): + """IPv4-forced H3 proxy works for h1/h2/h3 inner protocols.""" + _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) + for alpn_proto in ["http/1.1", "h2", "h3"]: + curl = CurlClient(env=env) + url = f"https://localhost:{h2o_server.port}/data.json" + proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) + proxy_args.append("--ipv4") + r = curl.http_download( + urls=[url], + alpn_proto=alpn_proto, + with_stats=True, + extra_args=proxy_args, + ) + r.check_response(count=1, http_status=200) diff --git a/tests/http/testenv/__init__.py b/tests/http/testenv/__init__.py index 08ab2e231162..834230a853bd 100644 --- a/tests/http/testenv/__init__.py +++ b/tests/http/testenv/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -23,7 +21,7 @@ # SPDX-License-Identifier: curl # ########################################################################### -# ruff: noqa: F401, E402 +# ruff: noqa: F401 import pytest pytest.register_assert_rewrite("testenv.env", "testenv.curl", "testenv.caddy", @@ -38,6 +36,7 @@ from .dante import Dante from .dnsd import Dnsd from .env import Env +from .h2o import H2oServer, H2oProxy from .httpd import Httpd from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic from .sshd import Sshd diff --git a/tests/http/testenv/caddy.py b/tests/http/testenv/caddy.py index eece1a5a394a..14f71f5bfd07 100644 --- a/tests/http/testenv/caddy.py +++ b/tests/http/testenv/caddy.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,9 +27,9 @@ import socket import subprocess import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from json import JSONEncoder -from typing import Dict +from typing import ClassVar, Dict from .curl import CurlClient from .env import Env @@ -42,19 +40,20 @@ class Caddy: - PORT_SPECS = { + PORT_SPECS: ClassVar[Dict[str, int]] = { 'caddy': socket.SOCK_STREAM, 'caddys': socket.SOCK_STREAM, } def __init__(self, env: Env): self.env = env - self._caddy = os.environ['CADDY'] if 'CADDY' in os.environ else env.caddy + self._caddy = os.environ.get('CADDY', env.caddy) self._caddy_dir = os.path.join(env.gen_dir, 'caddy') self._docs_dir = os.path.join(self._caddy_dir, 'docs') self._conf_file = os.path.join(self._caddy_dir, 'Caddyfile') self._error_log = os.path.join(self._caddy_dir, 'caddy.log') self._tmp_dir = os.path.join(self._caddy_dir, 'tmp') + self._error_fd = None self._process = None self._http_port = 0 self._https_port = 0 @@ -68,6 +67,11 @@ def docs_dir(self): def port(self) -> int: return self._https_port + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._error_log) @@ -107,8 +111,8 @@ def start(self, wait_live=True): args = [ self._caddy, 'run' ] - caddyerr = open(self._error_log, 'a') - self._process = subprocess.Popen(args=args, cwd=self._caddy_dir, stderr=caddyerr) + self._error_fd = open(self._error_log, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, cwd=self._caddy_dir, stderr=self._error_fd) if self._process.returncode is not None: return False return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) @@ -119,10 +123,12 @@ def stop(self, wait_dead=True): self._process.terminate() try: self._process.wait(timeout=1) - except Exception: + except subprocess.TimeoutExpired: self._process.kill() self._process = None + self.close_log() return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) + self.close_log() return True def restart(self): @@ -131,8 +137,8 @@ def restart(self): def wait_dead(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.domain1}:{self.port}/' r = curl.http_get(url=check_url) if r.exit_code != 0: @@ -144,8 +150,8 @@ def wait_dead(self, timeout: timedelta): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.domain1}:{self.port}/' r = curl.http_get(url=check_url) if r.exit_code == 0: diff --git a/tests/http/testenv/certs.py b/tests/http/testenv/certs.py index 3e206efcdc80..418590679ac6 100644 --- a/tests/http/testenv/certs.py +++ b/tests/http/testenv/certs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -57,6 +55,10 @@ ]]) +class CertError(Exception): + """Error in certificate handling.""" + + def _private_key(key_type): if isinstance(key_type, str): key_type = key_type.upper() @@ -150,7 +152,7 @@ def key_type(self): return f"rsa{self._pkey.key_size}" if isinstance(self._pkey, EllipticCurvePrivateKey): return f"{self._pkey.curve.name}" - raise Exception(f"unknown key type: {self._pkey}") + raise CertError(f"unknown key type: {self._pkey}") @property def private_key(self) -> Any: @@ -222,7 +224,7 @@ def issue_certs(self, specs: List[CertificateSpec], def issue_cert(self, spec: CertificateSpec, chain: Optional[List['Credentials']] = None) -> 'Credentials': - key_type = spec.key_type if spec.key_type else self.key_type + key_type = spec.key_type or self.key_type creds = None if self._store: creds = self._store.load_credentials( @@ -249,9 +251,7 @@ def create_hashdir(self, openssl): os.makedirs(self.hashdir, exist_ok=True) p = subprocess.run(args=[ openssl, 'x509', '-hash', '-noout', '-in', self.cert_file - ], capture_output=True, text=True) - if p.returncode != 0: - raise Exception(f'openssl failed to compute cert hash: {p}') + ], capture_output=True, text=True, check=True) cert_hname = f'{p.stdout.strip()}.0' shutil.copy(self.cert_file, os.path.join(self.hashdir, cert_hname)) @@ -280,8 +280,7 @@ def save(self, creds: Credentials, name: Optional[str] = None, with open(cert_file, "wb") as fd: fd.write(creds.cert_pem) if chain: - for c in chain: - fd.write(c.cert_pem) + fd.writelines(c.cert_pem for c in chain) if pkey_file is None: fd.write(creds.pkey_pem) if pkey_file is not None: @@ -290,8 +289,7 @@ def save(self, creds: Credentials, name: Optional[str] = None, with open(comb_file, "wb") as fd: fd.write(creds.cert_pem) if chain: - for c in chain: - fd.write(c.cert_pem) + fd.writelines(c.cert_pem for c in chain) fd.write(creds.pkey_pem) creds.set_files(cert_file, pkey_file, comb_file) self._add_credentials(name, creds) @@ -306,8 +304,7 @@ def save_chain(self, creds: Credentials, infix: str, with_root=False): chain = chain[:-1] chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem') with open(chain_file, "wb") as fd: - for c in chain: - fd.write(c.cert_pem) + fd.writelines(c.cert_pem for c in chain) def _add_credentials(self, name: str, creds: Credentials): if name not in self._creds_by_name: @@ -315,14 +312,14 @@ def _add_credentials(self, name: str, creds: Credentials): self._creds_by_name[name].append(creds) def get_credentials_for_name(self, name) -> List[Credentials]: - return self._creds_by_name[name] if name in self._creds_by_name else [] + return self._creds_by_name.get(name, []) def get_cert_file(self, name: str, key_type=None) -> str: - key_infix = ".{0}".format(key_type) if key_type is not None else "" + key_infix = f".{key_type}" if key_type is not None else "" return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem') def get_pkey_file(self, name: str, key_type=None) -> str: - key_infix = ".{0}".format(key_type) if key_type is not None else "" + key_infix = f".{key_type}" if key_type is not None else "" return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem') def get_combined_file(self, name: str, key_type=None) -> str: @@ -347,17 +344,15 @@ def load_credentials(self, name: str, key_type=None, cert = self.load_pem_cert(cert_file) pkey = self.load_pem_pkey(pkey_file) try: - now = datetime.now(tz=timezone.utc) - if check_valid and \ - ((cert.not_valid_after_utc < now) or - (cert.not_valid_before_utc > now)): - return None - except AttributeError: # older python - now = datetime.now() - if check_valid and \ - ((cert.not_valid_after < now) or - (cert.not_valid_before > now)): - return None + before = cert.not_valid_before_utc + after = cert.not_valid_after_utc + except AttributeError: # cryptography < 42.0.0 + # the timestamps are already returned in UTC, just missing the time zone + before = cert.not_valid_before.replace(tzinfo=timezone.utc) + after = cert.not_valid_after.replace(tzinfo=timezone.utc) + now = datetime.now(timezone.utc) + if check_valid and ((after < now) or (before > now)): + return None creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) creds.set_store(self) creds.set_files(cert_file, pkey_file, comb_file) @@ -389,20 +384,18 @@ def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any :returns: the certificate and private key PEM file paths """ if spec.domains and len(spec.domains): - creds = TestCA._make_server_credentials(name=spec.name, domains=spec.domains, - issuer=issuer, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) - elif spec.client: - creds = TestCA._make_client_credentials(name=spec.name, issuer=issuer, - email=spec.email, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) - elif spec.name: - creds = TestCA._make_ca_credentials(name=spec.name, issuer=issuer, - valid_from=valid_from, valid_to=valid_to, - key_type=key_type) - else: - raise Exception(f"unrecognized certificate specification: {spec}") - return creds + return TestCA._make_server_credentials(name=spec.name, domains=spec.domains, + issuer=issuer, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) + if spec.client: + return TestCA._make_client_credentials(name=spec.name, issuer=issuer, + email=spec.email, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) + if spec.name: + return TestCA._make_ca_credentials(name=spec.name, issuer=issuer, + valid_from=valid_from, valid_to=valid_to, + key_type=key_type) + raise CertError(f"unrecognized certificate specification: {spec}") @staticmethod def _make_x509_name(org_name: Optional[str] = None, common_name: Optional[str] = None, parent: x509.Name = None) -> x509.Name: @@ -427,10 +420,10 @@ def _make_csr( pubkey = pkey.public_key() issuer_subject = issuer_subject if issuer_subject is not None else subject - valid_from = datetime.now() + valid_from = datetime.now(timezone.utc) if valid_until_delta is not None: valid_from += valid_from_delta - valid_until = datetime.now() + valid_until = datetime.now(timezone.utc) if valid_until_delta is not None: valid_until += valid_until_delta @@ -484,8 +477,7 @@ def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any: else: try: names.append(x509.IPAddress(ipaddress.ip_address(name))) - # TODO: specify specific exceptions here - except: # noqa: E722 + except ValueError: names.append(x509.DNSName(name)) return csr.add_extension( diff --git a/tests/http/testenv/client.py b/tests/http/testenv/client.py index 36ac8a599e6d..d6918fc0f5a5 100644 --- a/tests/http/testenv/client.py +++ b/tests/http/testenv/client.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -28,7 +26,7 @@ import os import shutil import subprocess -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, Optional from . import ExecResult @@ -41,14 +39,14 @@ class LocalClient: def __init__(self, name: str, env: Env, run_dir: Optional[str] = None, timeout: Optional[float] = None, - run_env: Optional[Dict[str,str]] = None): + run_env: Optional[Dict[str, str]] = None): self.name = name self.path = os.path.join(env.build_dir, 'tests/libtest/libtests') self.env = env self._run_env = run_env - self._timeout = timeout if timeout else env.test_timeout - self._curl = os.environ['CURL'] if 'CURL' in os.environ else env.curl - self._run_dir = run_dir if run_dir else os.path.join(env.gen_dir, name) + self._timeout = timeout or env.test_timeout + self._curl = os.environ.get('CURL', env.curl) + self._run_dir = run_dir or os.path.join(env.gen_dir, name) self._stdoutfile = f'{self._run_dir}/stdout' self._stderrfile = f'{self._run_dir}/stderr' self._rmrf(self._run_dir) @@ -83,7 +81,7 @@ def _mkpath(self, path): def run(self, args): self._rmf(self._stdoutfile) self._rmf(self._stderrfile) - start = datetime.now() + start = datetime.now(timezone.utc) exception = None myargs = [self.path, self.name] myargs.extend(args) @@ -98,23 +96,26 @@ def run(self, args): p = subprocess.run(myargs, stderr=cerr, stdout=cout, cwd=self._run_dir, shell=False, input=None, env=run_env, - timeout=self._timeout) + timeout=self._timeout, check=False) exitcode = p.returncode except subprocess.TimeoutExpired: log.warning(f'Timeout after {self._timeout}s: {args}') exitcode = -1 exception = 'TimeoutExpired' - coutput = open(self._stdoutfile).readlines() - cerrput = open(self._stderrfile).readlines() + with open(self._stdoutfile) as fout, open(self._stderrfile) as ferr: + coutput = fout.readlines() + cerrput = ferr.readlines() return ExecResult(args=myargs, exit_code=exitcode, exception=exception, stdout=coutput, stderr=cerrput, - duration=datetime.now() - start) + duration=datetime.now(timezone.utc) - start) def dump_logs(self): lines = [] lines.append('>>--stdout ----------------------------------------------\n') - lines.extend(open(self._stdoutfile).readlines()) + with open(self._stdoutfile) as fd: + lines.extend(fd.readlines()) lines.append('>>--stderr ----------------------------------------------\n') - lines.extend(open(self._stderrfile).readlines()) + with open(self._stderrfile) as fd: + lines.extend(fd.readlines()) lines.append('<<-------------------------------------------------------\n') return ''.join(lines) diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index 99aa649bc0fd..34b28b9533ef 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -24,6 +22,7 @@ # ########################################################################### # +import contextlib import json import logging import os @@ -36,19 +35,19 @@ from functools import cmp_to_key from statistics import fmean, mean from threading import Thread -from typing import Any, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union from urllib.parse import urlparse import psutil -from .env import Env +from .env import Env, EnvError log = logging.getLogger(__name__) class RunProfile: - STAT_KEYS = ['cpu', 'rss', 'vsz'] + STAT_KEYS: ClassVar[List[str]] = ['cpu', 'rss', 'vsz'] @classmethod def AverageStats(cls, profiles: List['RunProfile']): @@ -57,6 +56,8 @@ def AverageStats(cls, profiles: List['RunProfile']): for key in cls.STAT_KEYS: vals = [s[key] for s in stats] avg[key] = mean(vals) if len(vals) else 0.0 + vals = [s['rss-max'] for s in [p.stats for p in profiles]] + avg['rss-max'] = mean(vals) if len(vals) else 0 return avg def __init__(self, pid: int, started_at: datetime, run_dir): @@ -73,11 +74,11 @@ def duration(self) -> timedelta: return self._duration @property - def stats(self) -> Optional[Dict[str,Any]]: + def stats(self) -> Optional[Dict[str, Any]]: return self._stats def sample(self): - elapsed = datetime.now() - self._started_at + elapsed = datetime.now(timezone.utc) - self._started_at try: if self._psu is None: self._psu = psutil.Process(pid=self._pid) @@ -89,15 +90,17 @@ def sample(self): 'rss': mem.rss, }) except psutil.NoSuchProcess: + # process may exit between sampling ticks: ignore this pass def finish(self): - self._duration = datetime.now() - self._started_at + self._duration = datetime.now(timezone.utc) - self._started_at if len(self._samples) > 0: weights = [s['time'].total_seconds() for s in self._samples] self._stats = {} for key in self.STAT_KEYS: self._stats[key] = fmean([s[key] for s in self._samples], weights) + self._stats['rss-max'] = max(s['rss'] for s in self._samples) else: self._stats = None self._psu = None @@ -132,12 +135,8 @@ def finish(self): self._proc.terminate() self._rc = self._proc.returncode with open(self._file, 'w') as cout: - p = subprocess.run([ - 'sudo', 'perf', 'script' - ], stdout=cout, cwd=self._run_dir, shell=False) - rc = p.returncode - if rc != 0: - raise Exception(f'perf returned error {rc}') + subprocess.run(['sudo', 'perf', 'script'], + stdout=cout, cwd=self._run_dir, shell=False, check=True) @property def file(self): @@ -187,17 +186,31 @@ def __init__(self, env, run_dir): self._stdoutfile = os.path.join(self._run_dir, 'tcpdump.out') self._stderrfile = os.path.join(self._run_dir, 'tcpdump.err') - def get_rsts(self, ports: List[int]|None = None) -> Optional[List[str]]: + def get_rsts(self, ports: Optional[List[int]] = None, + port_pairs: Optional[List[Tuple[int, int]]] = None + ) -> Optional[List[str]]: if self._proc: - raise Exception('tcpdump still running') + raise EnvError('tcpdump still running') + # a pair matches only a RST between exactly these two ports, while + # a port in `ports` matches any RST it is involved in + pairs = None + if port_pairs is not None: + pairs = set() + for p1, p2 in port_pairs: + pairs.add((p1, p2)) + pairs.add((p2, p1)) lines = [] - for line in open(self._stdoutfile): - m = re.match(r'.* IP 127\.0\.0\.1\.(\d+) [<>] 127\.0\.0\.1\.(\d+):.*', line) - if m: - sport = int(m.group(1)) - dport = int(m.group(2)) - if ports is None or sport in ports or dport in ports: - lines.append(line) + with open(self._stdoutfile) as fd: + for line in fd: + m = re.match(r'.* IP 127\.0\.0\.1\.(\d+) [<>] 127\.0\.0\.1\.(\d+):.*', line) + if m: + sport = int(m.group(1)) + dport = int(m.group(2)) + if pairs is not None: + if (sport, dport) in pairs: + lines.append(line) + elif ports is None or sport in ports or dport in ports: + lines.append(line) return lines @property @@ -207,8 +220,9 @@ def stats(self) -> Optional[List[str]]: @property def stderr(self) -> List[str]: if self._proc: - raise Exception('tcpdump still running') - return open(self._stderrfile).readlines() + raise EnvError('tcpdump still running') + with open(self._stderrfile) as fd: + return fd.readlines() def sample(self): # not sure how to make that detection reliable for all platforms @@ -216,7 +230,7 @@ def sample(self): try: tcpdump = self._env.tcpdump() if tcpdump is None: - raise Exception('tcpdump not available') + raise EnvError('tcpdump not available') # look with tcpdump for TCP RST packets which indicate # we did not shut down connections cleanly args = [] @@ -233,10 +247,9 @@ def sample(self): assert self._proc assert self._proc.returncode is None while self._proc: - try: + # timeout means tcpdump is still running + with contextlib.suppress(subprocess.TimeoutExpired): self._proc.wait(timeout=1) - except subprocess.TimeoutExpired: - pass except Exception: log.exception('Tcpdump') @@ -280,11 +293,11 @@ def __init__(self, args: List[str], exit_code: int, if with_stats: self._parse_stats() else: - # noinspection PyBroadException try: out = ''.join(self._stdout) self._json_out = json.loads(out) - except: # noqa: E722 + except (json.JSONDecodeError, TypeError, ValueError): + # stdout not guaranteed to be JSON, keep _json_out as None pass def __repr__(self): @@ -296,8 +309,7 @@ def _parse_stats(self): for line in self._stdout: try: self._stats.append(json.loads(line)) - # TODO: specify specific exceptions here - except: # noqa: E722 + except (json.JSONDecodeError, TypeError, ValueError): log.exception(f'not a JSON stat: {line}') break @@ -393,7 +405,7 @@ def check_exit_code(self, code: Union[int, bool]): f'got {self.exit_code}\n{self.dump_logs()}' elif code is False: assert self.exit_code != 0, f'expected exit code {code}, '\ - f'got {self.exit_code}\n{self.dump_logs()}' + f'got {self.exit_code}\n{self.dump_logs()}' else: assert self.exit_code == code, f'expected exit code {code}, '\ f'got {self.exit_code}\n{self.dump_logs()}' @@ -484,14 +496,14 @@ def check_stats(self, count: int, http_status: Optional[int] = None, for idx, x in enumerate(self.stats): assert 'remote_port' in x, f'remote_port missing\n{self.dump_stat(x)}' assert x['remote_port'] == remote_port, \ - f'status #{idx} remote_port: expected {remote_port}, '\ - f'got {x["remote_port"]}\n{self.dump_stat(x)}' + f'status #{idx} remote_port: expected {remote_port}, '\ + f'got {x["remote_port"]}\n{self.dump_stat(x)}' if remote_ip is not None: for idx, x in enumerate(self.stats): assert 'remote_ip' in x, f'remote_ip missing\n{self.dump_stat(x)}' assert x['remote_ip'] == remote_ip, \ - f'status #{idx} remote_ip: expected {remote_ip}, '\ - f'got {x["remote_ip"]}\n{self.dump_stat(x)}' + f'status #{idx} remote_ip: expected {remote_ip}, '\ + f'got {x["remote_ip"]}\n{self.dump_stat(x)}' def check_stat_positive(self, s, idx, key): assert key in s, f'stat #{idx} "{key}" missing: {s}' @@ -540,23 +552,8 @@ def check_stats_timeline(self, idx): ref_tl += ['time_namelookup', 'time_connect'] if url.startswith('https:'): ref_tl += ['time_appconnect'] - # what kind of transfer was it? - if s['size_upload'] == 0 and s['size_download'] > 0: - # this is a download - dl_tl = ['time_pretransfer'] - if s['size_request'] > 0: - dl_tl = ['time_posttransfer'] + dl_tl - ref_tl += dl_tl - # the first byte of the response may arrive before we - # track the other times when the client is slow (CI). - somewhere_keys.extend(['time_starttransfer']) - elif s['size_upload'] > 0 and s['size_download'] == 0: - # this is an upload - ul_tl = ['time_pretransfer', 'time_posttransfer'] - ref_tl += ul_tl - else: - # could be a 0-length upload or 0-length download, not sure - exact_match = False + ref_tl += ['time_pretransfer', 'time_posttransfer'] + somewhere_keys.extend(['time_starttransfer']) # always there at the end ref_tl += ['time_total'] @@ -605,13 +602,13 @@ def dump_stat(self, x): return ''.join(lines) def xfer_trace_for(self, xfer_id) -> List[str]: - pat = re.compile(f'^[^[]* \\[{xfer_id}-.*$') - return [line for line in self._stderr if pat.match(line)] + pat = re.compile(f'^[^[]* \\[{xfer_id}-.*$') + return [line for line in self._stderr if pat.match(line)] class CurlClient: - ALPN_ARG = { + ALPN_ARG: ClassVar[Dict[str, str]] = { 'http/0.9': '--http0.9', 'http/1.0': '--http1.0', 'http/1.1': '--http1.1', @@ -632,9 +629,9 @@ def __init__(self, env: Env, force_resolv: bool = True, socks_args: Optional[List[str]] = None): self.env = env - self._timeout = timeout if timeout else env.test_timeout - self._curl = os.environ['CURL'] if 'CURL' in os.environ else env.curl - self._run_dir = run_dir if run_dir else os.path.join(env.gen_dir, 'curl') + self._timeout = timeout or env.test_timeout + self._curl = os.environ.get('CURL', env.curl) + self._run_dir = run_dir or os.path.join(env.gen_dir, 'curl') self._stdoutfile = f'{self._run_dir}/curl.stdout' self._stderrfile = f'{self._run_dir}/curl.stderr' self._headerfile = f'{self._run_dir}/curl.headers' @@ -648,17 +645,17 @@ def __init__(self, env: Env, if 'FLAMEGRAPH' in os.environ: self._fg_dir = os.environ['FLAMEGRAPH'] if not os.path.exists(self._fg_dir): - raise Exception(f'FlameGraph checkout not found in {self._fg_dir}, set env variable FLAMEGRAPH') + raise EnvError(f'FlameGraph checkout not found in {self._fg_dir}, set env variable FLAMEGRAPH') if sys.platform.startswith('linux'): self._with_perf = True elif sys.platform.startswith('darwin'): self._with_dtrace = True else: - raise Exception(f'flame graphs unsupported on {sys.platform}') + raise EnvError(f'flame graphs unsupported on {sys.platform}') self._socks_args = socks_args self._silent = silent self._run_env = run_env - self._server_addr = server_addr if server_addr else '127.0.0.1' + self._server_addr = server_addr or '127.0.0.1' self._force_resolv = force_resolv self._rmrf(self._run_dir) self._mkpath(self._run_dir) @@ -684,11 +681,17 @@ def _mkpath(self, path): def get_proxy_args(self, proto: str = 'http/1.1', proxys: bool = True, tunnel: bool = False, - use_ip: bool = False, use_ipv6: bool = False): + use_ip: bool = False, use_ipv6: bool = False, + use_h2o: bool = False): proxy_name = '[::1]' if use_ipv6 else \ self._server_addr if use_ip else self.env.proxy_domain if proxys: - pport = self.env.pts_port(proto) if tunnel else self.env.proxys_port + if tunnel: + pport = self.env.pts_port(proto, use_h2o=use_h2o) + elif proto == 'h3': + pport = self.env.h3proxys_port + else: + pport = self.env.proxys_port xargs = [ '--proxy', f'https://{proxy_name}:{pport}/', '--proxy-cacert', self.env.ca.cert_file, @@ -697,6 +700,8 @@ def get_proxy_args(self, proto: str = 'http/1.1', xargs.extend(['--resolve', f'{proxy_name}:{pport}:{self._server_addr}']) if proto == 'h2': xargs.append('--proxy-http2') + elif proto == 'h3': + xargs.append('--proxy-http3') else: xargs = [ '--proxy', f'http://{proxy_name}:{self.env.proxy_port}/', @@ -729,11 +734,14 @@ def http_download(self, urls: List[str], no_save: bool = False, limit_rate: Optional[str] = None, extra_args: Optional[List[str]] = None, - url_options: Optional[Dict[str,List[str]]] = None): + url_options: Optional[Dict[str, List[str]]] = None): if extra_args is None: extra_args = [] if no_save: - extra_args.extend(['--out-null']) + if self.env.curl_version_at_least('8.16.0'): + extra_args.extend(['--out-null']) + else: + extra_args.extend(['-o', '/dev/null']) else: extra_args.extend(['-o', 'download_#1.data']) if limit_rate: @@ -845,11 +853,11 @@ def http_form(self, urls: List[str], form: Dict[str, str], with_headers=with_headers) def ftp_get(self, urls: List[str], - with_stats: bool = True, - with_profile: bool = False, - with_tcpdump: bool = False, - no_save: bool = False, - extra_args: Optional[List[str]] = None): + with_stats: bool = True, + with_profile: bool = False, + with_tcpdump: bool = False, + no_save: bool = False, + extra_args: Optional[List[str]] = None): if extra_args is None: extra_args = [] if no_save: @@ -874,11 +882,11 @@ def ftp_get(self, urls: List[str], with_tcpdump=with_tcpdump) def ftp_ssl_get(self, urls: List[str], - with_stats: bool = True, - with_profile: bool = False, - with_tcpdump: bool = False, - no_save: bool = False, - extra_args: Optional[List[str]] = None): + with_stats: bool = True, + with_profile: bool = False, + with_tcpdump: bool = False, + no_save: bool = False, + extra_args: Optional[List[str]] = None): if extra_args is None: extra_args = [] extra_args.extend([ @@ -907,7 +915,7 @@ def ftp_upload(self, urls: List[str], '--upload-file', '-' ]) else: - raise Exception('need either file or data to upload') + raise EnvError('need either file or data to upload') if with_stats: extra_args.extend([ '-w', '%{json}\\n' @@ -983,7 +991,7 @@ def ssh_upload(self, urls: List[str], '--upload-file', '-' ]) else: - raise Exception('need either file or data to upload') + raise EnvError('need either file or data to upload') if with_stats: extra_args.extend([ '-w', '%{json}\\n' @@ -1023,7 +1031,7 @@ def _run(self, args, intext='', with_stats: bool = False, if with_tcpdump: tcpdump = RunTcpDump(self.env, self._run_dir) tcpdump.start() - started_at = datetime.now() + started_at = datetime.now(timezone.utc) try: with open(self._stdoutfile, 'w') as cout, open(self._stderrfile, 'w') as cerr: if with_profile: @@ -1034,8 +1042,6 @@ def _run(self, args, intext='', with_stats: bool = False, cwd=self._run_dir, shell=False, env=self._run_env) profile = RunProfile(p.pid, started_at, self._run_dir) - if intext is not None and False: - p.communicate(input=intext.encode(), timeout=1) if self._with_perf: perf = PerfProfile(p.pid, self._run_dir) perf.start() @@ -1048,11 +1054,11 @@ def _run(self, args, intext='', with_stats: bool = False, p.wait(timeout=ptimeout) break except subprocess.TimeoutExpired as e: - if end_at and datetime.now() >= end_at: + if end_at and datetime.now(timezone.utc) >= end_at: p.kill() raise subprocess.TimeoutExpired(cmd=args, timeout=self._timeout) from e profile.sample() - ptimeout = 0.01 + ptimeout = 0.001 exitcode = p.returncode profile.finish() log.info(f'done: exit={exitcode}, profile={profile}') @@ -1061,16 +1067,16 @@ def _run(self, args, intext='', with_stats: bool = False, cwd=self._run_dir, shell=False, input=intext.encode() if intext else None, timeout=self._timeout, - env=self._run_env) + env=self._run_env, check=False) exitcode = p.returncode except subprocess.TimeoutExpired: - now = datetime.now() + now = datetime.now(timezone.utc) duration = now - started_at log.warning(f'Timeout at {now} after {duration.total_seconds()}s ' f'(configured {self._timeout}s): {args}') exitcode = -1 exception = 'TimeoutExpired' - ended_at = datetime.now() + ended_at = datetime.now(timezone.utc) if tcpdump: tcpdump.finish() if perf: @@ -1079,8 +1085,9 @@ def _run(self, args, intext='', with_stats: bool = False, dtrace.finish() if self._with_flame: self._generate_flame(args, dtrace=dtrace, perf=perf) - coutput = open(self._stdoutfile).readlines() - cerrput = open(self._stderrfile).readlines() + with open(self._stdoutfile) as fout, open(self._stderrfile) as ferr: + coutput = fout.readlines() + cerrput = ferr.readlines() return ExecResult(args=args, exit_code=exitcode, exception=exception, stdout=coutput, stderr=cerrput, duration=ended_at - started_at, @@ -1119,7 +1126,7 @@ def _complete_args(self, urls, timeout=None, options=None, else: force_resolve = self._force_resolv - args = [self._curl, "-s", "--path-as-is"] + args = [self._curl, "--disable", "-s", "--path-as-is"] if 'CURL_TEST_EVENT' in os.environ: args.append('--test-event') @@ -1149,7 +1156,7 @@ def _complete_args(self, urls, timeout=None, options=None, args.extend(options) if alpn_proto is not None: if alpn_proto not in self.ALPN_ARG: - raise Exception(f'unknown ALPN protocol: "{alpn_proto}"') + raise EnvError(f'unknown ALPN protocol: "{alpn_proto}"') args.append(self.ALPN_ARG[alpn_proto]) if u.scheme == 'http': @@ -1157,14 +1164,14 @@ def _complete_args(self, urls, timeout=None, options=None, elif insecure: args.append('--insecure') elif active_options and ("--cacert" in active_options or - "--capath" in active_options): + "--capath" in active_options): pass elif u.hostname: args.extend(["--cacert", self.env.ca.cert_file]) if force_resolve and u.hostname and u.hostname != 'localhost' \ and not re.match(r'^(\d+|\[|:).*', u.hostname): - port = u.port if u.port else 443 + port = u.port or 443 args.extend([ '--resolve', f'{u.hostname}:{port}:{self._server_addr}', ]) @@ -1174,7 +1181,8 @@ def _complete_args(self, urls, timeout=None, options=None, return args def _parse_headerfile(self, headerfile: str, r: Optional[ExecResult] = None) -> ExecResult: - lines = open(headerfile).readlines() + with open(headerfile) as fd: + lines = fd.readlines() if r is None: r = ExecResult(args=[], exit_code=0, stdout=[], stderr=[]) @@ -1231,36 +1239,28 @@ def fin_response(resp): def _perf_collapse(self, perf: PerfProfile, file_err): if not os.path.exists(perf.file): - raise Exception(f'dtrace output file does not exist: {perf.file}') + raise EnvError(f'perf output file does not exist: {perf.file}') fg_collapse = os.path.join(self._fg_dir, 'stackcollapse-perf.pl') if not os.path.exists(fg_collapse): - raise Exception(f'FlameGraph script not found: {fg_collapse}') + raise EnvError(f'FlameGraph script not found: {fg_collapse}') stacks_collapsed = f'{perf.file}.collapsed' log.info(f'collapsing stacks into {stacks_collapsed}') with open(stacks_collapsed, 'w') as cout, open(file_err, 'w') as cerr: - p = subprocess.run([ - fg_collapse, perf.file - ], stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False) - rc = p.returncode - if rc != 0: - raise Exception(f'{fg_collapse} returned error {rc}') + subprocess.run([fg_collapse, perf.file], + stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False, check=True) return stacks_collapsed def _dtrace_collapse(self, dtrace: DTraceProfile, file_err): if not os.path.exists(dtrace.file): - raise Exception(f'dtrace output file does not exist: {dtrace.file}') + raise EnvError(f'dtrace output file does not exist: {dtrace.file}') fg_collapse = os.path.join(self._fg_dir, 'stackcollapse.pl') if not os.path.exists(fg_collapse): - raise Exception(f'FlameGraph script not found: {fg_collapse}') + raise EnvError(f'FlameGraph script not found: {fg_collapse}') stacks_collapsed = f'{dtrace.file}.collapsed' log.info(f'collapsing stacks into {stacks_collapsed}') with open(stacks_collapsed, 'w') as cout, open(file_err, 'a') as cerr: - p = subprocess.run([ - fg_collapse, dtrace.file - ], stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False) - rc = p.returncode - if rc != 0: - raise Exception(f'{fg_collapse} returned error {rc}') + subprocess.run([fg_collapse, dtrace.file], + stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False, check=True) return stacks_collapsed def _generate_flame(self, curl_args: List[str], @@ -1269,7 +1269,7 @@ def _generate_flame(self, curl_args: List[str], fg_gen_flame = os.path.join(self._fg_dir, 'flamegraph.pl') file_svg = os.path.join(self._run_dir, 'curl.flamegraph.svg') if not os.path.exists(fg_gen_flame): - raise Exception(f'FlameGraph script not found: {fg_gen_flame}') + raise EnvError(f'FlameGraph script not found: {fg_gen_flame}') log.info('waiting a sec for perf/dtrace to finish flushing') time.sleep(2) @@ -1280,7 +1280,7 @@ def _generate_flame(self, curl_args: List[str], elif dtrace: stacks_collapsed = self._dtrace_collapse(dtrace, file_err) else: - raise Exception('no stacks measure given') + raise EnvError('no stacks measure given') log.info(f'generating graph into {file_svg}') cmdline = ' '.join(curl_args) @@ -1291,14 +1291,9 @@ def _generate_flame(self, curl_args: List[str], title = cmdline subtitle = '' with open(file_svg, 'w') as cout, open(file_err, 'a') as cerr: - p = subprocess.run([ - fg_gen_flame, '--colors', 'green', - '--title', title, '--subtitle', subtitle, - stacks_collapsed - ], stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False) - rc = p.returncode - if rc != 0: - raise Exception(f'{fg_gen_flame} returned error {rc}') + subprocess.run([fg_gen_flame, '--colors', 'green', '--title', title, '--subtitle', + subtitle, stacks_collapsed], + stdout=cout, stderr=cerr, cwd=self._run_dir, shell=False, check=True) def mk_altsvc_file(self, name, src_alpn, src_host, src_port, dest_alpn, dest_host, dest_port): diff --git a/tests/http/testenv/dante.py b/tests/http/testenv/dante.py index 2feb42eb7cca..f42e3f875534 100644 --- a/tests/http/testenv/dante.py +++ b/tests/http/testenv/dante.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,7 +27,7 @@ import socket import subprocess import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict from . import CurlClient @@ -57,6 +55,7 @@ def __init__(self, env: Env): self._dante_log = os.path.join(self._dante_dir, 'dante.log') self._error_log = os.path.join(self._dante_dir, 'error.log') self._pid_file = os.path.join(self._dante_dir, 'dante.pid') + self._error_fd = None self._process = None self.clear_logs() @@ -65,6 +64,11 @@ def __init__(self, env: Env): def port(self) -> int: return self._port + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._error_log) self._rmf(self._dante_log) @@ -89,7 +93,7 @@ def stop(self, wait_dead=True): self._process.terminate() self._process.wait(timeout=2) self._process = None - return not wait_dead or True + self.close_log() return True def restart(self): @@ -122,8 +126,8 @@ def start(self, wait_live=True): '-p', f'{self._pid_file}', '-d', '0', ] - procerr = open(self._error_log, 'a') - self._process = subprocess.Popen(args=args, stderr=procerr) + self._error_fd = open(self._error_log, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: return False return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) @@ -131,10 +135,10 @@ def start(self, wait_live=True): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir, timeout=timeout.total_seconds(), socks_args=[ - '--socks5', f'127.0.0.1:{self._port}' - ]) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + '--socks5', f'127.0.0.1:{self._port}' + ]) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/') if r.exit_code == 0: return True diff --git a/tests/http/testenv/dnsd.py b/tests/http/testenv/dnsd.py index b8ff097280a3..4411b07391ae 100644 --- a/tests/http/testenv/dnsd.py +++ b/tests/http/testenv/dnsd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,9 +27,10 @@ import socket import subprocess import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict, List, Optional +from . import CurlClient from .env import Env from .ports import alloc_ports_and_do @@ -52,10 +51,12 @@ def __init__(self, env: Env): self._dnsd_dir = os.path.join(env.gen_dir, self.name) self._log_dir = self._dnsd_dir self._lock_dir = os.path.join(self._dnsd_dir, 'lock') + self._tmp_dir = os.path.join(self._dnsd_dir, 'tmp') self._log_file = os.path.join(self._log_dir, 'dnsd.log') self._conf_file = os.path.join(self._log_dir, 'dnsd.cmd') self._pid_file = os.path.join(self._log_dir, 'dnsd.pid') self._error_log = os.path.join(self._log_dir, 'dnsd.err.log') + self._error_fd = None self._process = None self.clear_logs() @@ -64,6 +65,11 @@ def __init__(self, env: Env): def port(self) -> int: return self._port + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._log_file) self._rmf(self._error_log) @@ -83,12 +89,14 @@ def start_if_needed(self): return True def stop(self, wait_dead=True): + result = True if self._process: self._process.terminate() self._process.wait(timeout=2) self._process = None - return not wait_dead or True - return True + result = self.wait_dead(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) + self.close_log() + return result def restart(self): self.stop() @@ -96,6 +104,7 @@ def restart(self): def initial_start(self): self._mkpath(self._lock_dir) + self._mkpath(self._tmp_dir) def startup(ports: Dict[str, int]) -> bool: self._port = ports[self._port_skey] @@ -122,16 +131,30 @@ def start(self, wait_live=True): '--logfile', f'{self._log_file}', '--pidfile', f'{self._pid_file}', ] - procerr = open(self._error_log, 'a') - self._process = subprocess.Popen(args=args, stderr=procerr) + self._error_fd = open(self._error_log, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: return False return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) + def wait_dead(self, timeout: timedelta): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: + r = curl.http_get(url=f'http://127.0.0.1:{self._port}/') + if r.exit_code != 0: + return True + time.sleep(.1) + log.debug(f"Server still responding after {timeout}") + return False + def wait_live(self, timeout: timedelta): - try_until = datetime.now() + timeout - while datetime.now() < try_until: - if os.path.exists(self._log_file): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir, + timeout=timeout.total_seconds()) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: + r = curl.http_get(url=f'http://127.0.0.1:{self._port}/') + if r.exit_code == 0: return True time.sleep(.1) log.error(f"Server still not responding after {timeout}") @@ -150,7 +173,9 @@ def set_answers(self, addr_a: Optional[List[str]] = None, https: Optional[List[str]] = None, delay_a_ms: int = 0, delay_aaaa_ms: int = 0, - delay_https_ms: int = 0): + delay_https_ms: int = 0, + rcode_a: int = 0, + rcode_aaaa: int = 0): conf = [] if addr_a: conf.extend([f'A: {addr}' for addr in addr_a]) @@ -164,6 +189,10 @@ def set_answers(self, addr_a: Optional[List[str]] = None, conf.append(f'Delay-AAAA: {delay_aaaa_ms}') if delay_https_ms: conf.append(f'Delay-HTTPS: {delay_https_ms}') + if rcode_a: + conf.append(f'Rcode-A: {rcode_a}') + if rcode_aaaa: + conf.append(f'Rcode-AAAA: {rcode_aaaa}') conf.append('\n') with open(self._conf_file, 'w') as fd: fd.write("\n".join(conf)) diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 30fc0d0a734a..9571907d8622 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -#*************************************************************************** +# *************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -29,6 +27,7 @@ import os import re import shutil +import string import subprocess import tempfile from configparser import ConfigParser, ExtendedInterpolation @@ -43,6 +42,10 @@ log = logging.getLogger(__name__) +class EnvError(Exception): + """Exception from the test environment.""" + + def init_config_from(conf_path): if os.path.isfile(conf_path): config = ConfigParser(interpolation=ExtendedInterpolation()) @@ -54,20 +57,21 @@ def init_config_from(conf_path): TESTS_HTTPD_PATH = os.path.dirname(os.path.dirname(__file__)) PROJ_PATH = os.path.dirname(os.path.dirname(TESTS_HTTPD_PATH)) TOP_PATH = os.path.join(os.getcwd(), os.path.pardir) -CONFIG_PATH = os.path.join(TOP_PATH, 'tests', 'http', 'config.ini') +CONFIG_PATH = os.path.join(TOP_PATH, "tests", "http", "config.ini") if not os.path.exists(CONFIG_PATH): - ALT_CONFIG_PATH = os.path.join(PROJ_PATH, 'tests', 'http', 'config.ini') + ALT_CONFIG_PATH = os.path.join(PROJ_PATH, "tests", "http", "config.ini") if not os.path.exists(ALT_CONFIG_PATH): - raise Exception(f'unable to find config.ini in {CONFIG_PATH} nor {ALT_CONFIG_PATH}') + raise EnvError( + f"unable to find config.ini in {CONFIG_PATH} nor {ALT_CONFIG_PATH}" + ) TOP_PATH = PROJ_PATH CONFIG_PATH = ALT_CONFIG_PATH DEF_CONFIG = init_config_from(CONFIG_PATH) -CURL = os.path.join(TOP_PATH, 'src', 'curl') -CURLINFO = os.path.join(TOP_PATH, 'src', 'curlinfo') +CURL = os.path.join(TOP_PATH, "src", "curl") +CURLINFO = os.path.join(TOP_PATH, "src", "curlinfo") class NghttpxUtil: - CMD = None VERSION_FULL = None @@ -76,34 +80,33 @@ def version(cls, cmd): if cmd is None: return None if cls.VERSION_FULL is None or cmd != cls.CMD: - p = subprocess.run(args=[cmd, '--version'], - capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'{cmd} --version failed with exit code: {p.returncode}') + p = subprocess.run(args=[cmd, "--version"], capture_output=True, text=True, check=True) cls.CMD = cmd for line in p.stdout.splitlines(keepends=False): - if line.startswith('nghttpx '): + if line.startswith("nghttpx "): cls.VERSION_FULL = line if cls.VERSION_FULL is None: - raise RuntimeError(f'{cmd}: unable to determine version') + raise RuntimeError(f"{cmd}: unable to determine version") return cls.VERSION_FULL @staticmethod def version_with_h3(version): - return re.match(r'.* ngtcp2/\d+\.\d+\.\d+.*', version) is not None + return re.match(r".* ngtcp2/\d+\.\d+\.\d+.*", version) is not None class EnvConfig: - - def __init__(self, pytestconfig: Optional[pytest.Config] = None, - testrun_uid=None, - worker_id=None): + def __init__( + self, + pytestconfig: Optional[pytest.Config] = None, + testrun_uid=None, + worker_id=None, + ): self.pytestconfig = pytestconfig self.testrun_uid = testrun_uid - self.worker_id = worker_id if worker_id is not None else 'master' + self.worker_id = worker_id if worker_id is not None else "master" self.tests_dir = TESTS_HTTPD_PATH - self.gen_root = self.gen_dir = os.path.join(self.tests_dir, 'gen') - if self.worker_id != 'master': + self.gen_root = self.gen_dir = os.path.join(self.tests_dir, "gen") + if self.worker_id != "master": self.gen_dir = os.path.join(self.gen_dir, self.worker_id) self.project_dir = os.path.dirname(os.path.dirname(self.tests_dir)) self.build_dir = TOP_PATH @@ -111,76 +114,72 @@ def __init__(self, pytestconfig: Optional[pytest.Config] = None, # check cur and its features self.curl = CURL self.curlinfo = CURLINFO - if 'CURL' in os.environ: - self.curl = os.environ['CURL'] + if "CURL" in os.environ: + self.curl = os.environ["CURL"] self.curl_props = { - 'version_string': '', - 'version': '', - 'os': '', - 'fullname': '', - 'features_string': '', - 'features': set(), - 'protocols_string': '', - 'protocols': set(), - 'libs': set(), - 'lib_versions': set(), + "version_string": "", + "version": "", + "os": "", + "fullname": "", + "features_string": "", + "features": set(), + "protocols_string": "", + "protocols": set(), + "libs": set(), + "lib_versions": set(), } self.curl_is_debug = False self.curl_protos = [] - p = subprocess.run(args=[self.curl, '-V'], - capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'{self.curl} -V failed with exit code: {p.returncode}') - if p.stderr.startswith('WARNING:'): + p = subprocess.run(args=[self.curl, "-V"], capture_output=True, text=True, check=True) + if p.stderr.startswith("WARNING:"): self.curl_is_debug = True for line in p.stdout.splitlines(keepends=False): - if line.startswith('curl '): - self.curl_props['version_string'] = line - m = re.match(r'^curl (?P\S+) (?P\S+) (?P.*)$', line) + if line.startswith("curl "): + self.curl_props["version_string"] = line + m = re.match(r"^curl (?P\S+) (?P\S+) (?P.*)$", line) if m: - self.curl_props['fullname'] = m.group(0) - self.curl_props['version'] = m.group('version') - self.curl_props['os'] = m.group('os') - self.curl_props['lib_versions'] = { - lib.lower() for lib in m.group('libs').split(' ') + self.curl_props["fullname"] = m.group(0) + self.curl_props["version"] = m.group("version") + self.curl_props["os"] = m.group("os") + self.curl_props["lib_versions"] = { + lib.lower() for lib in m.group("libs").split(" ") } - self.curl_props['libs'] = { - re.sub(r'/[a-z0-9.-]*', '', lib) for lib in self.curl_props['lib_versions'] + self.curl_props["libs"] = { + re.sub(r"/[a-z0-9.-]*", "", lib) + for lib in self.curl_props["lib_versions"] } - if line.startswith('Features: '): - self.curl_props['features_string'] = line[10:] - self.curl_props['features'] = { - feat.lower() for feat in line[10:].split(' ') + if line.startswith("Features: "): + self.curl_props["features_string"] = line[10:] + self.curl_props["features"] = { + feat.lower() for feat in line[10:].split(" ") } - if line.startswith('Protocols: '): - self.curl_props['protocols_string'] = line[11:] - self.curl_props['protocols'] = { - prot.lower() for prot in line[11:].split(' ') + if line.startswith("Protocols: "): + self.curl_props["protocols_string"] = line[11:] + self.curl_props["protocols"] = { + prot.lower() for prot in line[11:].split(" ") } - p = subprocess.run(args=[self.curlinfo], - capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'{self.curlinfo} failed with exit code: {p.returncode}') + p = subprocess.run(args=[self.curlinfo], capture_output=True, text=True, check=True) self.curl_is_verbose = 'verbose-strings: ON' in p.stdout self.curl_can_cert_status = 'cert-status: ON' in p.stdout self.curl_override_dns = 'override-dns: ON' in p.stdout self.curl_resolv_threaded = 'resolv-threaded: ON' in p.stdout + self.curl_can_doh = 'DoH: ON' in p.stdout self.ports = {} - self.httpd = self.config['httpd']['httpd'] - self.apxs = self.config['httpd']['apxs'] + self.httpd = self.config["httpd"]["httpd"] + self.apxs = self.config["httpd"]["apxs"] if len(self.apxs) == 0: self.apxs = None self._httpd_version = None self.examples_pem = { - 'key': 'xxx', - 'cert': 'xxx', + "key": "xxx", + "cert": "xxx", } - self.htdocs_dir = os.path.join(self.gen_dir, 'htdocs') - self.tld = 'http.curl.se' + self.htdocs_dir = os.path.join(self.gen_dir, "htdocs") + self.tld = "http.curl.se" self.domain1 = f"one.{self.tld}" self.domain1brotli = f"brotli.one.{self.tld}" self.domain2 = f"two.{self.tld}" @@ -188,22 +187,43 @@ def __init__(self, pytestconfig: Optional[pytest.Config] = None, self.proxy_domain = f"proxy.{self.tld}" self.expired_domain = f"expired.{self.tld}" self.cert_specs = [ - CertificateSpec(domains=[self.domain1, self.domain1brotli, 'localhost', '127.0.0.1'], key_type='rsa2048'), - CertificateSpec(name='domain1-no-ip', domains=[self.domain1, self.domain1brotli], key_type='rsa2048'), - CertificateSpec(name='domain1-very-bad', domains=[self.domain1, 'dns:127.0.0.1'], key_type='rsa2048'), - CertificateSpec(domains=[self.domain2], key_type='rsa2048'), - CertificateSpec(domains=[self.ftp_domain], key_type='rsa2048'), - CertificateSpec(domains=[self.proxy_domain, '127.0.0.1'], key_type='rsa2048'), - CertificateSpec(domains=[self.expired_domain], key_type='rsa2048', - valid_from=timedelta(days=-100), valid_to=timedelta(days=-10)), - CertificateSpec(name="clientsX", sub_specs=[ - CertificateSpec(name="user1", client=True), - ]), + CertificateSpec( + domains=[self.domain1, self.domain1brotli, "localhost", "127.0.0.1"], + key_type="rsa2048", + ), + CertificateSpec( + name="domain1-no-ip", + domains=[self.domain1, self.domain1brotli], + key_type="rsa2048", + ), + CertificateSpec( + name="domain1-very-bad", + domains=[self.domain1, "dns:127.0.0.1"], + key_type="rsa2048", + ), + CertificateSpec(domains=[self.domain2], key_type="rsa2048"), + CertificateSpec(domains=[self.ftp_domain], key_type="rsa2048"), + CertificateSpec( + domains=[self.proxy_domain, "127.0.0.1"], key_type="rsa2048" + ), + CertificateSpec( + domains=[self.expired_domain], + key_type="rsa2048", + valid_from=timedelta(days=-100), + valid_to=timedelta(days=-10), + ), + CertificateSpec( + name="clientsX", + sub_specs=[ + CertificateSpec(name="user1", client=True), + ], + ), ] - self.openssl = 'openssl' - p = subprocess.run(args=[self.openssl, 'version'], - capture_output=True, text=True) + self.openssl = "openssl" + p = subprocess.run( + args=[self.openssl, "version"], capture_output=True, text=True, check=False + ) if p.returncode != 0: # no openssl in path self.openssl = None @@ -211,7 +231,7 @@ def __init__(self, pytestconfig: Optional[pytest.Config] = None, else: self.openssl_version = p.stdout.strip() - self.nghttpx = self.config['nghttpx']['nghttpx'] + self.nghttpx = self.config["nghttpx"]["nghttpx"] if len(self.nghttpx.strip()) == 0: self.nghttpx = None self._nghttpx_version = None @@ -220,30 +240,58 @@ def __init__(self, pytestconfig: Optional[pytest.Config] = None, self._nghttpx_version = NghttpxUtil.version(self.nghttpx) self.nghttpx_with_h3 = NghttpxUtil.version_with_h3(self._nghttpx_version) - self.caddy = self.config['caddy']['caddy'] + self.caddy = self.config["caddy"]["caddy"] self._caddy_version = None if len(self.caddy.strip()) == 0: self.caddy = None + + self.h2o = self.config["h2o"]["h2o"] + if len(self.h2o.strip()) == 0: + self.h2o = None + self._h2o_version = None + if self.h2o is not None: + try: + p = subprocess.run( + args=[self.h2o, "--version"], capture_output=True, text=True, check=False + ) + if p.returncode != 0: + # not a working h2o + self.h2o = None + else: + # h2o --version output format: "h2o version 2.3.0" + m = re.search(r"h2o version (\S+)", p.stdout) + if m: + self._h2o_version = m.group(1) + else: + self.h2o = None + except Exception: + log.exception("checking h2o version") + self.h2o = None + if self.caddy is not None: - p = subprocess.run(args=[self.caddy, 'version'], - capture_output=True, text=True) + p = subprocess.run( + args=[self.caddy, "version"], capture_output=True, text=True, check=False + ) if p.returncode != 0: # not a working caddy self.caddy = None - m = re.match(r'v?(\d+\.\d+\.\d+).*', p.stdout) + m = re.match(r"v?(\d+\.\d+\.\d+).*", p.stdout) if m: self._caddy_version = m.group(1) else: - raise RuntimeError(f'Unable to determine caddy version from: {p.stdout}') + raise RuntimeError( + f"Unable to determine caddy version from: {p.stdout}" + ) - self.vsftpd = self.config['vsftpd']['vsftpd'] - if self.vsftpd == '': + self.vsftpd = self.config["vsftpd"]["vsftpd"] + if self.vsftpd == "": self.vsftpd = None self._vsftpd_version = None if self.vsftpd is not None: - with tempfile.TemporaryFile('w+') as tmp: - p = subprocess.run(args=[self.vsftpd, '-v'], - capture_output=True, text=True, stdin=tmp) + with tempfile.TemporaryFile("w+") as tmp: + p = subprocess.run( + args=[self.vsftpd, "-v"], capture_output=True, text=True, stdin=tmp, check=False + ) if p.returncode != 0: # not a working vsftpd self.vsftpd = None @@ -256,80 +304,84 @@ def __init__(self, pytestconfig: Optional[pytest.Config] = None, # any data there instead. tmp.seek(0) ver_text = tmp.read() - m = re.match(r'vsftpd: version (\d+\.\d+\.\d+)', ver_text) + m = re.match(r"vsftpd: version (\d+\.\d+\.\d+)", ver_text) if m: self._vsftpd_version = m.group(1) elif len(p.stderr) == 0: # vsftp does not use stdout or stderr for printing its version... -.- - self._vsftpd_version = 'unknown' + self._vsftpd_version = "unknown" else: - raise Exception(f'Unable to determine VsFTPD version from: {p.stderr}') + raise EnvError(f"Unable to determine VsFTPD version from: {p.stderr}") - self.danted = self.config['danted']['danted'] - if self.danted == '': + self.danted = self.config["danted"]["danted"] + if self.danted == "": self.danted = None self._danted_version = None if self.danted is not None: - p = subprocess.run(args=[self.danted, '-v'], - capture_output=True, text=True) + p = subprocess.run(args=[self.danted, "-v"], capture_output=True, text=True, check=False) assert p.returncode == 0 if p.returncode != 0: # not a working vsftpd self.danted = None - m = re.match(r'^Dante v(\d+\.\d+\.\d+).*', p.stdout) + m = re.match(r"^Dante v(\d+\.\d+\.\d+).*", p.stdout) if not m: - m = re.match(r'^Dante v(\d+\.\d+\.\d+).*', p.stderr) + m = re.match(r"^Dante v(\d+\.\d+\.\d+).*", p.stderr) if m: self._danted_version = m.group(1) else: self.danted = None - raise Exception(f'Unable to determine danted version from: {p.stderr}') + raise EnvError(f"Unable to determine danted version from: {p.stderr}") - self.sshd = self.config['sshd']['sshd'] - if self.sshd == '': + self.sshd = self.config["sshd"]["sshd"] + if self.sshd == "": self.sshd = None self._sshd_version = None if self.sshd is not None: - p = subprocess.run(args=[self.sshd, '-V'], - capture_output=True, text=True) + p = subprocess.run(args=[self.sshd, "-V"], capture_output=True, text=True, check=False) assert p.returncode == 0 if p.returncode != 0: self.sshd = None else: - m = re.match(r'^OpenSSH_(\d+\.\d+.*),.*', p.stderr) - assert m, f'version: {p.stderr}' + m = re.match(r"^OpenSSH_(\d+\.\d+.*),.*", p.stderr) + assert m, f"version: {p.stderr}" if m: self._sshd_version = m.group(1) else: self.sshd = None - raise Exception(f'Unable to determine sshd version from: {p.stderr}') + raise EnvError( + f"Unable to determine sshd version from: {p.stderr}" + ) if self.sshd: - self.sftpd = self.config['sshd']['sftpd'] - if self.sftpd == '': + self.sftpd = self.config["sshd"]["sftpd"] + if self.sftpd == "": self.sftpd = None else: self.sftpd = None - self._tcpdump = shutil.which('tcpdump') + self._tcpdump = shutil.which("tcpdump") @property def httpd_version(self): if self._httpd_version is None and self.apxs is not None: try: - p = subprocess.run(args=[self.apxs, '-q', 'HTTPD_VERSION'], - capture_output=True, text=True) + p = subprocess.run( + args=[self.apxs, "-q", "HTTPD_VERSION"], + capture_output=True, + text=True, + check=False, + ) if p.returncode != 0: - log.error(f'{self.apxs} failed to query HTTPD_VERSION: {p}') + log.error(f"{self.apxs} failed to query HTTPD_VERSION: {p}") else: self._httpd_version = p.stdout.strip() except Exception: - log.exception(f'{self.apxs} failed to run') + log.exception(f"{self.apxs} failed to run") return self._httpd_version def versiontuple(self, v): - v = re.sub(r'(\d+\.\d+(\.\d+)?)(-\S+)?', r'\1', v) - return tuple(map(int, v.split('.'))) + v = re.sub(r"(\d+\.\d+(\.\d+)?)(-\S+)?", r"\1", v) + return tuple(map(int, v.split("."))) def httpd_is_at_least(self, minv): if self.httpd_version is None: @@ -344,15 +396,17 @@ def caddy_is_at_least(self, minv): return hv >= self.versiontuple(minv) def is_complete(self) -> bool: - return os.path.isfile(self.httpd) and \ - self.apxs is not None and \ - os.path.isfile(self.apxs) + return ( + os.path.isfile(self.httpd) + and self.apxs is not None + and os.path.isfile(self.apxs) + ) def get_incomplete_reason(self) -> Optional[str]: if self.httpd is None or len(self.httpd.strip()) == 0: - return 'httpd not configured, see `--with-test-httpd=`' + return "httpd not configured, see `--with-test-httpd=`" if not os.path.isfile(self.httpd): - return f'httpd ({self.httpd}) not found' + return f"httpd ({self.httpd}) not found" if self.apxs is None: return "command apxs not found (commonly provided in apache2-dev)" if not os.path.isfile(self.apxs): @@ -372,17 +426,20 @@ def vsftpd_version(self): return self._vsftpd_version @property - def tcpdmp(self) -> Optional[str]: + def h2o_version(self): + return self._h2o_version + + @property + def tcpdump(self) -> Optional[str]: return self._tcpdump def clear_locks(self): - ca_lock = os.path.join(self.gen_root, 'ca/ca.lock') + ca_lock = os.path.join(self.gen_root, "ca/ca.lock") if os.path.exists(ca_lock): os.remove(ca_lock) class Env: - SERVER_TIMEOUT = 30 # seconds to wait for server to come up/reload CONFIG = EnvConfig() @@ -407,98 +464,110 @@ def have_nghttpx() -> bool: def have_h3_server() -> bool: return Env.CONFIG.nghttpx_with_h3 + @staticmethod + def have_h2o() -> bool: + return Env.CONFIG.h2o is not None + @staticmethod def have_ssl_curl() -> bool: - return Env.curl_has_feature('ssl') or Env.curl_has_feature('multissl') + return Env.curl_has_feature("ssl") or Env.curl_has_feature("multissl") @staticmethod def have_h2_curl() -> bool: - return 'http2' in Env.CONFIG.curl_props['features'] + return "http2" in Env.CONFIG.curl_props["features"] @staticmethod def have_h3_curl() -> bool: - return 'http3' in Env.CONFIG.curl_props['features'] + return "http3" in Env.CONFIG.curl_props["features"] @staticmethod def have_compressed_curl() -> bool: - return 'brotli' in Env.CONFIG.curl_props['libs'] or \ - 'zlib' in Env.CONFIG.curl_props['libs'] or \ - 'zstd' in Env.CONFIG.curl_props['libs'] + return ( + "brotli" in Env.CONFIG.curl_props["libs"] + or "zlib" in Env.CONFIG.curl_props["libs"] + or "zstd" in Env.CONFIG.curl_props["libs"] + ) @staticmethod def curl_uses_lib(libname: str) -> bool: - return libname.lower() in Env.CONFIG.curl_props['libs'] + return libname.lower() in Env.CONFIG.curl_props["libs"] @staticmethod def curl_uses_any_libs(libs: List[str]) -> bool: - for libname in libs: - if libname.lower() in Env.CONFIG.curl_props['libs']: - return True - return False + return any(libname.lower() in Env.CONFIG.curl_props["libs"] for libname in libs) @staticmethod def curl_uses_ossl_quic() -> bool: if Env.have_h3_curl(): - return not Env.curl_uses_lib('ngtcp2') and Env.curl_uses_lib('nghttp3') + return not Env.curl_uses_lib("ngtcp2") and Env.curl_uses_lib("nghttp3") return False @staticmethod def curl_version_string() -> str: - return Env.CONFIG.curl_props['version_string'] + return Env.CONFIG.curl_props["version_string"] + + @staticmethod + def curl_version_at_least(min_version) -> bool: + version = Env.curl_version() + return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + version + ) @staticmethod def curl_features_string() -> str: - return Env.CONFIG.curl_props['features_string'] + return Env.CONFIG.curl_props["features_string"] @staticmethod def curl_has_feature(feature: str) -> bool: - return feature.lower() in Env.CONFIG.curl_props['features'] + return feature.lower() in Env.CONFIG.curl_props["features"] @staticmethod def curl_protocols_string() -> str: - return Env.CONFIG.curl_props['protocols_string'] + return Env.CONFIG.curl_props["protocols_string"] @staticmethod def curl_has_protocol(protocol: str) -> bool: - return protocol.lower() in Env.CONFIG.curl_props['protocols'] + return protocol.lower() in Env.CONFIG.curl_props["protocols"] @staticmethod def curl_lib_version(libname: str) -> str: - prefix = f'{libname.lower()}/' - for lversion in Env.CONFIG.curl_props['lib_versions']: + prefix = f"{libname.lower()}/" + for lversion in Env.CONFIG.curl_props["lib_versions"]: if lversion.startswith(prefix): return lversion[len(prefix):] - return 'unknown' + return "unknown" @staticmethod def curl_lib_version_at_least(libname: str, min_version) -> bool: lversion = Env.curl_lib_version(libname) - if lversion != 'unknown': - return Env.CONFIG.versiontuple(min_version) <= \ - Env.CONFIG.versiontuple(lversion) + if lversion != "unknown": + return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + lversion + ) return False @staticmethod def curl_lib_version_before(libname: str, lib_version) -> bool: lversion = Env.curl_lib_version(libname) - if lversion != 'unknown': - if m := re.match(r'(\d+\.\d+\.\d+).*', lversion): + if lversion != "unknown": + if m := re.match(r"(\d+\.\d+\.\d+).*", lversion): lversion = m.group(1) - return Env.CONFIG.versiontuple(lib_version) > \ - Env.CONFIG.versiontuple(lversion) + return Env.CONFIG.versiontuple(lib_version) > Env.CONFIG.versiontuple( + lversion + ) return False @staticmethod def curl_os() -> str: - return Env.CONFIG.curl_props['os'] + return Env.CONFIG.curl_props["os"] @staticmethod def curl_fullname() -> str: - return Env.CONFIG.curl_props['fullname'] + return Env.CONFIG.curl_props["fullname"] @staticmethod def curl_version() -> str: - return Env.CONFIG.curl_props['version'] + return Env.CONFIG.curl_props["version"] @staticmethod def curl_is_debug() -> bool: @@ -520,40 +589,43 @@ def curl_override_dns() -> bool: def curl_resolv_threaded() -> bool: return Env.CONFIG.curl_resolv_threaded + @staticmethod + def curl_can_doh() -> bool: + return Env.CONFIG.curl_can_doh + @staticmethod def curl_can_early_data() -> bool: if Env.curl_uses_lib('gnutls'): - return Env.curl_lib_version_at_least('gnutls', '3.6.13') + return Env.curl_lib_version_at_least('gnutls', '3.7.2') return Env.curl_uses_any_libs(['wolfssl', 'quictls', 'openssl']) @staticmethod def curl_can_h3_early_data() -> bool: - return Env.curl_can_early_data() and \ - Env.curl_uses_lib('ngtcp2') + return Env.curl_can_early_data() and Env.curl_uses_lib("ngtcp2") @staticmethod def http_protos() -> List[str]: # http protocols we can test if Env.have_h2_curl(): if Env.have_h3(): - return ['http/1.1', 'h2', 'h3'] - return ['http/1.1', 'h2'] - return ['http/1.1'] + return ["http/1.1", "h2", "h3"] + return ["http/1.1", "h2"] + return ["http/1.1"] @staticmethod def http_h1_h2_protos() -> List[str]: # http 1+2 protocols we can test if Env.have_h2_curl(): - return ['http/1.1', 'h2'] - return ['http/1.1'] + return ["http/1.1", "h2"] + return ["http/1.1"] @staticmethod def http_mplx_protos() -> List[str]: # http multiplexing protocols we can test if Env.have_h2_curl(): if Env.have_h3(): - return ['h2', 'h3'] - return ['h2'] + return ["h2", "h3"] + return ["h2"] return [] @staticmethod @@ -572,6 +644,10 @@ def nghttpx_version() -> str: def caddy_version() -> str: return Env.CONFIG.caddy_version + @staticmethod + def h2o_version() -> str: + return Env.CONFIG.h2o_version + @staticmethod def caddy_is_at_least(minv) -> bool: return Env.CONFIG.caddy_is_at_least(minv) @@ -606,26 +682,25 @@ def has_sftpd() -> bool: @staticmethod def tcpdump() -> Optional[str]: - return Env.CONFIG.tcpdmp + return Env.CONFIG.tcpdump def __init__(self, pytestconfig=None, env_config=None): if env_config: Env.CONFIG = env_config - self._verbose = pytestconfig.option.verbose \ - if pytestconfig is not None else 0 + self._verbose = pytestconfig.option.verbose if pytestconfig is not None else 0 self._ca = None self._test_timeout = 300.0 if self._verbose > 1 else 60.0 # seconds def issue_certs(self): if self._ca is None: # ca_dir = os.path.join(self.CONFIG.gen_root, 'ca') - ca_dir = os.path.join(self.gen_dir, 'ca') + ca_dir = os.path.join(self.gen_dir, "ca") os.makedirs(ca_dir, exist_ok=True) - lock_file = os.path.join(ca_dir, 'ca.lock') + lock_file = os.path.join(ca_dir, "ca.lock") with FileLock(lock_file): - self._ca = TestCA.create_root(name=self.CONFIG.tld, - store_dir=ca_dir, - key_type="rsa2048") + self._ca = TestCA.create_root( + name=self.CONFIG.tld, store_dir=ca_dir, key_type="rsa2048" + ) self._ca.issue_certs(self.CONFIG.cert_specs) if self.have_openssl(): self._ca.create_hashdir(self.openssl) @@ -714,19 +789,19 @@ def update_ports(self, ports: Dict[str, int]): @property def http_port(self) -> int: - return self.CONFIG.ports.get('http', 0) + return self.CONFIG.ports.get("http", 0) @property def https_port(self) -> int: - return self.CONFIG.ports['https'] + return self.CONFIG.ports["https"] @property def https_only_tcp_port(self) -> int: - return self.CONFIG.ports['https-tcp-only'] + return self.CONFIG.ports["https-tcp-only"] @property def nghttpx_https_port(self) -> int: - return self.CONFIG.ports['nghttpx_https'] + return self.CONFIG.ports["nghttpx_https"] @property def h3_port(self) -> int: @@ -734,27 +809,36 @@ def h3_port(self) -> int: @property def proxy_port(self) -> int: - return self.CONFIG.ports['proxy'] + return self.CONFIG.ports["proxy"] @property def proxys_port(self) -> int: - return self.CONFIG.ports['proxys'] + return self.CONFIG.ports["proxys"] @property def ftp_port(self) -> int: - return self.CONFIG.ports['ftp'] + return self.CONFIG.ports["ftp"] @property def ftps_port(self) -> int: - return self.CONFIG.ports['ftps'] + return self.CONFIG.ports["ftps"] @property def h2proxys_port(self) -> int: - return self.CONFIG.ports['h2proxys'] + return self.CONFIG.ports["h2proxys"] - def pts_port(self, proto: str = 'http/1.1') -> int: + @property + def h3proxys_port(self) -> int: + return self.CONFIG.ports["h2o_h3proxys"] + + def pts_port(self, proto: str = "http/1.1", use_h2o: bool = False) -> int: # proxy tunnel port - return self.CONFIG.ports['h2proxys' if proto == 'h2' else 'proxys'] + prefix = 'h2o_' if use_h2o else '' + if proto == "h3": + return self.CONFIG.ports.get("h2o_h3proxys", 0) + if proto == "h2": + return self.CONFIG.ports.get(f"{prefix}h2proxys", 0) + return self.CONFIG.ports[f"{prefix}proxys"] @property def caddy(self) -> str: @@ -762,11 +846,11 @@ def caddy(self) -> str: @property def caddy_https_port(self) -> int: - return self.CONFIG.ports['caddys'] + return self.CONFIG.ports["caddys"] @property def caddy_http_port(self) -> int: - return self.CONFIG.ports['caddy'] + return self.CONFIG.ports["caddy"] @property def danted(self) -> str: @@ -776,10 +860,6 @@ def danted(self) -> str: def vsftpd(self) -> str: return self.CONFIG.vsftpd - @property - def ws_port(self) -> int: - return self.CONFIG.ports['ws'] - @property def curl(self) -> str: return self.CONFIG.curl @@ -802,58 +882,65 @@ def nghttpx(self) -> Optional[str]: @property def slow_network(self) -> bool: - return "CURL_DBG_SOCK_WBLOCK" in os.environ or \ - "CURL_DBG_SOCK_WPARTIAL" in os.environ + return ( + "CURL_DBG_SOCK_WBLOCK" in os.environ + or "CURL_DBG_SOCK_WPARTIAL" in os.environ + ) @property def ci_run(self) -> bool: return "CURL_CI" in os.environ def port_for(self, alpn_proto: Optional[str] = None): - if alpn_proto is None or \ - alpn_proto in ['h2', 'http/1.1', 'http/1.0', 'http/0.9']: + if alpn_proto is None or alpn_proto in [ + "h2", + "http/1.1", + "http/1.0", + "http/0.9", + ]: return self.https_port - if alpn_proto in ['h3']: + if alpn_proto == "h3": return self.h3_port return self.http_port def authority_for(self, domain: str, alpn_proto: Optional[str] = None): - return f'{domain}:{self.port_for(alpn_proto=alpn_proto)}' + return f"{domain}:{self.port_for(alpn_proto=alpn_proto)}" - def make_data_file(self, indir: str, fname: str, fsize: int, - line_length: int = 1024) -> str: + def make_data_file( + self, indir: str, fname: str, fsize: int, line_length: int = 1024 + ) -> str: if line_length < 11: - raise RuntimeError('line_length less than 11 not supported') + raise RuntimeError("line_length less than 11 not supported") + os.makedirs(indir, exist_ok=True) fpath = os.path.join(indir, fname) - s10 = "0123456789" + s10 = string.digits s = round((line_length / 10) + 1) * s10 - s = s[0:line_length-11] - with open(fpath, 'w') as fd: - for i in range(int(fsize / line_length)): - fd.write(f"{i:09d}-{s}\n") + s = s[0:line_length - 11] + with open(fpath, "w") as fd: + fd.writelines(f"{i:09d}-{s}\n" for i in range(int(fsize / line_length))) remain = int(fsize % line_length) if remain != 0: i = int(fsize / line_length) + 1 - fd.write(f"{i:09d}-{s}"[0:remain-1] + "\n") + fd.write(f"{i:09d}-{s}"[0:remain - 1] + "\n") return fpath def make_data_gzipbomb(self, indir: str, fname: str, fsize: int) -> str: fpath = os.path.join(indir, fname) - gzpath = f'{fpath}.gz' - varpath = f'{fpath}.var' + gzpath = f"{fpath}.gz" + varpath = f"{fpath}.var" - with open(fpath, 'w') as fd: - fd.write('not what we are looking for!\n') + with open(fpath, "w") as fd: + fd.write("not what we are looking for!\n") count = int(fsize / 1024) zero1k = bytearray(1024) - with gzip.open(gzpath, 'wb') as fd: + with gzip.open(gzpath, "wb") as fd: for _ in range(count): fd.write(zero1k) - with open(varpath, 'w') as fd: - fd.write(f'URI: {fname}\n') - fd.write('\n') - fd.write(f'URI: {fname}.gz\n') - fd.write('Content-Type: text/plain\n') - fd.write('Content-Encoding: x-gzip\n') - fd.write('\n') + with open(varpath, "w") as fd: + fd.write(f"URI: {fname}\n") + fd.write("\n") + fd.write(f"URI: {fname}.gz\n") + fd.write("Content-Type: text/plain\n") + fd.write("Content-Encoding: x-gzip\n") + fd.write("\n") return fpath diff --git a/tests/http/testenv/h2o.py b/tests/http/testenv/h2o.py new file mode 100644 index 000000000000..82ab16b76bfb --- /dev/null +++ b/tests/http/testenv/h2o.py @@ -0,0 +1,444 @@ +# *************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +# +import logging +import os +import signal +import socket +import subprocess +import time +from datetime import datetime, timedelta, timezone +from typing import ClassVar, Dict, Optional + +from .curl import CurlClient +from .env import Env +from .ports import alloc_ports_and_do + +log = logging.getLogger(__name__) + + +class H2o: + def __init__(self, env: Env, name: str, domain: str, cred_name: str): + self.env = env + self._name = name + self._domain = domain + self._port = 0 # defaults to h3_port + self._cred_name = cred_name + self._loaded_cred_name = None + self._error_fd = None + self._process = None + self._tmp_dir = os.path.join(self.env.gen_dir, self._name) + self._run_dir = os.path.join(self._tmp_dir, "run") + self._conf_file = os.path.join(self._run_dir, "h2o.conf") + self._error_log = os.path.join(self._run_dir, "h2o.log") + self._pid_file = os.path.join(self._run_dir, "h2o.pid") + self._stderr = os.path.join(self._run_dir, "h2o.stderr") + self._cmd = env.CONFIG.h2o + # For proxy subclasses + self._h1_port = None + self._h2_port = None + + @property + def port(self) -> int: + return self._port + + @property + def h1_port(self) -> Optional[int]: + return getattr(self, "_h1_port", None) + + @property + def h2_port(self) -> Optional[int]: + return getattr(self, "_h2_port", None) + + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + + def clear_logs(self): + self._rmf(self._error_log) + self._rmf(self._stderr) + + def dump_logs(self): + lines = [] + lines.extend([f"stderr of {self._name}", + "-------------------------------------------"]) + self._dump_file(self._stderr, lines) + lines.extend(["", + f"errorlog of {self._name}", + "-------------------------------------------"]) + self._dump_file(self._error_log, lines) + lines.append("") + return lines + + def _rmf(self, path): + if os.path.isfile(path): + os.remove(path) + + def _dump_file(self, path, lines): + if os.path.isfile(path): + with open(path) as fd: + for line in fd: + lines.append(line.rstrip()) + + def _mkpath(self, path): + if not os.path.exists(path): + os.makedirs(path) + + def _log(self, level, msg): + getattr(log, level)(f"[{self._name}] {msg}") + + def is_running(self): + if self._process: + self._process.poll() + return self._process.returncode is None + return False + + def initial_start(self): + self._rmf(self._pid_file) + self._rmf(self._error_log) + self._mkpath(self._run_dir) + self.write_config() + + def start(self, wait_live=True): + self._mkpath(self._tmp_dir) + self._mkpath(self._run_dir) + if self._process: + self.stop() + self._loaded_cred_name = self._cred_name + self.write_config() + args = [self._cmd, "-c", self._conf_file] + self._error_fd = open(self._stderr, "a") # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) + if self._process.returncode is not None: + return False + if wait_live: + time.sleep(1) + # fail fast if h2o rejected the config and already exited + self._process.poll() + if self._process.returncode is not None: + self._log("error", + f"h2o exited early (rc={self._process.returncode})" + f" - check {self._stderr} for details") + self._process = None + return False + return not wait_live or self.wait_for_state( + live=True, timeout=timedelta(seconds=Env.SERVER_TIMEOUT) + ) + + def stop(self, wait_dead=True): + self._mkpath(self._tmp_dir) + if self._process: + self._process.terminate() + try: + self._process.wait(timeout=5) + except subprocess.TimeoutExpired: + self._process.kill() + self._process.wait(timeout=2) + self._process = None + self.close_log() + return not wait_dead or self.wait_for_state( + live=False, timeout=timedelta(seconds=5) + ) + self.close_log() + return True + + def kill(self, wait_dead=True): + if self._process: + self._process.kill() + self.close_log() + return True + self.close_log() + return False + + def restart(self): + self.stop() + return self.start() + + def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)): + if self._process: + running = self._process + self._process = None + os.kill(running.pid, signal.SIGQUIT) + end_wait = datetime.now(timezone.utc) + timedelta(seconds=5) + exited = False + if not self.start(wait_live=False): + self._process = running + return False + while datetime.now(timezone.utc) < end_wait: + try: + self._log("debug", f"waiting for h2o({running.pid}) to exit.") + running.wait(1) + self._log( + "debug", + f"h2o({running.pid}) terminated -> {running.returncode}", + ) + exited = True + break + except subprocess.TimeoutExpired: + self._log("warning", f"h2o({running.pid}), not shut down yet.") + os.kill(running.pid, signal.SIGQUIT) + if not exited and datetime.now(timezone.utc) >= end_wait: + self._log("error", f"h2o({running.pid}), terminate forcefully.") + os.kill(running.pid, signal.SIGKILL) + running.terminate() + running.wait(1) + return self.wait_for_state(live=True, timeout=timeout) + return False + + def wait_for_state( + self, + live: bool, + timeout: timedelta, + url: Optional[str] = None, + log_prefix: str = "h2o", + ): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir) + try_until = datetime.now(timezone.utc) + timeout + if url is None: + url = f"https://{self._domain}:{self._port}/" + while datetime.now(timezone.utc) < try_until: + if live: + r = curl.http_get( + url=url, extra_args=["--trace", "curl.trace", "--trace-time"] + ) + if r.exit_code == 0: + return True + else: + r = curl.http_get(url=url) + if r.exit_code != 0: + return True + time.sleep(0.1) + if live: + self._log("error", f"Server still not responding after {timeout}") + else: + self._log("debug", f"Server still responding after {timeout}") + return False + + def write_config(self): + # To be overridden by subclasses + with open(self._conf_file, "w") as fd: + fd.write("# h2o test config\n") + + +class H2oServer(H2o): + """h2o HTTP/3 server for testing.""" + + PORT_SPECS: ClassVar[Dict[str, int]] = { + "h2o_https": socket.SOCK_STREAM, + } + + def __init__(self, env: Env): + super().__init__( + env=env, name="h2o-server", domain=env.domain1, cred_name=env.domain1 + ) + self._docs_dir = os.path.join(self.env.gen_dir, "docs") + + @property + def docs_dir(self): + return self._docs_dir + + def initial_start(self): + super().initial_start() + + def startup(ports: Dict[str, int]) -> bool: + self._port = ports["h2o_https"] + if self.start(): + self.env.update_ports(ports) + return True + self.stop() + self._port = 0 + return False + + return alloc_ports_and_do( + H2oServer.PORT_SPECS, startup, self.env.gen_root, max_tries=3 + ) + + def write_config(self): + creds = self.env.get_credentials(self._cred_name) + assert creds # convince pytype this is not None + self._mkpath(self._docs_dir) + self._mkpath(self._run_dir) + # Create a simple test file + with open(os.path.join(self._docs_dir, "data.json"), "w") as f: + f.write('{"message": "Hello from h2o HTTP/3 server"}\n') + with open(self._conf_file, "w") as fd: + fd.write(f"""# h2o HTTP/3 server configuration +server-name: "h2o-test-server" +num-threads: 1 + +listen: &ssl_listen + port: {self._port} + ssl: + certificate-file: {creds.cert_file} + key-file: {creds.pkey_file} + neverbleed: OFF + minimum-version: TLSv1.2 + ocsp-update-interval: 0 + +listen: + <<: *ssl_listen + type: quic + +hosts: + "{self._domain}": + paths: + "/": + file.dir: {self._docs_dir} + +http2-reprioritize-blocking-assets: ON + +access-log: {self._run_dir}/access.log +error-log: {self._error_log} +""") + + +class H2oProxy(H2o): + """h2o MASQUE proxy for testing.""" + + def __init__(self, env: Env): + super().__init__( + env=env, + name="h2o-proxy", + domain=env.proxy_domain, + cred_name=env.proxy_domain, + ) + + def initial_start(self): + super().initial_start() + + def startup(ports: Dict[str, int]) -> bool: + self._port = ports["h2o_h3proxys"] + self._h2_port = ports["h2o_h2proxys"] + self._h1_port = ports["h2o_proxys"] + if self.start(): + self.env.update_ports(ports) + return True + self.stop() + self._port = 0 + self._h2_port = 0 + self._h1_port = 0 + return False + + return alloc_ports_and_do( + { + "h2o_h3proxys": socket.SOCK_DGRAM, + "h2o_h2proxys": socket.SOCK_STREAM, + "h2o_proxys": socket.SOCK_STREAM, + }, + startup, + self.env.gen_root, + max_tries=3, + ) + + def write_config(self): + creds = self.env.get_credentials(self._cred_name) + assert creds # convince pytype this is not None + self._mkpath(self._run_dir) + with open(self._conf_file, "w") as fd: + fd.write(f"""# h2o MASQUE proxy configuration +server-name: "h2o-test-proxy" +num-threads: 1 + +proxy.tunnel: ON + +# HTTP/1.1 proxy listener +listen: &h1_listen + port: {getattr(self, "_h1_port", self._port)} + ssl: + certificate-file: {creds.cert_file} + key-file: {creds.pkey_file} + neverbleed: OFF + minimum-version: TLSv1.2 + ocsp-update-interval: 0 + +# HTTP/2 proxy listener +listen: &h2_listen + port: {getattr(self, "_h2_port", self._port)} + ssl: + certificate-file: {creds.cert_file} + key-file: {creds.pkey_file} + neverbleed: OFF + minimum-version: TLSv1.2 + ocsp-update-interval: 0 + +# HTTP/3 proxy listener (main port) +listen: &h3_listen + port: {self._port} + ssl: + certificate-file: {creds.cert_file} + key-file: {creds.pkey_file} + neverbleed: OFF + minimum-version: TLSv1.2 + ocsp-update-interval: 0 + +# QUIC listener for HTTP/3 +listen: + <<: *h3_listen + type: quic + +hosts: + "{self._domain}": + paths: + "/": + proxy.connect: [+*] + proxy.ssl.verify-peer: OFF + "/.well-known/masque/udp": + proxy.connect-udp: [+*] + proxy.ssl.verify-peer: OFF + +http2-reprioritize-blocking-assets: ON + +access-log: {self._run_dir}/access.log +error-log: {self._error_log} +""") + + def wait_for_state( + self, + live: bool, + timeout: timedelta, + url: Optional[str] = None, + log_prefix: str = "h2o", + ): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir) + try_until = datetime.now(timezone.utc) + timeout + if url is None: + url = f"https://{self.env.proxy_domain}:{self._port}/" + while datetime.now(timezone.utc) < try_until: + if live: + r = curl.http_get( + url=url, extra_args=["--trace", "curl.trace", "--trace-time"] + ) + if r.exit_code == 0: + return True + else: + r = curl.http_get(url=url) + if r.exit_code != 0: + return True + time.sleep(0.1) + if live: + self._log("error", f"Proxy still not responding after {timeout}") + else: + self._log("debug", f"Proxy still responding after {timeout}") + return False diff --git a/tests/http/testenv/httpd.py b/tests/http/testenv/httpd.py index 64ca796d13b5..63dd01145f9e 100644 --- a/tests/http/testenv/httpd.py +++ b/tests/http/testenv/httpd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -31,13 +29,14 @@ import shutil import socket import subprocess +import textwrap import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from json import JSONEncoder -from typing import Dict, List, Optional, Union +from typing import ClassVar, Dict, List, Optional, Union from .curl import CurlClient, ExecResult -from .env import Env +from .env import Env, EnvError from .ports import alloc_ports_and_do log = logging.getLogger(__name__) @@ -45,7 +44,7 @@ class Httpd: - MODULES = [ + MODULES: ClassVar[List[str]] = [ 'log_config', 'logio', 'unixd', 'version', 'watchdog', 'authn_core', 'authn_file', 'authz_user', 'authz_core', 'authz_host', @@ -56,14 +55,14 @@ class Httpd: 'brotli', 'mpm_event', ] - COMMON_MODULES_DIRS = [ + COMMON_MODULES_DIRS: ClassVar[List[str]] = [ '/usr/lib/apache2/modules', # debian '/usr/libexec/apache2/', # macos ] MOD_CURLTEST = None - PORT_SPECS = { + PORT_SPECS: ClassVar[Dict[str, int]] = { 'http': socket.SOCK_STREAM, 'https': socket.SOCK_STREAM, 'https-tcp-only': socket.SOCK_STREAM, @@ -95,14 +94,12 @@ def __init__(self, env: Env): self._loaded_domain1_cred_name = None assert env.apxs p = subprocess.run(args=[env.apxs, '-q', 'libexecdir'], - capture_output=True, text=True) - if p.returncode != 0: - raise Exception(f'{env.apxs} failed to query libexecdir: {p}') + capture_output=True, text=True, check=True) self._mods_dir = p.stdout.strip() if self._mods_dir is None: - raise Exception('apache modules directory cannot be found') + raise EnvError('apache modules directory cannot be found') if not os.path.exists(self._mods_dir): - raise Exception(f'apache modules directory does not exist: {self._mods_dir}') + raise EnvError(f'apache modules directory does not exist: {self._mods_dir}') self._maybe_running = False self.ports = {} self._rmf(self._error_log) @@ -141,15 +138,14 @@ def _run(self, args, intext=''): env['APACHE_RUN_USER'] = os.environ['USER'] env['APACHE_LOCK_DIR'] = self._lock_dir env['APACHE_CONFDIR'] = self._apache_dir - p = subprocess.run(args, stderr=subprocess.PIPE, stdout=subprocess.PIPE, - cwd=self.env.gen_dir, + p = subprocess.run(args, capture_output=True, cwd=self.env.gen_dir, input=intext.encode() if intext else None, - env=env) - start = datetime.now() + env=env, check=True) + start = datetime.now(timezone.utc) return ExecResult(args=args, exit_code=p.returncode, stdout=p.stdout.decode().splitlines(), stderr=p.stderr.decode().splitlines(), - duration=datetime.now() - start) + duration=datetime.now(timezone.utc) - start) def _cmd_httpd(self, cmd: str): args = [self.env.httpd, @@ -174,7 +170,7 @@ def startup(ports: Dict[str, int]) -> bool: def start(self): # assure ports are allocated - for key, _ in Httpd.PORT_SPECS.items(): + for key in Httpd.PORT_SPECS: assert self.ports[key] is not None if self._maybe_running: self.stop() @@ -225,8 +221,8 @@ def reload_if_config_changed(self): def wait_dead(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: r = curl.http_get(url=f'http://{self.env.domain1}:{self.ports["http"]}/') if r.exit_code != 0: self._maybe_running = False @@ -238,8 +234,8 @@ def wait_dead(self, timeout: timedelta): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir, timeout=timeout.total_seconds()) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: r = curl.http_get(url=f'http://{self.env.domain1}:{self.ports["http"]}/') if r.exit_code == 0: self._maybe_running = True @@ -318,7 +314,7 @@ def _write_config(self): 'AddEncoding x-gzip .gz .tgz .gzip', 'AddHandler type-map .var', ] - conf.extend([f'Listen {port}' for _, port in self.ports.items()]) + conf.extend([f'Listen {port}' for port in self.ports.values()]) if 'base' in self._extra_configs: conf.extend(self._extra_configs['base']) @@ -483,14 +479,13 @@ def _write_config(self): fd.write("\n".join(conf)) with open(os.path.join(self._conf_dir, 'mime.types'), 'w') as fd: - fd.write("\n".join([ - 'text/plain txt', - 'text/html html', - 'application/json json', - 'application/x-gzip gzip', - 'application/x-gzip gz', - '' - ])) + fd.write(textwrap.dedent("""\ + text/plain txt + text/html html + application/json json + application/x-gzip gzip + application/x-gzip gz + """)) def _get_proxy_conf(self): if self._proxy_auth_basic: @@ -585,11 +580,10 @@ def _init_curltest(self): if not os.path.exists(out_source) or \ os.stat(in_source).st_mtime > os.stat(out_source).st_mtime: shutil.copy(in_source, out_source) - p = subprocess.run([ - self.env.apxs, '-c', out_source - ], capture_output=True, cwd=out_dir) + p = subprocess.run([self.env.apxs, '-c', out_source], + capture_output=True, cwd=out_dir, check=False) rv = p.returncode if rv != 0: log.error(f"compiling mod_curltest failed: {p.stderr}") - raise Exception(f"compiling mod_curltest failed: {p.stderr}") + raise EnvError(f"compiling mod_curltest failed: {p.stderr}") Httpd.MOD_CURLTEST = os.path.join(out_dir, '.libs/mod_curltest.so') diff --git a/tests/http/testenv/mod_curltest/mod_curltest.c b/tests/http/testenv/mod_curltest/mod_curltest.c index 5e0f6400fb57..d783af1ab641 100644 --- a/tests/http/testenv/mod_curltest/mod_curltest.c +++ b/tests/http/testenv/mod_curltest/mod_curltest.c @@ -328,18 +328,16 @@ static int curltest_tweak_handler(request_rec *r) } else if(!strcmp("chunk_size", arg)) { chunk_size = (int)apr_atoi64(val); - if(chunk_size >= 0) { - if(chunk_size > sizeof(buffer)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "chunk_size %zu too large", chunk_size); - ap_die(HTTP_BAD_REQUEST, r); - return OK; - } - continue; + if(chunk_size > sizeof(buffer)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "chunk_size %zu too large", chunk_size); + ap_die(HTTP_BAD_REQUEST, r); + return OK; } + continue; } else if(!strcmp("id", arg)) { - /* just an id for repeated requests with curl's URL globbing */ + /* an id for repeated requests with curl's URL globbing */ request_id = val; continue; } @@ -396,8 +394,7 @@ static int curltest_tweak_handler(request_rec *r) continue; } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not " - "understood: '%s' in %s", - arg, r->args); + "understood: '%s' in %s", arg, r->args); ap_die(HTTP_BAD_REQUEST, r); return OK; } @@ -406,7 +403,10 @@ static int curltest_tweak_handler(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: processing " "request, %s", r->args? r->args : "(no args)"); r->status = http_status; - r->clength = with_cl ? (chunks * chunk_size) : -1; + if(with_cl) + r->clength = (apr_off_t)chunks * chunk_size; + else + r->clength = -1; r->chunked = (r->proto_num >= HTTP_VERSION(1, 1)) && !with_cl; apr_table_setn(r->headers_out, "request-id", request_id); if(r->clength >= 0) { @@ -418,7 +418,7 @@ static int curltest_tweak_handler(request_rec *r) /* Discourage content-encodings */ apr_table_unset(r->headers_out, "Content-Encoding"); if(x_hd_len > 0) { - int i, hd_len = (16 * 1024); + int hd_len = (16 * 1024); int n = (x_hd_len / hd_len); char *hd_val = apr_palloc(r->pool, hd_len); memset(hd_val, 'X', hd_len); @@ -497,7 +497,7 @@ static int curltest_tweak_handler(request_rec *r) r->connection->keepalive = AP_CONN_CLOSE; } ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, - "error_handler: request cleanup, r->status=%d, aborted=%d, " + "error_handler: request cleanup, r->status=%d, aborted=%u, " "close=%d", r->status, c->aborted, close_conn); if(rv == APR_SUCCESS) { return OK; @@ -551,7 +551,7 @@ static int curltest_put_handler(request_rec *r) *s = '\0'; val = s + 1; if(!strcmp("id", arg)) { - /* just an id for repeated requests with curl's URL globbing */ + /* an id for repeated requests with curl's URL globbing */ request_id = val; continue; } @@ -573,8 +573,7 @@ static int curltest_put_handler(request_rec *r) } } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not " - "understood: '%s' in %s", - arg, r->args); + "understood: '%s' in %s", arg, r->args); ap_die(HTTP_BAD_REQUEST, r); return OK; } @@ -748,7 +747,7 @@ static int curltest_sslinfo_handler(request_rec *r) *s = '\0'; val = s + 1; if(!strcmp("id", arg)) { - /* just an id for repeated requests with curl's URL globbing */ + /* an id for repeated requests with curl's URL globbing */ request_id = val; continue; } @@ -759,8 +758,7 @@ static int curltest_sslinfo_handler(request_rec *r) continue; } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not " - "understood: '%s' in %s", - arg, r->args); + "understood: '%s' in %s", arg, r->args); ap_die(HTTP_BAD_REQUEST, r); return OK; } @@ -833,7 +831,7 @@ struct curltest_limit_rec { }; static struct curltest_limit_rec limitrec = { - 0, 5, 0, 2 + 0, 5, 0, 2, NULL }; static int curltest_limit_handler(request_rec *r) @@ -862,14 +860,13 @@ static int curltest_limit_handler(request_rec *r) *s = '\0'; val = s + 1; if(!strcmp("id", arg)) { - /* just an id for repeated requests with curl's URL globbing */ + /* an id for repeated requests with curl's URL globbing */ request_id = val; continue; } } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not " - "understood: '%s' in %s", - arg, r->args); + "understood: '%s' in %s", arg, r->args); ap_die(HTTP_BAD_REQUEST, r); return OK; } @@ -911,7 +908,7 @@ static int curltest_limit_handler(request_rec *r) apr_table_setn(r->subprocess_env, "no-gzip", "1"); if(denied) { - char *v = apr_psprintf(r->pool, "%d", limitrec.duration_sec); + char *v = apr_psprintf(r->pool, "%ld", limitrec.duration_sec); apr_table_set(r->headers_out, "Retry-After", v); } @@ -952,8 +949,7 @@ static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { void *data = NULL; - const char *key = "mod_curltest_init_counter"; - apr_status_t rv; + static const char *key = "mod_curltest_init_counter"; (void)p; (void)plog; diff --git a/tests/http/testenv/nghttpx.py b/tests/http/testenv/nghttpx.py index 0d95a34bceb9..ba0cbe1a798e 100644 --- a/tests/http/testenv/nghttpx.py +++ b/tests/http/testenv/nghttpx.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -29,9 +27,10 @@ import signal import socket import subprocess +import textwrap import time -from datetime import datetime, timedelta -from typing import Dict, Optional +from datetime import datetime, timedelta, timezone +from typing import ClassVar, Dict, Optional from .curl import CurlClient from .env import Env, NghttpxUtil @@ -47,7 +46,7 @@ def __init__(self, env: Env, name: str, domain: str, cred_name: str): self._name = name self._domain = domain self._port = 0 - self._https_port = 0 + self._port_is_quic = False self._cmd = env.nghttpx self._run_dir = os.path.join(env.gen_dir, name) self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid') @@ -55,6 +54,7 @@ def __init__(self, env: Env, name: str, domain: str, cred_name: str): self._error_log = os.path.join(self._run_dir, 'nghttpx.log') self._stderr = os.path.join(self._run_dir, 'nghttpx.stderr') self._tmp_dir = os.path.join(self._run_dir, 'tmp') + self._error_fd = None self._process: Optional[subprocess.Popen] = None self._cred_name = self._def_cred_name = cred_name self._loaded_cred_name = '' @@ -76,12 +76,21 @@ def reload_if_config_changed(self): return self.reload() @property - def https_port(self): - return self._https_port + def port(self): + return self._port + + @property + def port_is_quic(self): + return self._port_is_quic def exists(self): return self._cmd and os.path.exists(self._cmd) + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._error_log) self._rmf(self._stderr) @@ -112,7 +121,9 @@ def stop(self, wait_dead=True): self._process.terminate() self._process.wait(timeout=2) self._process = None + self.close_log() return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) + self.close_log() return True def restart(self): @@ -124,11 +135,11 @@ def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)): running = self._process self._process = None os.kill(running.pid, signal.SIGQUIT) - end_wait = datetime.now() + timedelta(seconds=5) + end_wait = datetime.now(timezone.utc) + timedelta(seconds=5) if not self.start(wait_live=False): self._process = running return False - while datetime.now() < end_wait: + while datetime.now(timezone.utc) < end_wait: try: log.debug(f'waiting for nghttpx({running.pid}) to exit.') running.wait(1) @@ -138,7 +149,7 @@ def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)): except subprocess.TimeoutExpired: log.warning(f'nghttpx({running.pid}), not shut down yet.') os.kill(running.pid, signal.SIGQUIT) - if running and datetime.now() >= end_wait: + if running and datetime.now(timezone.utc) >= end_wait: log.error(f'nghttpx({running.pid}), terminate forcefully.') os.kill(running.pid, signal.SIGKILL) running.terminate() @@ -148,20 +159,16 @@ def reload(self, timeout: timedelta = timedelta(seconds=Env.SERVER_TIMEOUT)): def wait_dead(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: - if self._https_port > 0: - check_url = f'https://{self._domain}:{self._port}/' - r = curl.http_get(url=check_url, extra_args=[ - '--trace', 'curl.trace', '--trace-time', - '--connect-timeout', '1' - ]) - else: - check_url = f'https://{self._domain}:{self._port}/' - r = curl.http_get(url=check_url, extra_args=[ - '--trace', 'curl.trace', '--trace-time', - '--http3-only', '--connect-timeout', '1' - ]) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: + xargs = [ + '--trace', 'curl.trace', '--trace-time', + '--connect-timeout', '1' + ] + if self.port_is_quic: + xargs.extend(['--http3-only']) + check_url = f'https://{self._domain}:{self.port}/' + r = curl.http_get(url=check_url, extra_args=xargs) if r.exit_code != 0: return True log.debug(f'waiting for nghttpx to stop responding: {r}') @@ -171,20 +178,16 @@ def wait_dead(self, timeout: timedelta): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: - if self._https_port > 0: - check_url = f'https://{self._domain}:{self._port}/' - r = curl.http_get(url=check_url, extra_args=[ - '--trace', 'curl.trace', '--trace-time', - '--connect-timeout', '1' - ]) - else: - check_url = f'https://{self._domain}:{self._port}/' - r = curl.http_get(url=check_url, extra_args=[ - '--http3-only', '--trace', 'curl.trace', '--trace-time', - '--connect-timeout', '1' - ]) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: + xargs = [ + '--trace', 'curl.trace', '--trace-time', + '--connect-timeout', '1' + ] + if self.port_is_quic: + xargs.extend(['--http3-only']) + check_url = f'https://{self._domain}:{self.port}/' + r = curl.http_get(url=check_url, extra_args=xargs) if r.exit_code == 0: return True time.sleep(.1) @@ -201,28 +204,33 @@ def _mkpath(self, path): def _write_config(self): with open(self._conf_file, 'w') as fd: - fd.write('# nghttpx test config') - fd.write("\n".join([ - '# do we need something here?' - ])) + fd.write(textwrap.dedent("""\ + # nghttpx test config + # do we need something here? + """)) class NghttpxQuic(Nghttpx): - PORT_SPECS = { + PORT_SPECS: ClassVar[Dict[str, int]] = { 'nghttpx_https': socket.SOCK_STREAM, } def __init__(self, env: Env): super().__init__(env=env, name='nghttpx-quic', domain=env.domain1, cred_name=env.domain1) - self._https_port = env.https_port + self._https_port = 0 def initial_start(self): super().initial_start() def startup(ports: Dict[str, int]) -> bool: - self._port = ports['nghttpx_https'] + self._https_port = ports['nghttpx_https'] + if self.supports_h3(): + self._port = self.env.h3_port + self._port_is_quic = True + else: + self._port = self._https_port if self.start(): self.env.update_ports(ports) return True @@ -240,10 +248,10 @@ def start(self, wait_live=True): creds = self.env.get_credentials(self._cred_name) assert creds # convince pytype this is not None self._loaded_cred_name = self._cred_name - args = [self._cmd, f'--frontend=*,{self._port};tls'] + args = [self._cmd, f'--frontend=*,{self._https_port};tls'] if self.supports_h3(): args.extend([ - f'--frontend=*,{self.env.h3_port};quic', + f'--frontend=*,{self._port};quic', '--frontend-quic-early-data', ]) args.extend([ @@ -261,8 +269,8 @@ def start(self, wait_live=True): '--frontend-http3-max-connection-window-size=100M', # f'--frontend-quic-debug-log', ]) - ngerr = open(self._stderr, 'a') - self._process = subprocess.Popen(args=args, stderr=ngerr) + self._error_fd = open(self._stderr, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: return False return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) @@ -311,16 +319,16 @@ def start(self, wait_live=True): creds.pkey_file, creds.cert_file, ] - ngerr = open(self._stderr, 'a') - self._process = subprocess.Popen(args=args, stderr=ngerr) + self._error_fd = open(self._stderr, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: return False return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) def wait_dead(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.proxy_domain}:{self._port}/' r = curl.http_get(url=check_url) if r.exit_code != 0: @@ -332,8 +340,8 @@ def wait_dead(self, timeout: timedelta): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.proxy_domain}:{self._port}/' r = curl.http_get(url=check_url, extra_args=[ '--trace', 'curl.trace', '--trace-time' diff --git a/tests/http/testenv/ports.py b/tests/http/testenv/ports.py index f3d2348fa679..8f25daedcaf1 100644 --- a/tests/http/testenv/ports.py +++ b/tests/http/testenv/ports.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -27,8 +25,7 @@ import logging import os import socket -from collections.abc import Callable -from typing import Dict +from typing import Callable, Dict from filelock import FileLock @@ -39,13 +36,10 @@ def alloc_port_set(port_specs: Dict[str, int]) -> Dict[str, int]: socks = [] ports = {} for name, ptype in port_specs.items(): - try: - s = socket.socket(type=ptype) - s.bind(('127.0.0.1', 0)) - ports[name] = s.getsockname()[1] - socks.append(s) - except Exception as e: - raise e + s = socket.socket(type=ptype) + s.bind(('127.0.0.1', 0)) + ports[name] = s.getsockname()[1] + socks.append(s) for s in socks: s.close() return ports diff --git a/tests/http/testenv/sshd.py b/tests/http/testenv/sshd.py index e800ced17b67..7753e855c9f1 100644 --- a/tests/http/testenv/sshd.py +++ b/tests/http/testenv/sshd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -30,7 +28,7 @@ import stat import subprocess import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict from . import CurlClient @@ -74,6 +72,7 @@ def __init__(self, env: Env): ] self._user_key_files = [] self._user_pub_files = [] + self._error_fd = None self._process = None self.clear_logs() @@ -124,24 +123,21 @@ def mk_host_keys(self): for alg in self._key_algs: key_file = os.path.join(self._sshd_dir, f'ssh_host_{alg}_key') if not os.path.exists(key_file): - p = subprocess.run(args=[ + subprocess.run(args=[ self._keygen, '-q', '-N', '', '-t', alg, '-f', key_file - ], capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'error generating host key {key_file}: {p.returncode}') + ], capture_output=True, text=True, check=True) self._host_key_files.append(key_file) pub_file = f'{key_file}.pub' self._host_pub_files.append(pub_file) - pubkey = open(pub_file).read() + with open(pub_file) as fp: + pubkey = fp.read() # fd_known.write(f'[127.0.0.1]:{self.port} {pubkey}') fd_known.write(f'[{self.env.domain1.lower()}]:{self.port} {pubkey}') fd_unknown.write(f'dummy.invalid {pubkey}') # hash the known_hosts file, libssh requires it - p = subprocess.run(args=[ + subprocess.run(args=[ self._keygen, '-H', '-f', self._known_hosts - ], capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'error hashing {self._known_hosts}: {p.returncode}') + ], capture_output=True, text=True, check=True) def mk_user_keys(self): self._user_key_files = [] @@ -150,26 +146,32 @@ def mk_user_keys(self): for user in self._users: key_file = os.path.join(self._sshd_dir, f'id_{user}_user_{alg}_key') if not os.path.exists(key_file): - p = subprocess.run(args=[ + subprocess.run(args=[ self._keygen, '-q', '-N', '', '-t', alg, '-f', key_file - ], capture_output=True, text=True) - if p.returncode != 0: - raise RuntimeError(f'error generating user key {key_file}: {p.returncode}') + ], capture_output=True, text=True, check=True) self._user_key_files.append(key_file) self._user_pub_files.append(f'{key_file}.pub') with open(self._auth_keys, 'w') as fd: os.chmod(self._auth_keys, stat.S_IRUSR | stat.S_IWUSR) - pubkey = open(self._user_pub_files[0]).read() + with open(self._user_pub_files[0]) as fp: + pubkey = fp.read() fd.write(pubkey) + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._sshd_log) def dump_log(self): lines = ['>>--sshd log ----------------------------------------------\n'] - lines.extend(open(self._sshd_log)) + with open(self._sshd_log) as fd: + lines.extend(fd.readlines()) lines.extend(['>>--curl log ----------------------------------------------\n']) - lines.extend(open(os.path.join(self._tmp_dir, 'curl.stderr'))) + with open(os.path.join(self._tmp_dir, 'curl.stderr')) as fd: + lines.extend(fd.readlines()) lines.append('<<-------------------------------------------------------\n') return ''.join(lines) @@ -193,7 +195,7 @@ def stop(self, wait_dead=True): self._process.terminate() self._process.wait(timeout=2) self._process = None - return not wait_dead or True + self.close_log() return True def restart(self): @@ -231,8 +233,8 @@ def start(self, wait_live=True): run_env = os.environ.copy() # does not have any effect, sadly # run_env['HOME'] = f'{self._home_dir}' - procerr = open(self._sshd_log, 'a') - self._process = subprocess.Popen(args=args, stderr=procerr, env=run_env) + self._error_fd = open(self._sshd_log, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd, env=run_env) if self._process.returncode is not None: return False return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) @@ -240,8 +242,8 @@ def start(self, wait_live=True): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir, timeout=timeout.total_seconds()) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: r = curl.http_get(url=f'scp://{self.env.domain1}:{self._port}/{self.home_dir}/data', extra_args=[ '--insecure', diff --git a/tests/http/testenv/vsftpd.py b/tests/http/testenv/vsftpd.py index ace2884c7d35..cda57b182208 100644 --- a/tests/http/testenv/vsftpd.py +++ b/tests/http/testenv/vsftpd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -30,8 +28,8 @@ import socket import subprocess import time -from datetime import datetime, timedelta -from typing import Dict, List +from datetime import datetime, timedelta, timezone +from typing import Dict, List, Tuple from .curl import CurlClient, ExecResult from .env import Env @@ -68,6 +66,7 @@ def __init__(self, env: Env, with_ssl=False, ssl_implicit=False): self._conf_file = os.path.join(self._vsftpd_dir, 'test.conf') self._pid_file = os.path.join(self._vsftpd_dir, 'vsftpd.pid') self._error_log = os.path.join(self._vsftpd_dir, 'vsftpd.log') + self._error_fd = None self._process = None self.clear_logs() @@ -84,6 +83,11 @@ def docs_dir(self): def port(self) -> int: return self._port + def close_log(self): + if self._error_fd: + self._error_fd.close() + self._error_fd = None + def clear_logs(self): self._rmf(self._error_log) @@ -107,7 +111,9 @@ def stop(self, wait_dead=True): self._process.terminate() self._process.wait(timeout=2) self._process = None + self.close_log() return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) + self.close_log() return True def restart(self): @@ -138,16 +144,16 @@ def start(self, wait_live=True): self._cmd, f'{self._conf_file}', ] - procerr = open(self._error_log, 'a') - self._process = subprocess.Popen(args=args, stderr=procerr) + self._error_fd = open(self._error_log, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: return False return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) def wait_dead(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'{self._scheme}://{self.domain}:{self.port}/' r = curl.ftp_get(urls=[check_url], extra_args=['-v']) if r.exit_code != 0: @@ -159,8 +165,8 @@ def wait_dead(self, timeout: timedelta): def wait_live(self, timeout: timedelta): curl = CurlClient(env=self.env, run_dir=self._tmp_dir) - try_until = datetime.now() + timeout - while datetime.now() < try_until: + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: check_url = f'{self._scheme}://{self.domain}:{self.port}/' r = curl.ftp_get(urls=[check_url], extra_args=[ '--trace', 'curl-start.trace', '--trace-time' @@ -209,7 +215,7 @@ def _write_config(self): f'rsa_cert_file={creds.cert_file}', f'rsa_private_key_file={creds.pkey_file}', # require_ssl_reuse=YES means ctrl and data connection need to use the same session - 'require_ssl_reuse=NO', + 'require_ssl_reuse=YES', ]) if self._ssl_implicit: conf.extend([ @@ -218,6 +224,7 @@ def _write_config(self): with open(self._conf_file, 'w') as fd: fd.write("\n".join(conf)) - def get_data_ports(self, r: ExecResult) -> List[int]: - return [int(m.group(1)) for line in r.trace_lines if - (m := re.match(r'.*Established 2nd connection to .* \(\S+ port (\d+)\)', line))] + def get_data_ports(self, r: ExecResult) -> List[Tuple[int, int]]: + return [(int(m.group(1)), int(m.group(2))) for line in r.trace_lines if + (m := re.match(r'.*Established 2nd connection to .* ' + r'\(\S+ port (\d+)\) from \S+ port (\d+)', line))] diff --git a/tests/http/testenv/ws_4frames_server.py b/tests/http/testenv/ws_4frames_server.py new file mode 100755 index 000000000000..488fb5a9cfc8 --- /dev/null +++ b/tests/http/testenv/ws_4frames_server.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +# +import argparse +import asyncio +import logging + +import websockets + +MESSAGES_SMALL = [ + "Hello 1", + "Hello 2", + "Hello 3", + "Hello 4", +] + +MESSAGES_LARGE = [ + b"x" * 65536, + b"x" * 65536, + b"x" * 65536, + b"x" * 65536, +] + + +async def handler(websocket): + peer = websocket.remote_address + print(f"client from {peer[0]}:{peer[1]}", flush=True) + print("handshake complete", flush=True) + + msgs = MESSAGES_LARGE if websocket.request.path == '/large' else MESSAGES_SMALL + + await asyncio.sleep(0.1) + for index, payload in enumerate(msgs, start=1): + await websocket.send(payload) + print(f"sent frame {index}: {payload!r}", flush=True) + # await asyncio.sleep(0.2) + + # await asyncio.sleep(2.0) + print("server done", flush=True) + + +async def main(): + parser = argparse.ArgumentParser(prog='scorecard', description=""" + Run a websocket 4frames server. + """) + parser.add_argument("--port", type=int, + default=9876, help="port to listen on") + args = parser.parse_args() + + logging.basicConfig( + format="%(asctime)s %(message)s", + level=logging.DEBUG, + ) + + print(f"listening on ws://localhost:{args.port}", flush=True) + async with websockets.serve(handler, 'localhost', args.port): + await asyncio.Future() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/http/testenv/ws_echo_server.py b/tests/http/testenv/ws_echo_server.py index 99eaa628d239..a063b030799c 100755 --- a/tests/http/testenv/ws_echo_server.py +++ b/tests/http/testenv/ws_echo_server.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -26,6 +25,7 @@ # import argparse import asyncio +import contextlib import logging from websockets import server @@ -33,11 +33,10 @@ async def echo(websocket): - try: + # exception when websocket connection closed by client + with contextlib.suppress(ConnectionClosedError): async for message in websocket: await websocket.send(message) - except ConnectionClosedError: - pass async def run_server(port): diff --git a/tests/http2-server.pl b/tests/http2-server.pl index 3ab38803f174..0729a00593a5 100755 --- a/tests/http2-server.pl +++ b/tests/http2-server.pl @@ -110,7 +110,7 @@ my $certfile = abs_path("certs/$cert.pem"); my $keyfile = abs_path("certs/$cert.key"); -my $cmdline="$nghttpx --backend=$connect ". +my $cmdline = "$nghttpx --backend=$connect ". "--backend-keep-alive-timeout=500ms ". "--frontend=\"*,$listenport;no-tls\" ". "--frontend=\"*,$listenport2\" ". diff --git a/tests/http3-server.pl b/tests/http3-server.pl index 13cc49a9dff0..b86a6a58b388 100755 --- a/tests/http3-server.pl +++ b/tests/http3-server.pl @@ -110,7 +110,7 @@ my $certfile = abs_path("certs/$cert.pem"); my $keyfile = abs_path("certs/$cert.key"); -my $cmdline="$nghttpx --http2-proxy --backend=$connect ". +my $cmdline = "$nghttpx --http2-proxy --backend=$connect ". "--backend-keep-alive-timeout=500ms ". "--frontend=\"*,$listenport\" ". "--frontend=\"*,$listenport;quic\" ". diff --git a/tests/libtest/Makefile.am b/tests/libtest/Makefile.am index db7c9d741b3e..74eb150742c7 100644 --- a/tests/libtest/Makefile.am +++ b/tests/libtest/Makefile.am @@ -44,30 +44,35 @@ AM_CPPFLAGS = -I$(top_srcdir)/include \ include Makefile.inc EXTRA_DIST = CMakeLists.txt $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C) \ - test307.pl test610.pl test613.pl test1013.pl test1022.pl mk-lib1521.pl + mk-lib1521.pl \ + test1013.pl \ + test1022.pl \ + test307.pl \ + test610.pl \ + test613.pl CFLAGS += @CURL_CFLAG_EXTRAS@ # Prevent LIBS from being used for all link targets LIBS = $(BLANK_AT_MAKETIME) -if USE_CPPFLAG_CURL_STATICLIB -AM_CPPFLAGS += -DCURL_STATICLIB -endif if DEBUGBUILD AM_CPPFLAGS += -DDEBUGBUILD endif if USE_CPPFLAG_CURL_STATICLIB +AM_CPPFLAGS += -DCURL_STATICLIB curlx_c_lib = else # These are part of the libcurl static lib. Add them here when linking shared. curlx_c_lib = $(CURLX_C) endif + $(BUNDLE).c: $(top_srcdir)/scripts/mk-unity.pl Makefile.inc $(FIRST_C) $(UTILS_C) $(curlx_c_lib) $(TOOLX_C) $(TESTS_C) lib1521.c @PERL@ $(top_srcdir)/scripts/mk-unity.pl --include $(UTILS_C) $(curlx_c_lib) $(TOOLX_C) --test $(TESTS_C) lib1521.c > $(BUNDLE).c noinst_PROGRAMS = $(BUNDLE) +nodist_libtests_SOURCES = $(BUNDLE).c LDADD = $(top_builddir)/lib/libcurl.la @LIBCURL_PC_LIBS_PRIVATE@ CLEANFILES = $(BUNDLE).c lib1521.c diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 724636464aab..26c72e5672ec 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -36,6 +36,8 @@ UTILS_H = testutil.h testtrace.h unitcheck.h CURLX_C = \ ../../lib/curl_threads.c \ ../../lib/curlx/fopen.c \ + ../../lib/curlx/inet_ntop.c \ + ../../lib/curlx/inet_pton.c \ ../../lib/curlx/multibyte.c \ ../../lib/curlx/strcopy.c \ ../../lib/curlx/strerr.c \ @@ -52,6 +54,7 @@ TOOLX_C = \ # All libtest programs TESTS_C = \ + cli_ev_download.c \ cli_ftp_upload.c \ cli_h2_pausing.c \ cli_h2_serverpush.c \ @@ -61,59 +64,266 @@ TESTS_C = \ cli_tls_session_reuse.c \ cli_upload_pausing.c \ cli_ws_data.c \ + cli_ws_pause.c \ cli_ws_pingpong.c \ + cli_ws_write_err.c \ \ - lib500.c lib501.c lib502.c lib503.c lib504.c lib505.c lib506.c lib507.c \ - lib508.c lib509.c lib510.c lib511.c lib512.c lib513.c lib514.c lib515.c \ - lib516.c lib517.c lib518.c lib519.c lib520.c lib521.c lib523.c lib524.c \ - lib525.c lib526.c lib530.c \ - lib533.c lib536.c lib537.c lib539.c lib540.c lib541.c \ - lib542.c lib543.c lib544.c lib547.c lib549.c \ - lib552.c lib553.c lib554.c lib555.c lib556.c lib557.c lib558.c lib559.c \ - lib560.c lib562.c lib564.c lib566.c lib567.c \ - lib568.c lib569.c lib570.c lib571.c lib572.c lib573.c lib574.c lib575.c \ - lib576.c lib578.c lib579.c lib582.c lib583.c \ - lib586.c lib589.c lib590.c lib591.c \ - lib597.c lib598.c lib599.c \ + lib500.c \ + lib501.c \ + lib502.c \ + lib503.c \ + lib504.c \ + lib505.c \ + lib506.c \ + lib507.c \ + lib508.c \ + lib509.c \ + lib510.c \ + lib511.c \ + lib512.c \ + lib513.c \ + lib514.c \ + lib515.c \ + lib516.c \ + lib517.c \ + lib518.c \ + lib519.c \ + lib520.c \ + lib521.c \ + lib523.c \ + lib524.c \ + lib525.c \ + lib526.c \ + lib530.c \ + lib533.c \ + lib536.c \ + lib537.c \ + lib539.c \ + lib540.c \ + lib541.c \ + lib542.c \ + lib543.c \ + lib544.c \ + lib547.c \ + lib549.c \ + lib552.c \ + lib553.c \ + lib554.c \ + lib555.c \ + lib556.c \ + lib557.c \ + lib558.c \ + lib559.c \ + lib560.c \ + lib562.c \ + lib564.c \ + lib566.c \ + lib567.c \ + lib568.c \ + lib569.c \ + lib570.c \ + lib571.c \ + lib572.c \ + lib573.c \ + lib574.c \ + lib575.c \ + lib576.c \ + lib578.c \ + lib579.c \ + lib582.c \ + lib583.c \ + lib586.c \ + lib589.c \ + lib590.c \ + lib591.c \ + lib597.c \ + lib598.c \ + lib599.c \ lib643.c \ - lib650.c lib651.c lib652.c lib653.c lib654.c lib655.c lib658.c lib659.c \ - lib661.c lib666.c lib667.c lib668.c \ - lib670.c lib674.c lib676.c lib677.c lib678.c \ - lib694.c lib695.c \ - lib751.c lib753.c lib758.c \ + lib650.c \ + lib651.c \ + lib652.c \ + lib653.c \ + lib654.c \ + lib655.c \ + lib658.c \ + lib659.c \ + lib661.c \ + lib666.c \ + lib667.c \ + lib668.c \ + lib670.c \ + lib674.c \ + lib676.c \ + lib677.c \ + lib678.c \ + lib694.c \ + lib695.c \ + lib751.c \ + lib753.c \ lib757.c \ + lib758.c \ lib766.c \ lib1156.c \ - lib1301.c lib1308.c \ + lib1301.c \ + lib1308.c \ + lib1396.c \ + lib1398.c \ lib1485.c \ - lib1500.c lib1501.c lib1502.c lib1506.c \ - lib1507.c lib1508.c lib1509.c lib1510.c lib1511.c lib1512.c lib1513.c \ - lib1514.c lib1515.c lib1517.c lib1518.c lib1520.c \ - lib1522.c lib1523.c lib1525.c lib1526.c lib1527.c lib1528.c \ - lib1529.c lib1530.c lib1531.c lib1532.c lib1533.c lib1534.c lib1535.c \ - lib1536.c lib1537.c lib1538.c lib1540.c lib1541.c lib1542.c \ - lib1545.c lib1549.c lib1550.c lib1551.c \ - lib1552.c lib1553.c lib1554.c lib1555.c lib1556.c lib1557.c lib1558.c \ - lib1559.c lib1560.c lib1564.c lib1565.c \ - lib1567.c lib1568.c lib1569.c lib1571.c \ - lib1576.c lib1582.c lib1587.c lib1588.c \ - lib1591.c lib1592.c lib1593.c lib1594.c lib1597.c \ - lib1598.c lib1599.c \ + lib1500.c \ + lib1501.c \ + lib1502.c \ + lib1506.c \ + lib1507.c \ + lib1508.c \ + lib1509.c \ + lib1510.c \ + lib1511.c \ + lib1512.c \ + lib1513.c \ + lib1514.c \ + lib1515.c \ + lib1517.c \ + lib1518.c \ + lib1520.c \ + lib1522.c \ + lib1523.c \ + lib1525.c \ + lib1526.c \ + lib1527.c \ + lib1528.c \ + lib1529.c \ + lib1530.c \ + lib1531.c \ + lib1532.c \ + lib1533.c \ + lib1534.c \ + lib1535.c \ + lib1536.c \ + lib1537.c \ + lib1538.c \ + lib1540.c \ + lib1541.c \ + lib1542.c \ + lib1545.c \ + lib1549.c \ + lib1550.c \ + lib1551.c \ + lib1552.c \ + lib1553.c \ + lib1554.c \ + lib1555.c \ + lib1556.c \ + lib1557.c \ + lib1558.c \ + lib1559.c \ + lib1560.c \ + lib1564.c \ + lib1565.c \ + lib1567.c \ + lib1568.c \ + lib1569.c \ + lib1571.c \ + lib1576.c \ + lib1582.c \ + lib1587.c \ + lib1588.c \ + lib1589.c \ + lib1591.c \ + lib1592.c \ + lib1593.c \ + lib1594.c \ + lib1597.c \ + lib1598.c \ + lib1599.c \ + lib1647.c \ + lib1648.c \ + lib1649.c \ lib1662.c \ - lib1900.c lib1901.c lib1902.c lib1903.c lib1905.c lib1906.c lib1907.c \ - lib1908.c lib1910.c lib1911.c lib1912.c lib1913.c \ - lib1915.c lib1916.c lib1918.c lib1919.c lib1920.c \ - lib1933.c lib1934.c lib1935.c lib1936.c lib1937.c lib1938.c lib1939.c \ - lib1940.c lib1945.c \ - lib1947.c lib1948.c \ - lib1955.c lib1956.c lib1957.c lib1958.c lib1959.c lib1960.c \ - lib1964.c lib1965.c lib1970.c \ - lib1971.c lib1972.c lib1973.c lib1974.c lib1975.c lib1977.c lib1978.c \ - lib2023.c lib2032.c lib2082.c \ - lib2301.c lib2302.c lib2304.c lib2306.c lib2308.c lib2309.c \ - lib2402.c lib2404.c lib2405.c \ - lib2502.c lib2504.c lib2505.c lib2506.c \ + lib1678.c \ + lib1686.c \ + lib1900.c \ + lib1901.c \ + lib1902.c \ + lib1903.c \ + lib1905.c \ + lib1906.c \ + lib1907.c \ + lib1908.c \ + lib1910.c \ + lib1911.c \ + lib1912.c \ + lib1913.c \ + lib1915.c \ + lib1916.c \ + lib1918.c \ + lib1919.c \ + lib1920.c \ + lib1921.c \ + lib1922.c \ + lib1933.c \ + lib1934.c \ + lib1935.c \ + lib1936.c \ + lib1937.c \ + lib1938.c \ + lib1939.c \ + lib1940.c \ + lib1945.c \ + lib1947.c \ + lib1948.c \ + lib1955.c \ + lib1956.c \ + lib1957.c \ + lib1958.c \ + lib1959.c \ + lib1960.c \ + lib1964.c \ + lib1965.c \ + lib1967.c \ + lib1970.c \ + lib1971.c \ + lib1972.c \ + lib1973.c \ + lib1974.c \ + lib1975.c \ + lib1977.c \ + lib1978.c \ + lib1985.c \ + lib2023.c \ + lib2032.c \ + lib2082.c \ + lib2118.c \ + lib2301.c \ + lib2302.c \ + lib2304.c \ + lib2306.c \ + lib2308.c \ + lib2309.c \ + lib2397.c \ + lib2402.c \ + lib2404.c \ + lib2405.c \ + lib2412.c \ + lib2414.c \ + lib2502.c \ + lib2504.c \ + lib2505.c \ + lib2506.c \ lib2700.c \ - lib3010.c lib3025.c lib3026.c lib3027.c lib3033.c lib3034.c \ - lib3100.c lib3101.c lib3102.c lib3103.c lib3104.c lib3105.c \ - lib3207.c lib3208.c + lib3010.c \ + lib3025.c \ + lib3026.c \ + lib3027.c \ + lib3033.c \ + lib3034.c \ + lib3100.c \ + lib3101.c \ + lib3102.c \ + lib3103.c \ + lib3104.c \ + lib3105.c \ + lib3207.c \ + lib3208.c \ + lib5000.c \ + lib5004.c diff --git a/tests/libtest/cli_ev_download.c b/tests/libtest/cli_ev_download.c new file mode 100644 index 000000000000..e3ca6ad406a7 --- /dev/null +++ b/tests/libtest/cli_ev_download.c @@ -0,0 +1,313 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* An event-based download client, driving transfers purely via + * curl_multi_socket_action() with its own socket/timer callbacks, the + * way an event-based application does. It runs its transfers serially + * with CURLOPT_FORBID_REUSE, so every transfer's connection enters + * shutdown when done, and reports whether the connection sockets + * remain under event supervision until their shutdown finished. */ + +#define EV_DL_MAX_SOCKS 8 + +struct ev_dl_ctx { + CURLM *multi; + struct curltime timer_started; + curl_socket_t fds[EV_DL_MAX_SOCKS]; + int actions[EV_DL_MAX_SOCKS]; + size_t nsocks; + long timer_ms; /* last timeout set, -1 for none */ +}; + +static int ev_dl_socket_cb(CURL *easy, curl_socket_t fd, int what, + void *clientp, void *socketp) +{ + struct ev_dl_ctx *ctx = clientp; + size_t i; + + (void)easy; + (void)socketp; + for(i = 0; i < ctx->nsocks; ++i) { + if(ctx->fds[i] == fd) + break; + } + if(what == CURL_POLL_REMOVE) { + if(i < ctx->nsocks) { + ctx->nsocks--; + ctx->fds[i] = ctx->fds[ctx->nsocks]; + ctx->actions[i] = ctx->actions[ctx->nsocks]; + } + } + else { + if(i == ctx->nsocks) { + if(ctx->nsocks >= EV_DL_MAX_SOCKS) { + curl_mfprintf(stderr, "[ev] too many sockets to track\n"); + return -1; + } + ctx->nsocks++; + } + ctx->fds[i] = fd; + ctx->actions[i] = what; + } + curl_mfprintf(stderr, "[ev] socket fd=%ld what=%d, tracking %zu\n", + (long)fd, what, ctx->nsocks); + return 0; +} + +static int ev_dl_timer_cb(CURLM *multi, long timeout_ms, void *clientp) +{ + struct ev_dl_ctx *ctx = clientp; + + (void)multi; + ctx->timer_ms = timeout_ms; + if(timeout_ms >= 0) + ctx->timer_started = curlx_now(); + return 0; +} + +static size_t ev_dl_discard_cb(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + (void)ptr; + (void)userdata; + return size * nmemb; +} + +/* wait for socket events or the timer, then feed libcurl. */ +static CURLMcode ev_dl_roundtrip(struct ev_dl_ctx *ctx) +{ + curl_socket_t fds[EV_DL_MAX_SOCKS]; + int actions[EV_DL_MAX_SOCKS]; + struct timeval tv; + fd_set rd, wr; + size_t i, nsocks; + long wait_ms = 100; + int maxfd = -1; + int running = 0; + CURLMcode mres = CURLM_OK; + + FD_ZERO(&rd); + FD_ZERO(&wr); + for(i = 0; i < ctx->nsocks; ++i) { + if(ctx->actions[i] & CURL_POLL_IN) + FD_SET(ctx->fds[i], &rd); + if(ctx->actions[i] & CURL_POLL_OUT) + FD_SET(ctx->fds[i], &wr); + if((int)ctx->fds[i] > maxfd) + maxfd = (int)ctx->fds[i]; + } + if(ctx->timer_ms >= 0) { + long left = ctx->timer_ms - + (long)curlx_timediff_ms(curlx_now(), ctx->timer_started); + if(left < 0) + left = 0; + if(left < wait_ms) + wait_ms = left; + } + tv.tv_sec = wait_ms / 1000; + tv.tv_usec = (int)(wait_ms % 1000) * 1000; + select_wrapper(maxfd + 1, &rd, &wr, NULL, &tv); + + /* the callbacks change our socket table while we dispatch */ + nsocks = ctx->nsocks; + memcpy(fds, ctx->fds, sizeof(fds)); + memcpy(actions, ctx->actions, sizeof(actions)); + for(i = 0; i < nsocks; ++i) { + int ev_bitmask = 0; + if((actions[i] & CURL_POLL_IN) && FD_ISSET(fds[i], &rd)) + ev_bitmask |= CURL_CSELECT_IN; + if((actions[i] & CURL_POLL_OUT) && FD_ISSET(fds[i], &wr)) + ev_bitmask |= CURL_CSELECT_OUT; + if(ev_bitmask) { + mres = curl_multi_socket_action(ctx->multi, fds[i], ev_bitmask, + &running); + if(mres) + return mres; + } + } + + if((ctx->timer_ms >= 0) && + (curlx_timediff_ms(curlx_now(), ctx->timer_started) >= ctx->timer_ms)) { + ctx->timer_ms = -1; + mres = curl_multi_socket_action(ctx->multi, CURL_SOCKET_TIMEOUT, 0, + &running); + } + return mres; +} + +static void usage_ev_download(const char *msg) +{ + if(msg) + curl_mfprintf(stderr, "%s\n", msg); + curl_mfprintf(stderr, + "usage: [options] url\n" + " event-based downloads with connection reuse forbidden\n" + " -n number of transfers to do serially (default: 5)\n" + " -C certfile for CA verification\n" + ); +} + +static CURLcode test_cli_ev_download(const char *URL) +{ + struct ev_dl_ctx ctx; + CURLM *multi = NULL; + char *cafile = NULL; + const char *url; + size_t transfer_count = 5; + size_t i; + int watched = 0; + struct curltime started; + CURLcode result = CURLE_OK; + int ch; + + (void)URL; + memset(&ctx, 0, sizeof(ctx)); + ctx.timer_ms = -1; + + while((ch = cgetopt(test_argc, test_argv, "hn:C:")) != -1) { + switch(ch) { + case 'h': + usage_ev_download(NULL); + result = (CURLcode)2; + goto optcleanup; + case 'n': { + const char *opt = coptarg; + curl_off_t num; + if(!curlx_str_number(&opt, &num, LONG_MAX)) + transfer_count = (size_t)num; + break; + } + case 'C': + curlx_free(cafile); + cafile = curlx_strdup(coptarg); + break; + default: + usage_ev_download("invalid option"); + result = (CURLcode)1; + goto optcleanup; + } + } + test_argc -= coptind; + test_argv += coptind; + + if(test_argc != 1) { + usage_ev_download("not enough arguments"); + result = (CURLcode)2; + goto optcleanup; + } + url = test_argv[0]; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + result = (CURLcode)3; + goto optcleanup; + } + curl_global_trace("ids,time"); + + multi = curl_multi_init(); + if(!multi) { + curl_mfprintf(stderr, "curl_multi_init() failed\n"); + result = (CURLcode)3; + goto cleanup; + } + ctx.multi = multi; + curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, ev_dl_socket_cb); + curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, &ctx); + curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, ev_dl_timer_cb); + curl_multi_setopt(multi, CURLMOPT_TIMERDATA, &ctx); + + for(i = 0; i < transfer_count && !result; ++i) { + CURL *easy = curl_easy_init(); + int done = 0; + int running = 0; + + if(!easy) { + curl_mfprintf(stderr, "[t-%zu] FAILED setup\n", i); + result = (CURLcode)1; + goto cleanup; + } + curl_easy_setopt(easy, CURLOPT_URL, url); + curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, ev_dl_discard_cb); + curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1L); + curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + if(cafile) + curl_easy_setopt(easy, CURLOPT_CAINFO, cafile); + + curl_multi_add_handle(multi, easy); + curl_mfprintf(stderr, "[t-%zu] STARTED\n", i); + curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running); + + started = curlx_now(); + while(!done) { + struct CURLMsg *m; + int msgq = 0; + + if(ev_dl_roundtrip(&ctx)) { + curl_mfprintf(stderr, "[t-%zu] multi failure\n", i); + result = (CURLcode)1; + goto cleanup; + } + m = curl_multi_info_read(multi, &msgq); + if(m && (m->msg == CURLMSG_DONE)) { + done = 1; + result = m->data.result; + curl_mfprintf(stderr, "[t-%zu] FINISHED with result %d\n", + i, (int)result); + } + else if(curlx_timediff_ms(curlx_now(), started) > (timediff_t)30000) { + curl_mfprintf(stderr, "[t-%zu] transfer timed out\n", i); + result = (CURLcode)1; + goto cleanup; + } + } + curl_multi_remove_handle(multi, easy); + curl_easy_cleanup(easy); + + /* The transfer is over and its connection may not be reused. A + * graceful shutdown that could not finish right away needs its + * socket watched by us, or it can never make progress. */ + curl_mfprintf(stderr, "[t-%zu] sockets tracked after done: %zu\n", + i, ctx.nsocks); + if(ctx.nsocks) + watched++; + } + + /* drive the remaining shutdowns via socket events */ + started = curlx_now(); + while(ctx.nsocks && + (curlx_timediff_ms(curlx_now(), started) < (timediff_t)5000)) { + if(ev_dl_roundtrip(&ctx)) + break; + } + curl_mfprintf(stderr, "[ev] final: watched=%d socks_left=%zu\n", + watched, ctx.nsocks); + +cleanup: + curl_multi_cleanup(multi); + curl_global_cleanup(); +optcleanup: + curlx_free(cafile); + return result; +} diff --git a/tests/libtest/cli_ftp_upload.c b/tests/libtest/cli_ftp_upload.c index 32835bc12329..7e05807b5c73 100644 --- a/tests/libtest/cli_ftp_upload.c +++ b/tests/libtest/cli_ftp_upload.c @@ -71,7 +71,7 @@ static CURLcode test_cli_ftp_upload(const char *URL) CURL *curl_handle; int running_handles = 0; int max_fd = -1; - struct timeval timeout = { 1, 0 }; + struct timeval timeout; fd_set fdread; fd_set fdwrite; fd_set fdexcep; @@ -82,6 +82,9 @@ static CURLcode test_cli_ftp_upload(const char *URL) CURLcode result = CURLE_FAILED_INIT; curl_off_t uploadsize = -1; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + (void)URL; while((ch = cgetopt(test_argc, test_argv, "r:")) != -1) { switch(ch) { @@ -169,7 +172,7 @@ static CURLcode test_cli_ftp_upload(const char *URL) curl_global_cleanup(); curl_slist_free_all(host); - curl_mfprintf(stderr, "transfer result: %d\n", result); + curl_mfprintf(stderr, "transfer result: %d\n", (int)result); return result; #else /* !CURL_DISABLE_FTP */ (void)URL; diff --git a/tests/libtest/cli_h2_pausing.c b/tests/libtest/cli_h2_pausing.c index ac57b5c5f8aa..19a2760245dc 100644 --- a/tests/libtest/cli_h2_pausing.c +++ b/tests/libtest/cli_h2_pausing.c @@ -64,7 +64,7 @@ static size_t cb(char *data, size_t size, size_t nmemb, void *clientp) ++handle->paused; curl_mfprintf(stderr, "INFO: [%zu] write, PAUSING %d time on %zu bytes\n", handle->idx, handle->paused, realsize); - assert(handle->paused == 1); + DEBUGASSERT(handle->paused == 1); return CURL_WRITEFUNC_PAUSE; } if(handle->fail_write) { @@ -265,7 +265,7 @@ static CURLcode test_cli_h2_pausing(const char *URL) curl_mfprintf(stderr, "ERROR: [%zu] done, paused=%d, " "resumed=%d, result %d - wtf?\n", i, handles[i].paused, - handles[i].resumed, msg->data.result); + handles[i].resumed, (int)msg->data.result); result = (CURLcode)1; goto cleanup; } diff --git a/tests/libtest/cli_h2_upgrade_extreme.c b/tests/libtest/cli_h2_upgrade_extreme.c index 62aa0bc36145..9df8640cffec 100644 --- a/tests/libtest/cli_h2_upgrade_extreme.c +++ b/tests/libtest/cli_h2_upgrade_extreme.c @@ -126,7 +126,7 @@ static CURLcode test_cli_h2_upgrade_extreme(const char *URL) } else if(msg->data.result) { curl_mfprintf(stderr, "transfer #%" CURL_FORMAT_CURL_OFF_T - ": failed with %d\n", xfer_id, msg->data.result); + ": failed with %d\n", xfer_id, (int)msg->data.result); goto cleanup; } else if(status != 206) { diff --git a/tests/libtest/cli_hx_download.c b/tests/libtest/cli_hx_download.c index fca5a7dec110..6afa53914282 100644 --- a/tests/libtest/cli_hx_download.c +++ b/tests/libtest/cli_hx_download.c @@ -147,16 +147,16 @@ static int my_progress_d_cb(void *userdata, result = curl_easy_getinfo(t->curl, CURLINFO_TLS_SSL_PTR, &tls); if(result) { curl_mfprintf(stderr, "[t-%zu] info CURLINFO_TLS_SSL_PTR failed: %d\n", - t->idx, result); - assert(0); + t->idx, (int)result); + DEBUGASSERT(0); } else { switch(tls->backend) { #ifdef USE_OPENSSL case CURLSSLBACKEND_OPENSSL: { const char *version = SSL_get_version((SSL *)tls->internals); - assert(version); - assert(strcmp(version, "unknown")); + DEBUGASSERT(version); + DEBUGASSERT(strcmp(version, "unknown")); curl_mfprintf(stderr, "[t-%zu] info OpenSSL using %s\n", t->idx, version); break; @@ -165,8 +165,8 @@ static int my_progress_d_cb(void *userdata, #ifdef USE_WOLFSSL case CURLSSLBACKEND_WOLFSSL: { const char *version = wolfSSL_get_version((WOLFSSL *)tls->internals); - assert(version); - assert(strcmp(version, "unknown")); + DEBUGASSERT(version); + DEBUGASSERT(strcmp(version, "unknown")); curl_mfprintf(stderr, "[t-%zu] info wolfSSL using %s\n", t->idx, version); break; @@ -176,7 +176,7 @@ static int my_progress_d_cb(void *userdata, case CURLSSLBACKEND_GNUTLS: { gnutls_protocol_t v = gnutls_protocol_get_version( (gnutls_session_t)tls->internals); - assert(v); + DEBUGASSERT(v); curl_mfprintf(stderr, "[t-%zu] info GnuTLS using %s\n", t->idx, gnutls_protocol_get_name(v)); break; @@ -186,8 +186,8 @@ static int my_progress_d_cb(void *userdata, case CURLSSLBACKEND_MBEDTLS: { const char *version = mbedtls_ssl_get_version((mbedtls_ssl_context *)tls->internals); - assert(version); - assert(strcmp(version, "unknown")); + DEBUGASSERT(version); + DEBUGASSERT(strcmp(version, "unknown")); curl_mfprintf(stderr, "[t-%zu] info mbedTLS using %s\n", t->idx, version); break; @@ -197,9 +197,9 @@ static int my_progress_d_cb(void *userdata, case CURLSSLBACKEND_RUSTLS: { int v = rustls_connection_get_protocol_version( (struct rustls_connection *)tls->internals); - assert(v); + DEBUGASSERT(v); curl_mfprintf(stderr, "[t-%zu] info rustls TLS version 0x%x\n", - t->idx, v); + t->idx, (unsigned int)v); break; } #endif @@ -211,16 +211,16 @@ static int my_progress_d_cb(void *userdata, sspi_status = QueryContextAttributes(ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info); - assert(sspi_status == SEC_E_OK); + DEBUGASSERT(sspi_status == SEC_E_OK); (void)sspi_status; curl_mfprintf(stderr, "[t-%zu] info Schannel TLS version 0x%08lx\n", - t->idx, info.dwProtocol); + t->idx, (unsigned long)info.dwProtocol); break; } #endif default: curl_mfprintf(stderr, "[t-%zu] info SSL_PTR backend=%d, ptr=%p\n", - t->idx, tls->backend, tls->internals); + t->idx, (int)tls->backend, tls->internals); break; } } @@ -285,9 +285,10 @@ static void usage_hx_download(const char *msg) " -M number max concurrent connections to a host\n" " -P number pause transfer after `number` response bytes\n" " -r :: resolve information\n" + " -S share connections between easy handles\n" " -T number max concurrent connections total\n" " -V http_version (http/1.1, h2, h3) http version to use\n" - " -6 use ipv6 for resolving the FIRST url\n" + " -6 use IPv6 for resolving the FIRST URL\n" ); } @@ -314,13 +315,14 @@ static CURLcode test_cli_hx_download(const char *URL) size_t max_host_conns = 0; size_t max_total_conns = 0; int fresh_connect = 0; + int share_connect = 0; char *cafile = NULL; bool first_ipv6 = FALSE; CURLcode result = CURLE_OK; (void)URL; - while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:6")) + while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:ST:V:6")) != -1) { const char *opt = coptarg; curl_off_t num; @@ -373,6 +375,9 @@ static CURLcode test_cli_hx_download(const char *URL) curlx_free(resolve); resolve = curlx_strdup(coptarg); break; + case 'S': + share_connect = 1; + break; case 'T': if(!curlx_str_number(&opt, &num, LONG_MAX)) max_total_conns = (size_t)num; @@ -430,9 +435,8 @@ static CURLcode test_cli_hx_download(const char *URL) curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); -#if 0 - curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); -#endif + if(share_connect) + curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL); curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS); @@ -502,7 +506,7 @@ static CURLcode test_cli_hx_download(const char *URL) t->done = 1; t->result = m->data.result; curl_mfprintf(stderr, "[t-%zu] FINISHED with result %d\n", - t->idx, t->result); + t->idx, (int)t->result); if(use_earlydata) { curl_off_t sent; curl_easy_getinfo(easy, CURLINFO_EARLYDATA_SENT_T, &sent); @@ -587,8 +591,8 @@ static CURLcode test_cli_hx_download(const char *URL) } if(t->result) result = t->result; - else /* on success we expect ssl to have been checked */ - assert(t->checked_ssl); + else /* on success we expect SSL to have been checked */ + DEBUGASSERT(t->checked_ssl); } curlx_free(transfer_d); } diff --git a/tests/libtest/cli_hx_upload.c b/tests/libtest/cli_hx_upload.c index d0434a2eab1b..64c3bbb3a508 100644 --- a/tests/libtest/cli_hx_upload.c +++ b/tests/libtest/cli_hx_upload.c @@ -395,7 +395,7 @@ static CURLcode test_cli_hx_upload(const char *URL) curl_mfprintf(stderr, "[t-%zu] STARTING\n", t->idx); rc = curl_easy_perform(curl); - curl_mfprintf(stderr, "[t-%zu] DONE -> %d\n", t->idx, rc); + curl_mfprintf(stderr, "[t-%zu] DONE -> %d\n", t->idx, (int)rc); t->curl = NULL; curl_easy_reset(curl); } @@ -448,7 +448,7 @@ static CURLcode test_cli_hx_upload(const char *URL) t->done = 1; curl_mfprintf(stderr, "[t-%zu] FINISHED, " "result=%d, response=%ld\n", - t->idx, m->data.result, res_status); + t->idx, (int)m->data.result, res_status); if(use_earlydata) { curl_off_t sent; curl_easy_getinfo(easy, CURLINFO_EARLYDATA_SENT_T, &sent); diff --git a/tests/libtest/cli_tls_session_reuse.c b/tests/libtest/cli_tls_session_reuse.c index 031d4cfc2ac6..ba97485a96c2 100644 --- a/tests/libtest/cli_tls_session_reuse.c +++ b/tests/libtest/cli_tls_session_reuse.c @@ -209,7 +209,7 @@ static CURLcode test_cli_tls_session_reuse(const char *URL) } else if(msg->data.result) { curl_mfprintf(stderr, "transfer #%" CURL_FORMAT_CURL_OFF_T - ": failed with %d\n", xfer_id, msg->data.result); + ": failed with %d\n", xfer_id, (int)msg->data.result); goto cleanup; } else if(status != 200) { diff --git a/tests/libtest/cli_upload_pausing.c b/tests/libtest/cli_upload_pausing.c index c52117bf7887..228304368a32 100644 --- a/tests/libtest/cli_upload_pausing.c +++ b/tests/libtest/cli_upload_pausing.c @@ -169,12 +169,12 @@ static CURLcode test_cli_upload_pausing(const char *URL) /* We want to use our own read function. */ curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); - /* It will help us to continue the read function. */ + /* It helps us to continue the read function. */ curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, curl); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); - /* It will help us to ensure that keepalive does not help. */ + /* It helps us to ensure that keepalive does not help. */ curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 1L); diff --git a/tests/libtest/cli_ws_data.c b/tests/libtest/cli_ws_data.c index 856d41ba1f26..20d15c89d8ae 100644 --- a/tests/libtest/cli_ws_data.c +++ b/tests/libtest/cli_ws_data.c @@ -37,13 +37,13 @@ static CURLcode test_ws_data_m2_check_recv(const struct curl_ws_frame *frame, if(frame->flags & CURLWS_CLOSE) { curl_mfprintf(stderr, "recv_data: unexpected CLOSE frame from server, " "got %zu bytes, offset=%zu, rflags %x\n", - nread, r_offset, frame->flags); + nread, r_offset, (unsigned int)frame->flags); return CURLE_RECV_ERROR; } if(!r_offset && !(frame->flags & CURLWS_BINARY)) { curl_mfprintf(stderr, "recv_data: wrong frame, got %zu bytes, offset=%zu, " "rflags %x\n", - nread, r_offset, frame->flags); + nread, r_offset, (unsigned int)frame->flags); return CURLE_RECV_ERROR; } if(frame->offset != (curl_off_t)r_offset) { @@ -104,7 +104,7 @@ static CURLcode test_ws_data_m2_echo(const char *url, curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ result = curl_easy_perform(curl); - curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", (int)result); if(result != CURLE_OK) goto out; @@ -120,8 +120,8 @@ static CURLcode test_ws_data_m2_echo(const char *url, sblock = (result == CURLE_AGAIN); if(!result || (result == CURLE_AGAIN)) { curl_mfprintf(stderr, "curl_ws_send(len=%zu) -> %d, " - "%zu (%" CURL_FORMAT_CURL_OFF_T "/%zu)\n", - slen, result, nwritten, (curl_off_t)(len - slen), len); + "%zu (%" CURL_FORMAT_CURL_OFF_T "/%zu)\n", slen, + (int)result, nwritten, (curl_off_t)(len - slen), len); sbuf += nwritten; slen -= nwritten; } @@ -140,8 +140,8 @@ static CURLcode test_ws_data_m2_echo(const char *url, &nread, &frame); if(!result || (result == CURLE_AGAIN)) { rblock = (result == CURLE_AGAIN); - curl_mfprintf(stderr, "curl_ws_recv(len=%zu) -> %d, %zu (%ld/%zu) " - "\n", rlen, result, nread, (long)(len - rlen), len); + curl_mfprintf(stderr, "curl_ws_recv(len=%zu) -> %d, %zu (%ld/%zu)\n", + rlen, (int)result, nread, (long)(len - rlen), len); if(!result) { result = test_ws_data_m2_check_recv(frame, len - rlen, nread, len); if(result) diff --git a/tests/libtest/cli_ws_pause.c b/tests/libtest/cli_ws_pause.c new file mode 100644 index 000000000000..b42331400324 --- /dev/null +++ b/tests/libtest/cli_ws_pause.c @@ -0,0 +1,171 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#include "testtrace.h" + +#ifndef CURL_DISABLE_WEBSOCKETS + +struct test_ws_pause_ctx { + CURL *easy; + int callback_count; + int paused; + int frames; + int errors; + int closed; +}; + +static size_t test_ws_pause_write_cb(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct test_ws_pause_ctx *ctx = userdata; + size_t nbytes = size * nmemb; + const struct curl_ws_frame *meta = curl_ws_meta(ctx->easy); + + ctx->callback_count++; + if(!meta) { + curl_mfprintf(stderr, "write_cb: ERROR call #%d with meta=NULL\n", + ctx->callback_count); + ++ctx->errors; + return CURL_WRITEFUNC_ERROR; + } + + ++ctx->frames; + if(meta->len < nbytes) { + curl_mfprintf(stderr, "write_cb: ERROR call #%d more data than current " + "frame, FRAME[flags=0x%x age=%d offset=%" FMT_OFF_T + " bytesleft=%" FMT_OFF_T " len=%zu], bytes=%zu\n", + ctx->callback_count, + (unsigned int)meta->flags, meta->age, + meta->offset, meta->bytesleft, meta->len, nbytes); + ++ctx->errors; + return CURL_WRITEFUNC_ERROR; + } + + if(meta->flags == 0x1) { /* TEXT frame */ + curl_mfprintf(stderr, "write_cb: call #%d FRAME[TEXT age=%d offset=%" + FMT_OFF_T " bytesleft=%" FMT_OFF_T " len=%zu] '%.*s'\n", + ctx->callback_count, meta->age, + meta->offset, meta->bytesleft, meta->len, + (int)nbytes, ptr); + } + else if(meta->flags == 0x8) { + curl_mfprintf(stderr, "write_cb: call #%d FRAME[CLOSE age=%d offset=%" + FMT_OFF_T " bytesleft=%" FMT_OFF_T " len=%zu] bytes=%zu\n", + ctx->callback_count, meta->age, + meta->offset, meta->bytesleft, meta->len, nbytes); + ctx->closed = TRUE; + } + else { + curl_mfprintf(stderr, "write_cb: call #%d FRAME[flags=0x%x age=%d offset=%" + FMT_OFF_T " bytesleft=%" FMT_OFF_T " len=%zu\n", + ctx->callback_count, + (unsigned int)meta->flags, meta->age, + meta->offset, meta->bytesleft, meta->len); + } + + if(ctx->callback_count == 1 || ctx->callback_count == 3) { + ctx->paused = 1; + curl_mfprintf(stderr, "write_cb: call #%d PAUSING\n", ctx->callback_count); + return CURL_WRITEFUNC_PAUSE; + } + return nbytes; +} +#endif /* CURL_DISABLE_WEBSOCKETS */ + +static CURLcode test_cli_ws_pause(const char *URL) +{ +#ifndef CURL_DISABLE_WEBSOCKETS + struct test_ws_pause_ctx ctx; + CURLM *multi; + int still_running = 0; + int msgs_left = 0; + int done = 0; + + memset(&ctx, 0, sizeof(ctx)); + setbuf(stdout, NULL); + + curl_global_init(CURL_GLOBAL_ALL); + + ctx.easy = curl_easy_init(); + multi = curl_multi_init(); + if(!ctx.easy || !multi) { + curl_mfprintf(stderr, "main: ERROR creating easy/multi\n"); + ctx.errors = 1; + goto out; + } + + curl_easy_setopt(ctx.easy, CURLOPT_URL, URL); + curl_easy_setopt(ctx.easy, CURLOPT_WRITEFUNCTION, test_ws_pause_write_cb); + curl_easy_setopt(ctx.easy, CURLOPT_WRITEDATA, &ctx); + curl_easy_setopt(ctx.easy, CURLOPT_VERBOSE, 1L); + + curl_multi_add_handle(multi, ctx.easy); + curl_multi_perform(multi, &still_running); + + while(still_running && !ctx.closed && !ctx.errors) { + + if(ctx.paused) { + curl_mfprintf(stderr, "main: wait and UNPAUSE\n"); + curlx_wait_ms(500); + ctx.paused = 0; + curl_easy_pause(ctx.easy, CURLPAUSE_CONT); + } + + curl_mfprintf(stderr, "main: poll\n"); + curl_multi_poll(multi, NULL, 0, 100, NULL); + curl_mfprintf(stderr, "main: perform\n"); + curl_multi_perform(multi, &still_running); + + while(!done) { + CURLMsg *msg = curl_multi_info_read(multi, &msgs_left); + if(!msg) + break; + if(msg->msg == CURLMSG_DONE) { + curl_mfprintf(stderr, "main: done result=%d (%s)\n", + (int)msg->data.result, + curl_easy_strerror(msg->data.result)); + done = 1; + } + } + } + +out: + if(ctx.easy) { + if(multi) + curl_multi_remove_handle(multi, ctx.easy); + curl_easy_cleanup(ctx.easy); + } + if(multi) + curl_multi_cleanup(multi); + curl_global_cleanup(); + + return ctx.errors ? CURLE_WRITE_ERROR : CURLE_OK; + +#else /* !CURL_DISABLE_WEBSOCKETS */ + (void)URL; + curl_mfprintf(stderr, "WebSockets not enabled in libcurl\n"); + return (CURLcode)1; +#endif /* CURL_DISABLE_WEBSOCKETS */ +} diff --git a/tests/libtest/cli_ws_pingpong.c b/tests/libtest/cli_ws_pingpong.c index 2ed12c8cab5a..32961da72123 100644 --- a/tests/libtest/cli_ws_pingpong.c +++ b/tests/libtest/cli_ws_pingpong.c @@ -78,7 +78,7 @@ static CURLcode test_cli_ws_pingpong(const char *URL) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ result = curl_easy_perform(curl); - curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", (int)result); if(result == CURLE_OK) result = pingpong(curl, payload); diff --git a/tests/libtest/cli_ws_write_err.c b/tests/libtest/cli_ws_write_err.c new file mode 100644 index 000000000000..7018224ff686 --- /dev/null +++ b/tests/libtest/cli_ws_write_err.c @@ -0,0 +1,136 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#include "testtrace.h" + +#ifndef CURL_DISABLE_WEBSOCKETS + +struct test_ws_write_err_ctx { + CURL *easy; + int callback_count; + int errors; + int closed; +}; + +static size_t test_ws_write_err_write_cb(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct test_ws_write_err_ctx *ctx = userdata; + size_t nbytes = size * nmemb; + const struct curl_ws_frame *meta = curl_ws_meta(ctx->easy); + + (void)ptr; + ctx->callback_count++; + if(!meta) { + curl_mfprintf(stderr, "write_cb: ERROR call #%d with meta=NULL\n", + ctx->callback_count); + ++ctx->errors; + return CURL_WRITEFUNC_ERROR; + } + if(ctx->callback_count > 2) { + ctx->errors++; + curl_mfprintf(stderr, "write_cb: call #%d should not happen\n", + ctx->callback_count); + return 0; + } + if(ctx->callback_count > 1) { + curl_mfprintf(stderr, "write_cb: call #%d return error\n", + ctx->callback_count); + return 0; + } + return nbytes; +} +#endif /* CURL_DISABLE_WEBSOCKETS */ + +static CURLcode test_cli_ws_write_err(const char *URL) +{ +#ifndef CURL_DISABLE_WEBSOCKETS + struct test_ws_write_err_ctx ctx; + CURLM *multi; + int still_running = 0; + int msgs_left = 0; + int done = 0; + + memset(&ctx, 0, sizeof(ctx)); + setbuf(stdout, NULL); + + curl_global_init(CURL_GLOBAL_ALL); + + ctx.easy = curl_easy_init(); + multi = curl_multi_init(); + if(!ctx.easy || !multi) { + curl_mfprintf(stderr, "main: ERROR creating easy/multi\n"); + ctx.errors = 1; + goto out; + } + + curl_easy_setopt(ctx.easy, CURLOPT_URL, URL); + curl_easy_setopt(ctx.easy, CURLOPT_WRITEFUNCTION, + test_ws_write_err_write_cb); + curl_easy_setopt(ctx.easy, CURLOPT_WRITEDATA, &ctx); + curl_easy_setopt(ctx.easy, CURLOPT_VERBOSE, 1L); + + curl_multi_add_handle(multi, ctx.easy); + curl_multi_perform(multi, &still_running); + + while(still_running && !ctx.closed && !ctx.errors) { + + curl_mfprintf(stderr, "main: poll\n"); + curl_multi_poll(multi, NULL, 0, 100, NULL); + curl_mfprintf(stderr, "main: perform\n"); + curl_multi_perform(multi, &still_running); + + while(!done) { + CURLMsg *msg = curl_multi_info_read(multi, &msgs_left); + if(!msg) + break; + if(msg->msg == CURLMSG_DONE) { + curl_mfprintf(stderr, "main: done result=%d (%s)\n", + (int)msg->data.result, + curl_easy_strerror(msg->data.result)); + done = 1; + } + } + } + +out: + if(ctx.easy) { + if(multi) + curl_multi_remove_handle(multi, ctx.easy); + curl_easy_cleanup(ctx.easy); + } + if(multi) + curl_multi_cleanup(multi); + curl_global_cleanup(); + + return (ctx.errors || (ctx.callback_count != 2)) ? + CURLE_WRITE_ERROR : CURLE_OK; + +#else /* !CURL_DISABLE_WEBSOCKETS */ + (void)URL; + curl_mfprintf(stderr, "WebSockets not enabled in libcurl\n"); + return (CURLcode)1; +#endif /* CURL_DISABLE_WEBSOCKETS */ +} diff --git a/tests/libtest/first.c b/tests/libtest/first.c index e4e9dbd8c8e4..2968ee611a97 100644 --- a/tests/libtest/first.c +++ b/tests/libtest/first.c @@ -55,6 +55,7 @@ int select_wrapper(int nfds, fd_set *rd, fd_set *wr, fd_set *exc, const char *libtest_arg2 = NULL; const char *libtest_arg3 = NULL; const char *libtest_arg4 = NULL; +const char *libtest_arg5 = NULL; int test_argc; const char **test_argv; int testnum; @@ -78,7 +79,7 @@ int cgetopt(int argc, const char * const argv[], const char *optstring) } arg = argv[coptind]; - if(arg && strcmp(arg, "--") == 0) { + if(arg && !strcmp(arg, "--")) { coptind++; return -1; } @@ -168,7 +169,7 @@ CURLcode ws_send_ping(CURL *curl, const char *send_payload) CURLcode result = curl_ws_send(curl, send_payload, strlen(send_payload), &sent, 0, CURLWS_PING); curl_mfprintf(stderr, "ws: curl_ws_send returned %d, sent %zu\n", - result, sent); + (int)result, sent); return result; } @@ -180,13 +181,13 @@ CURLcode ws_recv_pong(CURL *curl, const char *expected_payload) CURLcode result = curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &meta); if(result) { curl_mfprintf(stderr, "ws: curl_ws_recv returned %d, received %zu\n", - result, rlen); + (int)result, rlen); return result; } if(!(meta->flags & CURLWS_PONG)) { curl_mfprintf(stderr, "recv_pong: wrong frame, got %zu bytes rflags %x\n", - rlen, meta->flags); + rlen, (unsigned int)meta->flags); return CURLE_RECV_ERROR; } @@ -200,17 +201,17 @@ CURLcode ws_recv_pong(CURL *curl, const char *expected_payload) return CURLE_RECV_ERROR; } -/* just close the connection */ +/* close the connection */ void ws_close(CURL *curl) { size_t sent; CURLcode result = curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE); curl_mfprintf(stderr, "ws: curl_ws_send returned %d, sent %zu\n", - result, sent); + (int)result, sent); } #endif /* CURL_DISABLE_WEBSOCKETS */ -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { const char *URL = ""; CURLcode result; @@ -247,7 +248,7 @@ int main(int argc, const char **argv) entry_name = argv[1]; entry_func = NULL; for(tmp = 0; s_entries[tmp].ptr; ++tmp) { - if(strcmp(entry_name, s_entries[tmp].name) == 0) { + if(!strcmp(entry_name, s_entries[tmp].name)) { entry_func = s_entries[tmp].ptr; break; } @@ -272,6 +273,9 @@ int main(int argc, const char **argv) if(argc > 5) libtest_arg4 = argv[5]; + if(argc > 6) + libtest_arg5 = argv[6]; + testnum = 0; env = getenv("CURL_TESTNUM"); if(env) { @@ -285,7 +289,7 @@ int main(int argc, const char **argv) #endif result = entry_func(URL); - curl_mfprintf(stderr, "Test ended with result %d\n", result); + curl_mfprintf(stderr, "Test ended with result %d\n", (int)result); #ifdef _WIN32 /* flush buffers of all streams regardless of mode */ @@ -294,5 +298,5 @@ int main(int argc, const char **argv) /* Regular program status codes are limited to 0..127 and 126 and 127 have * special meanings by the shell, so limit a normal return code to 125 */ - return (int)result <= 125 ? (int)result : 125; + return result <= 125 ? result : 125; } diff --git a/tests/libtest/first.h b/tests/libtest/first.h index 062cd169be09..75ae75c6fb43 100644 --- a/tests/libtest/first.h +++ b/tests/libtest/first.h @@ -31,6 +31,7 @@ we need both of them in the include path), so that we get good in-depth knowledge about the system we are building this on */ #include "curl_setup.h" +#include "testutil.h" typedef CURLcode (*entry_func_t)(const char *); @@ -46,6 +47,8 @@ extern int unitfail; /* for unittests */ #include "curlx/base64.h" /* for curlx_base64* */ #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ #include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/inet_ntop.h" /* for curlx_inet_ntop() */ +#include "curlx/inet_pton.h" /* for curlx_inet_pton() */ #include "curlx/strcopy.h" /* for curlx_strcopy() */ #include "curlx/strerr.h" /* for curlx_strerror() */ #include "curlx/strparse.h" /* for curlx_str_* parsing functions */ @@ -54,27 +57,14 @@ extern int unitfail; /* for unittests */ #include "curlx/wait.h" /* for curlx_wait_ms() */ #ifdef HAVE_SYS_SELECT_H -/* since so many tests use select(), we can just as well include it here */ +/* since so many tests use select(), we can as well include it here */ #include #endif -#define test_setopt(A, B, C) \ - do { \ - result = curl_easy_setopt(A, B, C); \ - if(result != CURLE_OK) \ - goto test_cleanup; \ - } while(0) - -#define test_multi_setopt(A, B, C) \ - do { \ - result = curl_multi_setopt(A, B, C); \ - if(result != CURLE_OK) \ - goto test_cleanup; \ - } while(0) - extern const char *libtest_arg2; /* set by first.c to the argv[2] or NULL */ extern const char *libtest_arg3; /* set by first.c to the argv[3] or NULL */ extern const char *libtest_arg4; /* set by first.c to the argv[4] or NULL */ +extern const char *libtest_arg5; /* set by first.c to the argv[5] or NULL */ /* argc and argv as passed in to the main() function */ extern int test_argc; @@ -94,16 +84,15 @@ extern char *hexdump(const unsigned char *buf, size_t len); #ifndef CURL_DISABLE_WEBSOCKETS CURLcode ws_send_ping(CURL *curl, const char *send_payload); CURLcode ws_recv_pong(CURL *curl, const char *expected_payload); -void ws_close(CURL *curl); /* just close the connection */ +void ws_close(CURL *curl); /* close the connection */ #endif /* - * TEST_ERR_* values must within the CURLcode range to not cause compiler + * TEST_ERR_* values must be within the CURLcode range to not cause compiler * errors. * * For portability reasons TEST_ERR_* values should be less than 127. */ - #define TEST_ERR_MAJOR_BAD CURLE_OBSOLETE20 #define TEST_ERR_RUNS_FOREVER CURLE_OBSOLETE24 #define TEST_ERR_EASY_INIT CURLE_OBSOLETE29 @@ -141,10 +130,11 @@ void ws_close(CURL *curl); /* just close the connection */ * should be immediately followed by checking if 'res' variable has been * set. * - * 'res' variable when set will hold a CURLcode, CURLMcode, or any of the + * 'res' variable when set holds a CURLcode, CURLMcode, or any of the * TEST_ERR_* values defined above. It is advisable to return this value * as test result. */ +#ifndef UNITTESTS /* ---------------------------------------------------------------- */ @@ -196,16 +186,14 @@ void ws_close(CURL *curl); /* just close the connection */ /* ---------------------------------------------------------------- */ -#define exe_easy_setopt(A, B, C, Y, Z) \ - do { \ - CURLcode ec = curl_easy_setopt(A, B, C); \ - if(ec != CURLE_OK) { \ - curl_mfprintf(stderr, \ - "%s:%d curl_easy_setopt() failed, " \ - "with code %d (%s)\n", \ - Y, Z, ec, curl_easy_strerror(ec)); \ - result = ec; \ - } \ +#define exe_easy_setopt(A, B, C, Y, Z) \ + do { \ + result = curl_easy_setopt(A, B, C); \ + if(result) \ + curl_mfprintf(stderr, \ + "%s:%d curl_easy_setopt() failed, " \ + "with code %d (%s)\n", \ + Y, Z, (int)result, curl_easy_strerror(result)); \ } while(0) #define res_easy_setopt(A, B, C) \ @@ -235,9 +223,6 @@ void ws_close(CURL *curl); /* just close the connection */ } \ } while(0) -#define res_multi_setopt(A, B, C) \ - exe_multi_setopt(A, B, C, __FILE__, __LINE__) - #define chk_multi_setopt(A, B, C, Y, Z) \ do { \ exe_multi_setopt(A, B, C, Y, Z); \ @@ -289,9 +274,6 @@ void ws_close(CURL *curl); /* just close the connection */ } \ } while(0) -#define res_multi_remove_handle(A, B) \ - exe_multi_remove_handle(A, B, __FILE__, __LINE__) - #define chk_multi_remove_handle(A, B, Y, Z) \ do { \ exe_multi_remove_handle(A, B, Y, Z); \ @@ -425,9 +407,6 @@ void ws_close(CURL *curl); /* just close the connection */ } \ } while(0) -#define res_multi_poll(A, B, C, D, E) \ - exe_multi_poll(A, B, C, D, E, __FILE__, __LINE__) - #define chk_multi_poll(A, B, C, D, E, Y, Z) \ do { \ exe_multi_poll(A, B, C, D, E, Y, Z); \ @@ -455,27 +434,17 @@ void ws_close(CURL *curl); /* just close the connection */ #define res_multi_wakeup(A) \ exe_multi_wakeup(A, __FILE__, __LINE__) -#define chk_multi_wakeup(A, Y, Z) \ - do { \ - exe_multi_wakeup(A, Y, Z); \ - if(result) \ - goto test_cleanup; \ - } while(0) - -#define multi_wakeup(A) \ - chk_multi_wakeup(A, __FILE__, __LINE__) - /* ---------------------------------------------------------------- */ #define exe_select_test(A, B, C, D, E, Y, Z) \ do { \ if(select_wrapper(A, B, C, D, E) == -1) { \ - int ec = SOCKERRNO; \ - char ecbuf[STRERROR_LEN]; \ + int sockerr = SOCKERRNO; \ + char sockerrbuf[STRERROR_LEN]; \ curl_mfprintf(stderr, \ - "%s:%d select() failed, with " \ - "errno %d (%s)\n", \ - Y, Z, ec, curlx_strerror(ec, ecbuf, sizeof(ecbuf))); \ + "%s:%d select() failed, with errno %d (%s)\n", Y, Z, \ + sockerr, curlx_strerror(sockerr, sockerrbuf, \ + sizeof(sockerrbuf))); \ result = TEST_ERR_SELECT; \ } \ } while(0) @@ -517,9 +486,6 @@ void ws_close(CURL *curl); /* just close the connection */ #define res_test_timedout() \ exe_test_timedout(TEST_HANG_TIMEOUT, __FILE__, __LINE__) -#define res_test_timedout_custom(T) \ - exe_test_timedout(T, __FILE__, __LINE__) - #define chk_test_timedout(T, Y, Z) \ do { \ exe_test_timedout(T, Y, Z); \ @@ -533,23 +499,27 @@ void ws_close(CURL *curl); /* just close the connection */ #define abort_on_test_timeout_custom(T) \ chk_test_timedout(T, __FILE__, __LINE__) -/* ---------------------------------------------------------------- */ - -#define exe_global_init(A, Y, Z) \ - do { \ - CURLcode ec = curl_global_init(A); \ - if(ec != CURLE_OK) { \ - curl_mfprintf(stderr, \ - "%s:%d curl_global_init() failed, " \ - "with code %d (%s)\n", \ - Y, Z, ec, curl_easy_strerror(ec)); \ - result = ec; \ - } \ - } while(0) +#define NUM_HANDLES 4 /* global default */ #define res_global_init(A) \ exe_global_init(A, __FILE__, __LINE__) +#endif /* !UNITTESTS */ + +#if !defined(UNITTESTS) || defined(BUILDING_LIBCURL) + +/* ---------------------------------------------------------------- */ + +#define exe_global_init(A, Y, Z) \ + do { \ + result = curl_global_init(A); \ + if(result) \ + curl_mfprintf(stderr, \ + "%s:%d curl_global_init() failed, " \ + "with code %d (%s)\n", \ + Y, Z, (int)result, curl_easy_strerror(result)); \ + } while(0) + #define chk_global_init(A, Y, Z) \ do { \ exe_global_init(A, Y, Z); \ @@ -563,13 +533,6 @@ void ws_close(CURL *curl); /* just close the connection */ #define global_init(A) \ chk_global_init(A, __FILE__, __LINE__) -#define NO_SUPPORT_BUILT_IN \ - { \ - (void)URL; \ - curl_mfprintf(stderr, "Missing support\n"); \ - return CURLE_UNSUPPORTED_PROTOCOL; \ - } - -#define NUM_HANDLES 4 /* global default */ +#endif /* !UNITTESTS || BUILDING_LIBCURL */ #endif /* HEADER_LIBTEST_FIRST_H */ diff --git a/tests/libtest/lib1156.c b/tests/libtest/lib1156.c index fa75a6ae84b1..6b13374f0424 100644 --- a/tests/libtest/lib1156.c +++ b/tests/libtest/lib1156.c @@ -23,16 +23,14 @@ ***************************************************************************/ #include "first.h" -/* - Check range/resume returned error codes and data presence. +/* Check range/resume returned error codes and data presence. - The input parameters are: - - CURLOPT_RANGE/CURLOPT_RESUME_FROM - - CURLOPT_FAILONERROR - - Returned http code (2xx/416) - - Content-Range header present in reply. - -*/ + The input parameters are: + - CURLOPT_RANGE/CURLOPT_RESUME_FROM + - CURLOPT_FAILONERROR + - Returned http code (2xx/416) + - Content-Range header present in reply. + */ #define F_RESUME (1 << 0) /* resume/range. */ #define F_HTTP416 (1 << 1) /* Server returns http code 416. */ @@ -92,22 +90,22 @@ static int onetest(CURL *curl, const char *url, const struct testparams *p, if(p->flags & F_HTTP416) replyselector += 2; curl_msnprintf(urlbuf, sizeof(urlbuf), "%s%04u", url, replyselector); - test_setopt(curl, CURLOPT_URL, urlbuf); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_RESUME_FROM, (p->flags & F_RESUME) ? 3L : 0L); - test_setopt(curl, CURLOPT_RANGE, !(p->flags & F_RESUME) ? + easy_setopt(curl, CURLOPT_URL, urlbuf); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_RESUME_FROM, (p->flags & F_RESUME) ? 3L : 0L); + easy_setopt(curl, CURLOPT_RANGE, !(p->flags & F_RESUME) ? "3-1000000" : (char *)NULL); - test_setopt(curl, CURLOPT_FAILONERROR, (p->flags & F_FAIL) ? 1L : 0L); + easy_setopt(curl, CURLOPT_FAILONERROR, (p->flags & F_FAIL) ? 1L : 0L); hasbody = 0; result = curl_easy_perform(curl); if(result != p->result) { curl_mprintf("%zu: bad error code (%d): resume=%s, fail=%s, http416=%s, " - "content-range=%s, expected=%d\n", num, result, + "content-range=%s, expected=%d\n", num, (int)result, (p->flags & F_RESUME) ? "yes" : "no", (p->flags & F_FAIL) ? "yes" : "no", (p->flags & F_HTTP416) ? "yes" : "no", (p->flags & F_CONTENTRANGE) ? "yes" : "no", - p->result); + (int)p->result); return 1; } if(hasbody && (p->flags & F_IGNOREBODY)) { @@ -149,7 +147,7 @@ static CURLcode test_lib1156(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_WRITEFUNCTION, writedata); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, writedata); #ifdef SINGLETEST if(SINGLETEST == i) diff --git a/tests/unit/unit1396.c b/tests/libtest/lib1396.c similarity index 92% rename from tests/unit/unit1396.c rename to tests/libtest/lib1396.c index 33317c73e816..60d139a8500f 100644 --- a/tests/unit/unit1396.c +++ b/tests/libtest/lib1396.c @@ -37,7 +37,7 @@ static void t1396_stop(CURL *easy) curl_global_cleanup(); } -static CURLcode test_unit1396(const char *arg) +static CURLcode test_lib1396(const char *arg) { CURL *easy; @@ -51,7 +51,7 @@ static CURLcode test_unit1396(const char *arg) }; /* unescape, this => that */ - const struct test list1[] = { + static const struct test list1[] = { { "%61", 3, "a", 1 }, { "%61a", 4, "aa", 2 }, { "%61b", 4, "ab", 2 }, @@ -67,7 +67,7 @@ static CURLcode test_unit1396(const char *arg) { NULL, 0, NULL, 0 } /* end of list marker */ }; /* escape, this => that */ - const struct test list2[] = { + static const struct test list2[] = { { "a", 1, "a", 1 }, { "/", 1, "%2F", 3 }, { "a=b", 3, "a%3Db", 5 }, @@ -83,12 +83,12 @@ static CURLcode test_unit1396(const char *arg) int i; easy = curl_easy_init(); - abort_unless(easy != NULL, "returned NULL!"); + abort_unless(easy, "returned NULL!"); for(i = 0; list1[i].in; i++) { int outlen; char *out = curl_easy_unescape(easy, list1[i].in, list1[i].inlen, &outlen); - abort_unless(out != NULL, "returned NULL!"); + abort_unless(out, "returned NULL!"); fail_unless(outlen == list1[i].outlen, "wrong output length returned"); fail_unless(!memcmp(out, list1[i].out, list1[i].outlen), "bad output data returned"); @@ -101,7 +101,7 @@ static CURLcode test_unit1396(const char *arg) for(i = 0; list2[i].in; i++) { int outlen; char *out = curl_easy_escape(easy, list2[i].in, list2[i].inlen); - abort_unless(out != NULL, "returned NULL!"); + abort_unless(out, "returned NULL!"); outlen = (int)strlen(out); fail_unless(outlen == list2[i].outlen, "wrong output length returned"); diff --git a/tests/unit/unit1398.c b/tests/libtest/lib1398.c similarity index 99% rename from tests/unit/unit1398.c rename to tests/libtest/lib1398.c index 7f2267e4384c..6fd9cebb0409 100644 --- a/tests/unit/unit1398.c +++ b/tests/libtest/lib1398.c @@ -28,13 +28,13 @@ #pragma GCC diagnostic ignored "-Wformat" #endif -static CURLcode test_unit1398(const char *arg) +static CURLcode test_lib1398(const char *arg) { UNITTEST_BEGIN_SIMPLE int rc; char buf[3] = { 'b', 'u', 'g' }; - static const char *str = "bug"; + static const char str[] = "bug"; int width = 3; char output[130]; diff --git a/tests/libtest/lib1485.c b/tests/libtest/lib1485.c index a4eb07a62802..46b985151325 100644 --- a/tests/libtest/lib1485.c +++ b/tests/libtest/lib1485.c @@ -47,10 +47,10 @@ static size_t t1485_header_callback(char *ptr, size_t size, size_t nmemb, /* end of a response */ result = curl_easy_getinfo(st->curl, CURLINFO_RESPONSE_CODE, &httpcode); curl_mfprintf(stderr, "header_callback, get status: %ld, %d\n", - httpcode, result); + httpcode, (int)result); if(httpcode < 100 || httpcode >= 1000) { curl_mfprintf(stderr, "header_callback, invalid status: %ld, %d\n", - httpcode, result); + httpcode, (int)result); return CURLE_WRITE_ERROR; } st->http_status = (int)httpcode; @@ -58,7 +58,7 @@ static size_t t1485_header_callback(char *ptr, size_t size, size_t nmemb, result = curl_easy_getinfo(st->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &clen); curl_mfprintf(stderr, "header_callback, info Content-Length: " - "%" CURL_FORMAT_CURL_OFF_T ", %d\n", clen, result); + "%" CURL_FORMAT_CURL_OFF_T ", %d\n", clen, (int)result); if(result) { st->result = result; return CURLE_WRITE_ERROR; diff --git a/tests/libtest/lib1507.c b/tests/libtest/lib1507.c index 67ccce00ee5b..c63be0d5f8a9 100644 --- a/tests/libtest/lib1507.c +++ b/tests/libtest/lib1507.c @@ -43,7 +43,7 @@ static CURLcode test_lib1507(const char *URL) struct curltime mp_start; struct curl_slist *rcpt_list = NULL; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); easy_init(curl); @@ -105,8 +105,8 @@ static CURLcode test_lib1507(const char *URL) /* In a real-world program you OF COURSE check the return code of the function calls. On success, the value of maxfd is guaranteed to be greater or equal than -1. We call select(maxfd + 1, ...), specially in - case of (maxfd == -1), we call select(0, ...), which is basically equal - to sleep. */ + case of (maxfd == -1), we call select(0, ...), which is equal to sleep. + */ rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); diff --git a/tests/libtest/lib1509.c b/tests/libtest/lib1509.c index 1099c3c956f3..95258d678f5d 100644 --- a/tests/libtest/lib1509.c +++ b/tests/libtest/lib1509.c @@ -66,7 +66,7 @@ static CURLcode test_lib1509(const char *URL) if(code != CURLE_OK) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed, " "with code %d (%s)\n", - __FILE__, __LINE__, code, curl_easy_strerror(code)); + __FILE__, __LINE__, (int)code, curl_easy_strerror(code)); result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } @@ -75,7 +75,7 @@ static CURLcode test_lib1509(const char *URL) if(code != CURLE_OK) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed, " "with code %d (%s)\n", - __FILE__, __LINE__, code, curl_easy_strerror(code)); + __FILE__, __LINE__, (int)code, curl_easy_strerror(code)); result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } diff --git a/tests/libtest/lib1514.c b/tests/libtest/lib1514.c index 7a7a76fff0ba..528468eee026 100644 --- a/tests/libtest/lib1514.c +++ b/tests/libtest/lib1514.c @@ -29,7 +29,7 @@ #include "first.h" struct t1514_WriteThis { - char *readptr; + const char *readptr; size_t sizeleft; }; @@ -41,13 +41,13 @@ static size_t t1514_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) return 0; if(pooh->sizeleft) { - *ptr = pooh->readptr[0]; /* copy one single byte */ - pooh->readptr++; /* advance pointer */ - pooh->sizeleft--; /* less data left */ - return 1; /* we return 1 byte at a time! */ + *ptr = pooh->readptr[0]; /* copy one single byte */ + pooh->readptr++; /* advance pointer */ + pooh->sizeleft--; /* less data left */ + return 1; /* we return 1 byte at a time! */ } - return 0; /* no more data left to deliver */ + return 0; /* no more data left to deliver */ } static CURLcode test_lib1514(const char *URL) @@ -55,9 +55,9 @@ static CURLcode test_lib1514(const char *URL) CURL *curl; CURLcode result = CURLE_OK; - static char testdata[] = "dummy"; + static const char testdata[] = "dummy"; - struct t1514_WriteThis pooh = { testdata, sizeof(testdata) - 1 }; + struct t1514_WriteThis pooh = { testdata, CURL_CSTRLEN(testdata) }; global_init(CURL_GLOBAL_ALL); diff --git a/tests/libtest/lib1515.c b/tests/libtest/lib1515.c index dae7acce506d..f9bacf5c6033 100644 --- a/tests/libtest/lib1515.c +++ b/tests/libtest/lib1515.c @@ -128,7 +128,8 @@ static CURLcode test_lib1515(const char *URL) /* second request must succeed like the first one */ result = do_one_request(multi, target_url, dns_entry); if(result != CURLE_OK) { - curl_mfprintf(stderr, "request %s failed with %d\n", target_url, result); + curl_mfprintf(stderr, "request %s failed with %d\n", target_url, + (int)result); goto test_cleanup; } diff --git a/tests/libtest/lib1517.c b/tests/libtest/lib1517.c index 57b0a0d5a5b9..a88705ed6872 100644 --- a/tests/libtest/lib1517.c +++ b/tests/libtest/lib1517.c @@ -33,8 +33,8 @@ static size_t t1517_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) struct t1517_WriteThis *pooh = (struct t1517_WriteThis *)userp; size_t tocopy = size * nmemb; - /* Wait one second before return POST data * - * so libcurl will wait before sending request body */ + /* Wait one second before return POST data + so libcurl waits before sending request body */ curlx_wait_ms(1000); if(tocopy < 1 || !pooh->sizeleft) @@ -43,9 +43,9 @@ static size_t t1517_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) if(pooh->sizeleft < tocopy) tocopy = pooh->sizeleft; - memcpy(ptr, pooh->readptr, tocopy);/* copy requested data */ - pooh->readptr += tocopy; /* advance pointer */ - pooh->sizeleft -= tocopy; /* less data left */ + memcpy(ptr, pooh->readptr, tocopy); /* copy requested data */ + pooh->readptr += tocopy; /* advance pointer */ + pooh->sizeleft -= tocopy; /* less data left */ return tocopy; } @@ -60,7 +60,7 @@ static CURLcode test_lib1517(const char *URL) struct t1517_WriteThis pooh; pooh.readptr = testdata; - pooh.sizeleft = strlen(testdata); + pooh.sizeleft = CURL_CSTRLEN(testdata); if(curl_global_init(CURL_GLOBAL_ALL)) { curl_mfprintf(stderr, "curl_global_init() failed\n"); @@ -75,32 +75,32 @@ static CURLcode test_lib1517(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Set the expected POST size */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); /* we want to use our own read function */ - test_setopt(curl, CURLOPT_READFUNCTION, t1517_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t1517_read_cb); /* pointer to pass to our read function */ - test_setopt(curl, CURLOPT_READDATA, &pooh); + easy_setopt(curl, CURLOPT_READDATA, &pooh); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); #if 0 /* detect HTTP error codes >= 400 */ - test_setopt(curl, CURLOPT_FAILONERROR, 1L); + easy_setopt(curl, CURLOPT_FAILONERROR, 1L); #endif - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib1518.c b/tests/libtest/lib1518.c index f6fe44c5e94c..09b85148191c 100644 --- a/tests/libtest/lib1518.c +++ b/tests/libtest/lib1518.c @@ -25,16 +25,6 @@ /* Test inspired by github issue 3340 */ -static size_t t1518_write_cb(char *buffer, size_t size, size_t nitems, - void *outstream) -{ - (void)buffer; - (void)size; - (void)nitems; - (void)outstream; - return 0; -} - static CURLcode test_lib1518(const char *URL) { CURL *curl; @@ -59,16 +49,16 @@ static CURLcode test_lib1518(const char *URL) if(!urlu || rc) { goto test_cleanup; } - test_setopt(curl, CURLOPT_CURLU, urlu); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_CURLU, urlu); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); } else { - test_setopt(curl, CURLOPT_URL, URL); - /* just to make it explicit and visible in this test: */ - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); + easy_setopt(curl, CURLOPT_URL, URL); + /* to make it explicit and visible in this test: */ + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); } - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); if(result) goto test_cleanup; @@ -77,14 +67,14 @@ static CURLcode test_lib1518(const char *URL) curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &curlRedirectCount); curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effectiveUrl); curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &redirectUrl); - test_setopt(curl, CURLOPT_WRITEFUNCTION, t1518_write_cb); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); curl_mprintf("result %d\n" "status %ld\n" "redirects %ld\n" "effectiveurl %s\n" "redirecturl %s\n", - result, + (int)result, curlResponseCode, curlRedirectCount, effectiveUrl, diff --git a/tests/libtest/lib1520.c b/tests/libtest/lib1520.c index a76c64a5a91d..b97fd94fc84e 100644 --- a/tests/libtest/lib1520.c +++ b/tests/libtest/lib1520.c @@ -29,7 +29,7 @@ struct upload_status { static size_t t1520_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) { - static const char *payload_text[] = { + static const char * const payload_text[] = { "From: different\r\n", "To: another\r\n", "\r\n", @@ -87,13 +87,13 @@ static CURLcode test_lib1520(const char *URL) /* more addresses can be added here */ rcpt_list = curl_slist_append(rcpt_list, ""); #endif - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1520_read_cb); - test_setopt(curl, CURLOPT_READDATA, &upload_ctx); - test_setopt(curl, CURLOPT_MAIL_FROM, ""); - test_setopt(curl, CURLOPT_MAIL_RCPT, rcpt_list); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1520_read_cb); + easy_setopt(curl, CURLOPT_READDATA, &upload_ctx); + easy_setopt(curl, CURLOPT_MAIL_FROM, ""); + easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpt_list); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1522.c b/tests/libtest/lib1522.c index 56a6992bdb41..9f6dc335154d 100644 --- a/tests/libtest/lib1522.c +++ b/tests/libtest/lib1522.c @@ -61,9 +61,9 @@ static CURLcode test_lib1522(const char *URL) debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); - test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* Remove "Expect: 100-continue" */ pHeaderList = curl_slist_append(pHeaderList, "Expect:"); @@ -87,7 +87,7 @@ static CURLcode test_lib1522(const char *URL) } } else { - curl_mprintf("curl_easy_perform() failed. e = %d\n", code); + curl_mprintf("curl_easy_perform() failed. e = %d\n", (int)code); } test_cleanup: curl_slist_free_all(pHeaderList); diff --git a/tests/libtest/lib1523.c b/tests/libtest/lib1523.c index 47254ffcdf14..53ed580a59a5 100644 --- a/tests/libtest/lib1523.c +++ b/tests/libtest/lib1523.c @@ -36,14 +36,6 @@ static int dload_progress_cb(void *a, curl_off_t b, curl_off_t c, return 0; } -static size_t t1523_write_cb(char *d, size_t n, size_t l, void *p) -{ - /* take care of the data here, ignored in this example */ - (void)d; - (void)p; - return n * l; -} - static CURLcode run(CURL *curl, long limit, long time) { curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, limit); @@ -59,18 +51,18 @@ static CURLcode test_lib1523(const char *URL) curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, URL); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, t1523_write_cb); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, dload_progress_cb); result = run(curl, 1, 2); if(result) - curl_mfprintf(stderr, "error (%d) %s\n", result, buffer); + curl_mfprintf(stderr, "error (%d) %s\n", (int)result, buffer); result = run(curl, 12000, 1); if(result != CURLE_OPERATION_TIMEDOUT) - curl_mfprintf(stderr, "error (%d) %s\n", result, buffer); + curl_mfprintf(stderr, "error (%d) %s\n", (int)result, buffer); else result = CURLE_OK; diff --git a/tests/libtest/lib1525.c b/tests/libtest/lib1525.c index 29ce7a9a0bd0..7465eb59dd3e 100644 --- a/tests/libtest/lib1525.c +++ b/tests/libtest/lib1525.c @@ -24,14 +24,14 @@ ***************************************************************************/ /* - * This unit test PUT http data over proxy. Proxy header will be different + * This unit test PUT http data over proxy. Proxy header is different * from server http header */ #include "first.h" static const char t1525_data[] = "Hello Cloud!\n"; -static size_t const t1525_datalen = sizeof(t1525_data) - 1; +static const size_t t1525_datalen = CURL_CSTRLEN(t1525_data); static size_t t1525_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { @@ -69,20 +69,20 @@ static CURLcode test_lib1525(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_PROXYHEADER, hhl); - test_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_UNIFIED); - test_setopt(curl, CURLOPT_POST, 0L); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - test_setopt(curl, CURLOPT_READFUNCTION, t1525_read_cb); - test_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, (long)t1525_datalen); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_PROXYHEADER, hhl); + easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_UNIFIED); + easy_setopt(curl, CURLOPT_POST, 0L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + easy_setopt(curl, CURLOPT_READFUNCTION, t1525_read_cb); + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, (long)t1525_datalen); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1526.c b/tests/libtest/lib1526.c index 30c4f552359d..56b1b749b325 100644 --- a/tests/libtest/lib1526.c +++ b/tests/libtest/lib1526.c @@ -23,14 +23,14 @@ ***************************************************************************/ /* - * This unit test PUT http data over proxy. Proxy header will be different + * This unit test PUT http data over proxy. Proxy header is different * from server http header */ #include "first.h" static const char t1526_data[] = "Hello Cloud!\n"; -static size_t const t1526_datalen = sizeof(t1526_data) - 1; +static const size_t t1526_datalen = CURL_CSTRLEN(t1526_data); static size_t t1526_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { @@ -73,20 +73,20 @@ static CURLcode test_lib1526(const char *URL) } phl = tmp; - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_PROXYHEADER, phl); - test_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE); - test_setopt(curl, CURLOPT_POST, 0L); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - test_setopt(curl, CURLOPT_READFUNCTION, t1526_read_cb); - test_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, (long)t1526_datalen); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_PROXYHEADER, phl); + easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE); + easy_setopt(curl, CURLOPT_POST, 0L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + easy_setopt(curl, CURLOPT_READFUNCTION, t1526_read_cb); + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, (long)t1526_datalen); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1527.c b/tests/libtest/lib1527.c index efa39efb7369..1caa37d8d3d5 100644 --- a/tests/libtest/lib1527.c +++ b/tests/libtest/lib1527.c @@ -23,14 +23,14 @@ ***************************************************************************/ /* - * This unit test PUT http data over proxy. Same http header will be generated + * This unit test PUT http data over proxy. Same http header is generated * for server and proxy */ #include "first.h" static const char t1527_data[] = "Hello Cloud!\n"; -static size_t const t1527_datalen = sizeof(t1527_data) - 1; +static const size_t t1527_datalen = CURL_CSTRLEN(t1527_data); static size_t t1527_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { @@ -72,19 +72,19 @@ static CURLcode test_lib1527(const char *URL) } hhl = tmp; - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_POST, 0L); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - test_setopt(curl, CURLOPT_READFUNCTION, t1527_read_cb); - test_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, (long)t1527_datalen); - test_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_UNIFIED); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_POST, 0L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + easy_setopt(curl, CURLOPT_READFUNCTION, t1527_read_cb); + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, (long)t1527_datalen); + easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_UNIFIED); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1528.c b/tests/libtest/lib1528.c index d5c6030222a6..f8db6725353b 100644 --- a/tests/libtest/lib1528.c +++ b/tests/libtest/lib1528.c @@ -50,14 +50,14 @@ static CURLcode test_lib1528(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_PROXYHEADER, phl); - test_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_PROXYHEADER, phl); + easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + easy_setopt(curl, CURLOPT_HEADER, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1529.c b/tests/libtest/lib1529.c index df83e54eb7d1..ccf003203598 100644 --- a/tests/libtest/lib1529.c +++ b/tests/libtest/lib1529.c @@ -43,11 +43,11 @@ static CURLcode test_lib1529(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, bURL); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, bURL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + easy_setopt(curl, CURLOPT_HEADER, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1530.c b/tests/libtest/lib1530.c index 39ee30b6b805..ecce16803d1f 100644 --- a/tests/libtest/lib1530.c +++ b/tests/libtest/lib1530.c @@ -52,9 +52,9 @@ static CURLcode test_lib1530(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, "http://99.99.99.99:9999"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket); + easy_setopt(curl, CURLOPT_URL, "http://99.99.99.99:9999"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1531.c b/tests/libtest/lib1531.c index f8c9071e3b8b..20ac3639ebfb 100644 --- a/tests/libtest/lib1531.c +++ b/tests/libtest/lib1531.c @@ -25,8 +25,8 @@ static CURLcode test_lib1531(const char *URL) { - static char const testdata[] = ".abc\0xyz"; - static curl_off_t const testdatalen = sizeof(testdata) - 1; + static const char testdata[] = ".abc\0xyz"; + static const curl_off_t testdatalen = CURL_CSTRLEN(testdata); CURL *curl; CURLM *multi; @@ -128,7 +128,7 @@ static CURLcode test_lib1531(const char *URL) msg = curl_multi_info_read(multi, &msgs_left); if(msg && msg->msg == CURLMSG_DONE) { curl_mprintf("HTTP transfer completed with status %d\n", - msg->data.result); + (int)msg->data.result); break; } diff --git a/tests/libtest/lib1532.c b/tests/libtest/lib1532.c index 533abb3e7892..25c5ab439a3a 100644 --- a/tests/libtest/lib1532.c +++ b/tests/libtest/lib1532.c @@ -41,7 +41,7 @@ static CURLcode test_lib1532(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -49,7 +49,7 @@ static CURLcode test_lib1532(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(httpcode != 200) { @@ -66,7 +66,7 @@ static CURLcode test_lib1532(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(httpcode) { diff --git a/tests/libtest/lib1533.c b/tests/libtest/lib1533.c index 6e7d1a471b67..c0ad8e46cb9a 100644 --- a/tests/libtest/lib1533.c +++ b/tests/libtest/lib1533.c @@ -98,7 +98,7 @@ static CURLcode perform_and_check_connections(CURL *curl, result = curl_easy_perform(curl); if(result != CURLE_OK) { - curl_mfprintf(stderr, "curl_easy_perform() failed with %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() failed with %d\n", (int)result); return TEST_ERR_MAJOR_BAD; } @@ -139,15 +139,15 @@ static CURLcode test_lib1533(const char *URL) reset_data(&data, curl); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_POST, 1L); - test_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)data.remaining_bytes); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1533_read_cb); - test_setopt(curl, CURLOPT_READDATA, &data); - test_setopt(curl, CURLOPT_WRITEFUNCTION, t1533_write_cb); - test_setopt(curl, CURLOPT_WRITEDATA, &data); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1533_read_cb); + easy_setopt(curl, CURLOPT_READDATA, &data); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, t1533_write_cb); + easy_setopt(curl, CURLOPT_WRITEDATA, &data); result = perform_and_check_connections(curl, @@ -165,7 +165,7 @@ static CURLcode test_lib1533(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_KEEP_SENDING_ON_ERROR, 1L); + easy_setopt(curl, CURLOPT_KEEP_SENDING_ON_ERROR, 1L); reset_data(&data, curl); diff --git a/tests/libtest/lib1534.c b/tests/libtest/lib1534.c index e672d585ee11..9c4491b8bcef 100644 --- a/tests/libtest/lib1534.c +++ b/tests/libtest/lib1534.c @@ -42,7 +42,7 @@ static CURLcode test_lib1534(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(filetime != -1) { @@ -60,7 +60,7 @@ static CURLcode test_lib1534(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -71,7 +71,7 @@ static CURLcode test_lib1534(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(filetime != 30) { @@ -97,7 +97,7 @@ static CURLcode test_lib1534(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(filetime != -1) { @@ -117,7 +117,7 @@ static CURLcode test_lib1534(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(filetime != -1) { diff --git a/tests/libtest/lib1535.c b/tests/libtest/lib1535.c index aae63fc9fa71..68ec25fb60f4 100644 --- a/tests/libtest/lib1535.c +++ b/tests/libtest/lib1535.c @@ -43,7 +43,7 @@ static CURLcode test_lib1535(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(protocol) { @@ -60,7 +60,7 @@ static CURLcode test_lib1535(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -72,7 +72,7 @@ static CURLcode test_lib1535(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(protocol != CURLPROTO_HTTP) { @@ -100,7 +100,7 @@ static CURLcode test_lib1535(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(protocol) { @@ -121,7 +121,7 @@ static CURLcode test_lib1535(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(protocol) { diff --git a/tests/libtest/lib1536.c b/tests/libtest/lib1536.c index 9debd78807f7..f6632c736106 100644 --- a/tests/libtest/lib1536.c +++ b/tests/libtest/lib1536.c @@ -42,7 +42,7 @@ static CURLcode test_lib1536(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(scheme) { @@ -59,7 +59,7 @@ static CURLcode test_lib1536(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -70,14 +70,13 @@ static CURLcode test_lib1536(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } - if(!scheme || memcmp(scheme, "http", 5) != 0) { + if(!scheme || memcmp(scheme, "http", 5)) { curl_mfprintf(stderr, "%s:%d scheme of http resource is incorrect; " "expected 'http' but is %s\n", - __FILE__, __LINE__, - (scheme == NULL ? "NULL" : "invalid")); + __FILE__, __LINE__, scheme ? "invalid" : "NULL"); result = CURLE_HTTP_RETURNED_ERROR; goto test_cleanup; } @@ -97,7 +96,7 @@ static CURLcode test_lib1536(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(scheme) { @@ -116,7 +115,7 @@ static CURLcode test_lib1536(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } if(scheme) { diff --git a/tests/libtest/lib1537.c b/tests/libtest/lib1537.c index c9668c0ddf10..7fd91f9d4f43 100644 --- a/tests/libtest/lib1537.c +++ b/tests/libtest/lib1537.c @@ -25,8 +25,10 @@ static CURLcode test_lib1537(const char *URL) { - const unsigned char a[] = { 0x2f, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe6, 0xf7 }; + static const unsigned char a[] = { + 0x2f, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe6, 0xf7 + }; CURLcode result = CURLE_OK; char *ptr = NULL; int asize; @@ -77,7 +79,7 @@ static CURLcode test_lib1537(const char *URL) curl_mprintf("escape -1 length: %s\n", ptr); /* weird input length */ - outlen = 2017; /* just a value */ + outlen = 2017; /* an arbitrary value */ ptr = curl_easy_unescape(NULL, "moahahaha", -1, &outlen); curl_mprintf("unescape -1 length: %s %d\n", ptr, outlen); diff --git a/tests/libtest/lib1538.c b/tests/libtest/lib1538.c index 6a1db4e93e46..6433e571b766 100644 --- a/tests/libtest/lib1538.c +++ b/tests/libtest/lib1538.c @@ -43,17 +43,16 @@ static CURLcode test_lib1538(const char *URL) curl_url_strerror((CURLUcode)-INT_MAX); /* NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) */ for(easyret = CURLE_OK; easyret <= CURL_LAST; easyret++) { - curl_mprintf("e%d: %s\n", easyret, curl_easy_strerror(easyret)); + curl_mprintf("e%d: %s\n", (int)easyret, curl_easy_strerror(easyret)); } - for(mresult = CURLM_CALL_MULTI_PERFORM; mresult <= CURLM_LAST; - mresult++) { + for(mresult = CURLM_CALL_MULTI_PERFORM; mresult <= CURLM_LAST; mresult++) { curl_mprintf("m%d: %s\n", mresult, curl_multi_strerror(mresult)); } for(shareret = CURLSHE_OK; shareret <= CURLSHE_LAST; shareret++) { - curl_mprintf("s%d: %s\n", shareret, curl_share_strerror(shareret)); + curl_mprintf("s%d: %s\n", (int)shareret, curl_share_strerror(shareret)); } for(urlret = CURLUE_OK; urlret <= CURLUE_LAST; urlret++) { - curl_mprintf("u%d: %s\n", urlret, curl_url_strerror(urlret)); + curl_mprintf("u%d: %s\n", (int)urlret, curl_url_strerror(urlret)); } return result; diff --git a/tests/libtest/lib1540.c b/tests/libtest/lib1540.c index ee18046f4270..b4fc49eb2be4 100644 --- a/tests/libtest/lib1540.c +++ b/tests/libtest/lib1540.c @@ -107,7 +107,7 @@ static CURLcode test_lib1540(const char *URL) debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); easy_setopt(curl, CURLOPT_VERBOSE, 1L); diff --git a/tests/libtest/lib1541.c b/tests/libtest/lib1541.c index 2dc0d2cd2611..d6888dbb63a4 100644 --- a/tests/libtest/lib1541.c +++ b/tests/libtest/lib1541.c @@ -34,7 +34,7 @@ struct t1541_transfer_status { static void t1541_geterr(const char *name, CURLcode val, int lineno) { curl_mprintf("CURLINFO_%s returned %d, \"%s\" on line %d\n", - name, val, curl_easy_strerror(val), lineno); + name, (int)val, curl_easy_strerror(val), lineno); } static void report_time(const char *key, const char *where, curl_off_t time, diff --git a/tests/libtest/lib1549.c b/tests/libtest/lib1549.c index ce5d70233133..04510526bb5c 100644 --- a/tests/libtest/lib1549.c +++ b/tests/libtest/lib1549.c @@ -42,9 +42,9 @@ static CURLcode test_lib1549(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_COOKIEFILE, ""); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_COOKIEFILE, ""); result = curl_easy_perform(curl); @@ -55,7 +55,7 @@ static CURLcode test_lib1549(const char *URL) result = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies); if(!result && cookies) { /* a linked list of cookies in cookie file format */ - struct curl_slist *each = cookies; + const struct curl_slist *each = cookies; while(each) { curl_mprintf("%s\n", each->data); each = each->next; diff --git a/tests/libtest/lib1553.c b/tests/libtest/lib1553.c index dd236cf73b58..8aa2b1a1534c 100644 --- a/tests/libtest/lib1553.c +++ b/tests/libtest/lib1553.c @@ -72,7 +72,7 @@ static CURLcode test_lib1553(const char *URL) debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); easy_setopt(curl, CURLOPT_VERBOSE, 1L); diff --git a/tests/libtest/lib1554.c b/tests/libtest/lib1554.c index 4b4b07cb0c41..c553095c61ff 100644 --- a/tests/libtest/lib1554.c +++ b/tests/libtest/lib1554.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -static const char *ldata_names[] = { +static const char * const ldata_names[] = { "NONE", "SHARE", "COOKIE", @@ -72,8 +72,8 @@ static CURLcode test_lib1554(const char *URL) curl_share_setopt(share, CURLSHOPT_LOCKFUNC, t1554_test_lock); curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, t1554_test_unlock); - /* Loop the transfer and cleanup the handle properly every lap. This will - still reuse connections since the pool is in the shared object! */ + /* Loop the transfer and cleanup the handle properly every lap. This + still reuses connections since the pool is in the shared object! */ for(i = 0; i < 3; i++) { CURL *curl = curl_easy_init(); @@ -83,7 +83,7 @@ static CURLcode test_lib1554(const char *URL) /* use the share object */ curl_easy_setopt(curl, CURLOPT_SHARE, share); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); /* always cleanup */ diff --git a/tests/libtest/lib1555.c b/tests/libtest/lib1555.c index 4932fd24f7cd..c6c806426a05 100644 --- a/tests/libtest/lib1555.c +++ b/tests/libtest/lib1555.c @@ -44,9 +44,9 @@ static int progressCallback(void *arg, (void)ultotal; (void)ulnow; result = curl_easy_recv(t1555_curl, buffer, 256, &n); - curl_mprintf("curl_easy_recv returned %d\n", result); + curl_mprintf("curl_easy_recv returned %d\n", (int)result); result = curl_easy_send(t1555_curl, buffer, n, &n); - curl_mprintf("curl_easy_send returned %d\n", result); + curl_mprintf("curl_easy_send returned %d\n", (int)result); return 1; } diff --git a/tests/libtest/lib1556.c b/tests/libtest/lib1556.c index c91161628fa7..201ea4b15962 100644 --- a/tests/libtest/lib1556.c +++ b/tests/libtest/lib1556.c @@ -60,7 +60,7 @@ static CURLcode test_lib1556(const char *URL) if(code != CURLE_OK) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed, " "with code %d (%s)\n", - __FILE__, __LINE__, code, curl_easy_strerror(code)); + __FILE__, __LINE__, (int)code, curl_easy_strerror(code)); result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } diff --git a/tests/libtest/lib1557.c b/tests/libtest/lib1557.c index 60662aac809f..e94941934ad9 100644 --- a/tests/libtest/lib1557.c +++ b/tests/libtest/lib1557.c @@ -48,7 +48,7 @@ static CURLcode test_lib1557(const char *URL) multi_remove_handle(multi, curl2); - /* If curl2 is still in the connect-pending list, this will crash */ + /* If curl2 is still in the connect-pending list, this crashes */ multi_remove_handle(multi, curl1); test_cleanup: diff --git a/tests/libtest/lib1558.c b/tests/libtest/lib1558.c index 4077cb8eb1f5..0832f8b1c5ee 100644 --- a/tests/libtest/lib1558.c +++ b/tests/libtest/lib1558.c @@ -36,18 +36,18 @@ static CURLcode test_lib1558(const char *URL) result = curl_easy_perform(curl); if(result) { curl_mfprintf(stderr, "curl_easy_perform() returned %d (%s)\n", - result, curl_easy_strerror(result)); + (int)result, curl_easy_strerror(result)); goto test_cleanup; } result = curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol); if(result) { curl_mfprintf(stderr, "curl_easy_getinfo() returned %d (%s)\n", - result, curl_easy_strerror(result)); + (int)result, curl_easy_strerror(result)); goto test_cleanup; } - curl_mprintf("Protocol: %lx\n", protocol); + curl_mprintf("Protocol: %lx\n", (unsigned long)protocol); curl_easy_cleanup(curl); curl_global_cleanup(); diff --git a/tests/libtest/lib1559.c b/tests/libtest/lib1559.c index 4cf3953b4fe2..951b5db234ce 100644 --- a/tests/libtest/lib1559.c +++ b/tests/libtest/lib1559.c @@ -47,23 +47,23 @@ static CURLcode test_lib1559(const char *URL) result = curl_easy_setopt(curl, CURLOPT_URL, longurl); curl_mprintf("CURLOPT_URL %d bytes URL == %d\n", - EXCESSIVE, result); + EXCESSIVE, (int)result); result = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, longurl); curl_mprintf("CURLOPT_POSTFIELDS %d bytes data == %d\n", - EXCESSIVE, result); + EXCESSIVE, (int)result); u = curl_url(); if(u) { CURLUcode uc = curl_url_set(u, CURLUPART_URL, longurl, 0); curl_mprintf("CURLUPART_URL %d bytes URL == %d (%s)\n", - EXCESSIVE, uc, curl_url_strerror(uc)); + EXCESSIVE, (int)uc, curl_url_strerror(uc)); uc = curl_url_set(u, CURLUPART_SCHEME, longurl, CURLU_NON_SUPPORT_SCHEME); curl_mprintf("CURLUPART_SCHEME %d bytes scheme == %d (%s)\n", - EXCESSIVE, uc, curl_url_strerror(uc)); + EXCESSIVE, (int)uc, curl_url_strerror(uc)); uc = curl_url_set(u, CURLUPART_USER, longurl, 0); curl_mprintf("CURLUPART_USER %d bytes user == %d (%s)\n", - EXCESSIVE, uc, curl_url_strerror(uc)); + EXCESSIVE, (int)uc, curl_url_strerror(uc)); curl_url_cleanup(u); } diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 6d42b3569478..e18b2d6508e7 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -27,7 +27,7 @@ * * Since the URL parser by default only accepts schemes that *this instance* * of libcurl supports, make sure that the test1560 file lists all the schemes - * that this test will assume to be present! + * that this test assumes to be present! */ #include "first.h" @@ -76,7 +76,7 @@ static int checkparts(CURLU *u, const char *in, const char *wanted, z ? " " : "", z ? z : ""); } else - curl_msnprintf(bufp, len, "%s[%d]", buf[0] ? " | " : "", rc); + curl_msnprintf(bufp, len, "%s[%d]", buf[0] ? " | " : "", (int)rc); n = strlen(bufp); bufp += n; @@ -153,6 +153,46 @@ struct clearurlcase { }; static const struct testcase get_parts_list[] = { + /* backslash mistakes */ + {"http:\\\\hostname", "", + CURLU_GUESS_SCHEME, 0, CURLUE_BACKSLASH }, + {"http:\\\\hostname:1234", "", + CURLU_GUESS_SCHEME, 0, CURLUE_BACKSLASH }, + {"http://hostname:1111\\path", "", + 0, 0, CURLUE_BACKSLASH }, + + /* non-supported URL without hostname */ + {"weird:///path", + "weird | [11] | [12] | [13] | | [15] | /path | [16] | [17]", + CURLU_NON_SUPPORT_SCHEME|CURLU_NO_AUTHORITY, 0, CURLUE_OK}, + /* non-supported URL without hostname, using path with multiple leading + slashes */ + {"weird:////path", + "weird | [11] | [12] | [13] | | [15] | //path | [16] | [17]", + CURLU_NON_SUPPORT_SCHEME|CURLU_NO_AUTHORITY, 0, CURLUE_OK}, + + /* RFC 4291 IPv4-Mapped IPv6 Addresses */ + {"https://[0:0:0:0:0:FFFF:129.144.52.38]:1234", + "https | [11] | [12] | [13] | [::ffff:129.144.52.38] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[::FFFF:127.0.0.1]:1234", + "https | [11] | [12] | [13] | [::ffff:127.0.0.1] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[::13.1.68.3]:1234", + "https | [11] | [12] | [13] | [::13.1.68.3] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[0:0:0:0:0:FFFF:7f00:0001]:1234", + "https | [11] | [12] | [13] | [::ffff:127.0.0.1] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[::FFFF:7f00:0001]:1234", + "https | [11] | [12] | [13] | [::ffff:127.0.0.1] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[%3A%3A13%2e1%2e68%2e3%25eth0]:1234", + "https | [11] | [12] | [13] | [::13.1.68.3] eth0 | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[0:0:0:0:0:0:0d01:4403]:1234", + "https | [11] | [12] | [13] | [::13.1.68.3] | 1234 " + "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, { /* query and fragments with control characters */ "http://host/path/?\001#\002", "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, @@ -196,20 +236,19 @@ static const struct testcase get_parts_list[] = { "http://host:00080/", "http | [11] | [12] | [13] | host | 80 | / | [16] | [17]", 0, 0, CURLUE_OK }, - { /* Single dot host - technically valid in some contexts but often - rejected */ + { /* Single dot host - not ok */ "http://./", - "http | [11] | [12] | [13] | . | [15] | / | [16] | [17]", - 0, 0, CURLUE_OK }, + "", + 0, 0, CURLUE_BAD_HOSTNAME }, { /* Host starting with a dash (RFC 1123 technically allows it, but many - parsers don't) */ + parsers do not) */ "http://-atest/", "http | [11] | [12] | [13] | -atest | [15] | / | [16] | [17]", 0, 0, CURLUE_OK }, - { /* Multiple trailing dots, not okay in DNS but works in /etc/hosts */ + { /* Multiple trailing dots is not okay */ "http://example.com../", - "http | [11] | [12] | [13] | example.com.. | [15] | / | [16] | [17]", - 0, 0, CURLUE_OK }, + "", + 0, 0, CURLUE_BAD_HOSTNAME }, { /* Empty IPv6 Zone ID */ "http://[fe80::1%]/", "", 0, 0, CURLUE_BAD_IPV6 }, @@ -269,12 +308,11 @@ static const struct testcase get_parts_list[] = { "| [16] | [17]", 0, CURLU_URLDECODE, CURLUE_OK }, #ifdef USE_IDN - /* - https://sv.wikipedia.org/wiki/R%c3%a4ksm%c3%b6rg%c3%a5s - https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis - https://codepoints.net/U+00F6 Latin Small Letter O with Diaeresis - https://codepoints.net/U+00E5 Latin Small Letter A with Ring Above - */ + /* https://sv.wikipedia.org/wiki/R%c3%a4ksm%c3%b6rg%c3%a5s + https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis + https://codepoints.net/U+00F6 Latin Small Letter O with Diaeresis + https://codepoints.net/U+00E5 Latin Small Letter A with Ring Above + */ { "https://r\xc3\xa4ksm\xc3\xb6rg\xc3\xa5s.se", "https | [11] | [12] | [13] | xn--rksmrgs-5wao1o.se | " "[15] | / | [16] | [17]", 0, CURLU_PUNYCODE, CURLUE_OK }, @@ -292,15 +330,14 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | [30] | [15] | / | [16] | [17]", 0, CURLU_PUNYCODE, CURLUE_OK }, #endif - /* - https://codepoints.net/U+2102 Double-Struck Capital C - https://codepoints.net/U+1d64 Latin Subscript Small Letter U - https://codepoints.net/U+24c7 Circled Latin Capital Letter R - https://codepoints.net/U+2112 Script Capital L - https://codepoints.net/U+3002 Ideographic Full Stop - https://codepoints.net/U+1d412 Mathematical Bold Capital S - https://codepoints.net/U+1f134 Squared Latin Capital Letter E - */ + /* https://codepoints.net/U+2102 Double-Struck Capital C + https://codepoints.net/U+1d64 Latin Subscript Small Letter U + https://codepoints.net/U+24c7 Circled Latin Capital Letter R + https://codepoints.net/U+2112 Script Capital L + https://codepoints.net/U+3002 Ideographic Full Stop + https://codepoints.net/U+1d412 Mathematical Bold Capital S + https://codepoints.net/U+1f134 Squared Latin Capital Letter E + */ {"https://" "%e2%84%82%e1%b5%a4%e2%93%87%e2%84%92%e3%80%82%f0%9d%90%92%f0%9f%84%b4", "https | [11] | [12] | [13] | " @@ -327,6 +364,7 @@ static const struct testcase get_parts_list[] = { {"https://user@example.net?he l lo", "https | user | [12] | [13] | example.net | [15] | / | he l lo | [17]", CURLU_ALLOW_SPACE, 0, CURLUE_OK}, + {"https://exam|ple.net", "", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://exam{}[]ple.net", "", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://exam{ple.net", "", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://exam}ple.net", "", 0, 0, CURLUE_BAD_HOSTNAME}, @@ -532,9 +570,70 @@ static const struct testcase get_parts_list[] = { {"file:///hello.html", "file | [11] | [12] | [13] | [14] | [15] | /hello.html | [16] | [17]", 0, 0, CURLUE_OK}, + + /* verify that we get the right default ports */ {"https://127.0.0.1", "https | [11] | [12] | [13] | 127.0.0.1 | 443 | / | [16] | [17]", 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"http://127.0.0.1", + "http | [11] | [12] | [13] | 127.0.0.1 | 80 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"ftp://127.0.0.1", + "ftp | [11] | [12] | [13] | 127.0.0.1 | 21 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"ftps://127.0.0.1", + "ftps | [11] | [12] | [13] | 127.0.0.1 | 990 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"scp://127.0.0.1", + "scp | [11] | [12] | [13] | 127.0.0.1 | 22 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"sftp://127.0.0.1", + "sftp | [11] | [12] | [13] | 127.0.0.1 | 22 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"imap://127.0.0.1", + "imap | [11] | [12] | [13] | 127.0.0.1 | 143 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"imaps://127.0.0.1", + "imaps | [11] | [12] | [13] | 127.0.0.1 | 993 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"smtp://127.0.0.1", + "smtp | [11] | [12] | [13] | 127.0.0.1 | 25 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"smtps://127.0.0.1", + "smtps | [11] | [12] | [13] | 127.0.0.1 | 465 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"pop3://127.0.0.1", + "pop3 | [11] | [12] | [13] | 127.0.0.1 | 110 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"pop3s://127.0.0.1", + "pop3s | [11] | [12] | [13] | 127.0.0.1 | 995 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, +#ifndef CURL_DISABLE_WEBSOCKETS + {"ws://127.0.0.1", + "ws | [11] | [12] | [13] | 127.0.0.1 | 80 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"wss://127.0.0.1", + "wss | [11] | [12] | [13] | 127.0.0.1 | 443 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, +#endif + {"telnet://127.0.0.1", + "telnet | [11] | [12] | [13] | 127.0.0.1 | 23 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"gopher://127.0.0.1", + "gopher | [11] | [12] | [13] | 127.0.0.1 | 70 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"gophers://127.0.0.1", + "gophers | [11] | [12] | [13] | 127.0.0.1 | 70 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, +#ifndef CURL_DISABLE_LDAP + {"ldap://127.0.0.1", + "ldap | [11] | [12] | [13] | 127.0.0.1 | 389 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"ldaps://127.0.0.1", + "ldaps | [11] | [12] | [13] | 127.0.0.1 | 636 | / | [16] | [17]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, +#endif + {"https://127.0.0.1", "https | [11] | [12] | [13] | 127.0.0.1 | [15] | / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, @@ -626,6 +725,86 @@ static const struct testcase get_parts_list[] = { }; static const struct urltestcase get_url_list[] = { + /* percent-encoded IP addresses */ + {"https://127.0.0.%31.", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://127.0.0.0%78f%46.", "https://127.0.0.255/", 0, 0, CURLUE_OK}, + {"https://%30%31%37%37%2e%31", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://[fe80%3A%3A20c%3A29ff%3Afe9c%3A409b]/", + "https://[fe80::20c:29ff:fe9c:409b]/", 0, 0, CURLUE_OK }, + + /* IPvFuture format */ + {"http://[v1.fe80::abcd]/", "", 0, 0, CURLUE_BAD_IPV6}, + + /* trailing dot on valid host */ + {"http://example.com./", "http://example.com./", 0, 0, CURLUE_OK}, + + /* the exact upper valid port boundary */ + {"http://host:65535/", "http://host:65535/", 0, 0, CURLUE_OK}, + + /* Internationalized path (not host). */ + {"https://example.com/r\xc3\xa4ksm\xc3\xb6rg\xc3\xa5s", + "https://example.com/r%C3%A4ksm%C3%B6rg%C3%A5s", + CURLU_URLENCODE, 0, CURLUE_OK}, + + /* weird fragments */ + {"http://host/#a#b", "http://host/#a#b", 0, 0, CURLUE_OK}, + + /* Empty query parameter values */ + {"http://host/?a=", "http://host/?a=", 0, 0, CURLUE_OK}, + {"http://host/?a=&b=", "http://host/?a=&b=", 0, 0, CURLUE_OK}, + + /* Percent-encoded userinfo */ + {"https://user%20name@example.com/", "https://user%20name@example.com/", + 0, 0, CURLUE_OK}, + {"https://user:pa%3Ass@example.com/", "https://user:pa%3Ass@example.com/", + 0, 0, CURLUE_OK}, + + /* malformed unbracketed IPv6 */ + {"https://fe80:8080::1/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"https://::1/", "", 0, 0, CURLUE_NO_HOST}, + + /* Empty host with standard schemes */ + {"http:///", "", 0, 0, CURLUE_NO_HOST}, + {"https://?q=1", "", 0, 0, CURLUE_NO_HOST}, + + /* Empty path segment normalization */ + {"http://example.com//", "http://example.com//", 0, 0, CURLUE_OK}, + {"http://example.com///foo", "http://example.com///foo", 0, 0, CURLUE_OK}, + + /* Empty user and password combinations */ + {"http://@example.com/", "http://@example.com/", 0, 0, CURLUE_OK}, + {"http://user:@example.com/", "http://user:@example.com/", 0, 0, CURLUE_OK}, + {"http://:password@example.com/", "http://:password@example.com/", + 0, 0, CURLUE_OK}, + + {"https://127.1.0x", "https://127.1.0x/", 0, 0, CURLUE_OK}, + {"https://127.0x", "https://127.0x/", 0, 0, CURLUE_OK}, + {"https://127.0x.1", "https://127.0x.1/", 0, 0, CURLUE_OK}, + {"https://127.1.1.0x", "https://127.1.1.0x/", 0, 0, CURLUE_OK}, + {"https://127.1.", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://127.1.:443", "https://127.0.0.1:443/", 0, 0, CURLUE_OK}, + {"https://127.1.?moo", "https://127.0.0.1/?moo", 0, 0, CURLUE_OK}, + {"https://127.1.#moo", "https://127.0.0.1/#moo", 0, 0, CURLUE_OK}, + {"https://127.1.a", "https://127.1.a/", 0, 0, CURLUE_OK}, + {"https://127.1..", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"https://127.1..:443", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"https://127.1..?moo", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"https://127.1..#moo", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"https://127.1.1.", "https://127.1.0.1/", 0, 0, CURLUE_OK}, + {"https://127.1.1./foo", "https://127.1.0.1/foo", 0, 0, CURLUE_OK}, + {"https://127.1.1.1.", "https://127.1.1.1/", 0, 0, CURLUE_OK}, + {"https://127.1", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://127.0.0.1.", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://127.0.0.0xff.", "https://127.0.0.255/", 0, 0, CURLUE_OK}, + {"https://127.0.0.1..", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"https://127.0.0.256..", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://hej./", "http://hej./", 0, 0, CURLUE_OK}, + {"http://hej../", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://hej.../", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://hej..../index.html", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://.", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://..", "", 0, 0, CURLUE_BAD_HOSTNAME}, + {"http://...", "", 0, 0, CURLUE_BAD_HOSTNAME}, {"018.0.0.0", "http://018.0.0.0/", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, {"08", "http://08/", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, {"0", "http://0.0.0.0/", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, @@ -678,6 +857,7 @@ static const struct urltestcase get_url_list[] = { {"https://0xffffffff", "https://255.255.255.255/", 0, 0, CURLUE_OK}, {"https://1.0x1000000", "https://1.0x1000000/", 0, 0, CURLUE_OK}, {"https://0x7f.1", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://0X7F.1", "https://127.0.0.1/", 0, 0, CURLUE_OK}, {"https://1.2.3.256.com", "https://1.2.3.256.com/", 0, 0, CURLUE_OK}, {"https://10.com", "https://10.com/", 0, 0, CURLUE_OK}, {"https://1.2.com", "https://1.2.com/", 0, 0, CURLUE_OK}, @@ -737,12 +917,14 @@ static const struct urltestcase get_url_list[] = { {"https://16843009", "https://1.1.1.1/", 0, 0, CURLUE_OK}, {"https://0177.1", "https://127.0.0.1/", 0, 0, CURLUE_OK}, {"https://0111.02.0x3", "https://73.2.0.3/", 0, 0, CURLUE_OK}, - {"https://0111.02.0x3.", "https://0111.02.0x3./", 0, 0, CURLUE_OK}, + {"https://0111.02.0X3", "https://73.2.0.3/", 0, 0, CURLUE_OK}, + {"https://0111.02.0x3.", "https://73.2.0.3/", 0, 0, CURLUE_OK}, {"https://0111.02.030", "https://73.2.0.24/", 0, 0, CURLUE_OK}, - {"https://0111.02.030.", "https://0111.02.030./", 0, 0, CURLUE_OK}, + {"https://0111.02.030.", "https://73.2.0.24/", 0, 0, CURLUE_OK}, {"https://0xff.0xff.0377.255", "https://255.255.255.255/", 0, 0, CURLUE_OK}, + {"https://0XFF.0XFF.0377.255", "https://255.255.255.255/", 0, 0, CURLUE_OK}, {"https://1.0xffffff", "https://1.255.255.255/", 0, 0, CURLUE_OK}, - /* IPv4 numerical overflows or syntax errors will not normalize */ + /* IPv4 numerical overflows or syntax errors do not normalize */ {"https://a127.0.0.1", "https://a127.0.0.1/", 0, 0, CURLUE_OK}, {"https://\xff.127.0.0.1", "https://%FF.127.0.0.1/", 0, CURLU_URLENCODE, CURLUE_OK}, @@ -766,8 +948,8 @@ static const struct urltestcase get_url_list[] = { "", 0, 0, CURLUE_BAD_IPV6}, {"https://[fe80::20c:29ff:fe9c:409b%25]:1234", - "https://[fe80::20c:29ff:fe9c:409b%2525]:1234/", - 0, 0, CURLUE_OK}, + "", + 0, 0, CURLUE_BAD_IPV6}, {"https://[fe80::20c:29ff:fe9c:409b%eth0]:1234", "https://[fe80::20c:29ff:fe9c:409b%25eth0]:1234/", 0, 0, CURLUE_OK}, @@ -786,9 +968,11 @@ static const struct urltestcase get_url_list[] = { {"pop3.example.com/path/html", "pop3://pop3.example.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#ifndef CURL_DISABLE_LDAP {"ldap.example.com/path/html", "ldap://ldap.example.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#endif {"imap.example.com/path/html", "imap://imap.example.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, @@ -807,9 +991,11 @@ static const struct urltestcase get_url_list[] = { {"pop3.com/path/html", "pop3://pop3.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#ifndef CURL_DISABLE_LDAP {"ldap.com/path/html", "ldap://ldap.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#endif {"imap.com/path/html", "imap://imap.com/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, @@ -825,9 +1011,11 @@ static const struct urltestcase get_url_list[] = { {"pop3/path/html", "http://pop3/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#ifndef CURL_DISABLE_LDAP {"ldap/path/html", "http://ldap/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, +#endif {"imap/path/html", "http://imap/path/html", CURLU_GUESS_SCHEME, 0, CURLUE_OK}, @@ -858,7 +1046,11 @@ static const struct urltestcase get_url_list[] = { {"file:///.", "file:///", 0, 0, CURLUE_OK}, {"file:///./", "file:///", 0, 0, CURLUE_OK}, {"file:///a", "file:///a", 0, 0, CURLUE_OK}, - {"file:./", "file://", 0, 0, CURLUE_OK}, + {"file:./", "", 0, 0, CURLUE_BAD_FILE_URL}, + {"file:foo", "", 0, 0, CURLUE_BAD_FILE_URL}, + {"file:foo/bar", "", 0, 0, CURLUE_BAD_FILE_URL}, + {"file:?q", "", 0, 0, CURLUE_BAD_FILE_URL}, + {"file:#f", "", 0, 0, CURLUE_BAD_FILE_URL}, {"http://example.com/hello/../here", "http://example.com/hello/../here", CURLU_PATH_AS_IS, 0, CURLUE_OK}, @@ -946,7 +1138,7 @@ static int checkurl(const char *org, const char *url, const char *out) /* 1. Set the URL 2. Set components 3. Extract all components (not URL) -*/ + */ static const struct setgetcase setget_parts_list[] = { {"https://example.com/", "query=\"\",", @@ -983,7 +1175,7 @@ static const struct setcase set_parts_list[] = { "https://example.com/one%20/$!$&'()*+;=:@{}[]%25", 0, CURLU_URLENCODE, CURLUE_OK, CURLUE_OK}, {NULL, /* start fresh! */ - "scheme=https,path=/,url=\"\",", /* incomplete url, redirect to "" */ + "scheme=https,path=/,url=\"\",", /* incomplete URL, redirect to "" */ "https://example.com/", 0, 0, CURLUE_OK, CURLUE_MALFORMED_INPUT}, {NULL, /* start fresh! */ @@ -1137,8 +1329,8 @@ static const struct setcase set_parts_list[] = { "https://host:1234/", 0, 0, CURLUE_OK, CURLUE_BAD_PORT_NUMBER}, {"https://host/", - "path=%4A%4B%4C,", - "https://host/%4a%4b%4c", + "path=%4A%4b%4C,", + "https://host/%4A%4B%4C", 0, 0, CURLUE_OK, CURLUE_OK}, {"https://host/mooo?q#f", "path=NULL,query=NULL,fragment=NULL,", @@ -1324,6 +1516,33 @@ static const struct redircase set_url_list[] = { "", /* blank redirect */ "https://example.com/", 0, 0, CURLUE_OK }, + {"file:///test?test#test", + "", "file:///test?test", + 0, 0, CURLUE_OK}, + {"https://example.com/path?query#frag", + "", "https://example.com/path?query", + 0, 0, CURLUE_OK}, + {"ftp://example.com/dir/file#anchor", + "", "ftp://example.com/dir/file", + 0, 0, CURLUE_OK}, + {"http://example.com/path#frag", + "", "http://example.com/path", + 0, 0, CURLUE_OK}, + {"http://example.com/#frag", + "", "http://example.com/", + 0, 0, CURLUE_OK}, + {"http://user:pass@example.com/path?query#frag", + "", "http://user:pass@example.com/path?query", + 0, 0, CURLUE_OK}, + {"http://example.com:8080/path?query#frag", + "", "http://example.com:8080/path?query", + 0, 0, CURLUE_OK}, + {"https://user:pass@example.com:8443/path?query#frag", + "", "https://user:pass@example.com:8443/path?query", + 0, 0, CURLUE_OK}, + {"http://[::1]/path#frag", + "", "http://[::1]/path", + 0, 0, CURLUE_OK}, {"http://firstplace.example.com/want/1314", "//somewhere.example.com/reply/1314", "http://somewhere.example.com/reply/1314", @@ -1416,19 +1635,26 @@ static const struct redircase set_url_list[] = { 0, 0, CURLUE_OK}, {"http://example.org/foo/bar", "#", - "http://example.org/foo/bar", - /* This happens because the parser removes empty fragments */ + "http://example.org/foo/bar#", 0, 0, CURLUE_OK}, {"http://example.org/foo/bar", "?", - "http://example.org/foo/bar", - /* This happens because the parser removes empty queries */ + "http://example.org/foo/bar?", 0, 0, CURLUE_OK}, {"http://example.org/foo/bar", "?#", - "http://example.org/foo/bar", - /* This happens because the parser removes empty queries and fragments */ + "http://example.org/foo/bar?#", 0, 0, CURLUE_OK}, + {"http://host/path?", "#new", + "http://host/path?#new", 0, 0, CURLUE_OK}, + {"http://host/path?#", "#new", + "http://host/path?#new", 0, 0, CURLUE_OK}, + {"http://host/path#", "?new", + "http://host/path?new", 0, 0, CURLUE_OK}, + {"http://host/path?#", "?new", + "http://host/path?new", 0, 0, CURLUE_OK}, + {"http://host/path?#", "sub", + "http://host/sub", 0, 0, CURLUE_OK}, {"http://example.com/please/../gimme/%TESTNUMBER?foobar#hello", "http://example.net/there/it/is/../../tes t case=/%TESTNUMBER0002? yes no", "http://example.net/there/tes%20t%20case=/%TESTNUMBER0002?+yes+no", @@ -1486,8 +1712,10 @@ static int set_url(void) for(i = 0; set_url_list[i].in && !error; i++) { CURLUcode rc; CURLU *urlp = curl_url(); - if(!urlp) + if(!urlp) { + error++; break; + } rc = curl_url_set(urlp, CURLUPART_URL, set_url_list[i].in, set_url_list[i].urlflags); if(!rc) { @@ -1496,28 +1724,26 @@ static int set_url(void) if(rc) { curl_mfprintf(stderr, "%s:%d Set URL %s returned %d (%s)\n", __FILE__, __LINE__, set_url_list[i].set, - rc, curl_url_strerror(rc)); + (int)rc, curl_url_strerror(rc)); error++; } else { char *url = NULL; - rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); + rc = curl_url_get(urlp, CURLUPART_URL, &url, CURLU_GET_EMPTY); if(rc) { curl_mfprintf(stderr, "%s:%d Get URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } - else { - if(checkurl(set_url_list[i].in, url, set_url_list[i].out)) { - error++; - } + else if(checkurl(set_url_list[i].in, url, set_url_list[i].out)) { + error++; } curl_free(url); } } else if(rc != set_url_list[i].ucode) { curl_mfprintf(stderr, "Set URL\nin: %s\nreturned %d (expected %d)\n", - set_url_list[i].in, rc, set_url_list[i].ucode); + set_url_list[i].in, (int)rc, (int)set_url_list[i].ucode); error++; } curl_url_cleanup(urlp); @@ -1528,7 +1754,7 @@ static int set_url(void) /* 1. Set a URL 2. Set one or more parts 3. Extract and compare all parts - not the URL -*/ + */ static int setget_parts(bool has_utf8) { int i; @@ -1549,15 +1775,14 @@ static int setget_parts(bool has_utf8) else rc = CURLUE_OK; if(!rc) { - char *url = NULL; CURLUcode uc = updateurl(urlp, setget_parts_list[i].set, setget_parts_list[i].setflags); if(uc != setget_parts_list[i].pcode) { curl_mfprintf(stderr, "updateurl\nin: %s\nreturned %d (expected %d)\n", - setget_parts_list[i].set, uc, - setget_parts_list[i].pcode); + setget_parts_list[i].set, + (int)uc, (int)setget_parts_list[i].pcode); error++; } if(!uc) { @@ -1567,11 +1792,10 @@ static int setget_parts(bool has_utf8) setget_parts_list[i].getflags)) error++; /* add */ } - curl_free(url); } else if(rc != CURLUE_OK) { curl_mfprintf(stderr, "Set parts\nin: %s\nreturned %d (expected %d)\n", - setget_parts_list[i].in, rc, 0); + setget_parts_list[i].in, (int)rc, 0); error++; } } @@ -1604,16 +1828,16 @@ static int set_parts(void) if(uc != set_parts_list[i].pcode) { curl_mfprintf(stderr, "updateurl\nin: %s\nreturned %d (expected %d)\n", - set_parts_list[i].set, uc, set_parts_list[i].pcode); + set_parts_list[i].set, + (int)uc, (int)set_parts_list[i].pcode); error++; } if(!uc) { /* only do this if it worked */ rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); - if(rc) { curl_mfprintf(stderr, "%s:%d Get URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else if(checkurl(set_parts_list[i].in, url, set_parts_list[i].out)) { @@ -1624,7 +1848,8 @@ static int set_parts(void) } else if(rc != set_parts_list[i].ucode) { curl_mfprintf(stderr, "Set parts\nin: %s\nreturned %d (expected %d)\n", - set_parts_list[i].in, rc, set_parts_list[i].ucode); + set_parts_list[i].in, + (int)rc, (int)set_parts_list[i].ucode); error++; } curl_url_cleanup(urlp); @@ -1650,23 +1875,20 @@ static int get_url(bool has_utf8) if(!rc) { char *url = NULL; rc = curl_url_get(urlp, CURLUPART_URL, &url, get_url_list[i].getflags); - if(rc) { curl_mfprintf(stderr, "%s:%d returned %d (%s). URL: '%s'\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc), + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc), get_url_list[i].in); error++; } - else { - if(checkurl(get_url_list[i].in, url, get_url_list[i].out)) { - error++; - } + else if(checkurl(get_url_list[i].in, url, get_url_list[i].out)) { + error++; } curl_free(url); } if(rc != get_url_list[i].ucode) { curl_mfprintf(stderr, "Get URL\nin: %s\nreturned %d (expected %d)\n", - get_url_list[i].in, rc, get_url_list[i].ucode); + get_url_list[i].in, (int)rc, (int)get_url_list[i].ucode); error++; } } @@ -1693,7 +1915,8 @@ static int get_parts(bool has_utf8) get_parts_list[i].urlflags); if(rc != get_parts_list[i].ucode) { curl_mfprintf(stderr, "Get parts\nin: %s\nreturned %d (expected %d)\n", - get_parts_list[i].in, rc, get_parts_list[i].ucode); + get_parts_list[i].in, + (int)rc, (int)get_parts_list[i].ucode); error++; } else if(get_parts_list[i].ucode) { @@ -1750,7 +1973,7 @@ static int append(void) ; else if(rc != append_list[i].ucode) { curl_mfprintf(stderr, "Append\nin: %s\nreturned %d (expected %d)\n", - append_list[i].in, rc, append_list[i].ucode); + append_list[i].in, (int)rc, (int)append_list[i].ucode); error++; } else if(append_list[i].ucode) { @@ -1761,7 +1984,7 @@ static int append(void) rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); if(rc) { curl_mfprintf(stderr, "%s:%d Get URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1782,12 +2005,14 @@ static int scopeid(void) int error = 0; CURLUcode rc; char *url; + if(!u) + return 1; rc = curl_url_set(u, CURLUPART_URL, "https://[fe80::20c:29ff:fe9c:409b%25eth0]/hello.html", 0); if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_set returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } @@ -1795,7 +2020,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_HOST returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1806,7 +2031,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_set CURLUPART_HOST returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } @@ -1814,7 +2039,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1825,7 +2050,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_set CURLUPART_HOST returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } @@ -1833,19 +2058,18 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { curl_free(url); } - rc = curl_url_set(u, CURLUPART_HOST, - "[fe80::20c:29ff:fe9c:409b%25eth0]", 0); + rc = curl_url_set(u, CURLUPART_HOST, "[fe80::20c:29ff:fe9c:409b%25eth0]", 0); if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_set CURLUPART_HOST returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } @@ -1853,7 +2077,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1864,7 +2088,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_HOST returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1875,7 +2099,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_ZONEID returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1886,7 +2110,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_set CURLUPART_ZONEID returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } @@ -1894,7 +2118,7 @@ static int scopeid(void) if(rc != CURLUE_OK) { curl_mfprintf(stderr, "%s:%d curl_url_get CURLUPART_URL returned %d (%s)\n", - __FILE__, __LINE__, rc, curl_url_strerror(rc)); + __FILE__, __LINE__, (int)rc, curl_url_strerror(rc)); error++; } else { @@ -1909,52 +2133,80 @@ static int scopeid(void) static int get_nothing(void) { CURLU *u = curl_url(); - if(u) { - char *p; - CURLUcode rc; - - rc = curl_url_get(u, CURLUPART_SCHEME, &p, 0); - if(rc != CURLUE_NO_SCHEME) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + int error = 0; + CURLUcode rc; + char *p = NULL; + if(!u) + return 1; - rc = curl_url_get(u, CURLUPART_HOST, &p, 0); - if(rc != CURLUE_NO_HOST) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_SCHEME, &p, 0); + if(rc != CURLUE_NO_SCHEME) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_USER, &p, 0); - if(rc != CURLUE_NO_USER) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_HOST, &p, 0); + if(rc != CURLUE_NO_HOST) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_PASSWORD, &p, 0); - if(rc != CURLUE_NO_PASSWORD) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_USER, &p, 0); + if(rc != CURLUE_NO_USER) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_OPTIONS, &p, 0); - if(rc != CURLUE_NO_OPTIONS) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_PASSWORD, &p, 0); + if(rc != CURLUE_NO_PASSWORD) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_PATH, &p, 0); - if(rc != CURLUE_OK) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); - else - curl_free(p); + rc = curl_url_get(u, CURLUPART_OPTIONS, &p, 0); + if(rc != CURLUE_NO_OPTIONS) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_QUERY, &p, 0); - if(rc != CURLUE_NO_QUERY) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_PATH, &p, 0); + if(rc != CURLUE_OK) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + } + else + curl_free(p); - rc = curl_url_get(u, CURLUPART_FRAGMENT, &p, 0); - if(rc != CURLUE_NO_FRAGMENT) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_get(u, CURLUPART_QUERY, &p, 0); + if(rc != CURLUE_NO_QUERY) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - rc = curl_url_get(u, CURLUPART_ZONEID, &p, 0); - if(rc != CURLUE_NO_ZONEID) - curl_mfprintf(stderr, "unexpected return code %d on line %d\n", rc, - __LINE__); + rc = curl_url_get(u, CURLUPART_FRAGMENT, &p, 0); + if(rc != CURLUE_NO_FRAGMENT) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + curl_free(p); + } - curl_url_cleanup(u); + rc = curl_url_get(u, CURLUPART_ZONEID, &p, 0); + if(rc != CURLUE_NO_ZONEID) { + curl_mfprintf(stderr, "unexpected return code %d on line %d\n", (int)rc, + __LINE__); + error++; + curl_free(p); } - return 0; + + curl_url_cleanup(u); + + return error; } static const struct clearurlcase clear_url_list[] = { @@ -1975,29 +2227,33 @@ static int clear_url(void) { CURLU *u = curl_url(); int i, error = 0; - if(u) { - char *p = NULL; - CURLUcode rc; + CURLUcode rc; + char *p = NULL; + if(!u) + return 1; - for(i = 0; clear_url_list[i].in && !error; i++) { - rc = curl_url_set(u, clear_url_list[i].part, clear_url_list[i].in, 0); - if(rc != CURLUE_OK) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + for(i = 0; clear_url_list[i].in && !error; i++) { + rc = curl_url_set(u, clear_url_list[i].part, clear_url_list[i].in, 0); + if(rc != CURLUE_OK) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + } - rc = curl_url_set(u, CURLUPART_URL, NULL, 0); - if(rc != CURLUE_OK) - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + rc = curl_url_set(u, CURLUPART_URL, NULL, 0); + if(rc != CURLUE_OK) { + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; + } - rc = curl_url_get(u, clear_url_list[i].part, &p, 0); - if(rc != clear_url_list[i].ucode || - (clear_url_list[i].out && strcmp(p, clear_url_list[i].out) != 0)) { + rc = curl_url_get(u, clear_url_list[i].part, &p, 0); + if(rc != clear_url_list[i].ucode || + (p && clear_url_list[i].out && strcmp(p, clear_url_list[i].out))) { - curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); - error++; - } - if(rc == CURLUE_OK) - curl_free(p); + curl_mfprintf(stderr, "unexpected return code line %d\n", __LINE__); + error++; } + if(rc == CURLUE_OK) + curl_free(p); } curl_url_cleanup(u); @@ -2013,7 +2269,7 @@ static char bigpart[120000]; */ static int huge(void) { - static const char *smallpart = "c"; + static const char smallpart[] = "c"; int i; CURLU *urlp = curl_url(); CURLUcode rc; @@ -2055,7 +2311,7 @@ static int huge(void) if(!rc) { curl_url_get(urlp, part[i], &partp, 0); if(!partp || strcmp(partp, &bigpart[1 - (i == 4)])) { - curl_mprintf("URL %d part %u: failure\n", i, part[i]); + curl_mprintf("URL %d part %d: failure\n", i, (int)part[i]); error++; } curl_free(partp); @@ -2067,7 +2323,7 @@ static int huge(void) static int urldup(void) { - static const char *url[] = { + static const char * const url[] = { "http://" "user:pwd@" "[2a04:4e42:e00::347%25eth0]" @@ -2093,8 +2349,7 @@ static int urldup(void) goto err; for(i = 0; url[i]; i++) { - CURLUcode rc = curl_url_set(h, CURLUPART_URL, url[i], - CURLU_GUESS_SCHEME); + CURLUcode rc = curl_url_set(h, CURLUPART_URL, url[i], CURLU_GUESS_SCHEME); if(rc) goto err; copy = curl_url_dup(h); @@ -2132,7 +2387,7 @@ static int urldup(void) static int test_api_errors(void) { CURLU *u = curl_url(); - char *p; + char *p = NULL; CURLUcode rc; if(!u) return 1; @@ -2161,12 +2416,98 @@ static int test_api_errors(void) return 0; } +struct guessscheme1560 { + const char *url; + bool expectedfail; /* the URL should fail to parse */ + unsigned int setflags; + const char *scheme; + unsigned int getflags; +}; + +static int test_scheme_guess(void) +{ + const struct guessscheme1560 g[] = { + { "https://example.com", FALSE, 0, "https", 0 }, + { "example.com", TRUE, 0, NULL, 0 }, + { "example.com", FALSE, CURLU_GUESS_SCHEME, "http", 0 }, + { "ftp.example.com:22", FALSE, CURLU_GUESS_SCHEME, "ftp", 0 }, + { "ftp.example.com/foo", FALSE, CURLU_GUESS_SCHEME, + NULL, CURLU_NO_GUESS_SCHEME }, + { "dict.example.com:33", FALSE, CURLU_GUESS_SCHEME, "dict", 0 }, + { "dict.example.com/bar", FALSE, CURLU_GUESS_SCHEME, + NULL, CURLU_NO_GUESS_SCHEME }, + { "ldap.example.com#none", FALSE, CURLU_GUESS_SCHEME, "ldap", 0 }, + { "ldap.example.com?special", FALSE, CURLU_GUESS_SCHEME, + NULL, CURLU_NO_GUESS_SCHEME }, + { "smtp.example.com?hey#ho", FALSE, CURLU_GUESS_SCHEME, "smtp", 0 }, + { "smtp.example.com:99/moo", FALSE, CURLU_GUESS_SCHEME, + NULL, CURLU_NO_GUESS_SCHEME }, + { "pop3.example.com:100#foobar", FALSE, CURLU_GUESS_SCHEME, "pop3", 0 }, + { "pop3.example.com:101?gg", FALSE, CURLU_GUESS_SCHEME, + NULL, CURLU_NO_GUESS_SCHEME }, + { "example.com", FALSE, CURLU_GUESS_SCHEME, NULL, CURLU_NO_GUESS_SCHEME }, + { "https://example.com", FALSE, CURLU_GUESS_SCHEME, + "https", CURLU_NO_GUESS_SCHEME }, + { "foobar://example.com", FALSE, + CURLU_GUESS_SCHEME|CURLU_NON_SUPPORT_SCHEME, + "foobar", CURLU_NO_GUESS_SCHEME }, + { "foobar://example.com", FALSE, CURLU_NON_SUPPORT_SCHEME, + "foobar", CURLU_NO_GUESS_SCHEME }, + { "foobar://example.com", FALSE, CURLU_NON_SUPPORT_SCHEME, "foobar", 0 }, + }; + int error = 0; + unsigned int i; + for(i = 0; i < CURL_ARRAYSIZE(g) && !error; i++) { + CURLU *u = curl_url(); + char *schemep = NULL; + if(u) { + int rc = curl_url_set(u, CURLUPART_URL, g[i].url, g[i].setflags); + if(!rc) { + rc = curl_url_get(u, CURLUPART_SCHEME, &schemep, g[i].getflags); + if(!rc && !schemep) { + curl_mfprintf(stderr, "%u: returned success but no scheme\n", i); + error++; + } + else if(rc && schemep) { + curl_mfprintf(stderr, "%u: returned error but with scheme\n", i); + error++; + } + } + else if(!g[i].expectedfail) { + curl_mfprintf(stderr, "%u: URL parsing failed unexpectedly\n", i); + error++; + } + } + if(!schemep && g[i].scheme) { + curl_mfprintf(stderr, "%u: got no scheme when %s was expected\n", i, + g[i].scheme); + error++; + } + else if(schemep && !g[i].scheme) { + curl_mfprintf(stderr, "%u: got scheme %s when none was expected\n", i, + schemep); + error++; + } + else if(schemep && g[i].scheme && strcmp(schemep, g[i].scheme)) { + curl_mfprintf(stderr, "%u: got scheme %s when %s was expected\n", i, + schemep, g[i].scheme); + error++; + } + curl_free(schemep); + curl_url_cleanup(u); + } + return error; +} + static CURLcode test_lib1560(const char *URL) { bool has_utf8 = !!getenv("CURL_TEST_HAVE_CODESET_UTF8"); (void)URL; + if(test_scheme_guess()) + return (CURLcode)13; + if(test_api_errors()) return (CURLcode)12; diff --git a/tests/libtest/lib1565.c b/tests/libtest/lib1565.c index e6c266a7e5f4..825d88e2ed4d 100644 --- a/tests/libtest/lib1565.c +++ b/tests/libtest/lib1565.c @@ -93,7 +93,7 @@ static CURLcode test_lib1565(const char *URL) int started_num = 0; int finished_num = 0; pthread_t tid = 0; - bool tid_valid = false; + bool tid_valid = FALSE; struct CURLMsg *message; start_test_timing(); @@ -106,7 +106,7 @@ static CURLcode test_lib1565(const char *URL) rc = pthread_create(&tid, NULL, t1565_run_thread, NULL); if(!rc) - tid_valid = true; + tid_valid = TRUE; else { curl_mfprintf(stderr, "%s:%d Could not create thread, errno %d\n", __FILE__, __LINE__, rc); @@ -130,7 +130,7 @@ static CURLcode test_lib1565(const char *URL) else { curl_mfprintf(stderr, "%s:%d Got an unexpected message from curl: %d\n", - __FILE__, __LINE__, message->msg); + __FILE__, __LINE__, (int)message->msg); result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } diff --git a/tests/libtest/lib1571.c b/tests/libtest/lib1571.c index adb6cc21554a..2cf218bf224f 100644 --- a/tests/libtest/lib1571.c +++ b/tests/libtest/lib1571.c @@ -40,23 +40,23 @@ static CURLcode test_lib1571(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); if((testnum == 1571) || (testnum == 1572) || (testnum == 1575) || (testnum == 1581)) { - test_setopt(curl, CURLOPT_POSTFIELDS, "moo"); + easy_setopt(curl, CURLOPT_POSTFIELDS, "moo"); } if(testnum == 1581) { - test_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); + easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); } - test_setopt(curl, CURLOPT_CUSTOMREQUEST, "IGLOO"); + easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "IGLOO"); if((testnum == 1574) || (testnum == 1575)) { - test_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_FIRSTONLY); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_FIRSTONLY); } else { - test_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE); } result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1576.c b/tests/libtest/lib1576.c index 5357f2ec13fe..a052519a257c 100644 --- a/tests/libtest/lib1576.c +++ b/tests/libtest/lib1576.c @@ -23,8 +23,9 @@ ***************************************************************************/ #include "first.h" -static char t1576_data[] = "request indicates that the client, which made"; -static size_t const t1576_datalen = sizeof(t1576_data) - 1; +static const char t1576_data[] = "request indicates that the client, " + "which made"; +static const size_t t1576_datalen = CURL_CSTRLEN(t1576_data); static size_t t1576_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { @@ -64,20 +65,20 @@ static CURLcode test_lib1576(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1576_read_cb); - test_setopt(curl, CURLOPT_SEEKFUNCTION, t1576_seek_callback); - test_setopt(curl, CURLOPT_INFILESIZE, (long)t1576_datalen); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1576_read_cb); + easy_setopt(curl, CURLOPT_SEEKFUNCTION, t1576_seek_callback); + easy_setopt(curl, CURLOPT_INFILESIZE, (long)t1576_datalen); - test_setopt(curl, CURLOPT_CUSTOMREQUEST, "CURL"); + easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "CURL"); if(testnum == 1578 || testnum == 1580) { - test_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_FIRSTONLY); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_FIRSTONLY); } else { - test_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE); } /* Remove "Expect: 100-continue" */ pHeaderList = curl_slist_append(pHeaderList, "Expect:"); diff --git a/tests/libtest/lib1582.c b/tests/libtest/lib1582.c index 31e8d17c41c1..81f8664ef308 100644 --- a/tests/libtest/lib1582.c +++ b/tests/libtest/lib1582.c @@ -40,13 +40,13 @@ static CURLcode test_lib1582(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_NEGOTIATE); - test_setopt(curl, CURLOPT_USERPWD, ":"); - test_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - test_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE); + easy_setopt(curl, CURLOPT_USERPWD, ":"); + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1587.c b/tests/libtest/lib1587.c index ad66c003f0a1..9368b53d0b7f 100644 --- a/tests/libtest/lib1587.c +++ b/tests/libtest/lib1587.c @@ -21,23 +21,11 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ - #include "first.h" #ifdef USE_OPENSSL - -#include -#include -#include #include -#ifdef HAVE_BORINGSSL_LIKE -/* BoringSSL and AWS-LC */ -typedef uint32_t opt1587; -#else -typedef uint64_t opt1587; -#endif - static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) { const struct curl_tlssessioninfo *info; @@ -51,7 +39,7 @@ static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) if(result == CURLE_OK) { /* set and read stuff using the SSL_CTX to verify it */ - opt1587 opts = SSL_CTX_get_options(info->internals); + ctx_option_t opts = SSL_CTX_get_options(info->internals); SSL_CTX_set_options(info->internals, opts); curl_mprintf("CURLINFO_TLS_SESSION: OK\n"); } @@ -60,7 +48,7 @@ static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) if(result == CURLE_OK) { /* set and read stuff using the SSL pointer to verify it */ - opt1587 opts = SSL_get_options(info->internals); + ctx_option_t opts = SSL_get_options(info->internals); SSL_set_options(info->internals, opts); curl_mprintf("CURLINFO_TLS_SSL_PTR: OK\n"); } diff --git a/tests/libtest/lib1588.c b/tests/libtest/lib1588.c index fd5cd4717095..896a08a8544c 100644 --- a/tests/libtest/lib1588.c +++ b/tests/libtest/lib1588.c @@ -102,7 +102,7 @@ static CURLcode test_lib1588(const char *URL) curl_msnprintf(proxy2_resolve, sizeof(proxy2_resolve), "secondproxy:%s:%s", libtest_arg3, libtest_arg2); - /* we connect to the fake host name but the right port number */ + /* we connect to the fake hostname but the right port number */ curl_msnprintf(proxy1_connect, sizeof(proxy1_connect), "firstproxy:%s", libtest_arg3); curl_msnprintf(proxy2_connect, sizeof(proxy2_connect), diff --git a/tests/libtest/lib1589.c b/tests/libtest/lib1589.c new file mode 100644 index 000000000000..e8d029034aa4 --- /dev/null +++ b/tests/libtest/lib1589.c @@ -0,0 +1,132 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = URL + * argv2 = proxy host + * argv3 = proxy1 port + * argv4 = proxy2 port + * argv5 = proxyuser:password + */ + +#include "first.h" + +static CURLcode init1589(CURL *curl, const char *url, + const char *userpwd, const char *proxy, + int port) +{ + CURLcode result = CURLE_OK; + + res_easy_setopt(curl, CURLOPT_URL, url); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXY, proxy); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXYPORT, (long)port); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXYUSERPWD, userpwd); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(result) + goto init_failed; +#if 0 + res_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + if(result) + goto init_failed; +#endif + + res_easy_setopt(curl, CURLOPT_HEADER, 1L); + if(result) + goto init_failed; + + return CURLE_OK; /* success */ + +init_failed: + return result; /* failure */ +} + +static CURLcode run1589(CURL *curl, const char *url, const char *userpwd, + const char *proxy, int port) +{ + CURLcode result = CURLE_OK; + + result = init1589(curl, url, userpwd, proxy, port); + if(result) + return result; + + return curl_easy_perform(curl); +} + +static CURLcode test_lib1589(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + const char *proxy = libtest_arg2; + /* !checksrc! disable BANNEDFUNC 2 */ + int port1 = atoi(libtest_arg3); + int port2 = atoi(libtest_arg4); + const char *proxyuserpwd = libtest_arg5; + + if(test_argc < 5) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + result = run1589(curl, URL, proxyuserpwd, proxy, port1); + if(result) + goto test_cleanup; + + curl_mfprintf(stderr, "lib1589: now we do the request again\n"); + + result = run1589(curl, URL, proxyuserpwd, proxy, port2); + +test_cleanup: + + /* proper cleanup sequence - type PB */ + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1591.c b/tests/libtest/lib1591.c index 5b2b913037b8..427f9665645c 100644 --- a/tests/libtest/lib1591.c +++ b/tests/libtest/lib1591.c @@ -23,7 +23,7 @@ ***************************************************************************/ /* - * This unit test PUT http data over proxy. Proxy header will be different + * This unit test PUT http data over proxy. Proxy header is different * from server http header */ @@ -34,15 +34,16 @@ static size_t consumed = 0; static size_t t1591_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { static const char testdata[] = "Hello Cloud!\r\n"; + static const size_t datalen = CURL_CSTRLEN(testdata); size_t amount = nmemb * size; /* Total bytes curl wants */ - if(consumed == strlen(testdata)) { + if(consumed == datalen) { return 0; } - if(amount > strlen(testdata) - consumed) { - amount = strlen(testdata); + if(amount > datalen - consumed) { + amount = datalen - consumed; } consumed += amount; @@ -97,12 +98,12 @@ static CURLcode test_lib1591(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1591_read_cb); - test_setopt(curl, CURLOPT_TRAILERFUNCTION, t1591_trailers_callback); - test_setopt(curl, CURLOPT_TRAILERDATA, NULL); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1591_read_cb); + easy_setopt(curl, CURLOPT_TRAILERFUNCTION, t1591_trailers_callback); + easy_setopt(curl, CURLOPT_TRAILERDATA, NULL); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1592.c b/tests/libtest/lib1592.c index 29dbe0b70033..497659b10062 100644 --- a/tests/libtest/lib1592.c +++ b/tests/libtest/lib1592.c @@ -29,12 +29,11 @@ * only tests whichever resolver curl is actually built with. */ -/* We are willing to wait a generous two seconds for the removal. This is - as low as we can go while still easily supporting SIGALRM timing for the - non-threaded blocking resolver. It does not matter that much because when - the test passes, we never wait this long. We set it much higher via - the default TEST_HANG_TIMEOUT to avoid issues when running on overloaded - CI machines. */ +/* We are waiting a generous two seconds for the removal. This is as low as we + can go while still easily supporting SIGALRM timing for the non-threaded + blocking resolver. It does not matter that much because when the test + passes, we never wait this long. We set it much higher via the default + TEST_HANG_TIMEOUT to avoid issues when running on overloaded CI machines. */ #include "first.h" @@ -56,34 +55,33 @@ static CURLcode test_lib1592(const char *URL) easy_setopt(curl, CURLOPT_VERBOSE, 1L); easy_setopt(curl, CURLOPT_URL, URL); - /* Set a DNS server that hopefully will not respond when using c-ares. */ + /* Set a DNS server that hopefully does not respond when using c-ares. */ if(curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, "0.0.0.0") == CURLE_OK) /* Since we could set the DNS server, presume we are working with a resolver that can be cancelled (i.e. c-ares). Thus, curl_multi_remove_handle() should not block even when the resolver - request is outstanding. So, set a request timeout _longer_ than the - test hang timeout so we will fail if the handle removal call incorrectly + request is outstanding. Thus, set a request timeout _longer_ than the + test hang timeout so we fail if the handle removal call incorrectly blocks. */ timeout = TEST_HANG_TIMEOUT * 2; else { /* If we cannot set the DNS server, presume that we are configured to use a resolver that cannot be cancelled (i.e. the threaded resolver or the - non-threaded blocking resolver). So, we just test that the + non-threaded blocking resolver). Thus, we test that the curl_multi_remove_handle() call does finish well within our test timeout. - But, it is unlikely that the resolver request will take any time at + But, it is unlikely that the resolver request takes any time at all because we have not been able to configure the resolver to use an - non-responsive DNS server. At least we exercise the flow. - */ + non-responsive DNS server. At least we exercise the flow. */ curl_mfprintf(stderr, "CURLOPT_DNS_SERVERS not supported; " - "assuming curl_multi_remove_handle() will block\n"); + "assuming curl_multi_remove_handle() does block\n"); timeout = TEST_HANG_TIMEOUT / 2; } /* Setting a timeout on the request should ensure that even if we have to - wait for the resolver during curl_multi_remove_handle(), it will not take + wait for the resolver during curl_multi_remove_handle(), it does not take longer than this, because the resolver request inherits its timeout from this. */ easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout); diff --git a/tests/libtest/lib1597.c b/tests/libtest/lib1597.c index 5e3fab8cade0..11da472c4d8f 100644 --- a/tests/libtest/lib1597.c +++ b/tests/libtest/lib1597.c @@ -96,7 +96,8 @@ static CURLcode test_lib1597(const char *URL) for(i = 0; prots[i].in; i++) { result = curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, prots[i].in); if(result != *prots[i].result_exp) { - curl_mprintf("unexpectedly '%s' returned %d\n", prots[i].in, result); + curl_mprintf("unexpectedly '%s' returned %d\n", prots[i].in, + (int)result); break; } } diff --git a/tests/libtest/lib1598.c b/tests/libtest/lib1598.c index 50d715ffe772..106a339c0702 100644 --- a/tests/libtest/lib1598.c +++ b/tests/libtest/lib1598.c @@ -23,7 +23,7 @@ ***************************************************************************/ /* - * This unit test PUT http data over proxy. Proxy header will be different + * This unit test PUT http data over proxy. Proxy header is different * from server http header */ @@ -52,7 +52,7 @@ static int t1598_trailers_callback(struct curl_slist **list, void *userdata) static CURLcode test_lib1598(const char *URL) { - static const char *post_data = "xxx=yyy&aaa=bbbbb"; + static const char post_data[] = "xxx=yyy&aaa=bbbbb"; CURL *curl = NULL; CURLcode result = CURLE_FAILED_INIT; @@ -82,13 +82,13 @@ static CURLcode test_lib1598(const char *URL) hhl = list; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPHEADER, hhl); - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(post_data)); - test_setopt(curl, CURLOPT_POSTFIELDS, post_data); - test_setopt(curl, CURLOPT_TRAILERFUNCTION, t1598_trailers_callback); - test_setopt(curl, CURLOPT_TRAILERDATA, NULL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPHEADER, hhl); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)CURL_CSTRLEN(post_data)); + easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); + easy_setopt(curl, CURLOPT_TRAILERFUNCTION, t1598_trailers_callback); + easy_setopt(curl, CURLOPT_TRAILERDATA, NULL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1647.c b/tests/libtest/lib1647.c new file mode 100644 index 000000000000..8060e1bfe954 --- /dev/null +++ b/tests/libtest/lib1647.c @@ -0,0 +1,120 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * argv1 = the first URL + * argv2 = URL2 + * argv3 = credentials 1 + * argv4 = credentials 2 + */ + +#include "first.h" + +/* this is meant to pick up the proxy from the environment variable */ +static CURLcode init1647(CURL *curl, const char *url, const char *userpwd) +{ + CURLcode result = CURLE_OK; + + res_easy_setopt(curl, CURLOPT_URL, url); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXYUSERPWD, userpwd); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXY_SSL_VERIFYPEER, 0L); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXY_SSL_VERIFYHOST, 0L); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(result) + goto init_failed; + + return CURLE_OK; /* success */ + +init_failed: + return result; /* failure */ +} + +static CURLcode run1647(CURL *curl, const char *url, const char *userpwd) +{ + CURLcode result = CURLE_OK; + + result = init1647(curl, url, userpwd); + if(result) + return result; + + return curl_easy_perform(curl); +} + +static CURLcode test_lib1647(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + curl_mprintf("--- First get '%s'\n", URL); + result = run1647(curl, URL, libtest_arg3); + if(result) + goto test_cleanup; + + curl_mprintf("--- Then get '%s'\n", libtest_arg2); + result = run1647(curl, libtest_arg2, libtest_arg4); + +test_cleanup: + + /* proper cleanup sequence - type PB */ + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1648.c b/tests/libtest/lib1648.c new file mode 100644 index 000000000000..91afbcead4bc --- /dev/null +++ b/tests/libtest/lib1648.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 + * + ***************************************************************************/ +/* + * URL = host + * arg2 = port + */ + +#include "first.h" + +/* this is meant to pick up the proxy from the environment variable */ +static CURLcode init1648(CURL *curl, const char *url, const char *proxy) +{ + CURLcode result = CURLE_OK; + + res_easy_setopt(curl, CURLOPT_URL, url); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_PROXY, proxy); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(result) + goto init_failed; + + return CURLE_OK; /* success */ + +init_failed: + return result; /* failure */ +} + +static CURLcode run1648(CURL *curl, const char *url, const char *proxy) +{ + CURLcode result = CURLE_OK; + + result = init1648(curl, url, proxy); + if(result) + return result; + + return curl_easy_perform(curl); +} + +#define GET_THIS "http://example.com/" + +/* + * First get the URL over 'firstproxy' with auth. + * Then clear the auth and get the URL again over 'secondproxy'. + */ +static CURLcode test_lib1648(const char *hostip) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + struct curl_slist *host = NULL; + struct curl_slist *host2 = NULL; + char proxy1_resolve[128]; + char proxy2_resolve[128]; + char proxy1_connect[128]; + char proxy2_connect[128]; + + curl_msnprintf(proxy1_resolve, sizeof(proxy1_resolve), + "firstproxy:%s:%s", libtest_arg2, hostip); + curl_msnprintf(proxy2_resolve, sizeof(proxy2_resolve), + "secondproxy:%s:%s", libtest_arg2, hostip); + + /* we connect to the fake hostname but the right port number */ + curl_msnprintf(proxy1_connect, sizeof(proxy1_connect), + "firstproxy:%s", libtest_arg2); + curl_msnprintf(proxy2_connect, sizeof(proxy2_connect), + "secondproxy:%s", libtest_arg2); + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + host = curl_slist_append(NULL, proxy1_resolve); + if(!host) + goto test_cleanup; + host2 = curl_slist_append(host, proxy2_resolve); + if(!host2) + goto test_cleanup; + host = host2; + + start_test_timing(); + + easy_setopt(curl, CURLOPT_RESOLVE, host); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "victim:secret"); + + curl_mprintf("--- First get over %s\n", proxy1_connect); + result = run1648(curl, GET_THIS, proxy1_connect); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL); + + curl_mprintf("--- Then over '%s'\n", proxy2_connect); + result = run1648(curl, GET_THIS, proxy2_connect); + +test_cleanup: + + /* proper cleanup sequence - type PB */ + + curl_easy_cleanup(curl); + curl_global_cleanup(); + curl_slist_free_all(host); + return result; +} diff --git a/tests/libtest/lib1649.c b/tests/libtest/lib1649.c new file mode 100644 index 000000000000..2dd66c0231a2 --- /dev/null +++ b/tests/libtest/lib1649.c @@ -0,0 +1,90 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "first.h" + +/* this is meant to pick up the proxy from the environment variable */ +static CURLcode init1649(CURL *curl, const char *url) +{ + CURLcode result = CURLE_OK; + + res_easy_setopt(curl, CURLOPT_URL, url); + if(result) + goto init_failed; + + res_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(result) + goto init_failed; + + return CURLE_OK; /* success */ + +init_failed: + return result; /* failure */ +} + +static CURLcode run1649(CURL *curl, const char *url) +{ + CURLcode result = CURLE_OK; + + result = init1649(curl, url); + if(result) + return result; + + return curl_easy_perform(curl); +} + +static CURLcode test_lib1649(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + easy_setopt(curl, CURLOPT_REFERER, "https://secret.example.com/"); + + result = run1649(curl, URL); + if(result) + goto test_cleanup; + + /* reset it */ + easy_setopt(curl, CURLOPT_REFERER, NULL); + + result = run1649(curl, URL); + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1662.c b/tests/libtest/lib1662.c index 0e4942e9ad10..365e28ea56df 100644 --- a/tests/libtest/lib1662.c +++ b/tests/libtest/lib1662.c @@ -30,17 +30,17 @@ struct t1662_WriteThis { static size_t t1662_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) { static const char testdata[] = "mooaaa"; - static size_t const testdatalen = sizeof(testdata) - 1; + static const size_t testdatalen = CURL_CSTRLEN(testdata); struct t1662_WriteThis *pooh = (struct t1662_WriteThis *)userp; - if(size * nmemb < testdatalen) + if(size * nmemb < 1) return 0; if(pooh->sizeleft) { - memcpy(ptr, testdata, testdatalen); - pooh->sizeleft = 0; - return testdatalen; + *ptr = testdata[testdatalen - pooh->sizeleft]; + --pooh->sizeleft; + return 1; } return 0; /* no more data left to deliver */ @@ -52,7 +52,7 @@ static CURLcode test_lib1662(const char *URL) CURL *curl; curl_mime *mime1; curl_mimepart *part1; - struct t1662_WriteThis pooh = { 1 }; + struct t1662_WriteThis pooh = { CURL_CSTRLEN("mooaaa") }; mime1 = NULL; diff --git a/tests/libtest/lib1678.c b/tests/libtest/lib1678.c new file mode 100644 index 000000000000..a2d43c02e0e0 --- /dev/null +++ b/tests/libtest/lib1678.c @@ -0,0 +1,163 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#if defined(USE_SSL) && defined(USE_SSLS_EXPORT) + +#define T1678_IMPORT_COUNT 64 +#define T1678_FIRST_TICKET_SIZE 4096 +#define T1678_FIRST_QUICTP_SIZE 127 +#define T1678_FIRST_ALPN_SIZE 10 + +static uint8_t *t1678_make_packet(size_t *packet_len) +{ + const size_t t1678_total = + 1 + /* format version */ + 1 + 2 + T1678_FIRST_TICKET_SIZE + /* first ticket */ + 1 + 2 + T1678_FIRST_QUICTP_SIZE + /* first QUIC traffic params */ + 1 + 2 + T1678_FIRST_ALPN_SIZE + /* first ALPN params */ + 1 + 2 + 1 + /* second ticket */ + 1 + 2 + 1 + /* second ALPN */ + 1 + 2 + 1; /* second QUIC traffic params */ + + uint8_t *packet = curlx_malloc(t1678_total); + uint8_t *p; + + if(!packet) + return NULL; + + p = packet; + + /* CURL_SPACK_VERSION */ + *p++ = 0x01; + + /* First CURL_SPACK_TICKET */ + *p++ = 0x04; + *p++ = (uint8_t)(T1678_FIRST_TICKET_SIZE >> 8); + *p++ = (uint8_t)(T1678_FIRST_TICKET_SIZE & 0x0ff); + memset(p, 'A', T1678_FIRST_TICKET_SIZE); + p += T1678_FIRST_TICKET_SIZE; + + /* First CURL_SPACK_QUICTP */ + *p++ = 0x07; + *p++ = (uint8_t)(T1678_FIRST_QUICTP_SIZE >> 8); + *p++ = (uint8_t)(T1678_FIRST_QUICTP_SIZE & 0x0ff); + memset(p, 'Q', T1678_FIRST_QUICTP_SIZE); + p += T1678_FIRST_QUICTP_SIZE; + + /* First CURL_SPACK_ALPN */ + *p++ = 0x05; + *p++ = (uint8_t)(T1678_FIRST_ALPN_SIZE >> 8); + *p++ = (uint8_t)(T1678_FIRST_ALPN_SIZE & 0x0ff); + memset(p, 'a', T1678_FIRST_ALPN_SIZE); + p += T1678_FIRST_ALPN_SIZE; + + /* Second CURL_SPACK_TICKET: one byte. */ + *p++ = 0x04; + *p++ = 0x00; + *p++ = 0x01; + *p++ = 'B'; + + /* Second CURL_SPACK_ALPN: one byte. */ + *p++ = 0x05; + *p++ = 0x00; + *p++ = 0x01; + *p++ = 'b'; + + /* Second CURL_SPACK_QUICTP: one byte. */ + *p++ = 0x07; + *p++ = 0x00; + *p++ = 0x01; + *p++ = 'R'; + + *packet_len = t1678_total; + return packet; +} + +static CURLcode test_lib1678(const char *URL) +{ + uint8_t *packet; + size_t packet_len; + CURLSH *share = NULL; + CURL *easy = NULL; + CURLSHcode shrc; + CURLcode result = CURLE_FAILED_INIT; + int i; + + (void)URL; + packet = t1678_make_packet(&packet_len); + if(!packet) + goto test_cleanup; + + result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + goto test_cleanup; + + share = curl_share_init(); + easy = curl_easy_init(); + + if(!share || !easy) + goto test_cleanup; + + shrc = curl_share_setopt(share, + CURLSHOPT_SHARE, + CURL_LOCK_DATA_SSL_SESSION); + if(shrc != CURLSHE_OK) + goto test_cleanup; + + result = curl_easy_setopt(easy, CURLOPT_SHARE, share); + if(result) + goto test_cleanup; + + for(i = 0; i < T1678_IMPORT_COUNT; ++i) { + result = curl_easy_ssls_import(easy, + "example.test:443", + NULL, + 0, + packet, + packet_len); + if(result) { + curl_mfprintf(stderr, + "import %d failed: %d (%s)\n", + i, (int)result, + curl_easy_strerror(result)); + break; + } + } + +test_cleanup: + curlx_free(packet); + curl_easy_cleanup(easy); + curl_share_cleanup(share); + curl_global_cleanup(); + + return result; +} +#else +static CURLcode test_lib1678(const char *URL) +{ + (void)URL; + return CURLE_OK; +} +#endif /* USE_SSL && USE_SSLS_EXPORT */ diff --git a/tests/libtest/lib1686.c b/tests/libtest/lib1686.c new file mode 100644 index 000000000000..b7817463d25b --- /dev/null +++ b/tests/libtest/lib1686.c @@ -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 + * + ***************************************************************************/ +#include "first.h" + +#define FIRSTHOST "first.test" +#define SECONDHOST "second.test" + +static CURLcode test_lib1686(const char *hostip) +{ + CURL *curl = NULL; + CURLcode result = CURLE_OK; + const char *httpport = libtest_arg2; + char firsturl[100]; + char secondurl[100]; + char firstres[100]; + char secondres[100]; + struct curl_slist *host = NULL; + struct curl_slist *host2 = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + /* create strings for CURLOPT_RESOLVE */ + curl_msnprintf(firstres, sizeof(firstres), "%s:%s:%s", + FIRSTHOST, httpport, hostip); + curl_msnprintf(secondres, sizeof(secondres), "%s:%s:%s", + SECONDHOST, httpport, hostip); + + /* create URLs */ + curl_msnprintf(firsturl, sizeof(firsturl), "http://%s:%s/api", + FIRSTHOST, httpport); + curl_msnprintf(secondurl, sizeof(secondurl), "http://%s:%s/hook", + SECONDHOST, httpport); + + host = curl_slist_append(NULL, firstres); + if(!host) + goto test_cleanup; + host2 = curl_slist_append(host, secondres); + if(!host2) + goto test_cleanup; + host = host2; + + curl = curl_easy_init(); + if(curl) { + easy_setopt(curl, CURLOPT_RESOLVE, host); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + easy_setopt(curl, CURLOPT_USERPWD, "alice:bond"); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + + easy_setopt(curl, CURLOPT_URL, firsturl); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_URL, secondurl); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_USERPWD, "bob:secret"); + easy_setopt(curl, CURLOPT_URL, secondurl); + result = curl_easy_perform(curl); + } + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + curl_slist_free_all(host); + return result; +} diff --git a/tests/libtest/lib1901.c b/tests/libtest/lib1901.c index 80d06080de0f..dae52a7f2c31 100644 --- a/tests/libtest/lib1901.c +++ b/tests/libtest/lib1901.c @@ -25,13 +25,7 @@ static size_t t1901_read_cb(char *ptr, size_t size, size_t nmemb, void *stream) { - static const char *chunks[] = { - "one", - "two", - "three", - "four", - NULL - }; + static const char * const chunks[] = { "one", "two", "three", "four", NULL }; static int ix = 0; (void)stream; if(chunks[ix]) { diff --git a/tests/libtest/lib1902.c b/tests/libtest/lib1902.c index da3b23a349d4..b2ceb3dc39b5 100644 --- a/tests/libtest/lib1902.c +++ b/tests/libtest/lib1902.c @@ -33,7 +33,7 @@ static CURLcode test_lib1902(const char *URL) curl = curl_easy_init(); if(curl) { easy_setopt(curl, CURLOPT_COOKIEFILE, URL); - easy_setopt(curl, CURLOPT_COOKIEJAR, URL); + easy_setopt(curl, CURLOPT_COOKIEJAR, URL); /* Do not perform any actual network operation, the issue occur when not calling curl.*perform */ diff --git a/tests/libtest/lib1906.c b/tests/libtest/lib1906.c index 5ac2105dfe45..e649263338af 100644 --- a/tests/libtest/lib1906.c +++ b/tests/libtest/lib1906.c @@ -45,12 +45,11 @@ static CURLcode test_lib1906(const char *URL) if(result != CURLE_COULDNT_CONNECT && result != CURLE_OPERATION_TIMEDOUT) { curl_mfprintf(stderr, "failure expected, " "curl_easy_perform returned %d: <%s>, <%s>\n", - result, curl_easy_strerror(result), error_buffer); + (int)result, curl_easy_strerror(result), error_buffer); if(result == CURLE_OK) result = TEST_ERR_MAJOR_BAD; /* force an error return */ goto test_cleanup; } - result = CURLE_OK; /* reset for next use */ /* print the used URL */ curl_url_get(curlu, CURLUPART_URL, &url_after, 0); @@ -65,7 +64,7 @@ static CURLcode test_lib1906(const char *URL) if(result) curl_mfprintf(stderr, "success expected, " "curl_easy_perform returned %d: <%s>, <%s>\n", - result, curl_easy_strerror(result), error_buffer); + (int)result, curl_easy_strerror(result), error_buffer); /* print URL */ curl_url_get(curlu, CURLUPART_URL, &url_after, 0); diff --git a/tests/libtest/lib1907.c b/tests/libtest/lib1907.c index 6aac37515225..c6045bf327db 100644 --- a/tests/libtest/lib1907.c +++ b/tests/libtest/lib1907.c @@ -30,7 +30,7 @@ static CURLcode test_lib1907(const char *URL) CURLcode result = CURLE_OK; char error_buffer[CURL_ERROR_SIZE] = ""; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, URL); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer); @@ -39,7 +39,7 @@ static CURLcode test_lib1907(const char *URL) if(!result) curl_mfprintf(stderr, "failure expected, " "curl_easy_perform returned %d: <%s>, <%s>\n", - result, curl_easy_strerror(result), error_buffer); + (int)result, curl_easy_strerror(result), error_buffer); /* print the used URL */ if(!curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url_after)) diff --git a/tests/libtest/lib1908.c b/tests/libtest/lib1908.c index f7ead3406eb7..9d6a7a12199a 100644 --- a/tests/libtest/lib1908.c +++ b/tests/libtest/lib1908.c @@ -52,7 +52,7 @@ static CURLcode test_lib1908(const char *URL) curl_easy_reset(curl); /* using the same filename for the alt-svc cache, this clobbers the - content just written from the 'curldupe' handle */ + content written from the 'curldupe' handle */ curl_easy_cleanup(curl); } curl_global_cleanup(); diff --git a/tests/libtest/lib1911.c b/tests/libtest/lib1911.c index f265174891d7..662093a61fec 100644 --- a/tests/libtest/lib1911.c +++ b/tests/libtest/lib1911.c @@ -43,7 +43,7 @@ static CURLcode test_lib1911(const char *URL) return TEST_ERR_EASY_INIT; } - /* make it a null-terminated C string with just As */ + /* make it a null-terminated C string with only As */ memset(testbuf, 'A', MAX_INPUT_LENGTH + 1); testbuf[MAX_INPUT_LENGTH + 1] = 0; @@ -78,7 +78,7 @@ static CURLcode test_lib1911(const char *URL) default: /* all other return codes are unexpected */ curl_mfprintf(stderr, "curl_easy_setopt(%s...) returned %d\n", - o->name, result); + o->name, (int)result); error++; break; } diff --git a/tests/libtest/lib1912.c b/tests/libtest/lib1912.c index 98623bd339f5..65de8b3ffb95 100644 --- a/tests/libtest/lib1912.c +++ b/tests/libtest/lib1912.c @@ -23,16 +23,17 @@ ***************************************************************************/ #include "first.h" -#define print_err(name, exp) \ - curl_mfprintf(stderr, "Type mismatch for CURLOPT_%s (expected %s)\n", \ - name, exp) - static CURLcode test_lib1912(const char *URL) { /* Only test if GCC/clang type checking is available */ int error = 0; #ifdef CURLINC_TYPECHECK_GCC_H const struct curl_easyoption *o; + +#define print_err(name, exp) \ + curl_mfprintf(stderr, "Type mismatch for CURLOPT_%s (expected %s)\n", \ + name, exp) + for(o = curl_easy_option_next(NULL); o; o = curl_easy_option_next(o)) { /* Test for mismatch OR missing typecheck macros */ if(curlcheck_long_option(o->id) != diff --git a/tests/libtest/lib1915.c b/tests/libtest/lib1915.c index d5dd4dc2fcdf..eebf7ba755b1 100644 --- a/tests/libtest/lib1915.c +++ b/tests/libtest/lib1915.c @@ -38,7 +38,7 @@ static CURLSTScode hstsread(CURL *curl, struct curl_hstsentry *e, void *userp) }; static const struct entry preload_hosts[] = { -#if (SIZEOF_TIME_T < 5) +#if SIZEOF_TIME_T < 5 { "1.example.com", "20370320 01:02:03" }, { "2.example.com.", "20370320 03:02:01" }, { "3.example.com", "20370319 01:02:03" }, @@ -124,7 +124,7 @@ static CURLcode test_lib1915(const char *URL) curl = NULL; if(result == CURLE_OPERATION_TIMEDOUT) /* we expect that on Windows */ result = CURLE_COULDNT_CONNECT; - curl_mprintf("First request returned %d\n", result); + curl_mprintf("First request returned %d\n", (int)result); result = CURLE_OK; easy_init(curl); @@ -141,7 +141,7 @@ static CURLcode test_lib1915(const char *URL) result = curl_easy_perform(curl); curl_easy_cleanup(curl); curl = NULL; - curl_mprintf("Second request returned %d\n", result); + curl_mprintf("Second request returned %d\n", (int)result); test_cleanup: curl_easy_cleanup(curl); diff --git a/tests/libtest/lib1916.c b/tests/libtest/lib1916.c index a67b68fc9cd5..575080d09ff0 100644 --- a/tests/libtest/lib1916.c +++ b/tests/libtest/lib1916.c @@ -45,7 +45,7 @@ static CURLcode test_lib1916(const char *URL) } result = curl_easy_perform(curl); if(result) { - curl_mprintf("result: %d\n", result); + curl_mprintf("result: %d\n", (int)result); } curl_easy_cleanup(curl); } diff --git a/tests/libtest/lib1918.c b/tests/libtest/lib1918.c index 1c8ccb78f984..4932c1cfa6bb 100644 --- a/tests/libtest/lib1918.c +++ b/tests/libtest/lib1918.c @@ -38,11 +38,11 @@ static CURLcode test_lib1918(const char *URL) if(ename->id != o->id) { curl_mprintf("name lookup id %d does not match %d\n", - ename->id, o->id); + (int)ename->id, (int)o->id); } else if(eid->id != o->id) { curl_mprintf("ID lookup %d does not match %d\n", - ename->id, o->id); + (int)ename->id, (int)o->id); } } curl_global_cleanup(); diff --git a/tests/libtest/lib1920.c b/tests/libtest/lib1920.c index fe7e8b3206e3..84a747ee6f2c 100644 --- a/tests/libtest/lib1920.c +++ b/tests/libtest/lib1920.c @@ -33,9 +33,9 @@ static CURLcode test_lib1920(const char *URL) curl = curl_easy_init(); if(curl) { easy_setopt(curl, CURLOPT_COOKIEFILE, libtest_arg2); - easy_setopt(curl, CURLOPT_COOKIEJAR, libtest_arg2); - easy_setopt(curl, CURLOPT_URL, URL); - easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_COOKIEJAR, libtest_arg2); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); @@ -44,7 +44,7 @@ static CURLcode test_lib1920(const char *URL) curl_easy_reset(curl); /* set the cookie jar name so that curl knows where to store the cookies after reset */ - easy_setopt(curl, CURLOPT_COOKIEJAR, libtest_arg2); + easy_setopt(curl, CURLOPT_COOKIEJAR, libtest_arg2); } } diff --git a/tests/libtest/lib1921.c b/tests/libtest/lib1921.c new file mode 100644 index 000000000000..8799c2d96092 --- /dev/null +++ b/tests/libtest/lib1921.c @@ -0,0 +1,52 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib1921(const char *URL) +{ + CURLU *u = curl_url(); + CURLUcode rc; + if(!u) + return CURLE_FAILED_INIT; + (void)URL; /* unused */ + /* u->scheme remains NULL */ + rc = curl_url_set(u, CURLUPART_HOST, "example.com", 0); + if(!rc) + rc = curl_url_set(u, CURLUPART_PATH, "/original", 0); + + if(!rc) + /* Relative URL + CURLU_DEFAULT_SCHEME reaches redirect_url() */ + rc = curl_url_set(u, CURLUPART_URL, "/newpath", CURLU_DEFAULT_SCHEME); + + if(!rc) { + char *url; + rc = curl_url_get(u, CURLUPART_URL, &url, 0); + if(!rc) { + curl_mprintf("URL: %s\n", url); + curl_free(url); + } + } + curl_url_cleanup(u); + return rc ? CURLE_BAD_FUNCTION_ARGUMENT : CURLE_OK; +} diff --git a/tests/libtest/lib1922.c b/tests/libtest/lib1922.c new file mode 100644 index 000000000000..17b9cb8e34f3 --- /dev/null +++ b/tests/libtest/lib1922.c @@ -0,0 +1,116 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib1922(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + CURL *dup = NULL; + struct curl_slist *resolve = NULL; + char resolve_entry[256]; + char direct_url[256]; + char http_url[256]; + char proxy_url[256]; + const char *effective = NULL; + const char *host = libtest_arg2; /* %HOSTIP */ + const char *httpport = libtest_arg3; /* %HTTPPORT */ + const char *proxyport = libtest_arg4;/* %PROXYPORT */ + + (void)URL; + + if(!host || !httpport || !proxyport) { + curl_mfprintf(stderr, + "Usage: lib1922 - \n"); + return TEST_ERR_MAJOR_BAD; + } + + /* Synthetic DNS so hsts.example.com resolves to the test server. */ + curl_msnprintf(resolve_entry, sizeof(resolve_entry), + "hsts.example.com:%s:%s", httpport, host); + resolve = curl_slist_append(NULL, resolve_entry); + if(!resolve) { + return CURLE_OUT_OF_MEMORY; + } + + curl_msnprintf(direct_url, sizeof(direct_url), + "http://hsts.example.com:%s/%d", httpport, 1922); + curl_msnprintf(http_url, sizeof(http_url), + "http://hsts.example.com/%d", 1922); + curl_msnprintf(proxy_url, sizeof(proxy_url), + "http://%s:%s", host, proxyport); + + global_init(CURL_GLOBAL_ALL); + easy_init(curl); + + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + easy_setopt(curl, CURLOPT_RESOLVE, resolve); + easy_setopt(curl, CURLOPT_URL, direct_url); + easy_setopt(curl, CURLOPT_HSTS_CTRL, CURLHSTS_ENABLE); + + /* Direct HTTP request: Server returns Strict-Transport-Security. + * CURL_HSTS_HTTP env var (set in the test) allows processing it over + * HTTP in debug builds, populating the live HSTS cache. */ + result = curl_easy_perform(curl); + if(result) { + curl_mfprintf(stderr, "First perform failed: %d (%s)\n", + (int)result, curl_easy_strerror(result)); + goto test_cleanup; + } + curl_mprintf("First request: HSTS cache populated\n"); + + dup = curl_easy_duphandle(curl); + if(!dup) { + result = CURLE_FAILED_INIT; + goto test_cleanup; + } + + /* Point the dup at the plain HTTP URL for the same hostname, via a proxy. + * The copied HSTS cache upgrades the URL to HTTPS, causing a CONNECT to + * port 443. The test proxy rejects CONNECT with 403, so curl returns + * CURLE_COULDNT_CONNECT (7). The CONNECT to port 443 is itself the proof + * of the upgrade. */ + easy_setopt(dup, CURLOPT_URL, http_url); + easy_setopt(dup, CURLOPT_PROXY, proxy_url); + + result = curl_easy_perform(dup); + if(result != CURLE_COULDNT_CONNECT) { + curl_mfprintf(stderr, "Dup perform unexpected result: %d (%s)\n", + (int)result, curl_easy_strerror(result)); + goto test_cleanup; + } + + /* Confirm the dup's URL was upgraded to HTTPS by the copied HSTS cache. */ + curl_easy_getinfo(dup, CURLINFO_EFFECTIVE_URL, &effective); + if(effective) { + curl_mprintf("Dup effective URL: %s\n", effective); + } + +test_cleanup: + curl_easy_cleanup(curl); + curl_easy_cleanup(dup); + curl_slist_free_all(resolve); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1933.c b/tests/libtest/lib1933.c index bd31f9418a3e..abe4361bea09 100644 --- a/tests/libtest/lib1933.c +++ b/tests/libtest/lib1933.c @@ -42,16 +42,16 @@ static CURLcode test_lib1933(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1934.c b/tests/libtest/lib1934.c index 41959ad60ef7..1ddb8bee0b74 100644 --- a/tests/libtest/lib1934.c +++ b/tests/libtest/lib1934.c @@ -42,17 +42,17 @@ static CURLcode test_lib1934(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy"); - test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1935.c b/tests/libtest/lib1935.c index 9b7214405b4b..26d17c685114 100644 --- a/tests/libtest/lib1935.c +++ b/tests/libtest/lib1935.c @@ -42,17 +42,17 @@ static CURLcode test_lib1935(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr"); - test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1936.c b/tests/libtest/lib1936.c index b4908f09735c..dba9f1ccbac4 100644 --- a/tests/libtest/lib1936.c +++ b/tests/libtest/lib1936.c @@ -42,17 +42,17 @@ static CURLcode test_lib1936(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr:sss"); - test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr:sss"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1937.c b/tests/libtest/lib1937.c index 03160f4ccb8c..a2493751791a 100644 --- a/tests/libtest/lib1937.c +++ b/tests/libtest/lib1937.c @@ -42,19 +42,19 @@ static CURLcode test_lib1937(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_POST, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2:region:service"); - test_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2:region:service"); + easy_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); - test_setopt(curl, CURLOPT_POSTFIELDS, "postData"); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_POSTFIELDS, "postData"); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1938.c b/tests/libtest/lib1938.c index 6b56d7c2f820..300a092fd5a6 100644 --- a/tests/libtest/lib1938.c +++ b/tests/libtest/lib1938.c @@ -43,20 +43,20 @@ static CURLcode test_lib1938(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_POST, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2:region:service"); - test_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2:region:service"); + easy_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); list = curl_slist_append(list, "Content-Type: application/json"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); - test_setopt(curl, CURLOPT_POSTFIELDS, data); - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(data)); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_POSTFIELDS, data); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(data)); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1939.c b/tests/libtest/lib1939.c index 502b60051717..4b78a2f5621c 100644 --- a/tests/libtest/lib1939.c +++ b/tests/libtest/lib1939.c @@ -29,7 +29,7 @@ static CURLcode test_lib1939(const char *URL) CURL *curl; int running_handles; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); multi = curl_multi_init(); if(multi) { @@ -41,7 +41,7 @@ static CURLcode test_lib1939(const char *URL) /* Crash only happens when using HTTPS */ result = curl_easy_setopt(curl, CURLOPT_URL, URL); if(!result) - /* Any old HTTP tunneling proxy will do here */ + /* Any old HTTP tunneling proxy does the job here */ result = curl_easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); if(!result) { @@ -51,7 +51,7 @@ static CURLcode test_lib1939(const char *URL) mresult = curl_multi_add_handle(multi, curl); if(!mresult) - /* Run the multi handle once, just enough to start establishing an + /* Run the multi handle once, enough to start establishing an HTTPS connection. */ mresult = curl_multi_perform(multi, &running_handles); diff --git a/tests/libtest/lib1940.c b/tests/libtest/lib1940.c index a9114216a181..400d044f3af6 100644 --- a/tests/libtest/lib1940.c +++ b/tests/libtest/lib1940.c @@ -23,17 +23,9 @@ ***************************************************************************/ #include "first.h" -static size_t t1940_write_cb(char *data, size_t n, size_t l, void *userp) -{ - /* take care of the data here, ignored in this example */ - (void)data; - (void)userp; - return n * l; -} - static void t1940_showem(CURL *curl, int header_request, unsigned int type) { - static const char *testdata[] = { + static const char * const testdata[] = { "daTE", "Server", "content-type", @@ -76,6 +68,45 @@ static void t1940_showem(CURL *curl, int header_request, unsigned int type) } } +static CURLHcode t1940_negative(CURL *curl, int req_index) +{ + struct curl_header *hd1, *hd2; + CURLHcode result; + + result = curl_easy_header(NULL, "x", 0, CURLH_HEADER, -1, &hd1); + if(result != CURLHE_BAD_ARGUMENT) + return result; + + result = curl_easy_header(curl, NULL, 0, CURLH_HEADER, -1, &hd1); + if(result != CURLHE_BAD_ARGUMENT) + return result; + + result = curl_easy_header(curl, "x", 0, CURLH_HEADER, -1, NULL); + if(result != CURLHE_BAD_ARGUMENT) + return result; + + result = curl_easy_header(curl, "x", 0, 0, -1, &hd1); + if(result != CURLHE_BAD_ARGUMENT) + return result; + + result = curl_easy_header(curl, "date", 0, CURLH_HEADER, req_index, &hd1); + if(result != CURLHE_OK) + return result; + if(!hd1) + return CURLHE_NOHEADERS; + + /* Should give the first header */ + hd2 = curl_easy_nextheader(curl, CURLH_HEADER, -1, NULL); + if(!hd2) + return CURLHE_BADINDEX; + + hd2 = curl_easy_nextheader(NULL, CURLH_HEADER, -1, hd1); + if(hd2) /* should not work */ + return CURLHE_BADINDEX; + + return CURLHE_OK; +} + static CURLcode test_lib1940(const char *URL) { CURL *curl = NULL; @@ -89,13 +120,13 @@ static CURLcode test_lib1940(const char *URL) header_request = -1; } - global_init(CURL_GLOBAL_DEFAULT); + global_init(CURL_GLOBAL_ALL); easy_init(curl); easy_setopt(curl, CURLOPT_URL, URL); easy_setopt(curl, CURLOPT_VERBOSE, 1L); easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* ignores any content */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, t1940_write_cb); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); /* if there is a proxy set, use it */ if(libtest_arg2 && *libtest_arg2) { @@ -114,6 +145,8 @@ static CURLcode test_lib1940(const char *URL) t1940_showem(curl, header_request, CURLH_1XX); t1940_showem(curl, header_request, CURLH_TRAILER); + result = (CURLcode)t1940_negative(curl, header_request); + test_cleanup: curl_easy_cleanup(curl); curl_global_cleanup(); diff --git a/tests/libtest/lib1945.c b/tests/libtest/lib1945.c index 0eb5a6873e77..4ca3d160c831 100644 --- a/tests/libtest/lib1945.c +++ b/tests/libtest/lib1945.c @@ -36,27 +36,19 @@ static void t1945_showem(CURL *curl, unsigned int type) } } -static size_t t1945_write_cb(char *data, size_t n, size_t l, void *userp) -{ - /* take care of the data here, ignored in this example */ - (void)data; - (void)userp; - return n * l; -} - static CURLcode test_lib1945(const char *URL) { CURL *curl; CURLcode result = CURLE_OK; - global_init(CURL_GLOBAL_DEFAULT); + global_init(CURL_GLOBAL_ALL); easy_init(curl); curl_easy_setopt(curl, CURLOPT_URL, URL); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* ignores any content */ - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, t1945_write_cb); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); /* if there is a proxy set, use it */ if(libtest_arg2 && *libtest_arg2) { @@ -65,7 +57,7 @@ static CURLcode test_lib1945(const char *URL) } result = curl_easy_perform(curl); if(result) { - curl_mprintf("badness: %d\n", result); + curl_mprintf("badness: %d\n", (int)result); } t1945_showem(curl, CURLH_CONNECT | CURLH_HEADER | CURLH_TRAILER | CURLH_1XX); diff --git a/tests/libtest/lib1947.c b/tests/libtest/lib1947.c index 88a6336dbe82..342d2f60ac85 100644 --- a/tests/libtest/lib1947.c +++ b/tests/libtest/lib1947.c @@ -23,14 +23,6 @@ ***************************************************************************/ #include "first.h" -static size_t t1947_write_cb(char *data, size_t n, size_t l, void *userp) -{ - /* ignore the data */ - (void)data; - (void)userp; - return n * l; -} - static CURLcode test_lib1947(const char *URL) { CURL *curl; @@ -39,13 +31,13 @@ static CURLcode test_lib1947(const char *URL) int count = 0; unsigned int origins; - global_init(CURL_GLOBAL_DEFAULT); + global_init(CURL_GLOBAL_ALL); easy_init(curl); /* perform a request that involves redirection */ easy_setopt(curl, CURLOPT_URL, URL); - easy_setopt(curl, CURLOPT_WRITEFUNCTION, t1947_write_cb); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); result = curl_easy_perform(curl); if(result) { diff --git a/tests/libtest/lib1948.c b/tests/libtest/lib1948.c index 947469549ed8..95152aa1ab9d 100644 --- a/tests/libtest/lib1948.c +++ b/tests/libtest/lib1948.c @@ -43,10 +43,10 @@ static CURLcode test_lib1948(const char *URL) { CURL *curl; CURLcode result = CURLE_OK; - static const char *testput = "This is test PUT data\n"; + static const char testput[] = "This is test PUT data\n"; struct put_buffer pbuf; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); easy_init(curl); @@ -55,9 +55,9 @@ static CURLcode test_lib1948(const char *URL) easy_setopt(curl, CURLOPT_HEADER, 1L); easy_setopt(curl, CURLOPT_READFUNCTION, put_callback); pbuf.buf = testput; - pbuf.len = strlen(testput); + pbuf.len = CURL_CSTRLEN(testput); easy_setopt(curl, CURLOPT_READDATA, &pbuf); - easy_setopt(curl, CURLOPT_INFILESIZE, (long)strlen(testput)); + easy_setopt(curl, CURLOPT_INFILESIZE, (long)CURL_CSTRLEN(testput)); easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); if(result) @@ -66,7 +66,7 @@ static CURLcode test_lib1948(const char *URL) /* POST */ easy_setopt(curl, CURLOPT_POST, 1L); easy_setopt(curl, CURLOPT_POSTFIELDS, testput); - easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(testput)); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)CURL_CSTRLEN(testput)); result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib1955.c b/tests/libtest/lib1955.c index 79fd92d6ed2c..ff640c20d4b9 100644 --- a/tests/libtest/lib1955.c +++ b/tests/libtest/lib1955.c @@ -42,18 +42,18 @@ static CURLcode test_lib1955(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "test3: 1234"); if(!list) goto test_cleanup; if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); curl_slist_append(list, "Content-Type: application/json"); /* 'name;' user headers with no value are used to send an empty header in the @@ -73,7 +73,7 @@ static CURLcode test_lib1955(const char *URL) curl_slist_append(list, "test_space: t\ts m\t end "); curl_slist_append(list, "tesMixCase: MixCase"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1956.c b/tests/libtest/lib1956.c index c9a4d405e26b..5d569c785b14 100644 --- a/tests/libtest/lib1956.c +++ b/tests/libtest/lib1956.c @@ -42,21 +42,21 @@ static CURLcode test_lib1956(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); curl_slist_append(list, "X-Xxx-Content-Sha256: " "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1957.c b/tests/libtest/lib1957.c index ea4226303141..b422e48553fa 100644 --- a/tests/libtest/lib1957.c +++ b/tests/libtest/lib1957.c @@ -42,20 +42,20 @@ static CURLcode test_lib1957(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); curl_slist_append(list, "X-Xxx-Content-Sha256: arbitrary"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1958.c b/tests/libtest/lib1958.c index cae62c239297..dc4ac1cd9a40 100644 --- a/tests/libtest/lib1958.c +++ b/tests/libtest/lib1958.c @@ -42,20 +42,20 @@ static CURLcode test_lib1958(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); curl_slist_append(list, "X-Xxx-Content-Sha256: \tarbitrary "); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1959.c b/tests/libtest/lib1959.c index 1f6e725856f3..4df8bd71cdd3 100644 --- a/tests/libtest/lib1959.c +++ b/tests/libtest/lib1959.c @@ -42,22 +42,22 @@ static CURLcode test_lib1959(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "xxx"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); curl_slist_append(list, "X-Xxx-Content-Sha256: " "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1960.c b/tests/libtest/lib1960.c index 2ce9a7540d11..1cda44040a85 100644 --- a/tests/libtest/lib1960.c +++ b/tests/libtest/lib1960.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#ifdef HAVE_INET_PTON - #ifdef HAVE_NETINET_IN_H #include #endif @@ -62,12 +60,6 @@ static int sockopt_cb(void *clientp, return CURL_SOCKOPT_ALREADY_CONNECTED; } -#ifdef __AMIGA__ -#define my_inet_pton(x, y, z) inet_pton(x, (unsigned char *)y, z) -#else -#define my_inet_pton(x, y, z) inet_pton(x, y, z) -#endif - /* Expected args: URL IP PORT */ static CURLcode test_lib1960(const char *URL) { @@ -104,8 +96,8 @@ static CURLcode test_lib1960(const char *URL) serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons((unsigned short)port); - if(my_inet_pton(AF_INET, libtest_arg2, &serv_addr.sin_addr) <= 0) { - curl_mfprintf(stderr, "inet_pton failed\n"); + if(curlx_inet_pton(AF_INET, libtest_arg2, &serv_addr.sin_addr) <= 0) { + curl_mfprintf(stderr, "curlx_inet_pton() failed\n"); goto test_cleanup; } @@ -122,16 +114,16 @@ static CURLcode test_lib1960(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, socket_cb); - test_setopt(curl, CURLOPT_OPENSOCKETDATA, &client_fd); - test_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_cb); - test_setopt(curl, CURLOPT_SOCKOPTDATA, NULL); - test_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_cb); - test_setopt(curl, CURLOPT_CLOSESOCKETDATA, NULL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, socket_cb); + easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &client_fd); + easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_cb); + easy_setopt(curl, CURLOPT_SOCKOPTDATA, NULL); + easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_cb); + easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, NULL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); @@ -143,11 +135,3 @@ static CURLcode test_lib1960(const char *URL) return result; } -#else -static CURLcode test_lib1960(const char *URL) -{ - (void)URL; - curl_mprintf("lacks inet_pton\n"); - return CURLE_OK; -} -#endif diff --git a/tests/libtest/lib1965.c b/tests/libtest/lib1965.c index e09ff349b3e7..683e5a716acf 100644 --- a/tests/libtest/lib1965.c +++ b/tests/libtest/lib1965.c @@ -27,7 +27,7 @@ static CURLcode test_lib1965(const char *URL) { CURLcode result = CURLE_OK; CURLUcode rc; - static const char *schemes[] = { + static const char * const schemes[] = { "bad!", "bad{", "bad/", "bad\\", "a!", "a+123", "http-2", "http.1", "a+-.123", "http-+++2", "http.1--", diff --git a/tests/libtest/lib1967.c b/tests/libtest/lib1967.c new file mode 100644 index 000000000000..e271df28d94d --- /dev/null +++ b/tests/libtest/lib1967.c @@ -0,0 +1,42 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib1967(const char *URL) +{ + CURLU *u = curl_url(); + (void)URL; + if(u) { + char *url; + curl_url_set(u, CURLUPART_URL, "a.b", CURLU_GUESS_SCHEME); + curl_url_set(u, CURLUPART_URL, "/x", CURLU_NO_GUESS_SCHEME); + + if(!curl_url_get(u, CURLUPART_URL, &url, 0)) { + curl_mprintf("URL %s\n", url); + curl_free(url); + } + curl_url_cleanup(u); + } + return CURLE_OK; +} diff --git a/tests/libtest/lib1970.c b/tests/libtest/lib1970.c index d9b659988c94..8fd69f6f1e4d 100644 --- a/tests/libtest/lib1970.c +++ b/tests/libtest/lib1970.c @@ -42,21 +42,21 @@ static CURLcode test_lib1970(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, 0L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, 0L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1971.c b/tests/libtest/lib1971.c index 4ae7bef2a3c1..81d5956a4483 100644 --- a/tests/libtest/lib1971.c +++ b/tests/libtest/lib1971.c @@ -51,21 +51,21 @@ static CURLcode test_lib1971(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1971_read_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1971_read_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1972.c b/tests/libtest/lib1972.c index 98609bd92963..4315798aecf7 100644 --- a/tests/libtest/lib1972.c +++ b/tests/libtest/lib1972.c @@ -53,20 +53,20 @@ static CURLcode test_lib1972(const char *URL) curl_mime_name(part, "foo"); curl_mime_data(part, "bar", CURL_ZERO_TERMINATED); - test_setopt(curl, CURLOPT_MIMEPOST, mime); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1973.c b/tests/libtest/lib1973.c index 508b16d261d1..778b7af636ba 100644 --- a/tests/libtest/lib1973.c +++ b/tests/libtest/lib1973.c @@ -42,20 +42,20 @@ static CURLcode test_lib1973(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_POSTFIELDS, "post fields\n"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_POSTFIELDS, "post fields\n"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1974.c b/tests/libtest/lib1974.c index ae496591fc2d..84bd430f56e9 100644 --- a/tests/libtest/lib1974.c +++ b/tests/libtest/lib1974.c @@ -41,15 +41,15 @@ static CURLcode test_lib1974(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1975.c b/tests/libtest/lib1975.c index fded2cd8f0ed..05d6e7c67f84 100644 --- a/tests/libtest/lib1975.c +++ b/tests/libtest/lib1975.c @@ -51,23 +51,23 @@ static CURLcode test_lib1975(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, t1975_read_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, t1975_read_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_USERPWD, "xxx"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); list = curl_slist_append(list, "Content-Type: application/json"); if(!list) goto test_cleanup; curl_slist_append(list, "X-Amz-Content-Sha256: " "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib1977.c b/tests/libtest/lib1977.c index 608ba4e8b6d1..a5db34226c15 100644 --- a/tests/libtest/lib1977.c +++ b/tests/libtest/lib1977.c @@ -34,7 +34,7 @@ static CURLcode test_lib1977(const char *URL) global_init(CURL_GLOBAL_ALL); easy_init(curl); - /* first transfer: set just the URL in the first CURLU handle */ + /* first transfer: set the URL in the first CURLU handle */ curl_url_set(curlu, CURLUPART_URL, URL, CURLU_DEFAULT_SCHEME); easy_setopt(curl, CURLOPT_CURLU, curlu); diff --git a/tests/libtest/lib1978.c b/tests/libtest/lib1978.c index 4f3ef11fd064..0f51cc47bb75 100644 --- a/tests/libtest/lib1978.c +++ b/tests/libtest/lib1978.c @@ -42,13 +42,12 @@ static CURLcode test_lib1978(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, 0L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); - test_setopt(curl, CURLOPT_USERPWD, "xxx"); - test_setopt(curl, CURLOPT_HEADER, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, 0L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); /* We want to test a couple assumptions here. 1. the merging works with non-adjacent headers @@ -60,7 +59,7 @@ static CURLcode test_lib1978(const char *URL) same value 7. merging works for headers all with no values 8. merging works for headers some with no values - */ + */ list = curl_slist_append(list, "x-amz-meta-test: test2"); if(!list) @@ -84,13 +83,18 @@ static CURLcode test_lib1978(const char *URL) curl_slist_append(list, "header-some-no-value;"); curl_slist_append(list, "header-some-no-value: value"); - test_setopt(curl, CURLOPT_HTTPHEADER, list); + easy_setopt(curl, CURLOPT_HTTPHEADER, list); if(libtest_arg2) { connect_to = curl_slist_append(connect_to, libtest_arg2); } - test_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_AWS_SIGV4, NULL); + result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib1985.c b/tests/libtest/lib1985.c new file mode 100644 index 000000000000..256e57c9cd50 --- /dev/null +++ b/tests/libtest/lib1985.c @@ -0,0 +1,77 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* URL1, URL2, port, address */ + +static CURLcode test_lib1985(const char *URL) +{ + char host1[80]; + char host2[80]; + CURL *curl = NULL; + struct curl_slist *slist = NULL; + struct curl_slist *slist2 = NULL; + CURLcode r1 = CURLE_OK, r2 = CURLE_OK; + curl_global_init(CURL_GLOBAL_ALL); + + curl_msnprintf(host1, sizeof(host1), "firsthost:%s:%s", + libtest_arg3, libtest_arg4); + curl_msnprintf(host2, sizeof(host2), "secondhost:%s:%s", + libtest_arg3, libtest_arg4); + slist = curl_slist_append(slist, host1); + if(!slist) + goto error; + slist2 = curl_slist_append(slist, host2); + if(!slist2) + goto error; + slist = slist2; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_RESOLVE, slist); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + + curl_easy_setopt(curl, CURLOPT_USERPWD, + "alice:correct-horse-battery-staple"); + curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, "unused-token"); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_BEARER)); + curl_easy_setopt(curl, CURLOPT_URL, URL); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + r1 = curl_easy_perform(curl); + curl_mprintf("STEP1_CODE=%d\n", (int)r1); + + /* Step 2: reuse handle for HostB with Digest stale state leaks */ + curl_easy_setopt(curl, CURLOPT_URL, libtest_arg2); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_DIGEST); + r2 = curl_easy_perform(curl); + curl_mprintf("STEP2_CODE=%d\n", (int)r2); + } + +error: + curl_easy_cleanup(curl); + curl_slist_free_all(slist); + curl_global_cleanup(); + return r2; +} diff --git a/tests/libtest/lib2023.c b/tests/libtest/lib2023.c index 5d99860039c5..b0072b6b122e 100644 --- a/tests/libtest/lib2023.c +++ b/tests/libtest/lib2023.c @@ -35,19 +35,19 @@ static CURLcode send_request(CURL *curl, const char *url, int seq, size_t len = strlen(url) + 4 + 1; char *full_url = curlx_malloc(len); if(!full_url) { - curl_mfprintf(stderr, "Not enough memory for full url\n"); + curl_mfprintf(stderr, "Not enough memory for full URL\n"); return CURLE_OUT_OF_MEMORY; } curl_msnprintf(full_url, len, "%s%04d", url, seq); curl_mfprintf(stderr, "Sending new request %d to %s with credential %s " "(auth %ld)\n", seq, full_url, userpwd, auth_scheme); - test_setopt(curl, CURLOPT_URL, full_url); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_HTTPGET, 1L); - test_setopt(curl, CURLOPT_USERPWD, userpwd); - test_setopt(curl, CURLOPT_HTTPAUTH, auth_scheme); + easy_setopt(curl, CURLOPT_URL, full_url); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HTTPGET, 1L); + easy_setopt(curl, CURLOPT_USERPWD, userpwd); + easy_setopt(curl, CURLOPT_HTTPAUTH, auth_scheme); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib2032.c b/tests/libtest/lib2032.c index e87eb5ecad37..e06a4d704bdb 100644 --- a/tests/libtest/lib2032.c +++ b/tests/libtest/lib2032.c @@ -30,7 +30,7 @@ static CURL *ntlm_curls[MAX_EASY_HANDLES]; static curl_socket_t ntlm_sockets[MAX_EASY_HANDLES]; static CURLcode ntlmcb_res = CURLE_OK; -static size_t callback(char *ptr, size_t size, size_t nmemb, void *data) +static size_t cb2032(char *ptr, size_t size, size_t nmemb, void *data) { ssize_t idx = ((CURL **)data) - ntlm_curls; curl_socket_t sock; @@ -47,7 +47,7 @@ static size_t callback(char *ptr, size_t size, size_t nmemb, void *data) if(result != CURLE_OK) { curl_mfprintf(stderr, "%s:%d curl_easy_getinfo() failed, " "with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); ntlmcb_res = TEST_ERR_MAJOR_BAD; return failure; } @@ -95,7 +95,7 @@ static CURLcode test_lib2032(const char *URL) /* libntlmconnect */ start_test_timing(); if(!full_url) { - curl_mfprintf(stderr, "Not enough memory for full url\n"); + curl_mfprintf(stderr, "Not enough memory for full URL\n"); return TEST_ERR_MAJOR_BAD; } @@ -139,7 +139,7 @@ static CURLcode test_lib2032(const char *URL) /* libntlmconnect */ easy_setopt(ntlm_curls[num_handles], CURLOPT_HTTPGET, 1L); easy_setopt(ntlm_curls[num_handles], CURLOPT_USERPWD, "testuser:testpass"); - easy_setopt(ntlm_curls[num_handles], CURLOPT_WRITEFUNCTION, callback); + easy_setopt(ntlm_curls[num_handles], CURLOPT_WRITEFUNCTION, cb2032); easy_setopt(ntlm_curls[num_handles], CURLOPT_WRITEDATA, (void *)(ntlm_curls + num_handles)); easy_setopt(ntlm_curls[num_handles], CURLOPT_HEADER, 1L); @@ -153,7 +153,7 @@ static CURLcode test_lib2032(const char *URL) /* libntlmconnect */ multi_perform(multi, &running); curl_mfprintf(stderr, "%s:%d running %d state %d\n", - __FILE__, __LINE__, running, state); + __FILE__, __LINE__, running, (int)state); abort_on_test_timeout(); @@ -177,7 +177,8 @@ static CURLcode test_lib2032(const char *URL) /* libntlmconnect */ } state = num_handles < MAX_EASY_HANDLES ? ReadyForNewHandle : NoMoreHandles; - curl_mfprintf(stderr, "%s:%d new state %d\n", __FILE__, __LINE__, state); + curl_mfprintf(stderr, "%s:%d new state %d\n", + __FILE__, __LINE__, (int)state); } multi_timeout(multi, &timeout); diff --git a/tests/libtest/lib2082.c b/tests/libtest/lib2082.c index 87d0f8eada4b..8a404f232740 100644 --- a/tests/libtest/lib2082.c +++ b/tests/libtest/lib2082.c @@ -88,7 +88,8 @@ static CURLcode test_lib2082(const char *URL) /* libprereq */ if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, + curl_easy_strerror(result)); goto test_cleanup; } } diff --git a/tests/libtest/lib2118.c b/tests/libtest/lib2118.c new file mode 100644 index 000000000000..052ade015918 --- /dev/null +++ b/tests/libtest/lib2118.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* CURLOPT_SSL_VERIFYHOST is independent of CURLOPT_SSL_VERIFYPEER, so a + certificate issued for another name must be rejected even when the peer is + not verified. The server presents a certificate for "localhost.nn". */ + +static CURLcode test_lib2118(const char *URL) +{ + CURLcode result; + CURL *curl; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); + + result = curl_easy_perform(curl); + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib2301.c b/tests/libtest/lib2301.c index 8d036682a45a..0af579231e47 100644 --- a/tests/libtest/lib2301.c +++ b/tests/libtest/lib2301.c @@ -86,7 +86,7 @@ static CURLcode test_lib2301(const char *URL) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, t2301_write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl); result = curl_easy_perform(curl); - curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", (int)result); #if 0 if(result == CURLE_OK) t2301_websocket(curl); @@ -97,6 +97,8 @@ static CURLcode test_lib2301(const char *URL) curl_global_cleanup(); return result; #else - NO_SUPPORT_BUILT_IN + (void)URL; + curl_mfprintf(stderr, "Missing support\n"); + return CURLE_UNSUPPORTED_PROTOCOL; #endif } diff --git a/tests/libtest/lib2302.c b/tests/libtest/lib2302.c index ef8990fc177f..759ea5eafd24 100644 --- a/tests/libtest/lib2302.c +++ b/tests/libtest/lib2302.c @@ -48,7 +48,7 @@ static void flush_data(struct ws_data *wd) curl_mprintf("\n"); if(wd->has_meta) - curl_mprintf("RECFLAGS: %x\n", wd->meta_flags); + curl_mprintf("RECFLAGS: %x\n", (unsigned int)wd->meta_flags); else curl_mfprintf(stderr, "RECFLAGS: NULL\n"); wd->blen = 0; @@ -63,7 +63,7 @@ static size_t add_data(struct ws_data *wd, const char *buf, size_t blen, (meta && meta->flags != wd->meta_flags)) { if(wd->nwrites > 0) flush_data(wd); - wd->has_meta = (meta != NULL); + wd->has_meta = !!meta; wd->meta_flags = meta ? meta->flags : 0; } @@ -102,7 +102,7 @@ static CURLcode test_lib2302(const char *URL) global_init(CURL_GLOBAL_ALL); memset(&ws_data, 0, sizeof(ws_data)); - ws_data.buf = (char *)curlx_calloc(LIB2302_BUFSIZE, 1); + ws_data.buf = curlx_calloc(LIB2302_BUFSIZE, 1); if(ws_data.buf) { curl = curl_easy_init(); if(curl) { @@ -115,7 +115,7 @@ static CURLcode test_lib2302(const char *URL) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, t2302_write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ws_data); result = curl_easy_perform(curl); - curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", (int)result); /* always cleanup */ curl_easy_cleanup(curl); flush_data(&ws_data); @@ -125,6 +125,8 @@ static CURLcode test_lib2302(const char *URL) curl_global_cleanup(); return result; #else - NO_SUPPORT_BUILT_IN + (void)URL; + curl_mfprintf(stderr, "Missing support\n"); + return CURLE_UNSUPPORTED_PROTOCOL; #endif } diff --git a/tests/libtest/lib2304.c b/tests/libtest/lib2304.c index fd2a29da6c46..e4055315a7d4 100644 --- a/tests/libtest/lib2304.c +++ b/tests/libtest/lib2304.c @@ -34,7 +34,7 @@ static CURLcode recv_any(CURL *curl) return result; curl_mfprintf(stderr, "recv_any: got %zu bytes rflags %x\n", rlen, - meta->flags); + (unsigned int)meta->flags); return CURLE_OK; } @@ -75,7 +75,7 @@ static CURLcode test_lib2304(const char *URL) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ result = curl_easy_perform(curl); - curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", (int)result); if(result == CURLE_OK) t2304_websocket(curl); @@ -85,6 +85,8 @@ static CURLcode test_lib2304(const char *URL) curl_global_cleanup(); return result; #else - NO_SUPPORT_BUILT_IN + (void)URL; + curl_mfprintf(stderr, "Missing support\n"); + return CURLE_UNSUPPORTED_PROTOCOL; #endif } diff --git a/tests/libtest/lib2308.c b/tests/libtest/lib2308.c index 40d77019d1e9..d582c7135c86 100644 --- a/tests/libtest/lib2308.c +++ b/tests/libtest/lib2308.c @@ -42,7 +42,7 @@ static CURLcode test_lib2308(const char *URL) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb_curl); curl_easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); - curl_mprintf("Returned %d, should be %d.\n", result, CURLE_WRITE_ERROR); + curl_mprintf("Returned %d, should be %d.\n", (int)result, CURLE_WRITE_ERROR); fflush(stdout); curl_easy_cleanup(curl); curl_global_cleanup(); diff --git a/tests/libtest/lib2309.c b/tests/libtest/lib2309.c index c58b7ebbb4c6..212d82aa162d 100644 --- a/tests/libtest/lib2309.c +++ b/tests/libtest/lib2309.c @@ -51,7 +51,8 @@ static CURLcode test_lib2309(const char *URL) curldupe = curl_easy_duphandle(curl); if(curldupe) { result = curl_easy_perform(curldupe); - curl_mprintf("Returned %d, should be %d.\n", result, CURLE_WRITE_ERROR); + curl_mprintf("Returned %d, should be %d.\n", (int)result, + CURLE_WRITE_ERROR); fflush(stdout); curl_easy_cleanup(curldupe); } diff --git a/tests/libtest/lib2397.c b/tests/libtest/lib2397.c new file mode 100644 index 000000000000..e30fa235f644 --- /dev/null +++ b/tests/libtest/lib2397.c @@ -0,0 +1,64 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib2397(const char *URL) +{ + CURLcode result; + CURL *curl; + char *referer = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + curl_easy_setopt(curl, CURLOPT_URL, URL); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + + result = curl_easy_perform(curl); + if(result) + curl_mfprintf(stderr, "transfer failed: %s\n", curl_easy_strerror(result)); + else + result = curl_easy_getinfo(curl, CURLINFO_REFERER, &referer); + + if(!referer) + curl_mfprintf(stderr, "CURLINFO_REFERER was not populated\n"); + else + result = curl_easy_setopt(curl, CURLOPT_REFERER, referer); + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib2405.c b/tests/libtest/lib2405.c index eee6e9123708..6d98183a83ba 100644 --- a/tests/libtest/lib2405.c +++ b/tests/libtest/lib2405.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Dmitry Karpov + * Copyright (C) Dmitry Karpov * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -29,8 +29,8 @@ * HTTP1 amd HTTP2 (no multiplexing) two transfers (expected two descriptors), * HTTP2 with multiplexing (expected one descriptors) * Improper inputs to the API result in CURLM_BAD_FUNCTION_ARGUMENT. - * Sending an empty ufds, and size = 0 will return the number of fds needed. - * Sending a non-empty ufds, but smaller than the fds needed will result in a + * Sending an empty ufds, and size = 0 returns the number of fds needed. + * Sending a non-empty ufds, but smaller than the fds needed results in a * CURLM_OUT_OF_MEMORY, and a number of fds that is >= to the number needed. * * It is also expected that all transfers run by multi-handle should complete @@ -41,24 +41,6 @@ /* ---------------------------------------------------------------- */ -#define test_check(expected_fds) \ - if(result != CURLE_OK) { \ - curl_mfprintf(stderr, "test failed with code: %d\n", result); \ - goto test_cleanup; \ - } \ - else if(fd_count != (expected_fds)) { \ - curl_mfprintf(stderr, "Max number of waitfds: %u not as expected: %u\n", \ - fd_count, expected_fds); \ - result = TEST_ERR_FAILURE; \ - goto test_cleanup; \ - } - -#define test_run_check(option, expected_fds) \ - do { \ - result = test_run(URL, option, &fd_count); \ - test_check(expected_fds); \ - } while(0) - /* ---------------------------------------------------------------- */ enum { @@ -67,13 +49,6 @@ enum { TEST_USE_HTTP2_MPLEX }; -static size_t emptyWriteFunc(char *ptr, size_t size, size_t nmemb, void *data) -{ - (void)ptr; - (void)data; - return size * nmemb; -} - static CURLcode set_easy(const char *URL, CURL *curl, long option) { CURLcode result = CURLE_OK; @@ -110,7 +85,7 @@ static CURLcode set_easy(const char *URL, CURL *curl, long option) easy_setopt(curl, CURLOPT_HEADER, 1L); /* empty write function */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, emptyWriteFunc); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); test_cleanup: return result; @@ -309,6 +284,22 @@ static CURLcode test_run(const char *URL, long option, return result; } +static CURLcode test_run_check(const char *URL, long option, + unsigned int expected_fds) +{ + unsigned int fd_count = 0; + CURLcode result = test_run(URL, option, &fd_count); + if(result) + curl_mfprintf(stderr, "test failed with code: %d\n", (int)result); + else if(fd_count != expected_fds) { + curl_mfprintf(stderr, + "Max number of waitfds: %u not as expected: %u\n", + fd_count, expected_fds); + result = TEST_ERR_FAILURE; + } + return result; +} + static CURLcode empty_multi_test(void) { CURLMcode mresult = CURLM_OK; @@ -367,29 +358,50 @@ static CURLcode empty_multi_test(void) return result; } +static unsigned int uses_threaded(void) +{ + curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); + const char * const *n = ver->feature_names; + int i; + unsigned int uses = 0; + /* the 'asyn-rr' feature tells us libcurl uses the threaded resolver */ + for(i = 0; n[i]; i++) { + if(!strcmp("asyn-rr", n[i])) + uses = 1; + } + /* if not using asyn-rr, check if doing asynch DNS without using c-ares */ + if(!uses && (ver->features & CURL_VERSION_ASYNCHDNS) && !ver->ares) + uses = 1; + return uses; +} + static CURLcode test_lib2405(const char *URL) { CURLcode result = CURLE_OK; - unsigned int fd_count = 0; + int uses_threaded_resolver; global_init(CURL_GLOBAL_ALL); + uses_threaded_resolver = uses_threaded(); + /* Testing curl_multi_waitfds on empty and not started handles */ result = empty_multi_test(); if(result != CURLE_OK) goto test_cleanup; if(testnum == 2405) { - /* HTTP1, expected 3 waitfds - one for each transfer + wakeup */ - test_run_check(TEST_USE_HTTP1, 3U); + /* HTTP1, one for each transfer + possible wakeup */ + result = test_run_check(URL, TEST_USE_HTTP1, 2 + uses_threaded_resolver); } #ifdef USE_HTTP2 else { /* 2407 */ - /* HTTP2, expected 3 waitfds - one for each transfer + wakeup */ - test_run_check(TEST_USE_HTTP2, 3U); + /* HTTP2, one for each transfer + possible wakeup */ + result = test_run_check(URL, TEST_USE_HTTP2, 2 + uses_threaded_resolver); - /* HTTP2 with multiplexing, expected 2 waitfds - transfers + wakeup */ - test_run_check(TEST_USE_HTTP2_MPLEX, 2U); + /* HTTP2 with multiplexing, expected one waitfds + possible wakeup */ + if(!result) + result = test_run_check(URL, TEST_USE_HTTP2_MPLEX, + 1 + uses_threaded_resolver); } #endif diff --git a/tests/libtest/lib2412.c b/tests/libtest/lib2412.c new file mode 100644 index 000000000000..79d49a2d7680 --- /dev/null +++ b/tests/libtest/lib2412.c @@ -0,0 +1,95 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Dmitry Karpov + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "first.h" +#include "testtrace.h" + +static CURLcode test_lib2412(const char *URL) +{ + CURLcode result = CURLE_OK; + CURLM *multi = NULL; + CURL *easy = NULL; + CURLMcode rc; + fd_set readFdSet, writeFdSet, exceptFdSet; + int maxFd; + + (void)URL; + global_init(CURL_GLOBAL_ALL); + + multi = curl_multi_init(); + if(!multi) { + curl_mfprintf(stderr, "curl_multi_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + easy = curl_easy_init(); + if(!easy) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + debug_config.nohex = TRUE; + debug_config.tracetime = TRUE; + easy_setopt(easy, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(easy, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(easy, CURLOPT_VERBOSE, 1L); + + rc = curl_multi_add_handle(multi, easy); + if(rc) { + curl_mfprintf(stderr, "curl_multi_add_handle() failed: %d\n", rc); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + FD_ZERO(&readFdSet); + FD_ZERO(&writeFdSet); + FD_ZERO(&exceptFdSet); + maxFd = -1; + rc = curl_multi_fdset(multi, &readFdSet, &writeFdSet, &exceptFdSet, + &maxFd); + if(rc) { + curl_mfprintf(stderr, "curl_multi_fdset() failed: %d\n", rc); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + if(maxFd == -1) + curl_mfprintf(stderr, "There are no file descriptors to wait for\n"); + else { + curl_mfprintf(stderr, "libcurl supplied a file descriptor to " + "wait for (maxFd=%d). Waiting now ...\n", maxFd); + result = TEST_ERR_FAILURE; + } + +test_cleanup: + if(easy) { + curl_multi_remove_handle(multi, easy); + curl_easy_cleanup(easy); + } + if(multi) + curl_multi_cleanup(multi); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib2414.c b/tests/libtest/lib2414.c new file mode 100644 index 000000000000..e04dff3de7b5 --- /dev/null +++ b/tests/libtest/lib2414.c @@ -0,0 +1,53 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Dmitry Karpov + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "first.h" +#include "testtrace.h" + +static CURLcode test_lib2414(const char *URL) +{ + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + int running; + + (void)URL; + global_init(CURL_GLOBAL_ALL); + + multi = curl_multi_init(); + if(!multi) { + curl_mfprintf(stderr, "curl_multi_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + curl_multi_wakeup(multi); + curl_multi_perform(multi, &running); + curl_multi_poll(multi, NULL, 0, INT_MAX, NULL); + +test_cleanup: + if(multi) + curl_multi_cleanup(multi); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib2502.c b/tests/libtest/lib2502.c index 4743afc11996..18382436c63b 100644 --- a/tests/libtest/lib2502.c +++ b/tests/libtest/lib2502.c @@ -74,7 +74,7 @@ static CURLcode test_lib2502(const char *URL) /* go verbose */ debug_config.nohex = TRUE; debug_config.tracetime = FALSE; - test_setopt(curl[i], CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl[i], CURLOPT_DEBUGDATA, &debug_config); easy_setopt(curl[i], CURLOPT_DEBUGFUNCTION, libtest_debug_cb); easy_setopt(curl[i], CURLOPT_VERBOSE, 1L); /* include headers */ diff --git a/tests/libtest/lib2504.c b/tests/libtest/lib2504.c index 72b965d6e6ed..3b7b61fe8b47 100644 --- a/tests/libtest/lib2504.c +++ b/tests/libtest/lib2504.c @@ -25,13 +25,6 @@ #include "testtrace.h" -static size_t sink2504(char *ptr, size_t size, size_t nmemb, void *ud) -{ - (void)ptr; - (void)ud; - return size * nmemb; -} - static void dump_cookies2504(CURL *h, const char *tag) { struct curl_slist *cookies = NULL; @@ -68,17 +61,17 @@ static CURLcode test_lib2504(const char *URL) hdrs = curl_slist_append(hdrs, "Host: victim.internal"); if(hdrs) { - test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2504); - test_setopt(curl, CURLOPT_COOKIEFILE, ""); - test_setopt(curl, CURLOPT_HTTPHEADER, hdrs); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + easy_setopt(curl, CURLOPT_COOKIEFILE, ""); + easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); curl_mprintf("req1=%d\n", (int)result); dump_cookies2504(curl, "after request 1"); - test_setopt(curl, CURLOPT_HTTPHEADER, NULL); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); curl_mprintf("req2=%d\n", (int)result); diff --git a/tests/libtest/lib2505.c b/tests/libtest/lib2505.c index c17025987418..5d43eec3920f 100644 --- a/tests/libtest/lib2505.c +++ b/tests/libtest/lib2505.c @@ -25,13 +25,6 @@ #include "testtrace.h" -static size_t sink2505(char *ptr, size_t size, size_t nmemb, void *ud) -{ - (void)ptr; - (void)ud; - return size * nmemb; -} - static CURLcode test_lib2505(const char *URL) { CURL *curl; @@ -49,16 +42,16 @@ static CURLcode test_lib2505(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2505); - test_setopt(curl, CURLOPT_AUTOREFERER, 1L); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + easy_setopt(curl, CURLOPT_AUTOREFERER, 1L); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); curl_mprintf("req1=%d\n", (int)result); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); curl_mprintf("req2=%d\n", (int)result); diff --git a/tests/libtest/lib2506.c b/tests/libtest/lib2506.c index 8b3b3429f901..8779f6c9d5b6 100644 --- a/tests/libtest/lib2506.c +++ b/tests/libtest/lib2506.c @@ -25,13 +25,6 @@ #include "testtrace.h" -static size_t sink2506(char *ptr, size_t size, size_t nmemb, void *ud) -{ - (void)ptr; - (void)ud; - return size * nmemb; -} - static CURLcode test_lib2506(const char *URL) { CURL *curl; @@ -49,17 +42,17 @@ static CURLcode test_lib2506(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2506); - test_setopt(curl, CURLOPT_PROXY, URL); - test_setopt(curl, CURLOPT_URL, libtest_arg2); - test_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); - test_setopt(curl, CURLOPT_NETRC_FILE, libtest_arg3); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); + easy_setopt(curl, CURLOPT_PROXY, URL); + easy_setopt(curl, CURLOPT_URL, libtest_arg2); + easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); + easy_setopt(curl, CURLOPT_NETRC_FILE, libtest_arg3); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* CURLOPT_UNRESTRICTED_AUTH should not make a difference because the credentials come from netrc */ - test_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1L); + easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib2700.c b/tests/libtest/lib2700.c index e8199e0364b6..6d5a7ffc0806 100644 --- a/tests/libtest/lib2700.c +++ b/tests/libtest/lib2700.c @@ -39,7 +39,7 @@ static const char *descr_flags(int flags) return "pong"; if(flags & CURLWS_CLOSE) return "close"; - assert(false); + DEBUGASSERT(FALSE); return ""; } @@ -50,19 +50,19 @@ static CURLcode send_header(CURL *curl, int flags, size_t size) retry: result = curl_ws_send(curl, NULL, 0, &nsent, (curl_off_t)size, - flags | CURLWS_OFFSET); + flags | CURLWS_OFFSET); if(result == CURLE_AGAIN) { - assert(nsent == 0); + DEBUGASSERT(nsent == 0); goto retry; } if(result) { curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); - assert(nsent == 0); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); + DEBUGASSERT(nsent == 0); return result; } - assert(nsent == 0); + DEBUGASSERT(nsent == 0); return CURLE_OK; } @@ -81,20 +81,20 @@ static CURLcode recv_header(CURL *curl, int *flags, curl_off_t *offset, retry: result = curl_ws_recv(curl, NULL, 0, &nread, &meta); if(result == CURLE_AGAIN) { - assert(nread == 0); + DEBUGASSERT(nread == 0); goto retry; } if(result) { curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); - assert(nread == 0); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); + DEBUGASSERT(nread == 0); return result; } - assert(nread == 0); - assert(meta != NULL); - assert(meta->flags); - assert(meta->offset == 0); + DEBUGASSERT(nread == 0); + DEBUGASSERT(meta); + DEBUGASSERT(meta->flags); + DEBUGASSERT(meta->offset == 0); *flags = meta->flags; *offset = meta->offset; @@ -123,17 +123,17 @@ static CURLcode send_chunk(CURL *curl, int flags, const char *buffer, result = curl_ws_send(curl, buffer + *offset, size - *offset, &nsent, 0, flags); if(result == CURLE_AGAIN) { - assert(nsent == 0); + DEBUGASSERT(nsent == 0); goto retry; } if(result) { curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); - assert(nsent == 0); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); + DEBUGASSERT(nsent == 0); return result; } - assert(nsent <= size - *offset); + DEBUGASSERT(nsent <= size - *offset); *offset += nsent; @@ -152,21 +152,21 @@ static CURLcode recv_chunk(CURL *curl, int flags, curl_off_t *offset, retry: result = curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta); if(result == CURLE_AGAIN) { - assert(nread == 0); + DEBUGASSERT(nread == 0); goto retry; } if(result) { curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); - assert(nread == 0); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); + DEBUGASSERT(nread == 0); return result; } - assert(nread <= sizeof(buffer)); - assert(meta != NULL); - assert(meta->flags == flags); - assert(meta->offset == *offset); - assert(meta->bytesleft == (*bytesleft - (curl_off_t)nread)); + DEBUGASSERT(nread <= sizeof(buffer)); + DEBUGASSERT(meta); + DEBUGASSERT(meta->flags == flags); + DEBUGASSERT(meta->offset == *offset); + DEBUGASSERT(meta->bytesleft == (*bytesleft - (curl_off_t)nread)); *offset += nread; *bytesleft -= nread; @@ -200,7 +200,7 @@ static CURLcode recv_frame(CURL *curl, bool *stop) } if(flags & CURLWS_CLOSE) - *stop = true; + *stop = TRUE; curl_mfprintf(stdout, "\n"); @@ -212,7 +212,7 @@ static CURLcode test_lib2700(const char *URL) { #ifndef CURL_DISABLE_WEBSOCKETS CURLcode result = CURLE_OK; - bool stop = false; + bool stop = FALSE; CURL *curl; global_init(CURL_GLOBAL_ALL); @@ -234,7 +234,7 @@ static CURLcode test_lib2700(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -249,6 +249,8 @@ static CURLcode test_lib2700(const char *URL) curl_global_cleanup(); return result; #else - NO_SUPPORT_BUILT_IN + (void)URL; + curl_mfprintf(stderr, "Missing support\n"); + return CURLE_UNSUPPORTED_PROTOCOL; #endif } diff --git a/tests/libtest/lib3010.c b/tests/libtest/lib3010.c index 7a668b075133..7552b021ec77 100644 --- a/tests/libtest/lib3010.c +++ b/tests/libtest/lib3010.c @@ -39,7 +39,8 @@ static CURLcode test_lib3010(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, + curl_easy_strerror(result)); goto test_cleanup; } curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &follow_url); @@ -50,7 +51,8 @@ static CURLcode test_lib3010(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, + curl_easy_strerror(result)); goto test_cleanup; } diff --git a/tests/libtest/lib3025.c b/tests/libtest/lib3025.c index 362325311fe6..c7b38e465d12 100644 --- a/tests/libtest/lib3025.c +++ b/tests/libtest/lib3025.c @@ -42,10 +42,10 @@ static CURLcode test_lib3025(const char *URL) } icy = curl_slist_append(icy, "ICY 200 OK"); - test_setopt(curl, CURLOPT_HTTP200ALIASES, icy); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTP200ALIASES, icy); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib3026.c b/tests/libtest/lib3026.c index fb56da7ef846..f072dee282fb 100644 --- a/tests/libtest/lib3026.c +++ b/tests/libtest/lib3026.c @@ -76,7 +76,7 @@ static CURLcode test_lib3026(const char *URL) if(results[i] != CURLE_OK) { curl_mfprintf(stderr, "%s:%d thread[%u]: curl_global_init() failed," "with code %d (%s)\n", __FILE__, __LINE__, - i, results[i], curl_easy_strerror(results[i])); + i, (int)results[i], curl_easy_strerror(results[i])); result = TEST_ERR_MAJOR_BAD; } } @@ -133,7 +133,7 @@ static CURLcode test_lib3026(const char *URL) if(results[i] != CURLE_OK) { curl_mfprintf(stderr, "%s:%d thread[%u]: curl_global_init() failed," "with code %d (%s)\n", __FILE__, __LINE__, - i, results[i], curl_easy_strerror(results[i])); + i, (int)results[i], curl_easy_strerror(results[i])); result = TEST_ERR_MAJOR_BAD; } } diff --git a/tests/libtest/lib3027.c b/tests/libtest/lib3027.c index 845eea239a73..013c13f43ec4 100644 --- a/tests/libtest/lib3027.c +++ b/tests/libtest/lib3027.c @@ -41,7 +41,7 @@ static CURLcode test_lib3027(const char *URL) result = curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime); /* MTDM fails with 550, so filetime should be -1 */ if((result == CURLE_OK) && (filetime != -1)) { - /* we just need to return something which is not CURLE_OK */ + /* we need to return something which is not CURLE_OK */ result = CURLE_UNSUPPORTED_PROTOCOL; } } diff --git a/tests/libtest/lib3033.c b/tests/libtest/lib3033.c index 7fa4731badef..f43c1696ca36 100644 --- a/tests/libtest/lib3033.c +++ b/tests/libtest/lib3033.c @@ -69,7 +69,8 @@ static CURLcode t3033_req_test(CURLM *multi, CURL *curl, result = msg->data.result; if(result != CURLE_OK) { - curl_mfprintf(stderr, "curl_multi_info_read() returned %d\n", result); + curl_mfprintf(stderr, "curl_multi_info_read() returned %d\n", + (int)result); goto test_cleanup; } diff --git a/tests/libtest/lib3034.c b/tests/libtest/lib3034.c index 5c693c5ce854..0dbfee84391f 100644 --- a/tests/libtest/lib3034.c +++ b/tests/libtest/lib3034.c @@ -42,22 +42,22 @@ static CURLcode test_lib3034(const char *URL) global_init(CURL_GLOBAL_ALL); easy_init(curl); - /* This first request will receive a redirect response; deliberately only + /* This first request receives a redirect response; deliberately only * set the CURLOPT_READFUNCTION but not the CURLOPT_SEEKFUNCTION to force a * rewind failure (CURLE_SEND_FAIL_REWIND). */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_INFILESIZE, 5L); - test_setopt(curl, CURLOPT_READFUNCTION, t3034_read_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE, 5L); + easy_setopt(curl, CURLOPT_READFUNCTION, t3034_read_cb); result = curl_easy_perform(curl); if(result != CURLE_SEND_FAIL_REWIND) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } @@ -65,8 +65,8 @@ static CURLcode test_lib3034(const char *URL) curl_easy_reset(curl); /* Perform a second request, which should succeed. */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib3100.c b/tests/libtest/lib3100.c index 8d7988af0e49..121d0a8490e4 100644 --- a/tests/libtest/lib3100.c +++ b/tests/libtest/lib3100.c @@ -40,21 +40,21 @@ static CURLcode test_lib3100(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); - test_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - test_setopt(curl, CURLOPT_USERNAME, "user"); - test_setopt(curl, CURLOPT_PASSWORD, "password"); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + easy_setopt(curl, CURLOPT_USERNAME, "user"); + easy_setopt(curl, CURLOPT_PASSWORD, "password"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); result = curl_easy_perform(curl); if(result != CURLE_OK) { - curl_mfprintf(stderr, "Failed to send DESCRIBE: %d\n", result); + curl_mfprintf(stderr, "Failed to send DESCRIBE: %d\n", (int)result); result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } diff --git a/tests/libtest/lib3101.c b/tests/libtest/lib3101.c index 56fc27467ad5..2e2b45357a7f 100644 --- a/tests/libtest/lib3101.c +++ b/tests/libtest/lib3101.c @@ -40,14 +40,14 @@ static CURLcode test_lib3101(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - test_setopt(curl, CURLOPT_USERNAME, "user"); - test_setopt(curl, CURLOPT_PASSWORD, "password"); - test_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https"); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + easy_setopt(curl, CURLOPT_USERNAME, "user"); + easy_setopt(curl, CURLOPT_PASSWORD, "password"); + easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https"); result = curl_easy_perform(curl); if(result != CURLE_OK) { diff --git a/tests/libtest/lib3102.c b/tests/libtest/lib3102.c index 765488f36ec6..b3af3981a168 100644 --- a/tests/libtest/lib3102.c +++ b/tests/libtest/lib3102.c @@ -34,11 +34,11 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) /* Chains with only a single certificate are always in order */ if(cert_info->num_of_certs <= 1) - return true; + return TRUE; /* Enumerate each certificate in the chain */ for(cert = 0; cert < cert_info->num_of_certs; cert++) { - struct curl_slist *slist = cert_info->certinfo[cert]; + const struct curl_slist *slist = cert_info->certinfo[cert]; const char *issuer = NULL; const char *subject = NULL; @@ -47,11 +47,11 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) static const char issuer_prefix[] = "Issuer:"; static const char subject_prefix[] = "Subject:"; - if(!strncmp(slist->data, issuer_prefix, sizeof(issuer_prefix) - 1)) { - issuer = slist->data + sizeof(issuer_prefix) - 1; + if(!strncmp(slist->data, issuer_prefix, CURL_CSTRLEN(issuer_prefix))) { + issuer = slist->data + CURL_CSTRLEN(issuer_prefix); } - if(!strncmp(slist->data, subject_prefix, sizeof(subject_prefix) - 1)) { - subject = slist->data + sizeof(subject_prefix) - 1; + if(!strncmp(slist->data, subject_prefix, CURL_CSTRLEN(subject_prefix))) { + subject = slist->data + CURL_CSTRLEN(subject_prefix); } } @@ -63,12 +63,12 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) if(last_issuer) { /* If the last certificate's issuer matches the current certificate's * subject, then the chain is in order */ - if(strcmp(last_issuer, subject) != 0) { + if(strcmp(last_issuer, subject)) { curl_mfprintf(stderr, "cert %d issuer does not match cert %d subject\n", cert - 1, cert); curl_mfprintf(stderr, "certificate chain is not in order\n"); - return false; + return FALSE; } } } @@ -77,14 +77,7 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) } curl_mprintf("certificate chain is in order\n"); - return true; -} - -static size_t wrfu(char *ptr, size_t size, size_t nmemb, void *stream) -{ - (void)stream; - (void)ptr; - return size * nmemb; + return TRUE; } static CURLcode test_lib3102(const char *URL) @@ -105,19 +98,19 @@ static CURLcode test_lib3102(const char *URL) } /* Set the HTTPS URL to retrieve. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Capture certificate information */ - test_setopt(curl, CURLOPT_CERTINFO, 1L); + easy_setopt(curl, CURLOPT_CERTINFO, 1L); /* Ignore output */ - test_setopt(curl, CURLOPT_WRITEFUNCTION, wrfu); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); /* No peer verify */ - test_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - test_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); if(!result || result == CURLE_GOT_NOTHING) { struct curl_certinfo *cert_info = NULL; diff --git a/tests/libtest/lib3103.c b/tests/libtest/lib3103.c index 268f3f09874c..cedb21d28a9a 100644 --- a/tests/libtest/lib3103.c +++ b/tests/libtest/lib3103.c @@ -35,17 +35,17 @@ static CURLcode test_lib3103(const char *URL) curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl = curl_easy_init(); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_PROXY, URL); - test_setopt(curl, CURLOPT_URL, "http://localhost/"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_PROXY, URL); + easy_setopt(curl, CURLOPT_URL, "http://localhost/"); - test_setopt(curl, CURLOPT_COOKIEFILE, ""); + easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* Set a cookie without Max-age or Expires */ - test_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost"); + easy_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost"); result = curl_easy_perform(curl); if(result) { diff --git a/tests/libtest/lib3104.c b/tests/libtest/lib3104.c index 4c9b9090f0cd..96ca51205e27 100644 --- a/tests/libtest/lib3104.c +++ b/tests/libtest/lib3104.c @@ -35,16 +35,16 @@ static CURLcode test_lib3104(const char *URL) curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl = curl_easy_init(); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_PROXY, URL); - test_setopt(curl, CURLOPT_URL, "http://example.com/"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_PROXY, URL); + easy_setopt(curl, CURLOPT_URL, "http://example.com/"); - test_setopt(curl, CURLOPT_COOKIEFILE, ""); + easy_setopt(curl, CURLOPT_COOKIEFILE, ""); - test_setopt(curl, CURLOPT_COOKIELIST, + easy_setopt(curl, CURLOPT_COOKIELIST, "example.com\tFALSE\t/\tFALSE\t0\tname\tvalue"); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib3207.c b/tests/libtest/lib3207.c index ae2a3a18d1f5..f28085c8652d 100644 --- a/tests/libtest/lib3207.c +++ b/tests/libtest/lib3207.c @@ -73,8 +73,8 @@ static unsigned int test_thread(void *ptr) int i; - /* Loop the transfer and cleanup the handle properly every lap. This will - still reuse ssl session since the pool is in the shared object! */ + /* Loop the transfer and cleanup the handle properly every lap. This + still reuses SSL session since the pool is in the shared object. */ for(i = 0; i < PER_THREAD_SIZE; i++) { CURL *curl = curl_easy_init(); if(curl) { @@ -88,7 +88,7 @@ static unsigned int test_thread(void *ptr) curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); /* always cleanup */ @@ -197,7 +197,7 @@ static CURLcode test_lib3207(const char *URL) result = ctx[i].result; } else { - struct curl_slist *item = ctx[i].contents; + const struct curl_slist *item = ctx[i].contents; while(item) { curl_mprintf("%s", item->data); item = item->next; diff --git a/tests/libtest/lib500.c b/tests/libtest/lib500.c index 71b1eaed8fe3..786d8360b177 100644 --- a/tests/libtest/lib500.c +++ b/tests/libtest/lib500.c @@ -69,17 +69,17 @@ static CURLcode test_lib500(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); - test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); if(libtest_arg3 && !strcmp(libtest_arg3, "activeftp")) - test_setopt(curl, CURLOPT_FTPPORT, "-"); + easy_setopt(curl, CURLOPT_FTPPORT, "-"); if(testnum == 585 || testnum == 586 || testnum == 595 || testnum == 596) setupcallbacks(curl); @@ -108,7 +108,7 @@ static CURLcode test_lib500(const char *URL) &time_starttransfer); curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME_T, &time_total); - /* since the timing will always vary we only compare relative + /* since the timing always varies we only compare relative differences between these 5 times */ if(time_namelookup > time_connect) { curl_mfprintf(moo, "namelookup vs connect: %" CURL_FORMAT_CURL_OFF_T @@ -127,10 +127,7 @@ static CURLcode test_lib500(const char *URL) (time_pretransfer / 1000000), (long)(time_pretransfer % 1000000)); } - if(time_posttransfer > time_pretransfer) { - /* counter-intuitive: on a GET request, all bytes are sent *before* - * PRETRANSFER happens. Thus POSTTRANSFER has to be smaller. - * The reverse would be true for a POST/PUT. */ + if(time_pretransfer > time_posttransfer) { curl_mfprintf(moo, "pretransfer vs posttransfer: %" CURL_FORMAT_CURL_OFF_T ".%06ld %" CURL_FORMAT_CURL_OFF_T ".%06ld\n", diff --git a/tests/libtest/lib5000.c b/tests/libtest/lib5000.c new file mode 100644 index 000000000000..511b26c8e481 --- /dev/null +++ b/tests/libtest/lib5000.c @@ -0,0 +1,68 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static CURLcode test_lib5000(const char *URL) +{ + CURL *curl; + CURLcode result = TEST_ERR_MAJOR_BAD; + struct curl_slist *connect_to = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "test-key-ed25519"); + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); + if(libtest_arg2) { + connect_to = curl_slist_append(NULL, libtest_arg2); + if(!connect_to) + goto test_cleanup; + } + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_slist_free_all(connect_to); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib5004.c b/tests/libtest/lib5004.c new file mode 100644 index 000000000000..08ab3b6f384b --- /dev/null +++ b/tests/libtest/lib5004.c @@ -0,0 +1,96 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + * + ***************************************************************************/ + +/* RFC 9421 Appendix B.2.6 test vector: Ed25519 signing of a POST request + * with headers. Uses CURL_HTTPSIG_CREATED=1618884473 to match the RFC + * expected timestamp. */ + +#include "first.h" + +static CURLcode test_lib5004(const char *URL) +{ + CURL *curl; + CURLcode result = TEST_ERR_MAJOR_BAD; + struct curl_slist *connect_to = NULL; + struct curl_slist *headers = NULL; + struct curl_slist *nheaders; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "test-key-ed25519"); + easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS, + "date: method path authority content-type: content-length:"); + easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"hello\": \"world\"}"); + + headers = curl_slist_append(NULL, "Date: Tue, 20 Apr 2021 02:07:55 GMT"); + if(!headers) + goto test_cleanup; + + nheaders = curl_slist_append(headers, "Content-Type: application/json"); + if(!nheaders) + goto test_cleanup; + headers = nheaders; + + nheaders = curl_slist_append(headers, "Content-Length: 18"); + if(!nheaders) + goto test_cleanup; + headers = nheaders; + + easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + easy_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_URL, URL); + if(libtest_arg2) { + connect_to = curl_slist_append(NULL, libtest_arg2); + if(!connect_to) + goto test_cleanup; + } + easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_slist_free_all(headers); + curl_slist_free_all(connect_to); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib501.c b/tests/libtest/lib501.c index 358594c0377e..73bf010a1a9c 100644 --- a/tests/libtest/lib501.c +++ b/tests/libtest/lib501.c @@ -42,10 +42,10 @@ static CURLcode test_lib501(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* just verify that setting this to -1 is fine */ - test_setopt(curl, CURLOPT_MAXREDIRS, -1L); + /* verify that setting this to -1 is fine */ + easy_setopt(curl, CURLOPT_MAXREDIRS, -1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib505.c b/tests/libtest/lib505.c index 7ad6894ae88b..bf1310fcd2e6 100644 --- a/tests/libtest/lib505.c +++ b/tests/libtest/lib505.c @@ -24,7 +24,7 @@ #include "first.h" /* - * This example shows an FTP upload, with a rename of the file just after + * This example shows an FTP upload, with a rename of the file right after * a successful upload. * * Example based on source code provided by Erick Nuwendam. Thanks! @@ -38,12 +38,11 @@ static CURLcode test_lib505(const char *URL) FILE *hd_src; int hd; curlx_struct_stat file_info; - struct curl_slist *hl; + struct curl_slist *headerlist; + struct curl_slist *temp; - struct curl_slist *headerlist = NULL; - - static const char *buf_1 = "RNFR 505"; - static const char *buf_2 = "RNTO 505-forreal"; + static const char buf_1[] = "RNFR 505"; + static const char buf_2[] = "RNTO 505-forreal"; if(!libtest_arg2) { curl_mfprintf(stderr, "Usage: \n"); @@ -92,42 +91,42 @@ static CURLcode test_lib505(const char *URL) /* build a list of commands to pass to libcurl */ - hl = curl_slist_append(headerlist, buf_1); - if(!hl) { + headerlist = curl_slist_append(NULL, buf_1); + if(!headerlist) { curl_mfprintf(stderr, "curl_slist_append() failed\n"); curl_easy_cleanup(curl); curl_global_cleanup(); curlx_fclose(hd_src); return TEST_ERR_MAJOR_BAD; } - headerlist = curl_slist_append(hl, buf_2); - if(!headerlist) { + temp = curl_slist_append(headerlist, buf_2); + if(!temp) { curl_mfprintf(stderr, "curl_slist_append() failed\n"); - curl_slist_free_all(hl); + curl_slist_free_all(headerlist); curl_easy_cleanup(curl); curl_global_cleanup(); curlx_fclose(hd_src); return TEST_ERR_MAJOR_BAD; } - headerlist = hl; + headerlist = temp; /* enable uploading */ - test_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); /* enable verbose */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* specify target */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* pass in that last of FTP commands to run after the transfer */ - test_setopt(curl, CURLOPT_POSTQUOTE, headerlist); + easy_setopt(curl, CURLOPT_POSTQUOTE, headerlist); /* now specify which file to upload */ - test_setopt(curl, CURLOPT_READDATA, hd_src); + easy_setopt(curl, CURLOPT_READDATA, hd_src); /* and give the size of the upload (optional) */ - test_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); + easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib506.c b/tests/libtest/lib506.c index 0f6d75c9799d..8d36c32ec3c4 100644 --- a/tests/libtest/lib506.c +++ b/tests/libtest/lib506.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" - #define THREADS 2 /* struct containing data of a thread */ @@ -70,7 +68,7 @@ static void t506_test_lock(CURL *curl, curl_lock_data data, pcounter = &user->cookie_counter; break; default: - curl_mfprintf(stderr, "lock: no such data: %d\n", data); + curl_mfprintf(stderr, "lock: no such data: %d\n", (int)data); return; } @@ -109,7 +107,7 @@ static void t506_test_unlock(CURL *curl, curl_lock_data data, void *useptr) locknum = 2; break; default: - curl_mfprintf(stderr, "unlock: no such data: %d\n", data); + curl_mfprintf(stderr, "unlock: no such data: %d\n", (int)data); return; } @@ -158,7 +156,7 @@ static void *t506_test_fire(void *ptr) if(result) { int i = 0; curl_mfprintf(stderr, "perform URL '%s' repeat %d failed, curlcode %d\n", - tdata->url, i, result); + tdata->url, i, (int)result); } curl_mprintf("CLEANUP\n"); @@ -241,21 +239,21 @@ static CURLcode test_lib506(const char *URL) return TEST_ERR_MAJOR_BAD; } curl_mprintf("CURLOPT_SHARE\n"); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); curl_mprintf("CURLOPT_COOKIELIST injected_and_clobbered\n"); - test_setopt(curl, CURLOPT_COOKIELIST, + easy_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: injected_and_clobbered=yes; " "domain=host.foo.com; expires=Sat Feb 2 11:56:27 GMT 2030"); curl_mprintf("CURLOPT_COOKIELIST ALL\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "ALL"); + easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); curl_mprintf("CURLOPT_COOKIELIST session\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: session=elephants"); + easy_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: session=elephants"); curl_mprintf("CURLOPT_COOKIELIST injected\n"); - test_setopt(curl, CURLOPT_COOKIELIST, + easy_setopt(curl, CURLOPT_COOKIELIST, "Set-Cookie: injected=yes; domain=host.foo.com; " "expires=Sat Feb 2 11:56:27 GMT 2030"); curl_mprintf("CURLOPT_COOKIELIST SESS\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "SESS"); + easy_setopt(curl, CURLOPT_COOKIELIST, "SESS"); curl_mprintf("CLEANUP\n"); curl_easy_cleanup(curl); @@ -285,14 +283,14 @@ static CURLcode test_lib506(const char *URL) url = tutil_suburl(URL, i); headers = sethost(NULL); - test_setopt(curl, CURLOPT_HTTPHEADER, headers); - test_setopt(curl, CURLOPT_URL, url); + easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + easy_setopt(curl, CURLOPT_URL, url); curl_mprintf("CURLOPT_SHARE\n"); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); curl_mprintf("CURLOPT_COOKIEJAR\n"); - test_setopt(curl, CURLOPT_COOKIEJAR, jar); + easy_setopt(curl, CURLOPT_COOKIEJAR, jar); curl_mprintf("CURLOPT_COOKIELIST FLUSH\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); + easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); curl_mprintf("PERFORM\n"); curl_easy_perform(curl); @@ -312,16 +310,16 @@ static CURLcode test_lib506(const char *URL) } url = tutil_suburl(URL, i); headers = sethost(NULL); - test_setopt(curl, CURLOPT_HTTPHEADER, headers); - test_setopt(curl, CURLOPT_URL, url); + easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + easy_setopt(curl, CURLOPT_URL, url); curl_mprintf("CURLOPT_SHARE\n"); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); curl_mprintf("CURLOPT_COOKIELIST ALL\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "ALL"); + easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); curl_mprintf("CURLOPT_COOKIEJAR\n"); - test_setopt(curl, CURLOPT_COOKIEFILE, jar); + easy_setopt(curl, CURLOPT_COOKIEFILE, jar); curl_mprintf("CURLOPT_COOKIELIST RELOAD\n"); - test_setopt(curl, CURLOPT_COOKIELIST, "RELOAD"); + easy_setopt(curl, CURLOPT_COOKIELIST, "RELOAD"); result = CURLE_OK; @@ -369,7 +367,8 @@ static CURLcode test_lib506(const char *URL) curl_mprintf("SHARE_CLEANUP\n"); scode = curl_share_cleanup(share); if(scode != CURLSHE_OK) - curl_mfprintf(stderr, "curl_share_cleanup failed, code errno %d\n", scode); + curl_mfprintf(stderr, "curl_share_cleanup failed, code errno %d\n", + (int)scode); curl_mprintf("GLOBAL_CLEANUP\n"); curl_global_cleanup(); diff --git a/tests/libtest/lib508.c b/tests/libtest/lib508.c index e5823c510c14..20aa1ff86506 100644 --- a/tests/libtest/lib508.c +++ b/tests/libtest/lib508.c @@ -36,13 +36,13 @@ static size_t t508_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) return 0; if(pooh->sizeleft) { - *ptr = pooh->readptr[0]; /* copy one single byte */ - pooh->readptr++; /* advance pointer */ - pooh->sizeleft--; /* less data left */ - return 1; /* we return 1 byte at a time! */ + *ptr = pooh->readptr[0]; /* copy one single byte */ + pooh->readptr++; /* advance pointer */ + pooh->sizeleft--; /* less data left */ + return 1; /* we return 1 byte at a time! */ } - return 0; /* no more data left to deliver */ + return 0; /* no more data left to deliver */ } static CURLcode test_lib508(const char *URL) @@ -56,7 +56,7 @@ static CURLcode test_lib508(const char *URL) struct t508_WriteThis pooh; pooh.readptr = testdata; - pooh.sizeleft = strlen(testdata); + pooh.sizeleft = CURL_CSTRLEN(testdata); if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { curl_mfprintf(stderr, "curl_global_init() failed\n"); @@ -71,27 +71,27 @@ static CURLcode test_lib508(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Set the expected POST size */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); /* we want to use our own read function */ - test_setopt(curl, CURLOPT_READFUNCTION, t508_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t508_read_cb); /* pointer to pass to our read function */ - test_setopt(curl, CURLOPT_READDATA, &pooh); + easy_setopt(curl, CURLOPT_READDATA, &pooh); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib509.c b/tests/libtest/lib509.c index f888bbf156a4..38da36428fc6 100644 --- a/tests/libtest/lib509.c +++ b/tests/libtest/lib509.c @@ -99,7 +99,7 @@ static CURLcode test_lib509(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_USERAGENT, "test509"); /* uses curlx_strdup() */ + easy_setopt(curl, CURLOPT_USERAGENT, "test509"); /* uses curlx_strdup() */ asize = (int)sizeof(a); /* uses curlx_realloc() */ diff --git a/tests/libtest/lib510.c b/tests/libtest/lib510.c index c325dc0bb7e3..a4acbdddb623 100644 --- a/tests/libtest/lib510.c +++ b/tests/libtest/lib510.c @@ -87,32 +87,32 @@ static CURLcode test_lib510(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* we want to use our own read function */ - test_setopt(curl, CURLOPT_READFUNCTION, t510_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t510_read_cb); /* pointer to pass to our read function */ - test_setopt(curl, CURLOPT_READDATA, &pooh); + easy_setopt(curl, CURLOPT_READDATA, &pooh); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); /* enforce chunked transfer by setting the header */ - test_setopt(curl, CURLOPT_HTTPHEADER, slist); + easy_setopt(curl, CURLOPT_HTTPHEADER, slist); if(testnum == 565) { - test_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - test_setopt(curl, CURLOPT_USERPWD, "foo:bar"); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + easy_setopt(curl, CURLOPT_USERPWD, "foo:bar"); } - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib511.c b/tests/libtest/lib511.c index d87b4805bc7b..7907f500ee3a 100644 --- a/tests/libtest/lib511.c +++ b/tests/libtest/lib511.c @@ -40,10 +40,10 @@ static CURLcode test_lib511(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_FILETIME, 1L); - test_setopt(curl, CURLOPT_NOBODY, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_FILETIME, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib513.c b/tests/libtest/lib513.c index fb1ead53be92..9ed3f1dd424a 100644 --- a/tests/libtest/lib513.c +++ b/tests/libtest/lib513.c @@ -50,27 +50,27 @@ static CURLcode test_lib513(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Set the expected POST size */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, 1L); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 1L); /* we want to use our own read function */ - test_setopt(curl, CURLOPT_READFUNCTION, t513_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t513_read_cb); /* pointer to pass to our read function */ - test_setopt(curl, CURLOPT_READDATA, NULL); + easy_setopt(curl, CURLOPT_READDATA, NULL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib514.c b/tests/libtest/lib514.c index 89db0645821b..fb4b7e2385c1 100644 --- a/tests/libtest/lib514.c +++ b/tests/libtest/lib514.c @@ -41,32 +41,30 @@ static CURLcode test_lib514(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Based on a bug report by Niels van Tongeren on June 29, 2004: - A weird situation occurs when request 1 is a POST request and the request - 2 is a HEAD request. For the POST request we set the CURLOPT_POSTFIELDS, - CURLOPT_POSTFIELDSIZE and CURLOPT_POST options. For the HEAD request we - set the CURLOPT_NOBODY option to '1'. + A weird situation occurs when request 1 is a POST request and the request + 2 is a HEAD request. For the POST request we set the CURLOPT_POSTFIELDS, + CURLOPT_POSTFIELDSIZE and CURLOPT_POST options. For the HEAD request we + set the CURLOPT_NOBODY option to '1'. */ - */ - - test_setopt(curl, CURLOPT_POSTFIELDS, "moo"); - test_setopt(curl, CURLOPT_POSTFIELDSIZE, 3L); - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POSTFIELDS, "moo"); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 3L); + easy_setopt(curl, CURLOPT_POST, 1L); /* this is where transfer 1 would take place, but skip that and change options right away instead */ - test_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ - test_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ + easy_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ /* Now, we should be making a fine HEAD request */ - /* Perform the request 2, result will get the return code */ + /* Perform the request 2, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib515.c b/tests/libtest/lib515.c index 9dcb17271b35..825cbe56cf8e 100644 --- a/tests/libtest/lib515.c +++ b/tests/libtest/lib515.c @@ -41,11 +41,11 @@ static CURLcode test_lib515(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_POSTFIELDS, NULL); - test_setopt(curl, CURLOPT_POSTFIELDSIZE, 0L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ - test_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ + easy_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ /* Now, we should be making a zero byte POST request */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib516.c b/tests/libtest/lib516.c index 21d6d945bc0c..61090736b772 100644 --- a/tests/libtest/lib516.c +++ b/tests/libtest/lib516.c @@ -41,10 +41,10 @@ static CURLcode test_lib516(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPPOST, NULL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ - test_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPPOST, NULL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ + easy_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ /* Now, we should be making a zero byte POST request */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib517.c b/tests/libtest/lib517.c index ef8663c39abd..609a44a06b2b 100644 --- a/tests/libtest/lib517.c +++ b/tests/libtest/lib517.c @@ -73,9 +73,9 @@ static CURLcode test_lib517(const char *URL) { "1-Jan-2003 00:00:00 GMT", 1041379200 }, { "1-Jan-2003 00:00:00 GMT", 1041379200 }, { "Wed,18-Apr-07 22:50:12 GMT", 1176936612 }, - { "WillyWonka , 18-Apr-07 22:50:12 GMT", -1 }, - { "WillyWonka , 18-Apr-07 22:50:12", -1 }, - { "WillyWonka , 18-apr-07 22:50:12", -1 }, + { "BillyWonka , 18-Apr-07 22:50:12 GMT", -1 }, + { "BillyWonka , 18-Apr-07 22:50:12", -1 }, + { "BillyWonka , 18-apr-07 22:50:12", -1 }, { "Mon, 18-Apr-1977 22:50:13 GMT", 230251813 }, { "Mon, 18-Apr-77 22:50:13 GMT", 230251813 }, { "Sat, 15-Apr-17\"21:01:22\"GMT", 1492290082 }, diff --git a/tests/libtest/lib518.c b/tests/libtest/lib518.c index 5c50ca5cbf0a..c751574f2900 100644 --- a/tests/libtest/lib518.c +++ b/tests/libtest/lib518.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" +#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) #define T518_SAFETY_MARGIN 16 @@ -36,8 +36,6 @@ #define DEV_NULL "/dev/null" #endif -#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) - static int *t518_testfd = NULL; static struct rlimit t518_num_open; static char t518_msgbuff[256]; @@ -60,8 +58,7 @@ static void t518_close_file_descriptors(void) t518_num_open.rlim_cur++) if(t518_testfd[t518_num_open.rlim_cur] > 0) curlx_close(t518_testfd[t518_num_open.rlim_cur]); - curlx_free(t518_testfd); - t518_testfd = NULL; + curlx_safefree(t518_testfd); } static int t518_fopen_works(void) @@ -100,7 +97,7 @@ static int t518_test_rlimit(int keep_open) /* get initial open file limits */ - if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { + if(getrlimit(RLIMIT_NOFILE, &rl)) { t518_store_errmsg("getrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); return -1; @@ -126,7 +123,7 @@ static int t518_test_rlimit(int keep_open) * limit. Due to some other system limit the soft limit * might not be raised up to the hard limit. So from this * point the resulting soft limit is our limit. Trying to - * open more than soft limit file descriptors will fail. + * open more than soft limit file descriptors does fail. */ if(rl.rlim_cur != rl.rlim_max) { @@ -136,8 +133,8 @@ static int t518_test_rlimit(int keep_open) (rl.rlim_cur < OPEN_MAX)) { curl_mfprintf(stderr, "raising soft limit up to OPEN_MAX\n"); rl.rlim_cur = OPEN_MAX; - if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { - /* on failure do not abort just issue a warning */ + if(setrlimit(RLIMIT_NOFILE, &rl)) { + /* on failure do not abort, only issue a warning */ t518_store_errmsg("setrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); t518_msgbuff[0] = '\0'; @@ -147,8 +144,8 @@ static int t518_test_rlimit(int keep_open) curl_mfprintf(stderr, "raising soft limit up to hard limit\n"); rl.rlim_cur = rl.rlim_max; - if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { - /* on failure do not abort just issue a warning */ + if(setrlimit(RLIMIT_NOFILE, &rl)) { + /* on failure do not abort, only issue a warning */ t518_store_errmsg("setrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); t518_msgbuff[0] = '\0'; @@ -156,7 +153,7 @@ static int t518_test_rlimit(int keep_open) /* get current open file limits */ - if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { + if(getrlimit(RLIMIT_NOFILE, &rl)) { t518_store_errmsg("getrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); return -3; @@ -239,13 +236,13 @@ static int t518_test_rlimit(int keep_open) for(i = 0; i < nitems; i++) memchunk[i] = -1; - /* set the number of file descriptors we will try to open */ + /* set the number of file descriptors we try to open */ t518_num_open.rlim_max = NUM_OPEN; /* verify that we do not overflow size_t in curlx_malloc() */ - if((size_t)(t518_num_open.rlim_max) > ((size_t)-1) / sizeof(*t518_testfd)) { + if((size_t)t518_num_open.rlim_max > ((size_t)-1) / sizeof(*t518_testfd)) { tutil_rlim2str(strbuff1, sizeof(strbuff1), t518_num_open.rlim_max); curl_msnprintf(strbuff, sizeof(strbuff), "unable to allocate an array for %s " @@ -262,7 +259,7 @@ static int t518_test_rlimit(int keep_open) curl_mfprintf(stderr, "allocating array for %s file descriptors\n", strbuff); t518_testfd = curlx_malloc(sizeof(*t518_testfd) * - (size_t)(t518_num_open.rlim_max)); + (size_t)t518_num_open.rlim_max); if(!t518_testfd) { t518_store_errmsg("testfd, malloc() failed", errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); @@ -289,8 +286,7 @@ static int t518_test_rlimit(int keep_open) curl_msnprintf(strbuff, sizeof(strbuff), "opening of %s failed", DEV_NULL); t518_store_errmsg(strbuff, errno); curl_mfprintf(stderr, "%s\n", t518_msgbuff); - curlx_free(t518_testfd); - t518_testfd = NULL; + curlx_safefree(t518_testfd); curlx_free(memchunk); return -8; } @@ -330,8 +326,7 @@ static int t518_test_rlimit(int keep_open) t518_testfd[t518_num_open.rlim_cur] >= 0; t518_num_open.rlim_cur++) curlx_close(t518_testfd[t518_num_open.rlim_cur]); - curlx_free(t518_testfd); - t518_testfd = NULL; + curlx_safefree(t518_testfd); curlx_free(memchunk); return -9; } @@ -347,7 +342,7 @@ static int t518_test_rlimit(int keep_open) * greater than FD_SETSIZE. In any case, macro VERIFY_SOCK * in lib/select.c enforces this check and protects libcurl * from a possible crash. The effect of this protection - * is that test 518 will always fail, since the actual + * is that test 518 always fails, since the actual * call to select() never takes place. We skip test 518 * with an indication that select limit would be exceeded. */ @@ -452,8 +447,8 @@ static CURLcode test_lib518(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib519.c b/tests/libtest/lib519.c index 3ddc93541b46..30aee83ff5b5 100644 --- a/tests/libtest/lib519.c +++ b/tests/libtest/lib519.c @@ -40,17 +40,17 @@ static CURLcode test_lib519(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_USERPWD, "monster:underbed"); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_USERPWD, "monster:underbed"); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* get first page */ result = curl_easy_perform(curl); if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_USERPWD, "anothermonster:inwardrobe"); + easy_setopt(curl, CURLOPT_USERPWD, "anothermonster:inwardrobe"); /* get second page */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib520.c b/tests/libtest/lib520.c index 8987c9db6030..9da55e02737f 100644 --- a/tests/libtest/lib520.c +++ b/tests/libtest/lib520.c @@ -40,9 +40,9 @@ static CURLcode test_lib520(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_FILETIME, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_FILETIME, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib521.c b/tests/libtest/lib521.c index 002d07292374..1469d6dc0bad 100644 --- a/tests/libtest/lib521.c +++ b/tests/libtest/lib521.c @@ -44,10 +44,10 @@ static CURLcode test_lib521(const char *URL) return result; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PORT, (long)port); - test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PORT, (long)port); + easy_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib523.c b/tests/libtest/lib523.c index 40a2d3f741a8..ecc3e0ccceea 100644 --- a/tests/libtest/lib523.c +++ b/tests/libtest/lib523.c @@ -40,11 +40,11 @@ static CURLcode test_lib523(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PORT, 19999L); - test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PORT, 19999L); + easy_setopt(curl, CURLOPT_USERPWD, "xxx:yyy"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib524.c b/tests/libtest/lib524.c index 3d5ac1ff4beb..66ada8b0fa29 100644 --- a/tests/libtest/lib524.c +++ b/tests/libtest/lib524.c @@ -40,9 +40,9 @@ static CURLcode test_lib524(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib525.c b/tests/libtest/lib525.c index 88ea084b9a70..caedbde9350f 100644 --- a/tests/libtest/lib525.c +++ b/tests/libtest/lib525.c @@ -85,7 +85,7 @@ static CURLcode test_lib525(const char *URL) /* NOTE: if you want this code to work on Windows with libcurl as a DLL, you MUST also provide a read callback with CURLOPT_READFUNCTION. Failing to - do so will give you a crash since a DLL may not use the variable's memory + do so gives you a crash since a DLL may not use the variable's memory when passed in to it from an app like this. */ /* Set the size of the file to upload (optional). If you give a *_LARGE diff --git a/tests/libtest/lib526.c b/tests/libtest/lib526.c index 2a3461a80aa2..23fbe727c461 100644 --- a/tests/libtest/lib526.c +++ b/tests/libtest/lib526.c @@ -147,7 +147,7 @@ static CURLcode test_lib526(const char *URL) /* Upon non-failure test flow the easy's have already been cleanup'ed. In case there is a failure we arrive here with easy's that have not been cleanup'ed yet, in this case we have to cleanup them or otherwise these - will be leaked, let's use undocumented cleanup sequence - type UB */ + leak, let's use undocumented cleanup sequence - type UB */ if(result != CURLE_OK) for(i = 0; i < CURL_ARRAYSIZE(curl); i++) diff --git a/tests/libtest/lib530.c b/tests/libtest/lib530.c index 31cef1b702f6..94ecb7adf297 100644 --- a/tests/libtest/lib530.c +++ b/tests/libtest/lib530.c @@ -29,6 +29,7 @@ */ #include "first.h" +#include "testtrace.h" static struct t530_ctx { int socket_calls; @@ -203,8 +204,8 @@ static int t530_checkForCompletion(CURLM *multi, int *success) *success = 0; } else { - curl_mfprintf(stderr, "%s got an unexpected message from curl: %i\n", - t530_tag(), message->msg); + curl_mfprintf(stderr, "%s got an unexpected message from curl: %d\n", + t530_tag(), (int)message->msg); result = 1; *success = 0; } @@ -247,7 +248,7 @@ static CURLMcode socket_action(CURLM *multi, curl_socket_t s, int evBitmask, CURLMcode mresult = curl_multi_socket_action(multi, s, evBitmask, &numhandles); if(mresult != CURLM_OK) { - curl_mfprintf(stderr, "%s curl error on %s (%i) %s\n", + curl_mfprintf(stderr, "%s curl error on %s (%d) %s\n", t530_tag(), info, mresult, curl_multi_strerror(mresult)); } return mresult; @@ -300,6 +301,8 @@ static CURLcode testone(const char *URL, int timer_fail_at, int socket_fail_at) easy_setopt(curl, CURLOPT_URL, URL); /* go verbose */ + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); easy_setopt(curl, CURLOPT_VERBOSE, 1L); multi_init(multi); @@ -338,7 +341,11 @@ static CURLcode testone(const char *URL, int timer_fail_at, int socket_fail_at) tv.tv_usec = 100000; } - assert(maxFd); + if(!maxFd) { + t530_msg("maxFd == 0"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } select_test((int)maxFd, &readSet, &writeSet, NULL, &tv); /* Check the sockets for reading / writing */ @@ -394,23 +401,23 @@ static CURLcode test_lib530(const char *URL) callback calls */ result = testone(URL, 0, 0); /* no callback fails */ if(result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), (int)result); result = testone(URL, 1, 0); /* fail 1st call to timer callback */ if(!result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), (int)result); result = testone(URL, 2, 0); /* fail 2nd call to timer callback */ if(!result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), (int)result); result = testone(URL, 0, 2); /* fail 2nd call to socket callback */ if(!result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), (int)result); result = testone(URL, 0, 3); /* fail 3rd call to socket callback */ if(!result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t530_tag(), (int)result); return CURLE_OK; } diff --git a/tests/libtest/lib536.c b/tests/libtest/lib536.c index 69208d1dcfde..d6347545f572 100644 --- a/tests/libtest/lib536.c +++ b/tests/libtest/lib536.c @@ -37,7 +37,7 @@ static CURLcode test_lib536(const char *URL) CURL *curl; struct curl_slist *host = NULL; - static const char *url_with_proxy = "http://usingproxy.com/"; + static const char url_with_proxy[] = "http://usingproxy.test/"; const char *url_without_proxy = libtest_arg2; if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { @@ -56,16 +56,16 @@ static CURLcode test_lib536(const char *URL) if(!host) goto test_cleanup; - test_setopt(curl, CURLOPT_RESOLVE, host); - test_setopt(curl, CURLOPT_PROXY, URL); - test_setopt(curl, CURLOPT_URL, url_with_proxy); - test_setopt(curl, CURLOPT_NOPROXY, "goingdirect.com"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_RESOLVE, host); + easy_setopt(curl, CURLOPT_PROXY, URL); + easy_setopt(curl, CURLOPT_URL, url_with_proxy); + easy_setopt(curl, CURLOPT_NOPROXY, "goingdirect.test"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); if(!result) { proxystat(curl); - test_setopt(curl, CURLOPT_URL, url_without_proxy); + easy_setopt(curl, CURLOPT_URL, url_without_proxy); result = curl_easy_perform(curl); if(!result) proxystat(curl); diff --git a/tests/libtest/lib537.c b/tests/libtest/lib537.c index 82b92a02adc7..ab19316b0dd5 100644 --- a/tests/libtest/lib537.c +++ b/tests/libtest/lib537.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" +#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) #define T537_SAFETY_MARGIN 11 @@ -33,8 +33,6 @@ #define DEV_NULL "/dev/null" #endif -#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) - static int *t537_testfd = NULL; static struct rlimit t537_num_open; static char t537_msgbuff[256]; @@ -57,8 +55,7 @@ static void t537_close_file_descriptors(void) t537_num_open.rlim_cur++) if(t537_testfd[t537_num_open.rlim_cur] > 0) curlx_close(t537_testfd[t537_num_open.rlim_cur]); - curlx_free(t537_testfd); - t537_testfd = NULL; + curlx_safefree(t537_testfd); } static int t537_fopen_works(void) @@ -97,7 +94,7 @@ static int t537_test_rlimit(int keep_open) /* get initial open file limits */ - if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { + if(getrlimit(RLIMIT_NOFILE, &rl)) { t537_store_errmsg("getrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t537_msgbuff); return -1; @@ -127,7 +124,7 @@ static int t537_test_rlimit(int keep_open) * limit. Due to some other system limit the soft limit * might not be raised up to the hard limit. So from this * point the resulting soft limit is our limit. Trying to - * open more than soft limit file descriptors will fail. + * open more than soft limit file descriptors does fail. */ if(rl.rlim_cur != rl.rlim_max) { @@ -137,8 +134,8 @@ static int t537_test_rlimit(int keep_open) (rl.rlim_cur < OPEN_MAX)) { curl_mfprintf(stderr, "raising soft limit up to OPEN_MAX\n"); rl.rlim_cur = OPEN_MAX; - if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { - /* on failure do not abort just issue a warning */ + if(setrlimit(RLIMIT_NOFILE, &rl)) { + /* on failure do not abort, only issue a warning */ t537_store_errmsg("setrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t537_msgbuff); t537_msgbuff[0] = '\0'; @@ -148,8 +145,8 @@ static int t537_test_rlimit(int keep_open) curl_mfprintf(stderr, "raising soft limit up to hard limit\n"); rl.rlim_cur = rl.rlim_max; - if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { - /* on failure do not abort just issue a warning */ + if(setrlimit(RLIMIT_NOFILE, &rl)) { + /* on failure do not abort, only issue a warning */ t537_store_errmsg("setrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t537_msgbuff); t537_msgbuff[0] = '\0'; @@ -157,7 +154,7 @@ static int t537_test_rlimit(int keep_open) /* get current open file limits */ - if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { + if(getrlimit(RLIMIT_NOFILE, &rl)) { t537_store_errmsg("getrlimit() failed", errno); curl_mfprintf(stderr, "%s\n", t537_msgbuff); return -3; @@ -176,10 +173,10 @@ static int t537_test_rlimit(int keep_open) /* * test 537 is all about testing libcurl functionality * when the system has nearly exhausted the number of - * available file descriptors. Test 537 will try to run + * available file descriptors. Test 537 tries to run * with a small number of file descriptors available. * This implies that any file descriptor which is open - * when the test runs will have a number in the high range + * when the test runs does have a number in the high range * of whatever the system supports. */ @@ -219,7 +216,7 @@ static int t537_test_rlimit(int keep_open) for(i = 0; i < nitems; i++) memchunk[i] = -1; - /* set the number of file descriptors we will try to open */ + /* set the number of file descriptors we try to open */ #ifdef RLIM_INFINITY if((rl.rlim_cur > 0) && (rl.rlim_cur != RLIM_INFINITY)) { @@ -240,7 +237,7 @@ static int t537_test_rlimit(int keep_open) /* verify that we do not overflow size_t in curlx_malloc() */ - if((size_t)(t537_num_open.rlim_max) > ((size_t)-1) / sizeof(*t537_testfd)) { + if((size_t)t537_num_open.rlim_max > ((size_t)-1) / sizeof(*t537_testfd)) { tutil_rlim2str(strbuff1, sizeof(strbuff1), t537_num_open.rlim_max); curl_msnprintf(strbuff, sizeof(strbuff), "unable to allocate an array for %s " @@ -259,7 +256,7 @@ static int t537_test_rlimit(int keep_open) strbuff); t537_testfd = curlx_malloc(sizeof(*t537_testfd) * - (size_t)(t537_num_open.rlim_max)); + (size_t)t537_num_open.rlim_max); if(!t537_testfd) { curl_mfprintf(stderr, "testfd, malloc() failed\n"); t537_num_open.rlim_max /= 2; @@ -291,8 +288,7 @@ static int t537_test_rlimit(int keep_open) curl_msnprintf(strbuff, sizeof(strbuff), "opening of %s failed", DEV_NULL); t537_store_errmsg(strbuff, errno); curl_mfprintf(stderr, "%s\n", t537_msgbuff); - curlx_free(t537_testfd); - t537_testfd = NULL; + curlx_safefree(t537_testfd); curlx_free(memchunk); return -7; } @@ -342,7 +338,7 @@ static int t537_test_rlimit(int keep_open) tmpfd = curlx_realloc(t537_testfd, sizeof(*t537_testfd) * - (size_t)(t537_num_open.rlim_max)); + (size_t)t537_num_open.rlim_max); if(tmpfd) { t537_testfd = tmpfd; tmpfd = NULL; @@ -362,7 +358,7 @@ static int t537_test_rlimit(int keep_open) * greater than FD_SETSIZE. In any case, macro VERIFY_SOCK * in lib/select.c enforces this check and protects libcurl * from a possible crash. The effect of this protection - * is that test 537 will always fail, since the actual + * is that test 537 always fails, since the actual * call to select() never takes place. We skip test 537 * with an indication that select limit would be exceeded. */ @@ -467,8 +463,8 @@ static CURLcode test_lib537(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib539.c b/tests/libtest/lib539.c index 631718be053a..b6719ea53a54 100644 --- a/tests/libtest/lib539.c +++ b/tests/libtest/lib539.c @@ -45,15 +45,15 @@ static CURLcode test_lib539(const char *URL) /* * Begin with curl set to use a single CWD to the URL's directory. */ - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); result = curl_easy_perform(curl); if(result == CURLE_OK) { /* * Change the FTP_FILEMETHOD option to use full paths rather than a CWD - * command. Use an innocuous QUOTE command, after which curl will CWD to + * command. Use an innocuous QUOTE command, after which curl does CWD to * ftp_conn->entrypath and then (on the next call to ftp_statemach_act) * find a non-zero ftpconn->dirdepth even though no directories are stored * in the ftpconn->dirs array (after a call to freedirs). @@ -67,9 +67,9 @@ static CURLcode test_lib539(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, libtest_arg2); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); - test_setopt(curl, CURLOPT_QUOTE, slist); + easy_setopt(curl, CURLOPT_URL, libtest_arg2); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); + easy_setopt(curl, CURLOPT_QUOTE, slist); result = curl_easy_perform(curl); } diff --git a/tests/libtest/lib540.c b/tests/libtest/lib540.c index b8f2d3d8a486..ec9d3da7c95b 100644 --- a/tests/libtest/lib540.c +++ b/tests/libtest/lib540.c @@ -158,7 +158,7 @@ static CURLcode loop(int num, CURLM *multi, const char *url, if(msg->msg == CURLMSG_DONE) { size_t i; CURL *curl = msg->easy_handle; - curl_mfprintf(stderr, "R: %d - %s\n", msg->data.result, + curl_mfprintf(stderr, "R: %d - %s\n", (int)msg->data.result, curl_easy_strerror(msg->data.result)); curl_multi_remove_handle(multi, curl); curl_easy_cleanup(curl); @@ -170,7 +170,7 @@ static CURLcode loop(int num, CURLM *multi, const char *url, } } else - curl_mfprintf(stderr, "E: CURLMsg (%d)\n", msg->msg); + curl_mfprintf(stderr, "E: CURLMsg (%d)\n", (int)msg->msg); } res_test_timedout(); diff --git a/tests/libtest/lib541.c b/tests/libtest/lib541.c index 6cd8e3dce2de..35e9d5a0cda3 100644 --- a/tests/libtest/lib541.c +++ b/tests/libtest/lib541.c @@ -82,16 +82,16 @@ static CURLcode test_lib541(const char *URL) } /* enable uploading */ - test_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); /* enable verbose */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* specify target */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* now specify which file to upload */ - test_setopt(curl, CURLOPT_READDATA, hd_src); + easy_setopt(curl, CURLOPT_READDATA, hd_src); /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib542.c b/tests/libtest/lib542.c index fc71ecfdf074..fe3eb9cd1365 100644 --- a/tests/libtest/lib542.c +++ b/tests/libtest/lib542.c @@ -46,16 +46,16 @@ static CURLcode test_lib542(const char *URL) } /* enable verbose */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* enable NOBODY */ - test_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); /* disable HEADER */ - test_setopt(curl, CURLOPT_HEADER, 0L); + easy_setopt(curl, CURLOPT_HEADER, 0L); /* specify target */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib544.c b/tests/libtest/lib544.c index 6ceac095d72f..013c2ab32b30 100644 --- a/tests/libtest/lib544.c +++ b/tests/libtest/lib544.c @@ -52,18 +52,18 @@ static CURLcode test_lib544(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); if(testnum == 545) - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(teststring)); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(teststring)); - test_setopt(curl, CURLOPT_COPYPOSTFIELDS, teststring); + easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, teststring); - test_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ - test_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ + easy_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ /* Update the original data to detect non-copy. */ - curlx_strcopy(teststring, sizeof(teststring), "FAIL", strlen("FAIL")); + curlx_strcopy(teststring, sizeof(teststring), "FAIL", CURL_CSTRLEN("FAIL")); { CURL *curl2; diff --git a/tests/libtest/lib547.c b/tests/libtest/lib547.c index dd0f787ee654..66ad32afbb32 100644 --- a/tests/libtest/lib547.c +++ b/tests/libtest/lib547.c @@ -29,7 +29,7 @@ #include "first.h" static const char t547_uploadthis[] = "this is the blurb we want to upload\n"; -static size_t const t547_datalen = sizeof(t547_uploadthis) - 1; +static const size_t t547_datalen = CURL_CSTRLEN(t547_uploadthis); static size_t t547_read_cb(char *ptr, size_t size, size_t nmemb, void *clientp) { @@ -80,28 +80,28 @@ static CURLcode test_lib547(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); if(testnum == 548) { /* set the data to POST with a mere pointer to a null-terminated string */ - test_setopt(curl, CURLOPT_POSTFIELDS, t547_uploadthis); + easy_setopt(curl, CURLOPT_POSTFIELDS, t547_uploadthis); } else { /* 547 style, which means reading the POST data from a callback */ - test_setopt(curl, CURLOPT_IOCTLFUNCTION, t547_ioctl_callback); - test_setopt(curl, CURLOPT_IOCTLDATA, &counter); + easy_setopt(curl, CURLOPT_IOCTLFUNCTION, t547_ioctl_callback); + easy_setopt(curl, CURLOPT_IOCTLDATA, &counter); - test_setopt(curl, CURLOPT_READFUNCTION, t547_read_cb); - test_setopt(curl, CURLOPT_READDATA, &counter); + easy_setopt(curl, CURLOPT_READFUNCTION, t547_read_cb); + easy_setopt(curl, CURLOPT_READDATA, &counter); /* We CANNOT do the POST fine without setting the size (or choose chunked)! */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)t547_datalen); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)t547_datalen); } - test_setopt(curl, CURLOPT_POST, 1L); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_PROXYUSERPWD, libtest_arg3); - test_setopt(curl, CURLOPT_PROXYAUTH, + easy_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, libtest_arg3); + easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib549.c b/tests/libtest/lib549.c index e7c5023cb773..fb718988b20f 100644 --- a/tests/libtest/lib549.c +++ b/tests/libtest/lib549.c @@ -45,13 +45,13 @@ static CURLcode test_lib549(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_PROXY_TRANSFER_MODE, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY_TRANSFER_MODE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); if(libtest_arg3) { /* enable ASCII/text mode */ - test_setopt(curl, CURLOPT_TRANSFERTEXT, 1L); + easy_setopt(curl, CURLOPT_TRANSFERTEXT, 1L); } result = curl_easy_perform(curl); diff --git a/tests/libtest/lib552.c b/tests/libtest/lib552.c index fda2eee2c17e..69222d217fde 100644 --- a/tests/libtest/lib552.c +++ b/tests/libtest/lib552.c @@ -79,35 +79,35 @@ static CURLcode test_lib552(const char *URL) global_init(CURL_GLOBAL_ALL); easy_init(curl); - test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); /* the DEBUGFUNCTION has no effect until we enable VERBOSE */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* setup repeated data string */ for(i = 0; i < sizeof(databuf); ++i) databuf[i] = fill[i % sizeof(fill)]; /* Post */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Setup read callback */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(databuf)); - test_setopt(curl, CURLOPT_READFUNCTION, t552_read_cb); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)sizeof(databuf)); + easy_setopt(curl, CURLOPT_READFUNCTION, t552_read_cb); /* Write callback */ - test_setopt(curl, CURLOPT_WRITEFUNCTION, t552_write_cb); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, t552_write_cb); /* Ioctl function */ - test_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_callback); + easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_callback); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Accept any auth. But for this bug configure proxy with DIGEST, basic might work too, not NTLM */ - test_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib553.c b/tests/libtest/lib553.c index 75844144bd38..dc812aa4a42a 100644 --- a/tests/libtest/lib553.c +++ b/tests/libtest/lib553.c @@ -89,13 +89,13 @@ static CURLcode test_lib553(const char *URL) goto test_cleanup; headerlist = hl; - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HTTPHEADER, headerlist); - test_setopt(curl, CURLOPT_POST, 1L); - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)POSTLEN); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_READFUNCTION, myreadfunc); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + easy_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)POSTLEN); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_READFUNCTION, myreadfunc); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib554.c b/tests/libtest/lib554.c index 7b882959ddaa..ce4348a8d733 100644 --- a/tests/libtest/lib554.c +++ b/tests/libtest/lib554.c @@ -69,7 +69,7 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) struct t554_WriteThis pooh2; pooh.readptr = testdata; - pooh.sizeleft = strlen(testdata); + pooh.sizeleft = CURL_CSTRLEN(testdata); /* Fill in the file upload field */ if(oldstyle) { @@ -93,13 +93,13 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) } if(formrc) - curl_mprintf("curl_formadd(1) = %d\n", formrc); + curl_mprintf("curl_formadd(1) = %d\n", (int)formrc); /* Now add the same data with another name and make it not look like a file upload but still using the callback */ pooh2.readptr = testdata; - pooh2.sizeleft = strlen(testdata); + pooh2.sizeleft = CURL_CSTRLEN(testdata); /* Fill in the file upload field */ formrc = curl_formadd(&formpost, @@ -110,7 +110,7 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) CURLFORM_END); if(formrc) - curl_mprintf("curl_formadd(2) = %d\n", formrc); + curl_mprintf("curl_formadd(2) = %d\n", (int)formrc); /* Fill in the filename field */ formrc = curl_formadd(&formpost, @@ -119,7 +119,7 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) CURLFORM_COPYCONTENTS, "postit2.c", CURLFORM_END); if(formrc) - curl_mprintf("curl_formadd(3) = %d\n", formrc); + curl_mprintf("curl_formadd(3) = %d\n", (int)formrc); /* Fill in a submit field too */ formrc = curl_formadd(&formpost, @@ -130,7 +130,7 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) CURLFORM_END); if(formrc) - curl_mprintf("curl_formadd(4) = %d\n", formrc); + curl_mprintf("curl_formadd(4) = %d\n", (int)formrc); formrc = curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "somename", @@ -140,7 +140,7 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) CURLFORM_END); if(formrc) - curl_mprintf("curl_formadd(5) = %d\n", formrc); + curl_mprintf("curl_formadd(5) = %d\n", (int)formrc); curl = curl_easy_init(); if(!curl) { @@ -151,32 +151,32 @@ static CURLcode t554_test_once(const char *URL, bool oldstyle) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Set the expected POST size */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)pooh.sizeleft); /* we want to use our own read function */ if(testnum == 587) { - test_setopt(curl, CURLOPT_READFUNCTION, t587_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t587_read_cb); } else { - test_setopt(curl, CURLOPT_READFUNCTION, t554_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t554_read_cb); } /* send a multi-part formpost */ - test_setopt(curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(curl, CURLOPT_HTTPPOST, formpost); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib555.c b/tests/libtest/lib555.c index 9e11aae7d70c..e2cdfbc97128 100644 --- a/tests/libtest/lib555.c +++ b/tests/libtest/lib555.c @@ -33,7 +33,7 @@ #include "first.h" static const char t555_uploadthis[] = "this is the blurb we want to upload\n"; -static size_t const t555_datalen = sizeof(t555_uploadthis) - 1; +static const size_t t555_datalen = CURL_CSTRLEN(t555_uploadthis); static size_t t555_read_cb(char *ptr, size_t size, size_t nmemb, void *clientp) { diff --git a/tests/libtest/lib556.c b/tests/libtest/lib556.c index f71ec5e6adb9..1bc4638b9b29 100644 --- a/tests/libtest/lib556.c +++ b/tests/libtest/lib556.c @@ -41,9 +41,9 @@ static CURLcode test_lib556(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); again: diff --git a/tests/libtest/lib557.c b/tests/libtest/lib557.c index 514e71cd3c75..535e37d4acc8 100644 --- a/tests/libtest/lib557.c +++ b/tests/libtest/lib557.c @@ -46,6 +46,15 @@ #endif #endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + #define BUFSZ 256 struct unsshort_st { @@ -415,7 +424,7 @@ static int test_unsigned_long_formatting(void) int num_ulong_tests = 0; int failed = 0; -#if (SIZEOF_LONG == 4) +#if SIZEOF_LONG == 4 i = 1; ul_test[i].num = 0xFFFFFFFFUL; ul_test[i].expected = "4294967295"; i++; ul_test[i].num = 0xFFFF0000UL; ul_test[i].expected = "4294901760"; @@ -449,7 +458,7 @@ static int test_unsigned_long_formatting(void) num_ulong_tests = i; -#elif (SIZEOF_LONG == 8) +#elif SIZEOF_LONG == 8 /* !checksrc! disable LONGLINE all */ i = 1; ul_test[i].num = 0xFFFFFFFFFFFFFFFFUL; ul_test[i].expected = "18446744073709551615"; i++; ul_test[i].num = 0xFFFFFFFF00000000UL; ul_test[i].expected = "18446744069414584320"; @@ -541,7 +550,7 @@ static int test_signed_long_formatting(void) int num_slong_tests = 0; int failed = 0; -#if (SIZEOF_LONG == 4) +#if SIZEOF_LONG == 4 i = 1; sl_test[i].num = 0x7FFFFFFFL; sl_test[i].expected = "2147483647"; i++; sl_test[i].num = 0x7FFFFFFEL; sl_test[i].expected = "2147483646"; @@ -608,7 +617,7 @@ static int test_signed_long_formatting(void) num_slong_tests = i; -#elif (SIZEOF_LONG == 8) +#elif SIZEOF_LONG == 8 i = 1; sl_test[i].num = 0x7FFFFFFFFFFFFFFFL; sl_test[i].expected = "9223372036854775807"; i++; sl_test[i].num = 0x7FFFFFFFFFFFFFFEL; sl_test[i].expected = "9223372036854775806"; @@ -1047,6 +1056,53 @@ static int test_weird_arguments(void) return errors; } +static int double_check(void) +{ + const long double val = 1.234567890123456789L; + int mismatches = 0; + unsigned int i; + struct dbcheck { + const char *fmt; + const char *prefix; /* starts with this */ + const char *suffix; /* ends with this */ + }; + /* because floats are annoying beasts, different libc's produce different + outputs so we cannot compare exact output, only prefix + suffix */ + struct dbcheck c[] = { + { "%.17Lf", /* "1.23456789012345669" */ + "1.23456789012345", "", }, + { "%.17Le", /* "1.23456789012345669e+00" */ + "1.234567890123456", "e+00" }, + { "%.17LE", /* "1.23456789012345669E+00" */ + "1.234567890123456", "E+00" }, + { "%.17Lg", /* "1.2345678901234567" */ + "1.234567890123456", "" }, + { "%.17LG", /* "1.2345678901234567" */ + "1.234567890123456", ""} + }; + for(i = 0; i < CURL_ARRAYSIZE(c); i++) { + char curl_out[128]; + size_t len = + curl_msnprintf(curl_out, sizeof(curl_out), c[i].fmt, val); + if(strncmp(curl_out, c[i].prefix, strlen(c[i].prefix))) { + curl_mfprintf(stderr, + "MISMATCH (prefix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].prefix); + mismatches++; + } + if((len < strlen(c[i].suffix) || + strncmp(curl_out + len - strlen(c[i].suffix), + c[i].suffix, strlen(c[i].suffix)))) { + curl_mfprintf(stderr, + "MISMATCH (suffix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].suffix); + mismatches++; + } + } + curl_mfprintf(stderr, "mismatches=%d/5\n", mismatches); + return mismatches; +} + /* DBL_MAX value from Linux */ #define MAXIMIZE (-1.7976931348623157081452E+308) @@ -1057,6 +1113,9 @@ static int test_float_formatting(void) curl_msnprintf(buf, sizeof(buf), "%f", 9.0); errors += string_check(buf, "9.000000"); + curl_msnprintf(buf, sizeof(buf), "%F", 9.0); + errors += string_check(buf, "9.000000"); + curl_msnprintf(buf, sizeof(buf), "%.1f", 9.1); errors += string_check(buf, "9.1"); @@ -1165,6 +1224,8 @@ static int test_float_formatting(void) curl_msnprintf(buf, 6, "%f", MAXIMIZE); errors += strlen_check(buf, 5); + errors += double_check(); + if(!errors) curl_mprintf("All float strings tests OK!\n"); else @@ -1186,11 +1247,11 @@ static int test_oct_hex_formatting(void) 0xFABC1230U, 0xFABC1230U, 0xFABC1230U, 1234U); errors += string_check(buf, "37257011060 fabc1230 FABC1230 +2322"); -#if (SIZEOF_LONG == 4) +#if SIZEOF_LONG == 4 curl_msnprintf(buf, sizeof(buf), "%lo %lx %lX %+lo", 0xFABC1230UL, 0xFABC1230UL, 0xFABC1230UL, 1234UL); errors += string_check(buf, "37257011060 fabc1230 FABC1230 +2322"); -#elif (SIZEOF_LONG == 8) +#elif SIZEOF_LONG == 8 curl_msnprintf(buf, sizeof(buf), "%lo %lx %lX %+lo", 0xFABCDEF123456780UL, 0xFABCDEF123456780UL, 0xFABCDEF123456780UL, 1234UL); @@ -1237,6 +1298,88 @@ static int test_return_codes(void) return 0; } +/* + Basic verification of the vararg versions of the printf functions: + + - curl_mvfprintf + - curl_mvprintf + - curl_mvsnprintf + - curl_mvsprintf + - curl_mvaprintf + */ +static int var557(int expected_len, const char *format, ...) +{ + va_list arg; + int len; + int errors = 1; + char buffer[80]; + char *ptr; + va_start(arg, format); + + len = curl_mvfprintf(stderr, format, arg); + if(len != expected_len) { + curl_mfprintf(stderr, "curl_mvfprintf: expected length %d but got %d\n", + expected_len, len); + goto error; + } + + va_end(arg); + va_start(arg, format); + /* this unfortunately writes to stdout but we can't prevent it */ + len = curl_mvprintf(format, arg); + if(len != expected_len) { + curl_mfprintf(stderr, "curl_mvprintf: expected length %d but got %d\n", + expected_len, len); + goto error; + } + + va_end(arg); + va_start(arg, format); + len = curl_mvsnprintf(buffer, sizeof(buffer), format, arg); + if(len != expected_len) { + curl_mfprintf(stderr, "curl_mvsnprintf: expected length %d but got %d\n", + expected_len, len); + goto error; + } + + va_end(arg); + va_start(arg, format); + /* no buffer size, but we know it fits */ + len = curl_mvsprintf(buffer, format, arg); + if(len != expected_len) { + curl_mfprintf(stderr, "curl_mvsprintf: expected length %d but got %d\n", + expected_len, len); + goto error; + } + + va_end(arg); + va_start(arg, format); + ptr = curl_mvaprintf(format, arg); + len = ptr ? (int)strlen(ptr) : 0; + curl_free(ptr); + if(len != expected_len) { + curl_mfprintf(stderr, "curl_mvaprintf: expected length %d but got %d\n", + expected_len, len); + goto error; + } + + errors = 0; /* success */ +error: + va_end(arg); + if(!errors) + curl_mprintf("All v-functions test OK!\n"); + else + curl_mprintf("The v-functions test failed!\n"); + return errors; +} + +static int test_vversions(void) +{ + int rc = var557(51, "testing %s and %d is %s and %ld\n", + "a string", -443, "super fun", (long)9876543); + return rc; +} + static CURLcode test_lib557(const char *URL) { int errors = 0; @@ -1263,6 +1406,7 @@ static CURLcode test_lib557(const char *URL) errors += test_float_formatting(); errors += test_oct_hex_formatting(); errors += test_return_codes(); + errors += test_vversions(); if(errors) return TEST_ERR_MAJOR_BAD; @@ -1273,3 +1417,6 @@ static CURLcode test_lib557(const char *URL) #ifdef CURL_HAVE_DIAG #pragma GCC diagnostic pop #endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif diff --git a/tests/libtest/lib559.c b/tests/libtest/lib559.c index 1f14b7df435a..27a8cb9c22f3 100644 --- a/tests/libtest/lib559.c +++ b/tests/libtest/lib559.c @@ -40,9 +40,9 @@ static CURLcode test_lib559(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_BUFFERSIZE, 1L); /* the smallest! */ + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_BUFFERSIZE, 1L); /* the smallest! */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib562.c b/tests/libtest/lib562.c index bc3bcad7e144..5cec3f7472dc 100644 --- a/tests/libtest/lib562.c +++ b/tests/libtest/lib562.c @@ -53,13 +53,13 @@ static CURLcode test_lib562(const char *URL) } /* enable verbose */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* set port number */ - test_setopt(curl, CURLOPT_PORT, (long)port); + easy_setopt(curl, CURLOPT_PORT, (long)port); /* specify target */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib566.c b/tests/libtest/lib566.c index 3b30092b289e..9042894618a4 100644 --- a/tests/libtest/lib566.c +++ b/tests/libtest/lib566.c @@ -42,8 +42,8 @@ static CURLcode test_lib566(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib567.c b/tests/libtest/lib567.c index ac4516e63ed8..dc17f53b0ba4 100644 --- a/tests/libtest/lib567.c +++ b/tests/libtest/lib567.c @@ -45,17 +45,17 @@ static CURLcode test_lib567(const char *URL) } /* Dump data to stdout for protocol verification */ - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); - test_setopt(curl, CURLOPT_USERAGENT, "test567"); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_USERAGENT, "test567"); custom_headers = curl_slist_append(custom_headers, "Test-Number: 567"); - test_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); + easy_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib568.c b/tests/libtest/lib568.c index 8f650e53ee56..654de68461a3 100644 --- a/tests/libtest/lib568.c +++ b/tests/libtest/lib568.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" - /* * Test the Client->Server ANNOUNCE functionality (PUT style) */ @@ -52,17 +50,17 @@ static CURLcode test_lib568(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -85,19 +83,19 @@ static CURLcode test_lib568(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_ANNOUNCE); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_ANNOUNCE); - test_setopt(curl, CURLOPT_READDATA, sdpf); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_READDATA, sdpf); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* Do the ANNOUNCE */ result = curl_easy_perform(curl); if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_UPLOAD, 0L); + easy_setopt(curl, CURLOPT_UPLOAD, 0L); curlx_fclose(sdpf); sdpf = NULL; @@ -107,11 +105,11 @@ static CURLcode test_lib568(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); result = curl_easy_perform(curl); if(result) goto test_cleanup; @@ -123,7 +121,7 @@ static CURLcode test_lib568(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -133,17 +131,17 @@ static CURLcode test_lib568(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_ANNOUNCE); - test_setopt(curl, CURLOPT_POSTFIELDS, + easy_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_ANNOUNCE); + easy_setopt(curl, CURLOPT_POSTFIELDS, "postyfield=postystuff&project=curl\n"); result = curl_easy_perform(curl); if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_POSTFIELDS, NULL); - test_setopt(curl, CURLOPT_RTSPHEADER, NULL); + easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); + easy_setopt(curl, CURLOPT_RTSPHEADER, NULL); curl_slist_free_all(custom_headers); custom_headers = NULL; @@ -153,11 +151,11 @@ static CURLcode test_lib568(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib569.c b/tests/libtest/lib569.c index e29b7d71ee15..8f79eeb71b3b 100644 --- a/tests/libtest/lib569.c +++ b/tests/libtest/lib569.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" - /* * Test Session ID capture */ @@ -56,13 +54,13 @@ static CURLcode test_lib569(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); result = curl_easy_perform(curl); if(result != CURLE_BAD_FUNCTION_ARGUMENT) { curl_mfprintf(stderr, "This should have failed. " @@ -79,12 +77,12 @@ static CURLcode test_lib569(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); - test_setopt(curl, CURLOPT_RTSP_TRANSPORT, + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, "Fake/NotReal/JustATest;foo=baz"); result = curl_easy_perform(curl); if(result) @@ -99,17 +97,17 @@ static CURLcode test_lib569(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_TEARDOWN); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_TEARDOWN); result = curl_easy_perform(curl); if(result) goto test_cleanup; /* Clear for the next go-round */ - test_setopt(curl, CURLOPT_RTSP_SESSION_ID, NULL); + easy_setopt(curl, CURLOPT_RTSP_SESSION_ID, NULL); } test_cleanup: diff --git a/tests/libtest/lib570.c b/tests/libtest/lib570.c index 5f2ab2d6eff1..998dd68cfa4e 100644 --- a/tests/libtest/lib570.c +++ b/tests/libtest/lib570.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" - static CURLcode test_lib570(const char *URL) { CURLcode result; @@ -44,20 +42,20 @@ static CURLcode test_lib570(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -68,17 +66,17 @@ static CURLcode test_lib570(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_CLIENT_CSEQ, 999L); - test_setopt(curl, CURLOPT_RTSP_TRANSPORT, + easy_setopt(curl, CURLOPT_RTSP_CLIENT_CSEQ, 999L); + easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, "RAW/RAW/UDP;unicast;client_port=3056-3057"); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -86,14 +84,14 @@ static CURLcode test_lib570(const char *URL) if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; diff --git a/tests/libtest/lib571.c b/tests/libtest/lib571.c index a5e479d37552..4f506bc3b9dc 100644 --- a/tests/libtest/lib571.c +++ b/tests/libtest/lib571.c @@ -33,8 +33,6 @@ #include #endif -#include "testutil.h" - #define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) #define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ @@ -46,7 +44,7 @@ static int rtp_packet_count = 0; static size_t rtp_write(char *data, size_t size, size_t nmemb, void *stream) { - static const char *RTP_DATA = "$_1234\n\0Rsdf"; + static const char RTP_DATA[] = "$_1234\n\0Rsdf"; int channel = RTP_PKT_CHANNEL(data); int message_size; @@ -67,7 +65,7 @@ static size_t rtp_write(char *data, size_t size, size_t nmemb, void *stream) data += 4; for(i = 0; i < message_size; i += RTP_DATA_SIZE) { if(message_size - i > RTP_DATA_SIZE) { - if(memcmp(RTP_DATA, data + i, RTP_DATA_SIZE) != 0) { + if(memcmp(RTP_DATA, data + i, RTP_DATA_SIZE)) { curl_mprintf("RTP PAYLOAD CORRUPTED [%s]\n", data + i); #if 0 return failure; @@ -75,7 +73,7 @@ static size_t rtp_write(char *data, size_t size, size_t nmemb, void *stream) } } else { - if(memcmp(RTP_DATA, data + i, message_size - i) != 0) { + if(memcmp(RTP_DATA, data + i, message_size - i)) { curl_mprintf("RTP PAYLOAD END CORRUPTED (%d), [%s]\n", message_size - i, data + i); #if 0 @@ -117,24 +115,24 @@ static CURLcode test_lib571(const char *URL) curl_global_cleanup(); return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_INTERLEAVEFUNCTION, rtp_write); - test_setopt(curl, CURLOPT_TIMEOUT, 30L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_WRITEDATA, protofile); + easy_setopt(curl, CURLOPT_INTERLEAVEFUNCTION, rtp_write); + easy_setopt(curl, CURLOPT_TIMEOUT, 30L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_WRITEDATA, protofile); - test_setopt(curl, CURLOPT_RTSP_TRANSPORT, "RTP/AVP/TCP;interleaved=0-1"); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, "RTP/AVP/TCP;interleaved=0-1"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); result = curl_easy_perform(curl); if(result) @@ -146,25 +144,25 @@ static CURLcode test_lib571(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); result = curl_easy_perform(curl); if(result) goto test_cleanup; - /* The DESCRIBE request will try to consume data after the Content */ + /* The DESCRIBE request tries to consume data after the Content */ stream_uri = tutil_suburl(URL, request++); if(!stream_uri) { result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); result = curl_easy_perform(curl); if(result) @@ -175,10 +173,10 @@ static CURLcode test_lib571(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); result = curl_easy_perform(curl); if(result) @@ -189,7 +187,7 @@ static CURLcode test_lib571(const char *URL) /* Use Receive to get the rest of the data */ while(!result && rtp_packet_count < 19) { curl_mfprintf(stderr, "LOOPY LOOP!\n"); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_RECEIVE); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_RECEIVE); result = curl_easy_perform(curl); } diff --git a/tests/libtest/lib572.c b/tests/libtest/lib572.c index 86256c020bbb..1ed292761f2f 100644 --- a/tests/libtest/lib572.c +++ b/tests/libtest/lib572.c @@ -23,8 +23,6 @@ ***************************************************************************/ #include "first.h" -#include "testutil.h" - /* * Test GET_PARAMETER: PUT, HEARTBEAT, and POST */ @@ -52,11 +50,11 @@ static CURLcode test_lib572(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_HEADERDATA, stdout); - test_setopt(curl, CURLOPT_WRITEDATA, stdout); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* SETUP */ stream_uri = tutil_suburl(URL, request++); @@ -64,12 +62,12 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_TRANSPORT, "Planes/Trains/Automobiles"); - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, "Planes/Trains/Automobiles"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); result = curl_easy_perform(curl); if(result) goto test_cleanup; @@ -79,7 +77,7 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -103,17 +101,17 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_GET_PARAMETER); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_GET_PARAMETER); - test_setopt(curl, CURLOPT_READDATA, paramsf); - test_setopt(curl, CURLOPT_UPLOAD, 1L); - test_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); + easy_setopt(curl, CURLOPT_READDATA, paramsf); + easy_setopt(curl, CURLOPT_UPLOAD, 1L); + easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); result = curl_easy_perform(curl); if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_UPLOAD, 0L); + easy_setopt(curl, CURLOPT_UPLOAD, 0L); curlx_fclose(paramsf); paramsf = NULL; @@ -123,7 +121,7 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; @@ -138,18 +136,18 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_GET_PARAMETER); - test_setopt(curl, CURLOPT_POSTFIELDS, "packets_received\njitter\n"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_GET_PARAMETER); + easy_setopt(curl, CURLOPT_POSTFIELDS, "packets_received\njitter\n"); result = curl_easy_perform(curl); if(result) goto test_cleanup; - test_setopt(curl, CURLOPT_POSTFIELDS, NULL); + easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); /* Make sure we can do a normal request now */ stream_uri = tutil_suburl(URL, request++); @@ -157,11 +155,11 @@ static CURLcode test_lib572(const char *URL) result = TEST_ERR_MAJOR_BAD; goto test_cleanup; } - test_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, stream_uri); curl_free(stream_uri); stream_uri = NULL; - test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib574.c b/tests/libtest/lib574.c index 6f1f061ee2b2..6b5e415fa74b 100644 --- a/tests/libtest/lib574.c +++ b/tests/libtest/lib574.c @@ -48,19 +48,19 @@ static CURLcode test_lib574(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); - test_setopt(curl, CURLOPT_FNMATCH_FUNCTION, new_fnmatch); - test_setopt(curl, CURLOPT_TIMEOUT_MS, (long)TEST_HANG_TIMEOUT); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); + easy_setopt(curl, CURLOPT_FNMATCH_FUNCTION, new_fnmatch); + easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)TEST_HANG_TIMEOUT); result = curl_easy_perform(curl); if(result) { - curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", (int)result); goto test_cleanup; } result = curl_easy_perform(curl); if(result) { - curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", result); + curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", (int)result); goto test_cleanup; } diff --git a/tests/libtest/lib576.c b/tests/libtest/lib576.c index a50d34c02503..889969d5b852 100644 --- a/tests/libtest/lib576.c +++ b/tests/libtest/lib576.c @@ -73,7 +73,7 @@ static long chunk_bgn(const void *f, void *ptr, int remains) "-------------------------------------------" "------------------\n"); } - if(strcmp(finfo->filename, "someothertext.txt") == 0) { + if(!strcmp(finfo->filename, "someothertext.txt")) { curl_mprintf("# THIS CONTENT WAS SKIPPED IN CHUNK_BGN CALLBACK #\n"); return CURL_CHUNK_BGN_FUNC_SKIP; } @@ -106,11 +106,11 @@ static CURLcode test_lib576(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); - test_setopt(curl, CURLOPT_CHUNK_BGN_FUNCTION, chunk_bgn); - test_setopt(curl, CURLOPT_CHUNK_END_FUNCTION, chunk_end); - test_setopt(curl, CURLOPT_CHUNK_DATA, &chunk_data); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); + easy_setopt(curl, CURLOPT_CHUNK_BGN_FUNCTION, chunk_bgn); + easy_setopt(curl, CURLOPT_CHUNK_END_FUNCTION, chunk_end); + easy_setopt(curl, CURLOPT_CHUNK_DATA, &chunk_data); result = curl_easy_perform(curl); diff --git a/tests/libtest/lib578.c b/tests/libtest/lib578.c index 75b3e8af36fa..434178552467 100644 --- a/tests/libtest/lib578.c +++ b/tests/libtest/lib578.c @@ -66,26 +66,26 @@ static CURLcode test_lib578(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* Set the expected POST size */ - test_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)data_size); - test_setopt(curl, CURLOPT_POSTFIELDS, t578_testdata); + easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)data_size); + easy_setopt(curl, CURLOPT_POSTFIELDS, t578_testdata); /* we want to use our own progress function */ - test_setopt(curl, CURLOPT_NOPROGRESS, 0L); - test_setopt(curl, CURLOPT_PROGRESSFUNCTION, t578_progress_callback); + easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, t578_progress_callback); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib579.c b/tests/libtest/lib579.c index 35ccd8485579..7ad133188bc5 100644 --- a/tests/libtest/lib579.c +++ b/tests/libtest/lib579.c @@ -127,34 +127,34 @@ static CURLcode test_lib579(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Now specify we want to POST data */ - test_setopt(curl, CURLOPT_POST, 1L); + easy_setopt(curl, CURLOPT_POST, 1L); /* we want to use our own read function */ - test_setopt(curl, CURLOPT_READFUNCTION, t579_read_cb); + easy_setopt(curl, CURLOPT_READFUNCTION, t579_read_cb); /* pointer to pass to our read function */ - test_setopt(curl, CURLOPT_READDATA, &pooh); + easy_setopt(curl, CURLOPT_READDATA, &pooh); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); /* enforce chunked transfer by setting the header */ - test_setopt(curl, CURLOPT_HTTPHEADER, slist); + easy_setopt(curl, CURLOPT_HTTPHEADER, slist); - test_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - test_setopt(curl, CURLOPT_USERPWD, "foo:bar"); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + easy_setopt(curl, CURLOPT_USERPWD, "foo:bar"); /* we want to use our own progress function */ - test_setopt(curl, CURLOPT_NOPROGRESS, 0L); - test_setopt(curl, CURLOPT_PROGRESSFUNCTION, t579_progress_callback); + easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, t579_progress_callback); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); progress_final_report(); diff --git a/tests/libtest/lib582.c b/tests/libtest/lib582.c index 3a4e278e383f..50d683398c5e 100644 --- a/tests/libtest/lib582.c +++ b/tests/libtest/lib582.c @@ -150,8 +150,8 @@ static int t582_checkForCompletion(CURLM *multi, int *success) *success = 0; } else { - curl_mfprintf(stderr, "Got an unexpected message from curl: %i\n", - message->msg); + curl_mfprintf(stderr, "Got an unexpected message from curl: %d\n", + (int)message->msg); result = 1; *success = 0; } @@ -194,7 +194,7 @@ static void notifyCurl(CURLM *multi, curl_socket_t s, int evBitmask, CURLMcode mresult = curl_multi_socket_action(multi, s, evBitmask, &numhandles); if(mresult != CURLM_OK) { - curl_mfprintf(stderr, "curl error on %s (%i) %s\n", + curl_mfprintf(stderr, "curl error on %s (%d) %s\n", info, mresult, curl_multi_strerror(mresult)); } } @@ -227,7 +227,8 @@ static CURLcode test_lib582(const char *URL) struct curltime timeout = { 0 }; timeout.tv_sec = (time_t)-1; - assert(test_argc >= 5); + if(test_argc < 5) + return TEST_ERR_MAJOR_BAD; start_test_timing(); diff --git a/tests/libtest/lib583.c b/tests/libtest/lib583.c index 984764eb12db..934c54b1cdd0 100644 --- a/tests/libtest/lib583.c +++ b/tests/libtest/lib583.c @@ -36,7 +36,8 @@ static CURLcode test_lib583(const char *URL) CURLcode result = CURLE_OK; CURLMcode mresult; - assert(test_argc >= 4); + if(test_argc < 4) + return TEST_ERR_MAJOR_BAD; global_init(CURL_GLOBAL_ALL); @@ -45,7 +46,7 @@ static CURLcode test_lib583(const char *URL) easy_init(curl); easy_setopt(curl, CURLOPT_USERPWD, libtest_arg2); - easy_setopt(curl, CURLOPT_SSH_PUBLIC_KEYFILE, test_argv[3]); + easy_setopt(curl, CURLOPT_SSH_PUBLIC_KEYFILE, test_argv[3]); easy_setopt(curl, CURLOPT_SSH_PRIVATE_KEYFILE, test_argv[4]); easy_setopt(curl, CURLOPT_UPLOAD, 1L); diff --git a/tests/libtest/lib586.c b/tests/libtest/lib586.c index 8609a1d16907..29eab1844362 100644 --- a/tests/libtest/lib586.c +++ b/tests/libtest/lib586.c @@ -60,7 +60,7 @@ static void t586_test_lock(CURL *curl, curl_lock_data data, what = "ssl_session"; break; default: - curl_mfprintf(stderr, "lock: no such data: %d\n", data); + curl_mfprintf(stderr, "lock: no such data: %d\n", (int)data); return; } curl_mprintf("lock: %-6s [%s]: %d\n", what, user->text, user->counter); @@ -87,7 +87,7 @@ static void t586_test_unlock(CURL *curl, curl_lock_data data, void *useptr) what = "ssl_session"; break; default: - curl_mfprintf(stderr, "unlock: no such data: %d\n", data); + curl_mfprintf(stderr, "unlock: no such data: %d\n", (int)data); return; } curl_mprintf("unlock: %-6s [%s]: %d\n", what, user->text, user->counter); @@ -118,7 +118,7 @@ static void *t586_test_fire(void *ptr) if(result != CURLE_OK) { int i = 0; curl_mfprintf(stderr, "perform URL '%s' repeat %d failed, curlcode %d\n", - tdata->url, i, result); + tdata->url, i, (int)result); } curl_mprintf("CLEANUP\n"); @@ -203,9 +203,9 @@ static CURLcode test_lib586(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); curl_mprintf("CURLOPT_SHARE\n"); - test_setopt(curl, CURLOPT_SHARE, share); + easy_setopt(curl, CURLOPT_SHARE, share); curl_mprintf("PERFORM\n"); result = curl_easy_perform(curl); @@ -231,7 +231,8 @@ static CURLcode test_lib586(const char *URL) curl_mprintf("SHARE_CLEANUP\n"); scode = curl_share_cleanup(share); if(scode != CURLSHE_OK) - curl_mfprintf(stderr, "curl_share_cleanup failed, code errno %d\n", scode); + curl_mfprintf(stderr, "curl_share_cleanup failed, code errno %d\n", + (int)scode); curl_mprintf("GLOBAL_CLEANUP\n"); curl_global_cleanup(); diff --git a/tests/libtest/lib589.c b/tests/libtest/lib589.c index cf8565237829..1f4b6cfbe124 100644 --- a/tests/libtest/lib589.c +++ b/tests/libtest/lib589.c @@ -43,9 +43,9 @@ static CURLcode test_lib589(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ - test_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* show verbose for debug */ + easy_setopt(curl, CURLOPT_HEADER, 1L); /* include header */ if(testnum == 584) { mime = curl_mime_init(curl); @@ -53,14 +53,14 @@ static CURLcode test_lib589(const char *URL) if(mime && part) { curl_mime_name(part, "fake"); curl_mime_data(part, "party", 5); - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); result = curl_easy_perform(curl); } if(result) goto test_cleanup; } - test_setopt(curl, CURLOPT_MIMEPOST, NULL); + easy_setopt(curl, CURLOPT_MIMEPOST, NULL); /* Now, we should be making a zero byte POST request */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib590.c b/tests/libtest/lib590.c index 1cea00b6e24a..cc424aba0486 100644 --- a/tests/libtest/lib590.c +++ b/tests/libtest/lib590.c @@ -23,19 +23,18 @@ ***************************************************************************/ #include "first.h" -/* - Based on a bug report recipe by Rene Bernhardt in - https://curl.se/mail/lib-2011-10/0323.html +/* Based on a bug report recipe by Rene Bernhardt in + https://curl.se/mail/lib-2011-10/0323.html - It is reproducible by the following steps: + It is reproducible by the following steps: - - Use a proxy that offers NTLM and Negotiate - (CURLOPT_PROXY and CURLOPT_PROXYPORT) - - Tell libcurl NOT to use Negotiate - curl_easy_setopt(CURLOPT_PROXYAUTH, - CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM) - - Start the request -*/ + - Use a proxy that offers NTLM and Negotiate + (CURLOPT_PROXY and CURLOPT_PROXYPORT) + - Tell libcurl NOT to use Negotiate + curl_easy_setopt(CURLOPT_PROXYAUTH, + CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM) + - Start the request + */ static CURLcode test_lib590(const char *URL) { @@ -55,23 +54,23 @@ static CURLcode test_lib590(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_PROXYAUTH, + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM); - test_setopt(curl, CURLOPT_PROXY, libtest_arg2); /* set in first.c */ + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); /* set in first.c */ /* set the name + password twice to test that the API is fine with it */ - test_setopt(curl, CURLOPT_PROXYUSERNAME, "me"); - test_setopt(curl, CURLOPT_PROXYPASSWORD, "password"); - test_setopt(curl, CURLOPT_PROXYUSERPWD, "me:password"); + easy_setopt(curl, CURLOPT_PROXYUSERNAME, "me"); + easy_setopt(curl, CURLOPT_PROXYPASSWORD, "password"); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "me:password"); result = curl_easy_perform(curl); if(result) goto test_cleanup; result = curl_easy_getinfo(curl, CURLINFO_PROXYAUTH_USED, &usedauth); - if(CURLAUTH_NTLM != usedauth) { + if(usedauth != CURLAUTH_NTLM) { curl_mprintf("CURLINFO_PROXYAUTH_USED did not say NTLM\n"); } diff --git a/tests/libtest/lib598.c b/tests/libtest/lib598.c index dc89a52f004a..c6025c578b5e 100644 --- a/tests/libtest/lib598.c +++ b/tests/libtest/lib598.c @@ -40,12 +40,12 @@ static CURLcode test_lib598(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_REFERER, "http://example.com/the-moo"); - test_setopt(curl, CURLOPT_USERAGENT, "the-moo agent next generation"); - test_setopt(curl, CURLOPT_COOKIE, "name=moo"); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_REFERER, "http://example.com/the-moo"); + easy_setopt(curl, CURLOPT_USERAGENT, "the-moo agent next generation"); + easy_setopt(curl, CURLOPT_COOKIE, "name=moo"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); if(result) { @@ -55,9 +55,9 @@ static CURLcode test_lib598(const char *URL) curl_easy_reset(curl); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); if(result) diff --git a/tests/libtest/lib599.c b/tests/libtest/lib599.c index 079fc578dfda..ba8ebca97bbf 100644 --- a/tests/libtest/lib599.c +++ b/tests/libtest/lib599.c @@ -58,28 +58,28 @@ static CURLcode test_lib599(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* we want to use our own progress function */ - test_setopt(curl, CURLOPT_NOPROGRESS, 0L); - test_setopt(curl, CURLOPT_PROGRESSFUNCTION, t599_progress_callback); + easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, t599_progress_callback); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* follow redirects */ - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); if(!result) { FILE *moo; result = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, - &content_length); + &content_length); moo = curlx_fopen(libtest_arg2, "wb"); if(moo) { curl_mfprintf(moo, "CL %.0f\n", content_length); diff --git a/tests/libtest/lib643.c b/tests/libtest/lib643.c index 63df7e0dc7e0..c2aed4f0d29c 100644 --- a/tests/libtest/lib643.c +++ b/tests/libtest/lib643.c @@ -46,12 +46,12 @@ static size_t t643_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) } if(!eof) { - *ptr = *pooh->readptr; /* copy one single byte */ - pooh->readptr++; /* advance pointer */ - return 1; /* we return 1 byte at a time! */ + *ptr = *pooh->readptr; /* copy one single byte */ + pooh->readptr++; /* advance pointer */ + return 1; /* we return 1 byte at a time! */ } - return 0; /* no more data left to deliver */ + return 0; /* no more data left to deliver */ } static CURLcode t643_test_once(const char *URL, bool oldstyle) @@ -69,7 +69,7 @@ static CURLcode t643_test_once(const char *URL, bool oldstyle) pooh.readptr = testdata; if(testnum == 643) - datasize = (curl_off_t)strlen(testdata); + datasize = (curl_off_t)CURL_CSTRLEN(testdata); pooh.sizeleft = datasize; curl = curl_easy_init(); @@ -123,7 +123,7 @@ static CURLcode t643_test_once(const char *URL, bool oldstyle) pooh2.readptr = testdata; if(testnum == 643) - datasize = (curl_off_t)strlen(testdata); + datasize = (curl_off_t)CURL_CSTRLEN(testdata); pooh2.sizeleft = datasize; part = curl_mime_addpart(mime); @@ -194,18 +194,18 @@ static CURLcode t643_test_once(const char *URL, bool oldstyle) curl_mprintf("curl_mime_xxx(5) = %s\n", curl_easy_strerror(result)); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* send a multi-part mimepost */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib650.c b/tests/libtest/lib650.c index 0d3c41998693..04b288725fc4 100644 --- a/tests/libtest/lib650.c +++ b/tests/libtest/lib650.c @@ -48,7 +48,6 @@ static CURLcode test_lib650(const char *URL) struct curl_forms formarray[3]; size_t formlength = 0; char flbuf[32]; - long contentlength = 0; static const char testname[] = "fieldname"; static char testdata[] = "this is what we post to the silly web server"; @@ -74,34 +73,32 @@ static CURLcode test_lib650(const char *URL) } headers = headers2; formrc = curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, &testname, - CURLFORM_COPYCONTENTS, &testdata, + CURLFORM_COPYNAME, testname, + CURLFORM_COPYCONTENTS, testdata, CURLFORM_CONTENTHEADER, headers, CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(1) = %d\n", formrc); + curl_mprintf("curl_formadd(1) = %d\n", (int)formrc); goto test_cleanup; } - contentlength = (long)(strlen(testdata) - 1); - /* Use a form array for the non-copy test. */ formarray[0].option = CURLFORM_PTRCONTENTS; formarray[0].value = testdata; formarray[1].option = CURLFORM_CONTENTSLENGTH; - formarray[1].value = (char *)(size_t)contentlength; + formarray[1].value = (char *)(strlen(testdata) - 1); formarray[2].option = CURLFORM_END; formarray[2].value = NULL; formrc = curl_formadd(&formpost, &lastptr, CURLFORM_PTRNAME, testname, - CURLFORM_NAMELENGTH, strlen(testname) - 1, + CURLFORM_NAMELENGTH, (long)CURL_CSTRLEN(testname) - 1, CURLFORM_ARRAY, formarray, CURLFORM_FILENAME, "remotefile.txt", CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(2) = %d\n", formrc); + curl_mprintf("curl_formadd(2) = %d\n", (int)formrc); goto test_cleanup; } @@ -121,7 +118,7 @@ static CURLcode test_lib650(const char *URL) CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(3) = %d\n", formrc); + curl_mprintf("curl_formadd(3) = %d\n", (int)formrc); goto test_cleanup; } @@ -132,7 +129,7 @@ static CURLcode test_lib650(const char *URL) CURLFORM_FILECONTENT, libtest_arg2, CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(4) = %d\n", formrc); + curl_mprintf("curl_formadd(4) = %d\n", (int)formrc); goto test_cleanup; } @@ -148,11 +145,11 @@ static CURLcode test_lib650(const char *URL) formrc = curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "formlength", - CURLFORM_COPYCONTENTS, &flbuf, + CURLFORM_COPYCONTENTS, flbuf, CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(5) = %d\n", formrc); + curl_mprintf("curl_formadd(5) = %d\n", (int)formrc); goto test_cleanup; } @@ -164,7 +161,7 @@ static CURLcode test_lib650(const char *URL) CURLFORM_END); if(formrc) { - curl_mprintf("curl_formadd(6) = %d\n", formrc); + curl_mprintf("curl_formadd(6) = %d\n", (int)formrc); goto test_cleanup; } @@ -175,21 +172,21 @@ static CURLcode test_lib650(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* send a multi-part formpost */ - test_setopt(curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(curl, CURLOPT_HTTPPOST, formpost); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - test_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib651.c b/tests/libtest/lib651.c index 784e17f02758..1c8fca2fd1ec 100644 --- a/tests/libtest/lib651.c +++ b/tests/libtest/lib651.c @@ -53,7 +53,7 @@ static CURLcode test_lib651(const char *URL) CURLFORM_COPYCONTENTS, testbuf, CURLFORM_END); if(formrc) - curl_mprintf("curl_formadd(1) = %d\n", formrc); + curl_mprintf("curl_formadd(1) = %d\n", (int)formrc); curl = curl_easy_init(); if(!curl) { @@ -64,18 +64,18 @@ static CURLcode test_lib651(const char *URL) } /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* send a multi-part formpost */ - test_setopt(curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(curl, CURLOPT_HTTPPOST, formpost); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib652.c b/tests/libtest/lib652.c index d606fa069419..bf225348ed8c 100644 --- a/tests/libtest/lib652.c +++ b/tests/libtest/lib652.c @@ -94,24 +94,24 @@ static CURLcode test_lib652(const char *URL) } /* First set the URL that is about to receive our mime mail. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Set sender. */ - test_setopt(curl, CURLOPT_MAIL_FROM, "somebody@example.com"); + easy_setopt(curl, CURLOPT_MAIL_FROM, "somebody@example.com"); /* Set recipients. */ - test_setopt(curl, CURLOPT_MAIL_RCPT, recipients); + easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); /* send a multi-part mail */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* Shorten upload buffer. */ - test_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, 16411L); + easy_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, 16411L); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib654.c b/tests/libtest/lib654.c index 472983559934..a10dc944788d 100644 --- a/tests/libtest/lib654.c +++ b/tests/libtest/lib654.c @@ -49,12 +49,12 @@ static size_t t654_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) pooh->sizeleft--; if(!eof) { - *ptr = *pooh->readptr; /* copy one single byte */ - pooh->readptr++; /* advance pointer */ - return 1; /* we return 1 byte at a time! */ + *ptr = *pooh->readptr; /* copy one single byte */ + pooh->readptr++; /* advance pointer */ + return 1; /* we return 1 byte at a time! */ } - return 0; /* no more data left to deliver */ + return 0; /* no more data left to deliver */ } static CURLcode test_lib654(const char *URL) @@ -82,17 +82,17 @@ static CURLcode test_lib654(const char *URL) curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); /* Prepare the callback structure. */ pooh.readptr = testdata; - pooh.sizeleft = (curl_off_t)strlen(testdata); + pooh.sizeleft = (curl_off_t)CURL_CSTRLEN(testdata); pooh.freecount = 0; /* Build the mime tree. */ @@ -111,7 +111,7 @@ static CURLcode test_lib654(const char *URL) free_callback, &pooh); /* Bind mime data to its easy handle. */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* Duplicate the handle. */ curl2 = curl_easy_duphandle(curl); @@ -143,7 +143,7 @@ static CURLcode test_lib654(const char *URL) /* Free the duplicated handle: it should call free_callback again. If the mime copy was bad or not automatically released, valgrind - will signal it. */ + signals it. */ curl_easy_cleanup(curl2); curl2 = NULL; /* Already cleaned up. */ diff --git a/tests/libtest/lib655.c b/tests/libtest/lib655.c index e74d2de17d6d..5e1f0990e21a 100644 --- a/tests/libtest/lib655.c +++ b/tests/libtest/lib655.c @@ -25,7 +25,7 @@ #include "testtrace.h" -static const char *TEST_DATA_STRING = "Test data"; +static const char TEST_DATA_STRING[] = "Test data"; static int cb_count = 0; static int resolver_alloc_cb_fail(void *resolver_state, void *reserved, @@ -78,32 +78,32 @@ static CURLcode test_lib655(const char *URL) } /* Set the URL that is about to receive our first request. */ - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); - test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_RESOLVER_START_DATA, TEST_DATA_STRING); - test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_fail); + easy_setopt(curl, CURLOPT_RESOLVER_START_DATA, TEST_DATA_STRING); + easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_fail); /* this should fail */ result = curl_easy_perform(curl); if(result != CURLE_ABORTED_BY_CALLBACK) { curl_mfprintf(stderr, "curl_easy_perform should have returned " "CURLE_ABORTED_BY_CALLBACK but instead returned error %d\n", - result); + (int)result); if(result == CURLE_OK) result = TEST_ERR_FAILURE; goto test_cleanup; } /* Set the URL that receives our second request. */ - test_setopt(curl, CURLOPT_URL, libtest_arg2); - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); - test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, libtest_arg2); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_pass); + easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_pass); /* this should succeed */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib658.c b/tests/libtest/lib658.c index 3d9b4575d460..8ed465715fea 100644 --- a/tests/libtest/lib658.c +++ b/tests/libtest/lib658.c @@ -61,8 +61,8 @@ static CURLcode test_lib658(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed " - "with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + "with code %d (%s)\n", __FILE__, __LINE__, + (int)result, curl_easy_strerror(result)); goto test_cleanup; } diff --git a/tests/libtest/lib659.c b/tests/libtest/lib659.c index d1a78e206dbf..c2fb30d4ea24 100644 --- a/tests/libtest/lib659.c +++ b/tests/libtest/lib659.c @@ -60,7 +60,7 @@ static CURLcode test_lib659(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed " "with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } diff --git a/tests/libtest/lib661.c b/tests/libtest/lib661.c index 12cb4b7250fd..80d46e8738bf 100644 --- a/tests/libtest/lib661.c +++ b/tests/libtest/lib661.c @@ -45,17 +45,17 @@ static CURLcode test_lib661(const char *URL) /* test: CURLFTPMETHOD_SINGLECWD with absolute path should skip CWD to entry path */ newURL = curl_maprintf("%s/folderA/661", URL); - test_setopt(curl, CURLOPT_URL, newURL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); + easy_setopt(curl, CURLOPT_URL, newURL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); result = curl_easy_perform(curl); if(result != CURLE_REMOTE_FILE_NOT_FOUND) goto test_cleanup; curl_free(newURL); newURL = curl_maprintf("%s/folderB/661", URL); - test_setopt(curl, CURLOPT_URL, newURL); + easy_setopt(curl, CURLOPT_URL, newURL); result = curl_easy_perform(curl); if(result != CURLE_REMOTE_FILE_NOT_FOUND) goto test_cleanup; @@ -72,10 +72,10 @@ static CURLcode test_lib661(const char *URL) curl_free(newURL); newURL = curl_maprintf("%s/folderA/661", URL); - test_setopt(curl, CURLOPT_URL, newURL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); + easy_setopt(curl, CURLOPT_URL, newURL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); result = curl_easy_perform(curl); if(result != CURLE_REMOTE_FILE_NOT_FOUND) goto test_cleanup; @@ -83,16 +83,16 @@ static CURLcode test_lib661(const char *URL) /* curve ball: CWD /folderB before reusing connection with _NOCWD */ curl_free(newURL); newURL = curl_maprintf("%s/folderB/661", URL); - test_setopt(curl, CURLOPT_URL, newURL); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); + easy_setopt(curl, CURLOPT_URL, newURL); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); result = curl_easy_perform(curl); if(result != CURLE_REMOTE_FILE_NOT_FOUND) goto test_cleanup; curl_free(newURL); newURL = curl_maprintf("%s/folderA/661", URL); - test_setopt(curl, CURLOPT_URL, newURL); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); + easy_setopt(curl, CURLOPT_URL, newURL); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); result = curl_easy_perform(curl); if(result != CURLE_REMOTE_FILE_NOT_FOUND) goto test_cleanup; @@ -114,11 +114,11 @@ static CURLcode test_lib661(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_NOBODY, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); - test_setopt(curl, CURLOPT_QUOTE, slist); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); + easy_setopt(curl, CURLOPT_QUOTE, slist); result = curl_easy_perform(curl); if(result) goto test_cleanup; @@ -133,11 +133,11 @@ static CURLcode test_lib661(const char *URL) goto test_cleanup; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_NOBODY, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); - test_setopt(curl, CURLOPT_QUOTE, slist); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); + easy_setopt(curl, CURLOPT_QUOTE, slist); result = curl_easy_perform(curl); if(result) goto test_cleanup; @@ -146,17 +146,17 @@ static CURLcode test_lib661(const char *URL) not emit CWD for second FTP access when not needed + bonus: see if path buffering survives curl_easy_reset() */ curl_easy_reset(curl); - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_NOBODY, 1L); - test_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); - test_setopt(curl, CURLOPT_QUOTE, slist); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_NOBODY, 1L); + easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); + easy_setopt(curl, CURLOPT_QUOTE, slist); result = curl_easy_perform(curl); test_cleanup: if(result) - curl_mfprintf(stderr, "test encountered error %d\n", result); + curl_mfprintf(stderr, "test encountered error %d\n", (int)result); curl_slist_free_all(slist); curl_free(newURL); curl_easy_cleanup(curl); diff --git a/tests/libtest/lib666.c b/tests/libtest/lib666.c index 55dd33ab35a5..43ea1eb88e80 100644 --- a/tests/libtest/lib666.c +++ b/tests/libtest/lib666.c @@ -89,21 +89,21 @@ static CURLcode test_lib666(const char *URL) } /* First set the URL that is about to receive our mime mail. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* Post form */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* Shorten upload buffer. */ - test_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, 16411L); + easy_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, 16411L); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); - /* Perform the request, result will get the return code */ + /* Perform the request, result gets the return code */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib667.c b/tests/libtest/lib667.c index c0b383d0ac21..38158dc2b48b 100644 --- a/tests/libtest/lib667.c +++ b/tests/libtest/lib667.c @@ -41,12 +41,12 @@ static size_t t667_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) pooh->sizeleft--; if(!eof) { - *ptr = *pooh->readptr; /* copy one single byte */ - pooh->readptr++; /* advance pointer */ - return 1; /* we return 1 byte at a time! */ + *ptr = *pooh->readptr; /* copy one single byte */ + pooh->readptr++; /* advance pointer */ + return 1; /* we return 1 byte at a time! */ } - return 0; /* no more data left to deliver */ + return 0; /* no more data left to deliver */ } static CURLcode test_lib667(const char *URL) @@ -72,17 +72,17 @@ static CURLcode test_lib667(const char *URL) curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); /* Prepare the callback structure. */ pooh.readptr = testdata; - pooh.sizeleft = (curl_off_t)strlen(testdata); + pooh.sizeleft = (curl_off_t)CURL_CSTRLEN(testdata); /* Build the mime tree. */ mime = curl_mime_init(curl); @@ -93,7 +93,7 @@ static CURLcode test_lib667(const char *URL) curl_mime_data_cb(part, (curl_off_t)-1, t667_read_cb, NULL, NULL, &pooh); /* Bind mime data to its easy handle. */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* Send data. */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib668.c b/tests/libtest/lib668.c index ae0f524e4799..fd59a0c3f92f 100644 --- a/tests/libtest/lib668.c +++ b/tests/libtest/lib668.c @@ -25,13 +25,13 @@ struct t668_WriteThis { const char *readptr; - curl_off_t sizeleft; + size_t sizeleft; }; static size_t t668_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) { struct t668_WriteThis *pooh = (struct t668_WriteThis *)userp; - size_t len = strlen(pooh->readptr); + size_t len = pooh->sizeleft; (void)size; /* Always 1 */ @@ -40,6 +40,7 @@ static size_t t668_read_cb(char *ptr, size_t size, size_t nmemb, void *userp) if(len) { memcpy(ptr, pooh->readptr, len); pooh->readptr += len; + pooh->sizeleft -= len; } return len; } @@ -66,17 +67,17 @@ static CURLcode test_lib668(const char *URL) curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HEADER, 1L); /* Prepare the callback structures. */ pooh1.readptr = testdata; - pooh1.sizeleft = (curl_off_t)strlen(testdata); + pooh1.sizeleft = CURL_CSTRLEN(testdata); pooh2 = pooh1; /* Build the mime tree. */ @@ -84,22 +85,21 @@ static CURLcode test_lib668(const char *URL) part = curl_mime_addpart(mime); curl_mime_name(part, "field1"); /* Early end of data detection can be done because the data size is known. */ - curl_mime_data_cb(part, (curl_off_t)strlen(testdata), + curl_mime_data_cb(part, (curl_off_t)pooh1.sizeleft, t668_read_cb, NULL, NULL, &pooh1); part = curl_mime_addpart(mime); curl_mime_name(part, "field2"); /* Using an undefined length forces chunked transfer and disables early end of data detection for this part. */ - curl_mime_data_cb(part, (curl_off_t)-1, - t668_read_cb, NULL, NULL, &pooh2); + curl_mime_data_cb(part, (curl_off_t)-1, t668_read_cb, NULL, NULL, &pooh2); part = curl_mime_addpart(mime); curl_mime_name(part, "field3"); - /* Regular file part sources early end of data can be detected because - the file size is known. In addition, and EOF test is performed. */ + /* For regular file parts, early end of data can be detected because + the file size is known. In addition, an EOF test is performed. */ curl_mime_filedata(part, libtest_arg2); /* Bind mime data to its easy handle. */ - test_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); /* Send data. */ result = curl_easy_perform(curl); diff --git a/tests/libtest/lib670.c b/tests/libtest/lib670.c index 60e4f7ef25c4..2daeb06576a7 100644 --- a/tests/libtest/lib670.c +++ b/tests/libtest/lib670.c @@ -105,13 +105,13 @@ static CURLcode test_lib670(const char *URL) pooh.curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(pooh.curl, CURLOPT_URL, URL); + easy_setopt(pooh.curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(pooh.curl, CURLOPT_VERBOSE, 1L); + easy_setopt(pooh.curl, CURLOPT_VERBOSE, 1L); /* include headers in the output */ - test_setopt(pooh.curl, CURLOPT_HEADER, 1L); + easy_setopt(pooh.curl, CURLOPT_HEADER, 1L); if(testnum == 670 || testnum == 671) { curl_mimepart *part; @@ -122,7 +122,7 @@ static CURLcode test_lib670(const char *URL) if(result != CURLE_OK) { curl_mfprintf(stderr, "Something went wrong when building the " - "mime structure: %d\n", result); + "mime structure: %d\n", (int)result); goto test_cleanup; } @@ -131,7 +131,7 @@ static CURLcode test_lib670(const char *URL) /* Bind mime data to its easy handle. */ if(result == CURLE_OK) - test_setopt(pooh.curl, CURLOPT_MIMEPOST, mime); + easy_setopt(pooh.curl, CURLOPT_MIMEPOST, mime); } else { struct curl_httppost *lastptr = NULL; @@ -140,18 +140,18 @@ static CURLcode test_lib670(const char *URL) formrc = curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, testname, CURLFORM_STREAM, &pooh, - CURLFORM_CONTENTLEN, (curl_off_t) 2, + CURLFORM_CONTENTLEN, (curl_off_t)2, CURLFORM_END); if(formrc) { - curl_mfprintf(stderr, "curl_formadd() = %d\n", formrc); + curl_mfprintf(stderr, "curl_formadd() = %d\n", (int)formrc); goto test_cleanup; } /* We want to use our own read function. */ - test_setopt(pooh.curl, CURLOPT_READFUNCTION, t670_read_cb); + easy_setopt(pooh.curl, CURLOPT_READFUNCTION, t670_read_cb); /* Send a multi-part formpost. */ - test_setopt(pooh.curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(pooh.curl, CURLOPT_HTTPPOST, formpost); } if(testnum == 670 || testnum == 672) { @@ -223,9 +223,9 @@ static CURLcode test_lib670(const char *URL) } else { /* Use the easy interface. */ - test_setopt(pooh.curl, CURLOPT_XFERINFODATA, &pooh); - test_setopt(pooh.curl, CURLOPT_XFERINFOFUNCTION, t670_xferinfo); - test_setopt(pooh.curl, CURLOPT_NOPROGRESS, 0L); + easy_setopt(pooh.curl, CURLOPT_XFERINFODATA, &pooh); + easy_setopt(pooh.curl, CURLOPT_XFERINFOFUNCTION, t670_xferinfo); + easy_setopt(pooh.curl, CURLOPT_NOPROGRESS, 0L); result = curl_easy_perform(pooh.curl); } diff --git a/tests/libtest/lib674.c b/tests/libtest/lib674.c index 11fb8be84922..761ee3e16f7c 100644 --- a/tests/libtest/lib674.c +++ b/tests/libtest/lib674.c @@ -62,7 +62,7 @@ static CURLcode test_lib674(const char *URL) if(result) { curl_mfprintf(stderr, "%s:%d curl_easy_perform() failed " "with code %d (%s)\n", - __FILE__, __LINE__, result, curl_easy_strerror(result)); + __FILE__, __LINE__, (int)result, curl_easy_strerror(result)); goto test_cleanup; } diff --git a/tests/libtest/lib676.c b/tests/libtest/lib676.c index 0f7fc735f9b4..e08f2d4b1230 100644 --- a/tests/libtest/lib676.c +++ b/tests/libtest/lib676.c @@ -40,11 +40,11 @@ static CURLcode test_lib676(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_USERAGENT, "the-moo agent next generation"); - test_setopt(curl, CURLOPT_COOKIEFILE, libtest_arg2); - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_USERAGENT, "the-moo agent next generation"); + easy_setopt(curl, CURLOPT_COOKIEFILE, libtest_arg2); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); result = curl_easy_perform(curl); if(result) { @@ -53,7 +53,7 @@ static CURLcode test_lib676(const char *URL) } /* now clear the cookies */ - test_setopt(curl, CURLOPT_COOKIEFILE, NULL); + easy_setopt(curl, CURLOPT_COOKIEFILE, NULL); result = curl_easy_perform(curl); if(result) diff --git a/tests/libtest/lib677.c b/tests/libtest/lib677.c index fdb7294caaa0..3b90cc4d83a3 100644 --- a/tests/libtest/lib677.c +++ b/tests/libtest/lib677.c @@ -36,7 +36,7 @@ static CURLcode test_lib677(const char *URL) ssize_t pos = 0; CURLcode result = CURLE_OK; - global_init(CURL_GLOBAL_DEFAULT); + global_init(CURL_GLOBAL_ALL); multi_init(mcurl); easy_init(curl); @@ -77,14 +77,14 @@ static CURLcode test_lib677(const char *URL) if(!state) { CURLcode ec; - ec = curl_easy_send(curl, testcmd + pos, - sizeof(testcmd) - 1 - pos, &len); + ec = curl_easy_send(curl, testcmd + pos, CURL_CSTRLEN(testcmd) - pos, + &len); if(ec == CURLE_AGAIN) { continue; } else if(ec) { curl_mfprintf(stderr, "curl_easy_send() failed, with code %d (%s)\n", - ec, curl_easy_strerror(ec)); + (int)ec, curl_easy_strerror(ec)); result = ec; goto test_cleanup; } @@ -92,7 +92,7 @@ static CURLcode test_lib677(const char *URL) pos += len; else pos = 0; - if(pos == sizeof(testcmd) - 1) { + if(pos == CURL_CSTRLEN(testcmd)) { state++; pos = 0; } @@ -105,7 +105,7 @@ static CURLcode test_lib677(const char *URL) } else if(ec) { curl_mfprintf(stderr, "curl_easy_recv() failed, with code %d (%s)\n", - ec, curl_easy_strerror(ec)); + (int)ec, curl_easy_strerror(ec)); result = ec; goto test_cleanup; } diff --git a/tests/libtest/lib678.c b/tests/libtest/lib678.c index 73aa4ee7e972..ef04609e5c9a 100644 --- a/tests/libtest/lib678.c +++ b/tests/libtest/lib678.c @@ -47,9 +47,8 @@ static int loadfile(const char *filename, void **filedata, size_t *filesize) continue_reading = FALSE; curlx_fclose(fInCert); if(!continue_reading) { - curlx_free(data); + curlx_safefree(data); datasize = 0; - data = NULL; } } } @@ -94,10 +93,10 @@ static CURLcode test_cert_blob(const char *url, const char *cafile) static CURLcode test_lib678(const char *URL) { CURLcode result = CURLE_OK; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); if(!strcmp("check", URL)) { CURLcode w = CURLE_OK; - struct curl_blob blob = { 0 }; + struct curl_blob blob = { CURL_UNCONST("silly"), 5, 0 }; CURL *curl = curl_easy_init(); if(curl) { w = curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); diff --git a/tests/libtest/lib694.c b/tests/libtest/lib694.c index 83d5e8f4169f..5bb212067ac0 100644 --- a/tests/libtest/lib694.c +++ b/tests/libtest/lib694.c @@ -42,12 +42,12 @@ static CURLcode test_lib694(const char *URL) return TEST_ERR_MAJOR_BAD; } - test_setopt(curl, CURLOPT_URL, URL); - test_setopt(curl, CURLOPT_HEADER, 1L); - test_setopt(curl, CURLOPT_VERBOSE, 1L); - test_setopt(curl, CURLOPT_HTTPAUTH, + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NTLM); - test_setopt(curl, CURLOPT_USERPWD, "me:password"); + easy_setopt(curl, CURLOPT_USERPWD, "me:password"); do { @@ -58,12 +58,12 @@ static CURLcode test_lib694(const char *URL) result = curl_easy_getinfo(curl, CURLINFO_HTTPAUTH_USED, &usedauth); if(result) goto test_cleanup; - if(CURLAUTH_NTLM != usedauth) { + if(usedauth != CURLAUTH_NTLM) { curl_mprintf("CURLINFO_HTTPAUTH_USED did not say NTLM\n"); } /* set a new URL for the second, so that we do not restart NTLM */ - test_setopt(curl, CURLOPT_URL, libtest_arg2); + easy_setopt(curl, CURLOPT_URL, libtest_arg2); } while(!result && ++count < 2); test_cleanup: diff --git a/tests/libtest/lib695.c b/tests/libtest/lib695.c index 90a658c598f1..de8986d88859 100644 --- a/tests/libtest/lib695.c +++ b/tests/libtest/lib695.c @@ -23,14 +23,6 @@ ***************************************************************************/ #include "first.h" -/* write callback that does nothing */ -static size_t write_it(char *ptr, size_t size, size_t nmemb, void *userdata) -{ - (void)ptr; - (void)userdata; - return size * nmemb; -} - static CURLcode test_lib695(const char *URL) { CURL *curl = NULL; @@ -51,13 +43,13 @@ static CURLcode test_lib695(const char *URL) curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* Do not write anything. */ - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_it); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); /* Build the first mime structure. */ mime1 = curl_mime_init(curl); @@ -88,7 +80,7 @@ static CURLcode test_lib695(const char *URL) result = curl_mime_subparts(part, mime1); if(result != CURLE_OK) - curl_mfprintf(stderr, "curl_mime_subparts() failed: %sn", + curl_mfprintf(stderr, "curl_mime_subparts() failed: %s\n", curl_easy_strerror(result)); else { mime1 = NULL; diff --git a/tests/libtest/lib751.c b/tests/libtest/lib751.c index cc140144162e..55f62d868eca 100644 --- a/tests/libtest/lib751.c +++ b/tests/libtest/lib751.c @@ -38,7 +38,7 @@ static CURLcode test_lib751(const char *URL) (void)URL; memset(curls, 0, sizeof(curls)); - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); multi = curl_multi_init(); if(!multi) { result = CURLE_OUT_OF_MEMORY; diff --git a/tests/libtest/lib753.c b/tests/libtest/lib753.c index a83f4c6b40ac..cf83f969e7fd 100644 --- a/tests/libtest/lib753.c +++ b/tests/libtest/lib753.c @@ -109,7 +109,7 @@ static CURLcode test_lib753(const char *URL) debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_ALL); curl_mfprintf(stderr, "init multi\n"); multi = curl_multi_init(); @@ -134,8 +134,8 @@ static CURLcode test_lib753(const char *URL) * 1. Violently cleanup EASY1 *without* removing it from the multi * handle first. This MUST discard the connection that EASY1 holds, * as EASY1 is not DONE at this point. - * With the env var CURL_FTP_PWD_STOP set, the connection will - * have no outstanding data at this point. This would allow + * With the env var CURL_FTP_PWD_STOP set, the connection has + * no outstanding data at this point. This would allow * reuse if the connection is not terminated by the cleanup. * 2. Add EASY2 for the same URL and observe in the expected result * that the connection is NOT reused, e.g. all FTP commands diff --git a/tests/libtest/lib757.c b/tests/libtest/lib757.c index 88f863746ae9..378c78cf8176 100644 --- a/tests/libtest/lib757.c +++ b/tests/libtest/lib757.c @@ -23,16 +23,8 @@ ***************************************************************************/ #include "first.h" -/* write callback that does nothing */ -static size_t write_757(char *ptr, size_t size, size_t nmemb, void *userdata) -{ - (void)ptr; - (void)userdata; - return size * nmemb; -} - static const char t757_data[] = "fun-times"; -static size_t const t757_datalen = sizeof(t757_data) - 1; +static const size_t t757_datalen = CURL_CSTRLEN(t757_data); static size_t read_757(char *buffer, size_t size, size_t nitems, void *arg) { @@ -72,13 +64,13 @@ static CURLcode test_lib757(const char *URL) curl = curl_easy_init(); /* First set the URL that is about to receive our POST. */ - test_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_URL, URL); /* get verbose debug output please */ - test_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); /* Do not write anything. */ - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_757); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, tutil_throwaway_cb); /* Build the first mime structure. */ mime1 = curl_mime_init(curl); @@ -109,7 +101,7 @@ static CURLcode test_lib757(const char *URL) result = curl_mime_subparts(part, mime1); if(result != CURLE_OK) - curl_mfprintf(stderr, "curl_mime_subparts() failed: %sn", + curl_mfprintf(stderr, "curl_mime_subparts() failed: %s\n", curl_easy_strerror(result)); else { mime1 = NULL; diff --git a/tests/libtest/lib758.c b/tests/libtest/lib758.c index b6642cb130aa..794e4cce066d 100644 --- a/tests/libtest/lib758.c +++ b/tests/libtest/lib758.c @@ -147,6 +147,7 @@ static int t758_curlSocketCallback(CURL *curl, curl_socket_t s, int action, void *userp, void *socketp) { struct t758_ReadWriteSockets *sockets = userp; + CURLcode result; (void)curl; (void)socketp; @@ -158,6 +159,14 @@ static int t758_curlSocketCallback(CURL *curl, curl_socket_t s, int action, return -1; } + /* Pause is forbidden in this callback. This also returns + CURLE_BAD_FUNCTION_ARGUMENT before the connection has been setup. */ + result = curl_easy_pause(curl, CURLPAUSE_ALL); + if(!result) { + t758_msg("<- curl_easy_pause should return error!"); + return -1; + } + if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) if(t758_addFd(&sockets->read, s, "read")) return -1; /* bail out */ @@ -249,8 +258,8 @@ static int t758_checkForCompletion(CURLM *multi, int *success) *success = 0; } else { - curl_mfprintf(stderr, "%s got an unexpected message from curl: %i\n", - t758_tag(), message->msg); + curl_mfprintf(stderr, "%s got an unexpected message from curl: %d\n", + t758_tag(), (int)message->msg); result = 1; *success = 0; } @@ -293,7 +302,7 @@ static CURLMcode t758_saction(CURLM *multi, curl_socket_t s, CURLMcode mresult = curl_multi_socket_action(multi, s, evBitmask, &numhandles); if(mresult != CURLM_OK) { - curl_mfprintf(stderr, "%s curl error on %s (%i) %s\n", + curl_mfprintf(stderr, "%s curl error on %s (%d) %s\n", t758_tag(), info, mresult, curl_multi_strerror(mresult)); } return mresult; @@ -352,7 +361,7 @@ static CURLcode t758_one(const char *URL, int timer_fail_at, easy_init(curl); debug_config.nohex = TRUE; debug_config.tracetime = TRUE; - test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); easy_setopt(curl, CURLOPT_VERBOSE, 1L); @@ -386,7 +395,7 @@ static CURLcode t758_one(const char *URL, int timer_fail_at, if(t758_ctx.fake_async_cert_verification_pending && !t758_ctx.fake_async_cert_verification_finished) { - /* the wakeup socket will be monitored */ + /* the wakeup socket is monitored */ if((sockets.read.count > 1) || sockets.write.count) { t758_msg("during verification there should be no sockets scheduled"); result = TEST_ERR_MAJOR_BAD; @@ -491,7 +500,7 @@ static CURLcode test_lib758(const char *URL) callback calls */ result = t758_one(URL, 0, 0); /* no callback fails */ if(result) - curl_mfprintf(stderr, "%s FAILED: %d\n", t758_tag(), result); + curl_mfprintf(stderr, "%s FAILED: %d\n", t758_tag(), (int)result); return result; } diff --git a/tests/libtest/mk-lib1521.pl b/tests/libtest/mk-lib1521.pl index f3d0c088e86d..6590d5257a7d 100755 --- a/tests/libtest/mk-lib1521.pl +++ b/tests/libtest/mk-lib1521.pl @@ -42,6 +42,7 @@ 'CURLOPT_DNS_LOCAL_IP4', 'CURLOPT_DNS_LOCAL_IP6', 'CURLOPT_DNS_SERVERS', + 'CURLOPT_ECH', 'CURLOPT_PROXY_TLSAUTH_TYPE', 'CURLOPT_SSLENGINE', 'CURLOPT_TLSAUTH_TYPE', @@ -243,20 +244,20 @@ static void errlongzero(const char *name, CURLcode result, int lineno) { curl_mprintf("%s set to 0 returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void errlong(const char *name, CURLcode result, int lineno) { $allowednumerrors curl_mprintf("%s set to non-zero returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void errneg(const char *name, CURLcode result, int lineno) { curl_mprintf("%s set to -1 returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void errstring(const char *name, CURLcode result, int lineno) @@ -265,25 +266,25 @@ when given a strange string input */ $allowedstringerrors curl_mprintf("%s set to a string returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void err(const char *name, CURLcode result, int lineno) { curl_mprintf("%s returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void errnull(const char *name, CURLcode result, int lineno) { curl_mprintf("%s set to NULL returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static void t1521_geterr(const char *name, CURLcode result, int lineno) { curl_mprintf("CURLINFO_%s returned %d, \\"%s\\" on line %d\\n", - name, result, curl_easy_strerror(result), lineno); + name, (int)result, curl_easy_strerror(result), lineno); } static curl_progress_callback progresscb; @@ -441,10 +442,10 @@ next; } if($_ =~ /^CURLOPT(?:DEPRECATED)?\(([^ ]*), ([^ ]*), (\d*)[,)]/) { - my ($name, $type, $val)=($1, $2, $3); - my $w=" "; - my $w2="$w$w"; - my $w3="$w$w$w"; + my ($name, $type, $val) = ($1, $2, $3); + my $w = " "; + my $w2 = "$w$w"; + my $w3 = "$w$w$w"; my $opt = $name; $opt =~ s/^CURLOPT_//; my $exists = "${w}{\n"; @@ -562,7 +563,7 @@ } elsif($type eq "CURLOPTTYPE_FUNCTIONPOINT") { if($name =~ /([^ ]*)FUNCTION/) { - my $l=lc($1); + my $l = lc($1); $l =~ s/^curlopt_//; print $fh "${fpref}\n$i${l}cb);\n$fcheck"; } @@ -593,7 +594,7 @@ } elsif($infomode && ($_ =~ /^CURLINFO_([^ ]*) *= *CURLINFO_([^ ]*)/)) { - my ($info, $type)=($1, $2); + my ($info, $type) = ($1, $2); my $c = " result = curl_easy_getinfo(curl, CURLINFO_$info,"; my $check = " if(result)\n t1521_geterr(\"$info\", result, __LINE__);\n"; if($type eq "STRING") { diff --git a/tests/libtest/test1013.pl b/tests/libtest/test1013.pl index e03e99af4364..b6bd5be7ff03 100755 --- a/tests/libtest/test1013.pl +++ b/tests/libtest/test1013.pl @@ -32,11 +32,11 @@ exit 3; } -my $what=$ARGV[2]; +my $what = $ARGV[2]; # Read the output of curl --version -my $curl_protocols=""; -open(CURL, $ARGV[1]) || die "Cannot get curl $what list\n"; +my $curl_protocols = ""; +open(CURL, $ARGV[1]) or die "Cannot get curl $what list\n"; while() { $curl_protocols = $_ if(/$what:/i); } @@ -48,7 +48,7 @@ # Read the output of curl-config my @curl_config; -open(CURLCONFIG, '-|', 'sh', $ARGV[0], "--$what") || die "Cannot get curl-config $what list\n"; +open(CURLCONFIG, '-|', 'sh', $ARGV[0], "--$what") or die "Cannot get curl-config $what list\n"; while() { chomp; $_ = lc($_) if($what eq "protocols"); # accept uppercase protocols in curl-config diff --git a/tests/libtest/test1022.pl b/tests/libtest/test1022.pl index c25ae3ce6c28..a74a180e1d04 100755 --- a/tests/libtest/test1022.pl +++ b/tests/libtest/test1022.pl @@ -31,10 +31,10 @@ exit 3; } -my $what=$ARGV[2]; +my $what = $ARGV[2]; # Read the output of curl --version -open(CURL, $ARGV[1]) || die "Cannot open curl --version list in $ARGV[1]\n"; +open(CURL, $ARGV[1]) or die "Cannot open curl --version list in $ARGV[1]\n"; $_ = ; chomp; /libcurl\/([\.\d]+((-DEV)|(-rc\d)|(-\d+))?)/; @@ -44,10 +44,10 @@ my $curlconfigversion; # Read the output of curl-config --version/--vernum -open(CURLCONFIG, '-|', 'sh', $ARGV[0], "--$what") || die "Cannot get curl-config --$what list\n"; +open(CURLCONFIG, '-|', 'sh', $ARGV[0], "--$what") or die "Cannot get curl-config --$what list\n"; $_ = ; chomp; -my $filever=$_; +my $filever = $_; if($what eq "version") { if($filever =~ /^libcurl ([\.\d]+((-DEV)|(-rc\d)|(-\d+))?)$/) { $curlconfigversion = $1; diff --git a/tests/libtest/test610.pl b/tests/libtest/test610.pl index 10f665620ae2..2834ba4ecab4 100755 --- a/tests/libtest/test610.pl +++ b/tests/libtest/test610.pl @@ -36,20 +36,20 @@ my $cmd = shift @ARGV; my $arg = shift @ARGV; if($cmd eq "mkdir") { - mkdir $arg || die "$!"; + mkdir $arg or die "$!"; } elsif($cmd eq "rmdir") { - rmdir $arg || die "$!"; + rmdir $arg or die "$!"; } elsif($cmd eq "rm") { - unlink $arg || die "$!"; + unlink $arg or die "$!"; } elsif($cmd eq "move") { my $arg2 = shift @ARGV; - move($arg,$arg2) || die "$!"; + move($arg, $arg2) or die "$!"; } elsif($cmd eq "gone") { - ! -e $arg || die "Path $arg exists"; + ! -e $arg or die "Path $arg exists"; } else { print "Unsupported command $cmd\n"; exit 1; diff --git a/tests/libtest/test613.pl b/tests/libtest/test613.pl index f653b36c1328..8647c9add2ad 100755 --- a/tests/libtest/test613.pl +++ b/tests/libtest/test613.pl @@ -41,39 +41,39 @@ sub errout { if($ARGV[0] eq "prepare") { my $dirname = $ARGV[1]; - mkdir $dirname || errout "$!"; + mkdir $dirname or errout "$!"; chdir $dirname; # Create the files in alphabetical order, to increase the chances # of receiving a consistent set of directory contents regardless # of whether the server alphabetizes the results or not. - mkdir "asubdir" || errout "$!"; + mkdir "asubdir" or errout "$!"; chmod 0777, "asubdir"; - open(FILE, ">plainfile.txt") || errout "$!"; + open(FILE, ">", 'plainfile.txt') or errout "$!"; binmode FILE; print FILE "Test file to support curl test suite\n"; close(FILE); # The mtime is specifically chosen to be an even number so that it can be # represented exactly on a FAT file system. - utime time, timegm(0,0,12,1,0,100), "plainfile.txt"; + utime time, timegm(0, 0, 12, 1, 0, 100), "plainfile.txt"; chmod 0666, "plainfile.txt"; - open(FILE, ">emptyfile.txt") || errout "$!"; + open(FILE, ">", 'emptyfile.txt') or errout "$!"; binmode FILE; close(FILE); # The mtime is specifically chosen to be an even number so that it can be # represented exactly on a FAT file system. - utime time, timegm(0,0,12,1,0,100), "emptyfile.txt"; + utime time, timegm(0, 0, 12, 1, 0, 100), "emptyfile.txt"; chmod 0666, "emptyfile.txt"; - open(FILE, ">rofile.txt") || errout "$!"; + open(FILE, ">", 'rofile.txt') or errout "$!"; binmode FILE; print FILE "Read-only test file to support curl test suite\n"; close(FILE); # The mtime is specifically chosen to be an even number so that it can be # represented exactly on a FAT file system. - utime time, timegm(0,0,12,31,11,100), "rofile.txt"; + utime time, timegm(0, 0, 12, 31, 11, 100), "rofile.txt"; chmod 0444, "rofile.txt"; if($^O eq 'cygwin') { system('chattr', ('+r', 'rofile.txt')); @@ -94,7 +94,7 @@ sub errout { unlink "$dirname/plainfile.txt"; rmdir "$dirname/asubdir"; - rmdir $dirname || die "$!"; + rmdir $dirname or die "$!"; if($#ARGV >= 3) { # Verify mtime if requested my $checkfile = $ARGV[2]; @@ -120,7 +120,7 @@ sub errout { # -r-?r-?r-? 12 U U 47 Dec 31 2000 rofile.txt my @canondir; - open(IN, "<$logfile") || die "$!"; + open(IN, "<", $logfile) or die "$!"; while() { /^(.)(..).(..).(..).\s*(\S+)\s+\S+\s+\S+\s+(\S+)\s+(\S+\s+\S+\s+\S+)\s+(.*)$/; if($1 eq "d") { @@ -137,10 +137,10 @@ sub errout { # some systems (e.g. on Windows) # Erase user and group names, as they are not consistent across # all test systems - my $line = sprintf("%s%s???????%5d U U %15d %s %s\n", $1,$2,$5,$6,$7,$8); + my $line = sprintf("%s%s???????%5d U U %15d %s %s\n", $1, $2, $5, $6, $7, $8); push @canondir, $line; } else { - # Unexpected format; just pass it through and let the test fail + # Unexpected format; pass it through and let the test fail push @canondir, $_; } } @@ -148,7 +148,7 @@ sub errout { @canondir = sort {substr($a, 57) cmp substr($b, 57)} @canondir; my $newfile = $logfile . ".new"; - open(OUT, ">$newfile") || die "$!"; + open(OUT, ">", $newfile) or die "$!"; print OUT join('', @canondir); close(OUT); diff --git a/tests/libtest/testutil.c b/tests/libtest/testutil.c index 81f174a647c2..7336a736f697 100644 --- a/tests/libtest/testutil.c +++ b/tests/libtest/testutil.c @@ -21,7 +21,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "testutil.h" +#include "first.h" /* build request URL */ char *tutil_suburl(const char *base, int i) @@ -44,3 +44,14 @@ void tutil_rlim2str(char *buf, size_t len, rlim_t val) curl_msnprintf(buf, len, "%lu", (unsigned long)val); } #endif + +/* + * Handy CURLOPT_WRITEFUNCTION for tests that do not need to keep received + * data. + */ +size_t tutil_throwaway_cb(char *data, size_t n, size_t l, void *userp) +{ + (void)data; + (void)userp; + return n * l; +} diff --git a/tests/libtest/testutil.h b/tests/libtest/testutil.h index e66dbdc1c17a..9799752cd50b 100644 --- a/tests/libtest/testutil.h +++ b/tests/libtest/testutil.h @@ -23,7 +23,6 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "first.h" /* build request URL */ char *tutil_suburl(const char *base, int i); @@ -36,4 +35,10 @@ char *tutil_suburl(const char *base, int i); void tutil_rlim2str(char *buf, size_t len, rlim_t val); #endif +/* + * Handy CURLOPT_WRITEFUNCTION for tests that do not need to keep received + * data. + */ +size_t tutil_throwaway_cb(char *data, size_t n, size_t l, void *userp); + #endif /* HEADER_CURL_LIBTEST_TESTUTIL_H */ diff --git a/tests/libtest/unitcheck.h b/tests/libtest/unitcheck.h index 462f8b45f32c..bc616d135b48 100644 --- a/tests/libtest/unitcheck.h +++ b/tests/libtest/unitcheck.h @@ -48,7 +48,7 @@ #define verify_memory(dynamic, check, len) \ do { \ - if((dynamic) && memcmp(dynamic, check, len)) { \ + if(memcmp(dynamic, check, len)) { \ curl_mfprintf(stderr, "%s:%d Memory buffer FAILED match size %d. " \ "'%s' is not\n", __FILE__, __LINE__, len, \ hexdump((const unsigned char *)(check), len)); \ diff --git a/tests/memanalyze.pl b/tests/memanalyze.pl index e30b263c4128..c1988500fa16 100755 --- a/tests/memanalyze.pl +++ b/tests/memanalyze.pl @@ -34,16 +34,16 @@ while(@ARGV) { if($ARGV[0] eq "-v") { - $verbose=1; + $verbose = 1; shift @ARGV; } elsif($ARGV[0] eq "-t") { - $trace=1; + $trace = 1; shift @ARGV; } elsif($ARGV[0] eq "-l") { # only show what alloc that caused a memlimit failure - $showlimit=1; + $showlimit = 1; shift @ARGV; } else { @@ -55,6 +55,6 @@ my @res = memanalyze($file, $verbose, $trace, $showlimit); -for (@res) { +for(@res) { print $_; } diff --git a/tests/memanalyzer.pm b/tests/memanalyzer.pm index 2a395da2e0fd..110b39d24111 100644 --- a/tests/memanalyzer.pm +++ b/tests/memanalyzer.pm @@ -46,7 +46,7 @@ my $memsum; my $maxmem; sub newtotal { - my ($newtot)=@_; + my ($newtot) = @_; # count a max here if($newtot > $maxmem) { @@ -152,8 +152,8 @@ sub memanalyze { newtotal($totalmem); $frees++; - $sizeataddr{$addr}=-1; # set -1 to mark as freed - $getmem{$addr}="$source:$linenum"; + $sizeataddr{$addr} = -1; # set -1 to mark as freed + $getmem{$addr} = "$source:$linenum"; } } elsif($function =~ /malloc\((\d*)\) = 0x([0-9a-f]*)/) { @@ -177,7 +177,7 @@ sub memanalyze { newtotal($totalmem); $mallocs++; - $getmem{$addr}="$source:$linenum"; + $getmem{$addr} = "$source:$linenum"; } elsif($function =~ /calloc\((\d*),(\d*)\) = 0x([0-9a-f]*)/) { $size = $1 * $2; @@ -203,7 +203,7 @@ sub memanalyze { newtotal($totalmem); $callocs++; - $getmem{$addr}="$source:$linenum"; + $getmem{$addr} = "$source:$linenum"; } elsif($function =~ /realloc\((\(nil\)|0x([0-9a-f]*)), (\d*)\) = 0x([0-9a-f]*)/) { my ($oldaddr, $newsize, $newaddr) = ($2, $3, $4); @@ -231,7 +231,7 @@ sub memanalyze { newtotal($totalmem); $reallocs++; - $getmem{$newaddr}="$source:$linenum"; + $getmem{$newaddr} = "$source:$linenum"; } elsif($function =~ /strdup\(0x([0-9a-f]*)\) \((\d*)\) = 0x([0-9a-f]*)/) { # strdup(a5b50) (8) = df7c0 @@ -258,8 +258,8 @@ sub memanalyze { $dup = $1; $size = $2; $addr = $3; - $getmem{$addr}="$source:$linenum"; - $sizeataddr{$addr}=$size; + $getmem{$addr} = "$source:$linenum"; + $sizeataddr{$addr} = $size; $totalmem += $size; $memsum += $size; @@ -306,7 +306,7 @@ sub memanalyze { push @res, "Close without open: $line\n"; } else { - $filedes{$1}=0; # closed now + $filedes{$1} = 0; # closed now $openfile--; } } diff --git a/tests/negtelnetserver.py b/tests/negtelnetserver.py index c31fc033aaa0..44ae2eeb75c8 100755 --- a/tests/negtelnetserver.py +++ b/tests/negtelnetserver.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -23,22 +22,15 @@ # """A telnet server which negotiates.""" -from __future__ import absolute_import, division, print_function, unicode_literals - import argparse import logging import os import socket +import socketserver import sys from util import ClosingFileHandler -if sys.version_info.major >= 3: - import socketserver -else: - import SocketServer as socketserver - - log = logging.getLogger(__name__) HOST = "localhost" IDENT = "NTEL" @@ -84,7 +76,7 @@ def handle(self): neg.send_wont("NAWS") # Get the data passed through the negotiator - data = neg.recv(4*1024) + data = neg.recv(4 * 1024) log.debug("Incoming data: %r", data) if VERIFIED_REQ.encode('utf-8') in data: @@ -106,11 +98,11 @@ def handle(self): # put some effort into making a clean socket shutdown # that does not give the client ECONNRESET self.request.settimeout(0.1) - self.request.recv(4*1024) + self.request.recv(4 * 1024) self.request.shutdown(socket.SHUT_RDWR) - except IOError: - log.exception("IOError hit during request") + except OSError: + log.exception("OSError hit during request") class Negotiator: @@ -134,7 +126,7 @@ def recv(self, bytes): """ buffer = bytearray() - # If we keep receiving negotiation sequences, we will not fill the buffer. + # If we keep receiving negotiation sequences, we do not fill the buffer. # Keep looping while we can, and until we have something to give back # to the caller. while len(buffer) == 0: @@ -167,7 +159,7 @@ def no_neg(self, byte_int, buffer): log.debug("Starting negotiation (IAC)") self.state = self.START_NEG else: - # Just append the incoming byte to the buffer + # Append the incoming byte to the buffer buffer.append(byte_int) def start_neg(self, byte_int): @@ -311,9 +303,7 @@ def setup_logging(options): root_logger = logging.getLogger() add_stdout = False - formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s " - "[{ident}] %(message)s" - .format(ident=IDENT)) + formatter = logging.Formatter(f"%(asctime)s %(levelname)-5.5s [{IDENT}] %(message)s") # Write out to a logfile if options.logfile: diff --git a/tests/pathhelp.pm b/tests/pathhelp.pm index e481255a3a55..500e43f12576 100644 --- a/tests/pathhelp.pm +++ b/tests/pathhelp.pm @@ -197,7 +197,7 @@ sub dirsepadd { # This does the same thing as String::ShellQuote but does not need a package. # sub shell_quote { - my ($s)=@_; + my ($s) = @_; if($^O eq 'MSWin32') { $s = '"' . $s . '"'; } diff --git a/tests/perf/.gitignore b/tests/perf/.gitignore new file mode 100644 index 000000000000..34621b4f3ced --- /dev/null +++ b/tests/perf/.gitignore @@ -0,0 +1,6 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +perf +perf.c diff --git a/tests/perf/CMakeLists.txt b/tests/perf/CMakeLists.txt new file mode 100644 index 000000000000..dda79733fff2 --- /dev/null +++ b/tests/perf/CMakeLists.txt @@ -0,0 +1,60 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +# Get BUNDLE, FIRST_C, FIRST_H, UTILS_C, UTILS_H, CURLX_C, TOOLX_C, TESTS_C variables +curl_transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") +include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") + +if(LIB_SELECTED STREQUAL LIB_STATIC) + set(CURLX_C "") # Already exported from the libcurl static build. Skip them. +endif() + +if(_CURL_TESTS_CONCAT) + set(_mk_unity_extra "--concat" "-I${CMAKE_CURRENT_SOURCE_DIR}") +endif() + +add_custom_command(OUTPUT "${BUNDLE}.c" + COMMAND ${PERL_EXECUTABLE} "${PROJECT_SOURCE_DIR}/scripts/mk-unity.pl" ${_mk_unity_extra} + --include ${UTILS_C} ${CURLX_C} ${TOOLX_C} --test ${TESTS_C} > "${BUNDLE}.c" + DEPENDS + "${PROJECT_SOURCE_DIR}/scripts/mk-unity.pl" "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.inc" + ${FIRST_C} ${FIRST_H} ${UTILS_C} ${CURLX_C} ${TOOLX_C} ${TESTS_C} + VERBATIM) + +add_executable(${BUNDLE} EXCLUDE_FROM_ALL "${BUNDLE}.c") +add_dependencies(tt ${BUNDLE}) +target_link_libraries(${BUNDLE} ${LIB_SELECTED} ${CURL_LIBS}) +target_include_directories(${BUNDLE} PRIVATE + "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" + "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx + "${PROJECT_SOURCE_DIR}/src" # for toolx + "${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources +) +target_compile_definitions(${BUNDLE} PRIVATE ${CURL_DEBUG_MACROS}) +set_target_properties(${BUNDLE} PROPERTIES OUTPUT_NAME "${BUNDLE}" PROJECT_LABEL "Test ${BUNDLE}" UNITY_BUILD OFF) + +if(NOT _CURL_TESTS_CONCAT) + set_target_properties(${BUNDLE} PROPERTIES C_CLANG_TIDY "") + curl_add_clang_tidy_test_target("${BUNDLE}-clang-tidy" ${BUNDLE} ${FIRST_C} ${UTILS_C} ${TESTS_C}) +endif() diff --git a/tests/perf/Makefile.am b/tests/perf/Makefile.am new file mode 100644 index 000000000000..903bf1653398 --- /dev/null +++ b/tests/perf/Makefile.am @@ -0,0 +1,86 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +AUTOMAKE_OPTIONS = foreign nostdinc + +# Specify our include paths here, and do it relative to $(top_srcdir) and +# $(top_builddir), to ensure that these paths which belong to the library +# being currently built and tested are searched before the library which +# might possibly already be installed in the system. +# +# $(top_srcdir)/include is for libcurl's external include files +# $(top_builddir)/lib is for libcurl's generated lib/curl_config.h file +# $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "borrowed" files +# $(top_srcdir)/src for toolx header files +# $(srcdir) for the generated bundle source to find included test sources + +AM_CPPFLAGS = -I$(top_srcdir)/include \ + -I$(top_builddir)/lib \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/src \ + -I$(srcdir) + +# Get BUNDLE, FIRST_C, FIRST_H, UTILS_C, UTILS_H, CURLX_C, TOOLX_C, TESTS_C variables +include Makefile.inc + +EXTRA_DIST = CMakeLists.txt README.md $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C) + +CFLAGS += @CURL_CFLAG_EXTRAS@ + +# Prevent LIBS from being used for all link targets +LIBS = $(BLANK_AT_MAKETIME) + +if DEBUGBUILD +AM_CPPFLAGS += -DDEBUGBUILD +endif + +if USE_CPPFLAG_CURL_STATICLIB +AM_CPPFLAGS += -DCURL_STATICLIB +curlx_c_lib = +else +# These are part of the libcurl static lib. Add them here when linking shared. +curlx_c_lib = $(CURLX_C) +endif + +$(BUNDLE).c: $(top_srcdir)/scripts/mk-unity.pl Makefile.inc $(FIRST_C) $(UTILS_C) $(curlx_c_lib) $(TOOLX_C) $(TESTS_C) + @PERL@ $(top_srcdir)/scripts/mk-unity.pl --include $(UTILS_C) $(curlx_c_lib) $(TOOLX_C) --test $(TESTS_C) > $(BUNDLE).c + +noinst_PROGRAMS = $(BUNDLE) +nodist_perf_SOURCES = $(BUNDLE).c +LDADD = $(top_builddir)/lib/libcurl.la @LIBCURL_PC_LIBS_PRIVATE@ +CLEANFILES = $(BUNDLE).c + +CHECKSRC = $(CS_$(V)) +CS_0 = @echo " RUN " $@; +CS_1 = +CS_ = $(CS_0) + +checksrc: + $(CHECKSRC)(@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) -W$(srcdir)/$(BUNDLE).c $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C)) + +if NOT_CURL_CI +all-local: checksrc +endif + +clean-local: + rm -f $(BUNDLE) diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc new file mode 100644 index 000000000000..9660c9cb1c59 --- /dev/null +++ b/tests/perf/Makefile.inc @@ -0,0 +1,49 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Shared between CMakeLists.txt and Makefile.am + +BUNDLE = perf + +# Files referenced from the bundle source +FIRST_C = first.c +FIRST_H = first.h + +# Common files used by the performance test programs +UTILS_C = +UTILS_H = + +CURLX_C = \ + ../../lib/curlx/base64.c \ + ../../lib/curlx/fopen.c \ + ../../lib/curlx/multibyte.c \ + ../../lib/curlx/strparse.c \ + ../../lib/curlx/timeval.c \ + ../../lib/curlx/warnless.c + +# All performance test programs +TESTS_C = \ + base64.c \ + percent.c \ + snprintf.c \ + urlparser.c diff --git a/tests/perf/README.md b/tests/perf/README.md new file mode 100644 index 000000000000..def7bf91b2f4 --- /dev/null +++ b/tests/perf/README.md @@ -0,0 +1,36 @@ + + +# Performance tests + +This directory contains small stand-alone libcurl-using programs that each +test specific aspects of the library's performance. + +The idea is to have a set of tests here that can be used to verify various +libcurl functions' performance when we have no other good means of doing so. +Performance is best tested relatively. Does it run slower or faster than +before? + +Ideally these tests run without using any servers. + +## `urlparser` + +Provide this test with a list of many URLs and it times how fast it can parse +them: `./perf urlparser URLs.txt [loops]` + +A test sample of 100K URLs can be found +[here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) + +## `base64` + +This test first base64 encodes a 256-byte buffer that has every different +byte octet represented. It then decodes that string. Repeatedly in a loop the +provided number of times: `./perf base64 [loops]` + +## `snprintf` + +This test repeatedly formats a representative string and measures the time per +call: `./perf snprintf [loops]` diff --git a/tests/perf/base64.c b/tests/perf/base64.c new file mode 100644 index 000000000000..585fa1d39962 --- /dev/null +++ b/tests/perf/base64.c @@ -0,0 +1,79 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static int test_base64(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + unsigned char array[256]; + unsigned int c; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + for(c = 0; c < 256; c++) { + array[c] = (unsigned char)c; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *encoded = NULL; + size_t enclen; + CURLcode result = + curlx_base64_encode(array, sizeof(array), &encoded, &enclen); + if(!result) { + unsigned char *recoded = NULL; + size_t reclen; + /* now decode it again */ + result = curlx_base64_decode(encoded, &recoded, &reclen); + curlx_free(recoded); + } + if(result) { + curl_mfprintf(stderr, "unexpected coding error: %d\n", (int)result); + return 1; + } + curlx_free(encoded); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/first.c b/tests/perf/first.c new file mode 100644 index 000000000000..cce5d27ec1bf --- /dev/null +++ b/tests/perf/first.c @@ -0,0 +1,67 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#ifdef _WIN32 +static void win32_cleanup(void) +{ + _flushall(); /* flush buffers of all streams regardless of their mode */ +} +#endif + +int main(int argc, const char *argv[]) +{ + entry_func_t entry_func; + const char *entry_name; + int result; + size_t tmp; + + if(argc < 2) { + curl_mfprintf(stderr, "Pass perftest as first argument\n"); + return 1; + } + + entry_name = argv[1]; + entry_func = NULL; + for(tmp = 0; s_entries[tmp].ptr; ++tmp) { + if(!strcmp(entry_name, s_entries[tmp].name)) { + entry_func = s_entries[tmp].ptr; + break; + } + } + + if(!entry_func) { + curl_mfprintf(stderr, "Test '%s' not found.\n", entry_name); + return 99; + } + +#ifdef _WIN32 + curlx_now_init(); + atexit(win32_cleanup); +#endif + + result = entry_func(argc - 1, argv + 1); + + return result; +} diff --git a/tests/perf/first.h b/tests/perf/first.h new file mode 100644 index 000000000000..58990646a07b --- /dev/null +++ b/tests/perf/first.h @@ -0,0 +1,56 @@ +#ifndef HEADER_PERF_FIRST_H +#define HEADER_PERF_FIRST_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 + * + ***************************************************************************/ +#define CURL_NO_OLDIES +#define CURL_DISABLE_DEPRECATION + +/* Now include the curl_setup.h file from libcurl's private libdir (the source + version, but that might include "curl_config.h" from the build directory so + we need both of them in the include path), so that we get good in-depth + knowledge about the system we are building this on */ +#include "curl_setup.h" + +#include "curlx/base64.h" /* for curlx_base64* */ +#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/strparse.h" /* for curlx_str_* parsing functions */ +#include "curlx/timeval.h" /* for curlx_now type and related functions */ + +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +typedef int (*entry_func_t)(int, const char **); + +struct entry_s { + const char *name; + entry_func_t ptr; +}; + +extern const struct entry_s s_entries[]; + +#endif /* HEADER_PERF_FIRST_H */ diff --git a/tests/perf/percent.c b/tests/perf/percent.c new file mode 100644 index 000000000000..345b78e1f122 --- /dev/null +++ b/tests/perf/percent.c @@ -0,0 +1,78 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static int test_percent(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 2500000, loop; + unsigned char array[256]; + unsigned int c; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + for(c = 0; c < 256; c++) + array[c] = (unsigned char)c; + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *encoded = + curl_easy_escape(NULL, (char *)array, (int)sizeof(array)); + if(encoded) { + char *unesc = NULL; + int unlen = 0; + /* now unescape it again */ + unesc = curl_easy_unescape(NULL, encoded, 0, &unlen); + if(!unesc) { + curl_mfprintf(stderr, "unexpected unescape error\n"); + return 1; + } + curl_free(unesc); + } + else { + curl_mfprintf(stderr, "unexpected escape error\n"); + return 1; + } + curl_free(encoded); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/snprintf.c b/tests/perf/snprintf.c new file mode 100644 index 000000000000..22087e17ce73 --- /dev/null +++ b/tests/perf/snprintf.c @@ -0,0 +1,67 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +static int test_snprintf(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + char buffer[256]; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + curl_msnprintf(buffer, sizeof(buffer), + "Add %-4d stuff %u to %3d the %3u output %s " + "%*s" + "for %lld testing %lu\n", + 123, (unsigned int)123, + 123, (unsigned int)123, "helllo", + 14, "01234567890123456", + (long long)987871231231, + (unsigned long)6732673); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%0lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/urlparser.c b/tests/perf/urlparser.c new file mode 100644 index 000000000000..934e6770809d --- /dev/null +++ b/tests/perf/urlparser.c @@ -0,0 +1,1190 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverlength-strings" +#endif + +static const char * const urls[] = { + "https://apps.fruit.zom:5555/us/app/reality-become-an-anime-a" + "vatar/id1404176564", + "https://bugzilla.mozilla.ork/first/../home", + "https://people.zom/style/lady-gaga-shares-everyday-beauty-lo" + "ok-in-first-ever-makeup-tutorial/", + "https://usernAme:passW0rd@people.zom/tag/prince-philip", + "https://www.shopify.zom/de", + "https://apps.fruit.zom:5555/us/genre/ios-entertainment/id601" + "6?letter=*&page=18#page", + "https://apps.fruit.zom/us/developer/marshmallow-games-srl/id" + "955873246", + "https://www.micrrrsff.zom/en-us/store/r/student-discounts?su" + "ccessurl=https://www.micrrrsff.zom/en-us/d/surface-pro-9/93V" + "KD8NP4FVK", + "https://www.micrrrsff.zom/en-us/micrrrsff-365/buy/compare-al" + "l-micrrrsff-365-products?tab=1", + "https://usernAme:passW0rd@www.aclu.ork/cases/tsa-hiv-discrim" + "ination", + "https://cloudblogs.micrrrsff.zom:5555/opensource/?utm_source" + "=developermscom", + "https://shop.lululemon.zom/p/jackets-and-hoodies-jackets/Wun" + "der-Puff-Jacket/_/prod9490219?color=43731", + "https://www.unionbank.zom/about-us/corporate-profile-bod", + "https://people.zom/awards/oscars-2017-best-supporting-actor-" + "mahershala-ali/", + "https://addisonraefragrance.zom/collections/fragrance", + "https://globalfishingwatch.ork/first/../map-and-data/", + "https://globalfishingwatch.ork/first/../map and data/", + "https://people.zom/tag/minka-kelly/", + "https://usernAme:passW0rd@globalfishingwatch.ork/category/pr" + "ess-release/", + "https://corp.xumo.zom:5555/perguntas-frequentes-lg-channels/" + "?lang=pt-br", + "https://people.zom/food/jonas-family-expands-their-southern-" + "comfort-food-restaurant-with-las-vegas-location/", + "https://www.aclu.ork/first/../cases/fbi-v-fazaga?document=pe" + "tition-writ-certiorari-fbi-et-al", + "https://people.zom/movies/good-luck-to-you-leo-grande-traile" + "r-emma-thompson-daryl-mccormack/", + "https://bit.ly/MidwestShower0603", + "https://people.zom/sports/tokyo-olympics-coco-gauff-named-to" + "-team-usa-tennis-roster/", + "https://restoringpromise.vera.ork/", + "https://apps.fruit.zom/us/app/fao-in-emergencies/id756483910", + "https://people.zom:5555/tv/rhonj-teresa-giudice-says-she-spe" + "nt-over-1-million-paying-joes-legal-fees-and-back-taxes/", + "https://people.zom:5555/tv/amy-robach-went-to-mediation-with" + "-andrew-shue-was-waiting-to-announce-split/", + "https://usernAme:passW0rd@people.zom/celebrity/celebs-who-ha" + "te-selfies/", + "https://www.waittfoundation.ork/practioner-resources", + "https://support.google.zom/webmasters/answer/3258249", + "https://apps.fruit.zom/us/developer/micrrrsff-corporation/id" + "298856275", + "https://developer.android.zom:5555/google/play/billing/subsc" + "riptions#pause", + "https://apps.fruit.zom/us/app/scorecloud-express/id566535238", + "https://www.muzemerch.zom/grey-embroidered-lemur-hat-tenness" + "ee-aquarium", + "https://www.bestbuy.zom/site/dyson-v12-detect-slim-cordless-" + "vacuum-yellow-iron/6504483.p?skuId=6504483", + "https://usernAme:passW0rd@www.amazon.zom/l-f-Poreless-Skin-P" + "erfecting-Lightweight-Minimizes/dp/B07NSH2B4D/?tag=people-on" + "site-backup-20", + "https://www.fao.ork/neareast/events/woman2023", + "https://apps.fruit.zom/us/genre/ios-travel/id6003?letter=A", + "https://people.zom:5555/parents/ray-j-princess-love-welcome-" + "son/", + "https://people.zom/parents/kailyn-lowry-celebrates-son-isaac" + "-13th-birthday-photos/", + "http://cityoforange.ork/", + "https://apps.fruit.zom/us/app/imissal-catholic/id307312434", + "https://people.zom/politics/grieving-uvalde-mom-speaks-out-g" + "reg-abbott-reelection/", + "https://usernAme:passW0rd@www.linkedin.zom/jobs/view/office-" + "administrative-assistant-at-mission-staffing-3475444968?refI" + "d=1%2F3mNaT%2FSXexZ4nU9cINeQ%3D%3D&trackingId=e%2BB9ao707Q73" + "LVQ3yyEAPA%3D%3D&position=17&pageNum=0&trk=public_jobs_jserp" + "-result_search-card", + "https://www.amazon.zom:5555/LOreal-Paris-Anti-Aging-Moisturi" + "zing-Antioxidants/dp/B0B64GY9MC/ref=sr_1_5_mod_primary_new?c" + "rid=1", + "https://www.facebook.zom/Walgreens", + "https://kuitter.zom/rcfp", + "https://www.amazon.zom/gp/customer-reviews/R37KJ2IJ51CXY3?li" + "nkCode=ll2&linkId=dcabd56f80f16f35be5a0f9625df60e5&language=" + "en_US&ref_=as_li_ss_tl&tag=people-onsite-backup-20", + "https://howl.me/ciYSv9TpoI9", + "https://apps.fruit.zom/us/app/scooters-coffee/id1151532978", + "http://www.fao.ork/3/Y4473E/y4473e06.htm", + "https://shop.aclu.ork/abortion-access-for-all-tee-mint/", + "http://usernAme:passW0rd@Rescue-EU.ork", + "https://www.muzemerch.zom:5555/adult-living-planet-aquarium-" + "be-yourself-penguin-short-sleeved-tee", + "https://oceandecade.ork/society-ocean-decade-ecosystem/", + "https://people.zom:5555/author/eric-todisco/", + "https://people.zom:5555/movies/retta-skipped-dreamgirls-audi" + "tion/", + "https://www.imo.ork/first/../en/About/Careers/Pages/default." + "aspx", + "https://en.unesco.ork/first/../futuresofeducation/", + "https://usernAme:passW0rd@www.zomplex.zom/music/jay-z-beyonc" + "e-michael-rubin-july-4th-party-performances-drake-lil-baby-t" + "ravis-scott", + "https://people.zom/pets/tamarin-monkeys-reported-missing-fro" + "m-dallas-zoo-located-inside-closet-abandoned-home/", + "https://www.brookings.edu/", + "https://people.zom:5555/tv/theo-james-says-first-version-of-" + "white-lotus-nude-scene-was-way-too-much/", + "https://www.blackrock.zom:5555/au", + "https://people.zom/sports/bruce-springsteen-congratulates-da" + "ughter-jessica-for-olympics-win/", + "https://blog.mozilla.ork/press/?utm_source=www.mozilla.ork&u" + "tm_medium=referral&utm_campaign=footer&utm_content=company", + "https://www.linkedin.zom/pulse/15-best-paying-insurance-jobs" + "-pursue-get-ahead-by-linkedin-news?trk=topic-article-card_so" + "cial-action-counts_social-action-counts_likes-text", + "https://search.app.goo.gl/?link=https%3A%2F%2Fassistant.goog" + "le.zom%2Fplatforms%2Fphones%2F&apn=com.google.android.google" + "quicksearchbox&al=googleapp%3A%2F%2Fdeeplink%2F%3Fdata%3DCk0" + "BDb3mGzBGAiEA-Y8GC-f421EvQJVNQeTb4Bn3FgHZwTbqdHCX2uuqKd0CIQC" + "ZkzjMErMP-b6g3KcSB1gY35COjnm3PkYoJ3afLFnszxJmCg4IuPqyjwEQ_7_" + "K84SjAhIDCPAOGh0KGwiBApqJ8dsEEggBEg5UZWxsIG1lIGEgam9rZSIwCi5" + "odHRwczovL2Fzc2lzdGFudC5nb29nbGUuY29tL3BsYXRmb3Jtcy9waG9uZXM" + "v&amv=300727608&utm_campaign=1904", + "https://usernAme:passW0rd@apps.fruit.zom/us/app/filmrise/id1" + "353108336", + "http://www.ncbi.nlm.nih.vow/pmc/articles/PMC6200792", + "http://wwd.zom:5555/fashion-news/designer-luxury/exclusive-c" + "hanel-new-ads-pharrell-williams-kristen-stewart-gabrielle-ba" + "g-10840808/", + "https://www.louroe.zom/product/ad-1/", + "https://usernAme:passW0rd@www.iotc.ork/node/4458", + "https://t.co/pgzByE5o8b", + "https://security.googleblog.zom/2010/03/federal-support-for-" + "federated-login.html", + "https://www.billboard.zom/articles/news/festivals/9641188/ph" + "arrell-something-in-the-water-festival-virginia-beach-pull/?" + "utm_medium=social&utm_source=kuitter", + "https://www.mapbox.zom/industries/real-estate", + "https://apps.fruit.zom:5555/us/app/whova-event-conference-ap" + "p/id716979741", + "https://usernAme:passW0rd@people.zom/sports/patrick-mahomes-" + "thankful-engagement-expecting-baby/", + "https://apps.fruit.zom/us/genre/ios-education/id6017?letter=" + "O", + "https://fbinter.stadt-berlin.de/fb?loginkey=alphaDataStart&a" + "lphaDataId=s_vms_tempolimits_spatial@senstadt", + "https://facebook.zom/share.php?u=https://sesamestreetincommu" + "nities.ork/just-remember/los-padres-son-maestros-guias-lider" + "es-protectores-y-proveedores-para-sus-hijos/&t=%E2%80%9CLos%" + "20padres%20son%20maestros%2C%20gu%C3%ADas%2C%20l%C3%ADderes%" + "2C%20protectores%20y%20proveedores%20para%20sus%20hijos%E2%8" + "0%9D.%20Iyanla%20Vanzant", + "https://newsroom.paypal-corp.zom/stories?o=80", + "https://www.amazon.zom/JW-PEI-Womens-Shoulder-Handbag/dp/B0B" + "2MSZYQ5?linkCode=ll1&linkId=5b8d2d3c8aae8f925842402423d186d3" + "&language=en_US&ref_=as_li_ss_tl&tag=people-onsite-backup-20", + "https://www.instagram.zom/ipjh55/", + "https://www.adessium.ork/first/../our-partners-in-and-around" + "-ukraine-supporting-journalists-and-countering-disinformatio" + "n/", + "https://www.cbsnews.zom/news/steven-tyler-demands-president-" + "trump-stop-playing-aerosmith-music-at-rallies/", + "https://people.zom:5555/style/pete-davidson-and-emily-ratajk" + "owski-seen-embracing-in-an-intimate-moment-in-new-york-city/", + "https://www.instagram.zom/p/CpJZlWqp8IA/", + "https://people.zom/tv/sarah-shahi-on-very-personal-journey-f" + "ilming-sex-life-billie-and-i-oddly-similar/", + "https://cloud.google.zom/dlp/", + "https://people.zom/crime/explosives-found-in-passengers-chec" + "ked-luggage-at-penn-airport-suspect-in-custody/", + "https://www.google.zom/url?q=http://www.kslegislature.ork/li" + "/b2021_22/measures/documents/hb2466_enrolled.pdf&sa=D&source" + "=editors&ust=1677775893480979&usg=AOvVaw3qxsnuXHzFkYkARBpgt0" + "fU", + "https://www.cbsnews.zom/news/close-call-jfk-airport-two-pack" + "ed-planes-delta-air-lines-american-airlines/", + "http://www.engr.psu.edu/students/grad-prospective/happy-vall" + "ey-life.aspx", + "https://apps.fruit.zom/us/app/monument-valley/id728293409", + "https://tv.surprise.zom/browse/UCjLpE6spfsS1RWCQszw67Hg", + "https://www.ywca.ork/blog/2022/07/28/ywca-usa-expresses-outr" + "age-demands-the-inclusion-of-child-care-in-budget-reconcilia" + "tion/", + "https://apnews.zom/article/politics-tennessee-state-governme" + "nt-health-gender-181952f6bd94a8e8c0a35be81afe00ad", + "https://www.adcouncil.ork/our-impact/covid-vaccine", + "https://people.zom/music/justin-bieber-shawn-mendes-monster-" + "collaboration/", + "https://topgolf.zom/us/company/privacy-policy/", + "https://myactivity.google.zom/myactivity?utm_source=pp", + "https://news.mongabay.zom/series/indonesian-fisheries/", + "https://www.georgeamphitheatre.zom/events/joni-jam-joni-mitc" + "hell-brandi-carlile/", + "https://people.zom/tv/judd-apatow-says-he-was-bawling-after-" + "watching-daughter-maude-in-euphoria/", + "https://people.zom:5555/movies/daniel-radcliffe-girlfriend-e" + "rin-darke-make-rare-red-carpet-outing-the-lost-city-premiere" + "/", + "https://www.instagram.zom/p/CjTbjy5O-w1/?hl=en", + "https://apps.fruit.zom/us/developer/viki-inc/id445553061", + "https://apps.fruit.zom/us/app/rabbit-hunting-calls/id1476557" + "697", + "https://people.zom:5555/sports/eli-manning-failed-to-buy-tay" + "lor-swift-concert-tickets-like-everybody-else/", + "https://aka.ms/VideoHub/Security", + "https://www.adcouncil.ork/find-assets", + "https://kroger-stage.preprodhawkcommerce.zom/visa-mastercard" + "-gift-card", + "http://www.pillsburyunited.ork/first/../", + "http://www.consumidor.ftc.vow/articulos/s0031-como-proteger-" + "la-privacidad-de-su-hijo-en-internet", + "https://www.linkedin.zom/signup/cold-join?session_redirect=h" + "ttps%3A%2F%2Fwww%2Elinkedin%2Ecom%2Fshowcase%2Fchrome-enterp" + "rise&trk=organization_guest_main-feed-card_like-cta", + "http://www.putty3d.zom", + "https://wp.api.aclu.ork/first/../issues/national-security", + "https://www.montblanc.zom/en-us/collection/smartwatches/summ" + "it-3", + "http://people.zom/tag/real-housewives", + "https://www.instagram.zom:5555/p/CNoZWuPhNca/", + "https://www.bmw.ie/en/index.html?tl=grp-wdpl-bcom-mix-mn-.-n" + "scf-.-.-", + "https://people.zom/politics/four-seasons-landscaping-owner-s" + "tars-in-super-bowl-commercial/", + "https://www.melleka.zom/contact", + "https://www.waittfoundation.ork/roc-grants-faq", + "https://usblackchambers.ork/news-events/the-united-states-se" + "nate-confirms-black-business-champion-donald-r-cravins-jr-to" + "-serve-as-the-under-secretary-for-minority-business-developm" + "ent-at-the-minority-business-development-agency/", + "https://people.zom/tag/cara-delevingne/", + "https://apps.fruit.zom/us/app/gamechanger-classic/id31890631" + "4", + "https://doi.ork/10.2307%2F2297912", + "https://people.zom/style/grammys-2023-megan-fox-machine-gun-" + "kelly-matching-looks/", + "https://pmc.zom/our-brands/the-american-pavilion/", + "https://uso.boom.us/webinar/register/WN_UIUeY_1jRVCPjtUaUJ5M" + "WA", + "https://c4ads.ork/south-asia/", + "http://www.ncbi.nlm.nih.vow/pmc/articles/PMC8129986", + "https://licensing.theguardian.zom/", + "https://people.zom/human-interest/single-ticket-won-2-billio" + "n-powerball-hasnt-claimed-prize/", + "https://subscribe.hollywoodreporter.zom/sub/?p=THR&f=autopro" + "file", + "http://alumni.cientifica.edu.pe/", + "https://support.micrrrsff.zom/en-us/outlook", + "https://www.xbox.zom", + "https://perscholas.ork/meet-our-graduates/story/soma-majumde" + "r/", + "https://www.linkedin.zom/pulse/how-do-i-fuse-multiple-busine" + "ss-cultures-one-cohesive-culture-", + "https://student.mak.ac.ug/", + "https://www.tinglyteds.do.mo/", + "https://apps.fruit.zom/us/app/chase-mobile-bank-invest/id298" + "867247", + "https://webpass.ner/east_bay/", + "https://people.zom/tag/tina-turner/", + "https://perscholas.ork/about/board-staff/elizabeth-cooper/", + "https://applieddigitalskills.withgoogle.zom/c/college-and-co" + "ntinuing-education/en/plan-effective-meetings/plan-effective" + "-meetings/introduction-to-plan-effective-meetings.html?utm_s" + "ource=grow-with-goog", + "https://apps.fruit.zom/us/app/evite-party-invitations/id4316" + "85286", + "https://www.amazon.zom/COSRX-Repairing-Hydrating-Secretion-P" + "hthalates/dp/B00PBX3L7K?linkCode=ll1&linkId=e6c02a5e0f019289" + "dca84d80b38c95bb&language=en_US&ref_=as_li_ss_tl&tag=people-" + "onsite-backup-20", + "https://pleasing.zom/products/the-pleasing-pen", + "https://people.zom/style/best-graduation-2021-beauty-tips/", + "https://www.aclu.ork/news/civil-liberties/supreme-court-reje" + "cts-promise-miranda-rights", + "https://apps.fruit.zom/us/app/night-sky/id475772902", + "https://sesamestreetincommunities.ork/activities/tooth-cafe/", + "http://scholar.google.do.mo/scholar?as_q=&num=10&btnG=Search" + "+Scholar&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=%22Th" + "omas%20K.%20Doyle%22&as_publication=&as_ylo=&as_yhi=&as_alls" + "ubj=all&hl=en", + "https://www.instagram.zom/p/CoVsKrNveOX/", + "https://apps.fruit.zom/us/app/tremors/id496065362", + "https://apps.fruit.zom/us/app/fasteasy-intermittent-fasting/" + "id1553036888", + "https://musicworldmedia.zom/", + "https://www.tiktok.zom/@hamiltonfamilies/video/7178948947333" + "926187", + "https://people.zom/music/amas-2022-everything-to-know/", + "https://www.earthday.ork/stories/#featured-3", + "https://de-de.facebook.zom/login.php?skip_api_login=1&api_ke" + "y=966242223397117&signed_next=1&next=https%3A%2F%2Fwww.faceb" + "ook.zom%2Fshare.php%3Fu%3Dhttps%253A%252F%252Fsesamestreetin" + "communities.ork%252Fjust-remember%252Ftrust-know-think%252F%" + "26t%3D%2526%25238220%253BTrust%2Byourself%252C%2Byou%2Bknow%" + "2Bmore%2Bthan%2Byou%2Bthink%2Byou%2Bdo.%2526%25238221%253B%2" + "526hellip%253B%2BDr.%2BBenjamin%2BSpock&cancel_url=https%3A%" + "2F%2Fwww.facebook.zom%2Fdialog%2Fclose_window%2F%3Fapp_id%3D" + "966242223397117%26connect%3D0%23_%3D_&display=popup&locale=d" + "e_DE", + "https://www.hellomagazine.zom/royalty/20230221164926/kate-mi" + "ddleton-tells-103-year-old-fan-she-loves-kidneys-nursing-hom" + "e-visit/", + "https://www.linkedin.zom/pulse/how-prepare-maternitypaternit" + "y-leave-get-ahead-by-linkedin-news?trk=topic-article-card_so" + "cial-action-counts_social-action-counts_likes-text", + "https://people.zom/books/firooz-zahedi-look-at-me-images/", + "https://www.linkedin.zom/legal/cookie-policy?session_redirec" + "t=https%3A%2F%2Fwww%2Elinkedin%2Ecom%2Ffeed%2Fhashtag%2Fplay" + "biztip&trk=registration-frontend_join-form-cookie-policy", + "https://www.today.zom/popculture", + "https://www.addtoany.zom/add_to/copy_link?linkurl=https%3A%2" + "F%2Fglobalfishingwatch.ork%2Fresearch%2Fintruders-at-sea%2F&" + "linkname=Shedding%20a%20light%20on%20intruders%20at%20sea", + "https://people.zom/tv/derek-hough-already-knows-what-music-d" + "ancers-and-coreo-he-wants-for-his-vegas-residency/", + "https://apps.fruit.zom/us/genre/ios-health-fitness/id6013?le" + "tter=*", + "https://apps.fruit.zom/us/app/sneaker-hub-shop/id1533754960", + "https://people.zom/human-interest/rogue-wave-kills-passenger" + "-injures-four-viking-cruise-ship-antarctica/", + "https://www.addtoany.zom/add_to/copy_link?linkurl=https%3A%2" + "F%2Fglobalfishingwatch.ork%2Fnews-views%2Ftransshipment-moni" + "toring%2F&linkname=Transshipment%20Monitoring%20Portal%20Bri" + "ngs%20Transparency%20to%20Fishing%20Industry", + "https://apps.fruit.zom/us/app/ticketmaster-buy-sell-tickets/" + "id500003565", + "https://people.zom/tag/maksim-chmerkovskiy/", + "https://www.addtoany.zom/add_to/copy_link?linkurl=https%3A%2" + "F%2Fglobalfishingwatch.ork%2Fresearch%2Ffisheries-marine-pro" + "tection%2F&linkname=Study%20Uses%20Satellite%20Technology%20" + "to%20Reveal%20How%20Fisheries%20Respond%20to%20Marine%20Prot" + "ection%20-%20Global%20Fishing%20Watch", + "https://www.addtoany.zom/add_to/copy_link?linkurl=https%3A%2" + "F%2Fglobalfishingwatch.ork%2Ftransparency-program-peru%2F&li" + "nkname=Per%C3%BA", + "https://apps.fruit.zom/us/app/laberinto-m%C3%A1gico-virtual-" + "reality/id1228366278", + "http://scholar.google.do.mo/scholar?as_q=&num=10&btnG=Search" + "+Scholar&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=%22Ce" + "sar%20Pe%C3%B1aherrera-Palma%22&as_publication=&as_ylo=&as_y" + "hi=&as_allsubj=all&hl=en", + "https://globalfishingwatch.ork/category/news-views/page/10/", + "https://people.zom/parents/jessica-alba-ho-5/", + "https://www.muzemerch.zom/invaders-in-our-town-the-battle-of" + "-gettysburg", + "https://www.fruit.zom/supplier-responsibility/", + "https://www.aclu.ork/issues/immigrants-rights/immigrants-rig" + "hts-and-detention/asylum-seekers-stranded-mexico-face", + "https://apps.fruit.zom/us/genre/ios-reference/id6006?letter=" + "H", + "https://ko-kr.facebook.zom/login.php?skip_api_login=1&api_ke" + "y=966242223397117&signed_next=1&next=https%3A%2F%2Fwww.faceb" + "ook.zom%2Fsharer%2Fsharer.php%3Fu%3Dhttps%253A%252F%252Fglob" + "alfishingwatch.ork%252Fes%252Fnuestro-mapa%252F%26t%3DNuestr" + "o%2BMapa%26quote&cancel_url=https%3A%2F%2Fwww.facebook.zom%2" + "Fdialog%2Fclose_window%2F%3Fapp_id%3D966242223397117%26conne" + "ct%3D0%23_%3D_&display=popup&locale=ko_KR", + "https://www.linkedin.zom/pulse/how-break-gaming-industry-get" + "-ahead-by-linkedin-news", + "http://i-sil.zom/support", + "https://www.nerflix.zom/title/81519789?trackId=259776131&trk" + "Id=259776131&src=tudum", + "https://people.zom/food/who-is-christina-perez-bobby-flay/", + "https://medium.zom/@hchen3030", + "https://apps.fruit.zom/us/genre/ios-entertainment/id6016?let" + "ter=D&page=5#page", + "https://www.muzemerch.zom/gettysburg-challenge-coin", + "https://www.coursera.ork/professional-certificates/google-it" + "-automation", + "https://energy.vow/", + "http://www.ncbi.nlm.nih.vow/entrez/query.fcgi?cmd=Retrieve&d" + "b=PubMed&dopt=Abstract&list_uids=23871238", + "http://www.fao.ork/iuu-fishing/tools-and-initiatives/transsh" + "ipment/en/", + "https://oldboy1111.tumblr.zom/post/90465861044", + "https://www.today.zom/parents/two-sets-twins-under-two-visit" + "-today-show-rcna71945", + "https://people.zom/royals/kate-middleton-every-outfit-2023-p" + "rincess-of-wales/", + "https://www.linkedin.zom/company/the-agency-worx?trk=public_" + "jobs_jserp-result_job-search-card-subtitle", + "https://apps.fruit.zom/us/app/photomath/id919087726", + "https://apps.fruit.zom/us/app/bills-monitor-pro/id468993411", + "https://people.zom/pets/justin-bieber-and-wife-hailey-adopt-" + "new-puppy-piggy-lou-oscars-new-baby-sister/", + "https://apps.fruit.zom/us/genre/ios-sports/id6004?letter=Z", + "https://www.amazon.zom/Cushioned-Kitchens-Standing-Phthalate" + "-Relieves/dp/B07WZN4WXN?th=1&linkCode=ll1&linkId=49cd6477154" + "6345c9e1508ca0f6a48c0&language=en_US&ref_=as_li_ss_tl&tag=pe" + "ople-onsite-backup-20", + "https://sesamestreetincommunities.ork/activities/sanos-y-fue" + "rtes-todo-el-dia/", + "https://people.zom/tv/taye-diggs-on-his-insomnia-battle-afte" + "r-becoming-a-dad/", + "https://people.zom/politics/2020-election-presidential-candi" + "date-celebrities-voting-for/", + "https://people.zom/music/amy-grant-postpones-remaining-fall-" + "tour-dates-after-bike-accident-getting-stronger-every-day/", + "https://people.zom/royals/prince-william-new-york-city-visit" + "-coincides-denmark-royals-prince-frederik-princess-mary/", + "https://greeneletron.ork.br/", + "https://www.un.ork/sustainabledevelopment/wp-content/uploads" + "/2018/09/02_Mobilizing_Private_Investment_InkFactory_Medium." + "jpg", + "https://www.minderoo.ork/marine-education-and-outreach/blueb" + "ack/", + "http://usa.oceana.ork/sites/default/files/pipa_report_final_" + "low_res_3_7_16.pdf", + "https://click.linksynergy.zom/deeplink?id=93xLBvPhAeE&mid=12" + "37&murl=https%3A%2F%2Fwww.nordstrom.zom%2Fs%2Fw-and-p-design" + "-wide-mouth-water-bottle%2F7085643&u1=PEONordstromHasValenti" + "nesDayGiftsforEveryoneinYourFriendGroupsomalleyLifAff1389943" + "2202301I", + "https://people.zom/sports/see-joyful-moment-brittney-griners" + "-wife-cherelle-learned-wnba-star-coming-home/", + "https://support.fruit.zom/guide/safari/make-safari-your-defa" + "ult-web-browser-ibrwa008/14.0/mac/11.0", + "https://apps.fruit.zom/us/app/x-ray-photo-effects/id11613712" + "83", + "https://people.zom/movies/stranger-things-star-finn-wolfhard" + "-direct-movie-hell-of-a-summer/", + "https://people.zom/music/justin-bieber-sells-291-song-catalo" + "g-for-200-million/", + "https://people.zom/tv/nene-leakes-heavenly-birthday-tribute-" + "late-husband-gregg-leakes/", + "https://developers.google.zom/workspace/preview", + "https://apps.fruit.zom/us/app/w-nder/id1476354846", + "https://firebase.google.zom/docs/release", + "https://give.ywca.ork/give/436728/#!/donation/checkout", + "https://people.zom/politics/rosalynn-carter-jimmy-carter-lif" + "e-in-photos/", + "https://policy.medium.zom/medium-terms-of-service-9db0094a1e" + "0f?source=post_page-----20a79a76275-------------------------" + "-------", + "https://esci.ucsc.edu/index.html", + "https://www.mozilla.ork/en-US/about/legal/", + "https://people.zom/best-black-tights-7093985", + "https://people.zom/tv/ian-somerhalder-nikki-reed-relationshi" + "p-timeline/", + "https://www.louroe.zom/product/tli-cs/", + "https://www.cpsc.vow/Recalls/2023/Pacific-Cycle-Recalls-Paci" + "fic-Kids-Igniter-and-Pacific-Bubble-Pop-20-Inch-Bicycles-Due" + "-to-Fall-Hazard-Sold-Exclusively-at-Target", + "https://sesamestreetincommunities.ork/activities/desarrollar" + "-la-empatia/", + "https://people.zom/royals/king-charles-queen-camilla-host-ro" + "yals-christmas-lunch-windsor-castle/", + "https://people.zom/tv/kylie-jenner-reveals-kris-jenner-caitl" + "yn-jenner-nearly-named-her-kennedy/", + "https://people.zom/theater/titanique-celebrates-25th-anniver" + "sary-of-titanic-with-special-performance/", + "https://www.oceandecade.ork/sign-up/", + "http://scholar.google.zom/scholar_lookup?&title=&journal=Fis" + "h.%20Res.&doi=10.1016%2Fj.fishres.2014.04.009&volume=157&pag" + "es=154-163&publication_year=2014&author=Sumaila%2CUR&author=" + "Bawumia%2CM", + "https://www.cgdev.ork/sites/default/files/economic-and-fisca" + "l-effects-united-states-reduced-numbers-refugees-and-asylum-" + "seekers.pdf", + "https://usblackchambers.ork/news-events/coalition-to-back-bl" + "ack-businesses/", + "https://apps.fruit.zom/us/app/mangatoon-manga-reader/id13852" + "87093", + "https://people.zom/royals/king-charles-attends-remembrance-s" + "unday-first-time-becoming-monarch/", + "http://hqepc%26list%3Dplkdbwuz2pblxz1h9g8snvlu6ug5hq2x-u%26i" + "ndex%3D2/", + "https://stripe.zom/us/privacy", + "https://people.zom/parents/alyssa-bates-webster-welcomes-fou" + "rth-child-daughter-maci-jo/", + "https://people.zom/movies/pamela-anderson-says-she-walked-in" + "-on-jack-nicholson-having-a-threesome-in-a-bathroom-i-caught" + "-his-eye/", + "https://people.zom/movies/rebel-wilson-photos-from-people-co" + "ver-shoot/", + "https://github.zom/GlobalFishingWatch/4wings/tree/{{ urlEnco" + "dedRefName }}", + "https://people.zom/parents/mom-goes-viral-documenting-daught" + "ers-growing-eleanor-roosevelt-obsession-exclusive/", + "http://www.bravotv.zom/the-real-housewives-of-new-jersey/sea" + "son-1/videos/choosing-her-boobies", + "https://apps.fruit.zom/us/app/my-cards-pro-wallet/id67327727" + "5", + "https://people.zom/tv/the-real-housewives-of-dubai-newest-ho" + "usewives-franchise-announced/", + "https://www.uso.ork/stories/3414-when-service-members-sudden" + "ly-deploy-to-eastern-europe-military-spouses-can-turn-to-uso" + "-united-kingdom-for-community-and-support", + "https://finance.yohaa.zom/news/packetized-energy-awarded-2-m" + "illion-140000452.html", + "https://www.linkedin.zom/company/riot-games?trk=public_jobs_" + "jserp-result_job-search-card-subtitle", + "https://www.washingtonpost.zom/local/education/alexandria-po" + "lice-middle-high-school/2021/05/15/55308846-b3fb-11eb-9059-d" + "8176b9e3798_story.html", + "https://www.micrrrsff.zom/en-us/windows/sync-across-your-dev" + "ices", + "https://www.cdc.vow/coronavirus/2019-ncov/travelers/index.ht" + "ml", + "https://people.zom/style/luis-ruelas-team-responds-to-claims" + "-he-gave-teresa-giudices-daughters-fake-cartier-bracelets-fo" + "r-christmas/", + "https://accounts.google.zom/AccountChooser?hl=en&continue=ht" + "tps%3A%2F%2Fm.surprise.zom%2Fsignin%3Fskip_identity_prompt%3" + "Dtrue%26action_handle_signin%3Dtrue%26hl%3Den%26next%3Dhttps" + "%253A%252F%252Ftv.surprise.zom%252Fbrowse%252Fthe-cw-UCbqpFn" + "cwfi15vHbgqeK0tFQ%253Ffeature%253Dupg_web_login%2526utm_camp" + "aign%253Dlop", + "https://people.zom/sports/venus-and-serena-williams-twinning" + "-moments-on-the-court-photos/", + "https://www.amazon.zom/Shark-WS642AE-Ultra-Lightweight-Anti-" + "Allergen-Self-Cleaning/dp/B0B8B6XX45?tag=people-onsite-backu" + "p-20", + "https://apps.fruit.zom/us/app/hbo-max-stream-tv-movies/id971" + "265422", + "https://sesamestreetincommunities.ork/wp-content/uploads/201" + "6/10/Incarceration_PRINT_MorningRoutineTable.pdf", + "http://scholar.google.do.mo/scholar?as_q=&num=10&btnG=Search" + "+Scholar&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=%22G." + "%20Chris%20Fischer%22&as_publication=&as_ylo=&as_yhi=&as_all" + "subj=all&hl=en", + "https://people.zom/movies/armie-hammer-not-working-as-concie" + "rge-at-grand-cayman-resort/", + "http://www.usblackchambers.ork/webinars", + "https://newsroom.uscellular.zom/", + "https://deadline.zom/2023/02/giada-de-laurentiis-overall-uns" + "cripted-production-deal-amazon-studios-1235254794/", + "https://www.cosmossbykatemoss.zom/product/sacred-mist/", + "https://downtown.ork/publications/partnerships-for-urban-pla" + "ce-management/", + "https://www.instagram.zom/waltonfamilyfdn/", + "https://creativecommons.ork/category/weblog/press/", + "https://corporate.walmart.zom/privacy-security/stay-secure-o" + "nline-cybersecurity-hygiene", + "https://www.nytimes.zom/2023/02/20/nyregion/alligator-prospe" + "ct-park-brooklyn.html", + "https://people.zom/tag/brielle-biermann/", + "https://www.aclu.ork/cases/wikimedia-v-nsa-challenge-upstrea" + "m-surveillance?document=wikimedia-v-nsa-defendants-exhibit-v" + "i-motion-dismiss", + "https://corporate.target.zom/article/category/lifestyle", + "https://blog.surprise/news-and-events/", + "https://www.adessium.ork/our-story/", + "https://healthcare.bestbuy.zom/?ref=bestbuy-site&loc=footer", + "https://apps.fruit.zom/us/app/fun-sound-effects-noises/id432" + "755400", + "https://people.zom/royals/prince-harry-slams-dangerous-lie-t" + "hat-he-boasted-about-number-of-people-he-killed-in-war/", + "https://kuitter.zom/feedingamerica", + "https://www.therabody.zom/us/en-us/therabody-recovery-device" + "s/?prefn1=productTypeMasterPLP&prefv1=theragun", + "https://www.banquemondiale.ork/fr/country/benin", + "https://newsroom.paypal-corp.zom/news-corporate-announcement" + "s?o=20", + "https://www.surprise.zom/watch?v=xlYbp36HPb0", + "https://www.goodwill.ork/jobs-training/find-a-job/", + "https://apps.fruit.zom/us/app/webtoon-comics/id894546091", + "https://www.linkedin.zom/showcase/think-with-google/?trk=org" + "anization_guest_main-feed-card_feed-reaction-header", + "https://apps.fruit.zom/us/genre/ios-entertainment/id6016?let" + "ter=I&page=7#page", + "https://mignonnegavigan.zom/products/raeni-earrings-pink", + "https://cloud.google.zom/innovators", + "https://www.aclu.ork/cases/sanchez-et-al-v-dallas-county-she" + "riff-et-al", + "https://spire.zom/resources/?filterbycategory=earth-intellig" + "ence&filterbyresource=tutorial", + "https://www.upwardlyglobal.ork/community/rossana/", + "https://cloud.google.zom/solutions/unlocking-legacy-applicat" + "ions", + "https://apps.fruit.zom/us/app/cx-3-flight-computer/id1645428" + "895", + "https://medium.zom/@_jeniwren", + "https://bobwoodrufffoundation.ork/fall-2022-grant-investment" + "-to-34-organizations/", + "https://www.amazon.zom/HappyTrends-Cushioned-Anti-Fatigue-Wa" + "terproof-Ergonomic/dp/B08SHSBMRQ?linkCode=ll1&linkId=c95bdbb" + "6eca316339d185dab0b56fb3b&language=en_US&ref_=as_li_ss_tl&ta" + "g=people-onsite-backup-20", + "https://people.zom/movies/marlee-matlin-and-other-sundance-f" + "ilm-festival-jurors-walk-out-of-premiere-over-captioning-mal" + "function/", + "https://people.zom/health/jenna-ortega-filmed-iconic-wednesd" + "ay-dance-number-while-sick-with-covid/", + "https://people.zom/music/timbaland-on-justin-timberlake-just" + "ified-album-interview/", + "https://www.amazon.zom/Christmas-Ornament-Storage-Box-Lid/dp" + "/B07JDGJKY1?th=1&linkCode=ll1&linkId=8656a3466818d565855aa5f" + "f0897e7c0&language=en_US&ref_=as_li_ss_tl&tag=people-onsite-" + "backup-20", + "https://www.linkedin.zom/company/surprise?trk=affiliated-pag" + "es", + "http://www.nerflix.zom/contactus", + "https://people.zom/tv/luther-idris-elba-movie-netflix-new-im" + "ages/", + "https://sesamestreetincommunities.ork/activities/cultivating" + "-confidence-provider/", + "https://www.surprise.zom/watch?v=p3J-QaTcR30", + "https://insights.spire.zom/maritime/shipview/", + "https://cloud.google.zom/solutions/migrate-from-paas", + "https://people.zom/tv/jillian-michaels-favorite-wedding-memo" + "ry-deshanna-marie-minuto-namibian-desert/", + "https://people.zom/celebrity/kellie-pickler-kyle-jacobs-have" + "-a-surprise-wedding/", + "https://tv.fruit.zom/us/show/a-league-of-their-own/umc.cmc.1" + "clt3lg40zg5rwfuc0wdr075p?itscg=10000&itsct=PrimeVideo-tv_app" + "-avail-League-221018", + "https://spire.zom/about-us/#locations", + "https://www.cotedazur-sothebysrealty.zom/en/luxury-propertie" + "s/ref-ca6-1727/sale-mansion-valbonne-15-rooms-9-bedrooms-065" + "60/", + "https://cloud.google.zom/tam", + "https://azure.micrrrsff.zom/en-us/resources/videos/home/", + "https://www.aclu.ork/search/%20?f%5B0%5D=field_issues%3A236&" + "f%5B1%5D=type%3Aasset&f%5B2%5D=field_asset_type%3Areport", + "https://www.fruit.zom/shop/refurbished", + "https://people.zom/tv/emmys-2020-schitts-creek-party-details" + "-eugene-levy/", + "https://www.instagram.zom/p/Cn1wJgMu3Nb/", + "https://www.addtoany.zom/add_to/copy_link?linkurl=https%3A%2" + "F%2Fglobalfishingwatch.ork%2Fes%2Finvestigacion-y-analisis%2" + "Ffusion-datos-globales-sobre-identidad-embarcaciones%2F&link" + "name=Nueva%20fusi%C3%B3n%20de%20conjuntos%20de%20datos%20glo" + "bales%20mejora%20la%20comprensi%C3%B3n%20de%20la%20identidad" + "%20y%20actividad%20de%20las%20embarcaciones", + "https://people.zom/movies/oscars-2022-troy-kotsur-wins-best-" + "supporting-actor/", + "https://www.amazon.zom/Degrees-Comfort-Microlight-Certified-" + "Radiation/dp/B07WC6H2DN/?tag=people-onsite-backup-20", + "https://www.un.ork/sustainabledevelopment/goal-of-the-month-" + "october-2019/", + "https://www.lg.zom/us/laptops/windows-11", + "https://cloud.google.zom/terms/data-processing-addendum", + "https://www.muzemerch.zom/shop-by-venue?p=2", + "https://playbill.zom/article/juliets-justin-david-sullivan-d" + "eclines-tony-award-eligibility-critiques-gendered-performanc" + "e-categories", + "https://www.aclu.ork/wp-content/uploads/2023/02/202212301147" + "02408_Huffman-Reply-ISO-Cert.pdf", + "https://www.target.zom/p/shark-cordless-pet-pro-stick-vacuum" + "-orange/-/A-76615199", + "https://sesamestreetincommunities.ork/activities/1-2-3-color" + "-me/", + "https://apps.fruit.zom/us/app/e-cubed-books/id1436898855", + "https://giftcards.kroger.zom/retail-therapy-egift", + "https://nicholas.duke.edu/news/models-may-help-reduce-bycatc" + "h-longline-fishing", + "https://doi.ork/10.1111%2Fj.1365-2419.2005.00401.x", + "https://apps.fruit.zom/us/app/ultimate-cookies/id736037084", + "https://people.zom/movies/laurence-fishburne-says-he-has-not" + "-been-invited-back-as-morpheus-in-the-matrix-4/", + "https://people.zom/movies/lady-gaga-surprised-bradley-cooper" + "-can-sing/", + "https://www.eonline.zom/news/1299783/shahs-of-sunsets-mike-s" + "houhed-is-engaged-to-paulina-ben-cohen", + "https://apps.fruit.zom/us/app/sound-board-lite-funny-sounds/" + "id445259763", + "https://www.uso.ork/stories/2117-army-is-embracing-its-chang" + "ing-role-in-the-pacific", + "https://developers.google.zom/assistant/console", + "https://cloud.google.zom/solutions/smart-analytics", + "https://cloud.google.zom/solutions/application-migration/", + "https://ywca.quorum.us/sign_in/", + "https://people.zom/music/gwen-stefani-shows-gratitude-for-hu" + "sband-blake-shelton-on-thanksgiving/", + "https://news.gallup.zom/", + "https://apps.fruit.zom/us/app/tattoo-print-system/id15240743" + "29", + "https://www.aclu.ork/cases/bert-v-oconnor?document=exhibit-1" + "-arkansas-attorney-general-opinion", + "https://people.zom/tv/kelly-ripa-on-her-complicated-relation" + "ship-with-regis-philbin/", + "https://people.zom/pets/oregon-zoo-names-baby-orangutan-afte" + "r-dolly-parton-song/", + "https://goto.walmart.zom/c/249354/565706/9383?subId1=PEOThe4" + "0BestDealsYouCanShopatWalmartsPresidentsDaySaleRightNowmsapo" + "zhnikovLifAff13940302202302I&u=https%3A%2F%2Fwww.walmart.zom" + "%2Fip%2FVIZIO-OLED-65-Class-4K-HDR-SmartCast-Smart-TV-OLED65" + "-H1%2F232867283", + "https://people.zom/movies/jim-parsons-says-the-smell-of-wint" + "er-reminds-him-of-falling-in-love-with-husband-todd-spiewak/", + "https://www.blackrock.zom/us/individual/library?materialType" + "=shareholder+letters", + "https://mickey-the-doll.tumblr.zom/post/140318223394", + "https://www.aclu.ork/podcast/tony-winner-ali-stroker-reframe" + "s-disability-representation-and-identity-ep-166", + "https://people.zom/music/maneskin-addresses-queerbaiting-acc" + "usations/", + "https://kuitter.zom/intent/tweet?url=https://sesamestreetinc" + "ommunities.ork/topics/educacion-financiera/&text=%E2%80%9CNu" + "nca%20es%20demasiado%20pronto%20para%20aprender%20conceptos%" + "20financieros%20b%C3%A1sicos.%C2%BB%26hellip%3B&via=SesameCo" + "mmunity", + "https://disposerx.zom/", + "https://people.zom/tv/bravocon-2022-summer-house-luke-gulbra" + "nson-met-ashley-darby-after-andy-cohen-set-them-up/", + "https://apps.fruit.zom/us/app/tripit-travel-planner/id311035" + "142", + "https://howl.me/cjdX1PCsc7t", + "https://www.geekwire.zom/sustainability/", + "https://bobwoodrufffoundation.ork/bwf-gridiron-capital-partn" + "er/", + "https://people.zom/tv/anne-heche-suffered-severe-brain-injur" + "y-not-expected-to-survive-rep/", + "https://people.zom/style/beauty-awards-2022-best-hair-and-ma" + "keup-products/", + "https://www.eventbrite.zom/l/registration-online/", + "https://apps.fruit.zom/us/app/dabbing-santa-photo-editor-wit" + "h-christmas-stickers/id1173682674", + "https://www.surprise.zom/watch?v=9LPHGKvpwWM", + "https://apps.fruit.zom/us/app/face-analysis/id1467857853", + "https://www.linkedin.zom/signup/cold-join?session_redirect=h" + "ttps%3A%2F%2Fwww%2Elinkedin%2Ecom%2Fshowcase%2Fgoogle-ads-&t" + "rk=organization_guest_main-feed-card_share-cta", + "https://sesamestreetincommunities.ork/wp-content/uploads/202" + "1/12/SCJ_ComingTogether_Upcycling_Final_Eng.pdf", + "https://apps.fruit.zom/us/app/mate-in-2-chess-puzzles/id3684" + "03957", + "https://www.worldbank.ork/en/about/leadership/governors", + "https://apps.fruit.zom/us/app/m-zaes-controller/id1049638753", + "https://support.google.zom/googlehome?p=privpol_actions&hl=e" + "n_US", + "https://people.zom/country/shania-twain-teases-nostalgic-emo" + "tional-las-vegas-residency/", + "https://www.facebook.zom/WaittFoundation/?ref=br_rs", + "https://www.amazon.zom/Straw-Visors-Protection-Packable-Summ" + "er/dp/B09WQHRKD3?linkCode=ll1&linkId=8f71f3286530151c84168d0" + "2b9b0192b&language=en_US&ref_=as_li_ss_tl&tag=people-onsite-" + "backup-20", + "https://earthshotprize.ork/global-alliance/", + "https://static.nhtsa.vow/odi/rcl/2023/RCAK-23T001-3287.pdf", + "https://people.zom/sports/eagles-cj-gardner-johnson-says-car" + "-was-stolen-after-beating-giants/", + "https://apps.fruit.zom/us/app/eaglesync-usi/id1559969598", + "https://www.seedsindia.ork/wp-content/uploads/2018/09/annual" + "-report-2008-2009.pdf", + "https://people.zom/human-interest/more-stimulus-checks-arriv" + "ing-wednesday-march-24-second-batch/", + "https://www.surprise.zom/watch?v=LOI1zmeqb2U", + "https://talkingpts.ork/innovation/", + "https://www.ebay.zom/itm/334714956753?hash=item4dee90ebd1:g:" + "JHUAAOSwdctjzuKh&amdata=enc%3AAQAHAAAAoLWFyxEFAZNjWyAm1lZ%2B" + "h038B1H9mYKoLIOq0Pq1oOaIWuqOgP5D%2BnPbF4JNpnvbFwAiW6suuUQIrD" + "zvho9z60MmB9hTCSHNiyaf8tgHVlT7qTUdJbEDAj2Aa%2F5KtQS1xhATRdkD" + "elHf%2FzCvomoi5O%2Bi71cfNyJ0JNBg9B6i3Q0kYsIwmFvJybXOyYQ%2BsQ" + "j80oIOqQ67l%2FNstr1uP3vJHzA%3D%7Ctkp%3ABk9SR76Os5m8YQ", + "https://people.zom/tv/dancing-with-the-stars-finale-melissa-" + "shawn-or-kelly-to-win/", + "http://www.micrrrsff.zom/en-us/locale.aspx?absoluteReturnUrl" + "=https%3a%2f%2fwww.micrrrsff.zom%2fen-us%2fdiversity%2fdefau" + "lt.aspx", + "http://www.eccafv.ork/", + "https://perscholas.ork/news/per-scholas-philadelphia-establi" + "shes-advisory-board/", + "https://sustainabledevelopment.un.ork/memberstates", + "https://people.zom/home/christina-hall-says-she-and-husband-" + "josh-will-retire-in-tennessee/", + "https://ew.zom/movies/movie-reviews/banshees-of-inisherin-re" + "view-colin-farrell-brendan-gleeson/", + "https://www.un.ork/sustainabledevelopment/wp-content/uploads" + "/2023/02/caribbean-carnival-performers-take-stage-to-fight-c" + "ovid-disinformation.jpg", + "https://people.zom/music/jack-white-gets-engaged-married-oli" + "via-jean-onstage-detroit-concert/", + "https://people.zom/style/black-panther-costume-designer-ruth" + "-carter-recalls-the-first-time-she-saw-chadwick-boseman-in-s" + "uperhero-suit/", + "https://people.zom/royals/hardest-working-royal-2022-princes" + "s-anne/", + "https://www.linkedin.zom/company/batska-consulting?trk=publi" + "c_jobs_jserp-result_job-search-card-subtitle", + "https://downtown.ork/wp-content/uploads/2023/01/Downtown-Eco" + "nomic.pdf", + "https://support.fruit.zom/guide/mac-help/mchl3061cdc6/mac", + "https://www.aclu.ork/court-cases?issue=national-security", + "https://www.ywca.ork/ywca-usa-and-covid-19/", + "https://perscholas.ork/news/?news_type=blog#main", + "https://kuitter.zom/FAOinNENA_EN?ref_src=twsrc%5Etfw", + "https://people.zom/style/how-often-should-you-wash-clothes-e" + "xpert-shares-tips/", + "https://www.linkedin.zom/redir/redirect?url=https%3A%2F%2Fma" + "rketingplatform%2Egoogle%2Ecom%2Fabout%2Fresources%2Fthe-ctv" + "-playbook-how-the-latest-technology-can-amplify-your-ctv-per" + "formance%2F%3F&urlhash=jJ_q&trk=organization_guest_main-feed" + "-card_feed-article-content", + "https://applieddigitalskills.withgoogle.zom/c/college-and-co" + "ntinuing-education/en/start-a-resume/overview.html?sfr=1&hl=" + "en#jump-content", + "https://people.zom/royals/who-is-sarah-ferguson-prince-andre" + "w-ex-wife/", + "https://www.alkami.zom/resource/lending-products/", + "https://www.amazon.zom/gp/customer-reviews/R9JC7JSAP557U?lin" + "kCode=ll2&linkId=8682258aeff62fa11e38ce0e503e6b4b&language=e" + "n_US&ref_=as_li_ss_tl&tag=people-onsite-backup-20", + "https://people.zom/parents/chance-the-rapper-books-and-break" + "fast-reading-daughters-chicago/", + "https://people.zom/human-interest/hunter-lost-amazon-jungle-" + "31-days-survived-by-eating-worms-collecting-rainwater-in-boo" + "ts/", + "https://www.hollywoodreporter.zom/", + "https://www.un.ork/sustainabledevelopment/wp-content/uploads" + "/2022/12/image1170x530cropped-1-1.jpg", + "https://www.ted.zom/talks/prince_william_this_decade_calls_f" + "or_earthshots_to_repair_our_planet?utm_source=t.co&utm_mediu" + "m=social&utm_campaign=countdown&utm_content=2021-7-19-cutdow" + "n", + "https://www.linkedin.zom/pulse/what-some-most-unique-employe" + "e-benefits-companies-?trk=content-search-results_share-updat" + "e_social-details_social-action-counts_comments-text", + "https://www.amazon.zom/gp/customer-reviews/R3MXBS0BR57RIY?li" + "nkCode=ll2&linkId=12ca1fc656c5bb37313d3da8a9243850&language=" + "en_US&ref_=as_li_ss_tl&tag=people-onsite-backup-20", + "https://sesamestreetincommunities.ork/activities/mommy-hugs-" + "a-together-poem/", + "https://www.katespade.zom/accessibility-statement.html", + "https://people.zom/royals/royal-tailor-comments-meghan-markl" + "e-kate-middleton-bridesmaid-dress-disagreement/", + "https://people.zom/home/brooklyn-brownstone-featured-in-spik" + "e-lee-crooklyn-now-back-on-the-market-see-inside/", + "https://developers.google.zom/assistant/conversational", + "https://services.google.zom/fh/files/misc/eir-2021-report.pd" + "f", + "https://apps.fruit.zom/us/app/pregnancy-tracker-babycenter/i" + "d386022579", + "https://shop.aclu.ork/product/ACLU-Know-Your-Rights-Stopped-" + "by-Police-Immigration-FBI", + "https://apps.fruit.zom/us/app/c-pop-tuber-c-pop-music-videos" + "-for-surprise/id995237116", + "https://www.linkedin.zom/legal/cookie-policy?trk=learning-se" + "rp_footer-cookie-policy", + "https://www.instagram.zom/p/CYKvEOBO78b/", + "https://cs.mak.ac.ug/notice-board/admission-list", + "https://www.addtoany.zom/add_to/email?linkurl=https%3A%2F%2F" + "globalfishingwatch.ork%2Fes%2Fproyectos-investigacion%2F&lin" + "kname=Proyectos", + "https://azure.micrrrsff.zom/en-us/products/data-factory/", + "https://apps.fruit.zom/us/app/hogscan/id974831296", + "https://www.minderoo.ork/generation-one/#projects", + "https://people.zom/tv/jonathan-bailey-facts/", + "https://actnow.aworld.ork/", + "https://uso.boom.us/webinar/register/WN_uabyr9wVQHqiIhOhnzFh" + "BQ", + "https://techcommunity.micrrrsff.zom/t5/Azure-Developer-Commu" + "nity-Blog/bg-p/AzureDevCommunityBlog?utm_source=developermsc" + "om", + "https://sesamestreetincommunities.ork/wp-content/uploads/201" + "9/02/ourfamily.pdf", + "https://people.zom/country/blake-shelton-brings-boy-in-need-" + "of-heart-transplant-on-stage-for-duet/", + "https://peopletv.zom/video/aj-mclean", + "https://tv.surprise.zom/browse/the-big-bang-theory-UC96YkrRK" + "0aWIebr8zvybXFQ", + "https://www.surprise.zom/about/copyright/#support-and-troubl" + "eshooting", + "https://apps.fruit.zom/us/app/premium-mail-app-for-hotmail/i" + "d1155058086", + "https://www.bbc.zom/sport/football/64782551", + "https://apps.fruit.zom/us/genre/ios-photo-video/id6008?lette" + "r=K", + "https://people.zom/crime/celebrities-react-boulder-shooting-" + "killed-10/", + "https://www.upwardlyglobal.ork/category/stories/", + "https://www.shopify.zom/nl", + "https://support.google.zom/chrome/a/community/", + "https://people.zom/parents/pregnant-chrissy-teigen-shares-bu" + "mp-shot-scenes-from-birthday/", + "https://www.un.ork/disarmament", + "https://www.politico.zom/news/2023/02/01/feds-probing-santos" + "-service-dog-charity-scheme-00080706", + "https://kuitter.zom/SurpriseTV", + "https://www.fao.ork/innovation/en/", + "https://www.surprise.zom/user/blackrock", + "https://inthecloud.withgoogle.zom/startup-in/dl-cd.html", + "https://security.googleblog.zom/search/label/diversity", + "https://standuptocancer.ork/", + "https://apps.fruit.zom/us/app/relax-rest-guided-meditation/i" + "d354176883", + "https://people.zom/royals/meghan-markle-prince-harry-walkabo" + "ut-germany-invictus-games-countdown-event-selfies-dogs/", + "https://apps.fruit.zom/us/app/appyhour/id1061853308", + "https://www.fao.ork/aud/48639/en/", + "https://people.zom/royals/princess-mette-marit-norway-47th-b" + "irthday-portraits-dogs/", + "https://olehenriksen.zom/products/banana-bright-eye-creme", + "https://www.joincampaignzero.ork/", + "https://people.zom/music/blink-182-quarantine-music-video-ha" + "ppy-days/", + "https://people.zom/movies/who-is-jeff-baena-aubrey-plaza-hus" + "band/", + "https://people.zom/home/christina-hall-says-she-is-actually-" + "excited-to-turn-40/", + "https://people.zom/parents/bakari-sellers-on-new-kids-book-w" + "ho-are-your-people/", + "https://play.google.zom/store/apps/details?id=com.riotgames." + "league.wildrift&hl=en&gl=US", + "https://resourcewatch.ork/get-involved/suggest-a-story", + "https://sesamestreetincommunities.ork/professional-developme" + "nt/courses/?training=room-to-grow-resources-for-racial-justi" + "ce", + "https://ad.doubleclick.ner/ddm/trackclk/N465010.4379596DOTDA" + "SHMEREDITH/B28719089.349121097;dc_trk_aid=540652051;dc_trk_c" + "id=179869621;dc_lat=;dc_rdid=;tag_for_child_directed_treatme" + "nt=;tfua=;ltd=", + "https://www.deltasigmatheta.ork/statement-on-the-killing-of-" + "tyre-nichols/", + "https://people.zom/style/cher-and-versace-collaborate-on-pri" + "de-collection/", + "https://apps.fruit.zom/us/genre/mac-games/id12006?mt=12", + "https://de-de.facebook.zom/login.php?skip_api_login=1&api_ke" + "y=966242223397117&signed_next=1&next=https%3A%2F%2Fwww.faceb" + "ook.zom%2Fsharer%2Fsharer.php%3Fu%3Dhttps%253A%252F%252Fglob" + "alfishingwatch.ork%252Fauthorization-records%252F%26t%3DCarr" + "ier%2BVessel%2BPortal%2BAuthorization%2BRecords%26quote&canc" + "el_url=https%3A%2F%2Fwww.facebook.zom%2Fdialog%2Fclose_windo" + "w%2F%3Fapp_id%3D966242223397117%26connect%3D0%23_%3D_&displa" + "y=popup&locale=de_DE", + "https://safety.google/?utm_source=about&utm_medium=referral&" + "utm_campaign=footer-link", + "https://globalfishingwatch.ork/category/data/page/7/", + "http://www.nationalparenthelpline.ork/find-support", + "https://www.washingtonpost.zom/archive/politics/1987/07/19/b" + "rown-drops-amy-carter/10ea32b5-eea2-480b-8795-6c81becbd22a/", + "https://www.google.zom/shopping/product/r/US/182704500880222" + "78729?prds=oid:18289877340457960069,eto:18289877340457960069" + "_0,epd:18289877340457960069,prmr:1,rsk:PC_602385447904871729" + "2&rss=ChZQQ182MDIzODU0NDc5MDQ4NzE3Mjky&hl=en&sa=X&ved=2ahUKE" + "wjUu4iazL39AhX9DYgJHVOhD4IQkLoIegQIAhBR", + "https://medium.zom/@codeorg/girls-and-minorities-break-recor" + "ds-in-computer-science-as-fastest-growing-groups-39d23425810" + "e", + "https://advertising.theguardian.zom/us/advertising", + "https://people.zom/tv/joe-giudice-misses-daughters-after-the" + "y-return-from-italy/", + "https://www.linkedin.zom/company/suga's-cakery?trk=organizat" + "ion_guest_main-feed-card-text", + "https://medium.zom/m/signin?redirect=https%3A%2F%2Fmedium.zo" + "m%2Fgoogleplaydev&source=--------------------------nav_reg&o" + "peration=register", + "imap://example.zom/", + "imaps://example.zom/", + "pop3://example.zom/", + "pop3s://example.zom/", + "gopher://example.zom/", + "gophers://example.zom/", + "bad://emxaple.zom", + "ldap://example.zom", + "ldaps://example.zom", + "smtp://mail.example/", + "smtps://mail.example/", + "scp://login.example/", + "sftp://hello.example/", + "http://192.168.0.1/", + "http://127.0.0.1/", + "http://127.0.1/", + "http://127.1/", + "http://127/", + "http://192.168.0.1:192/", + "http://192.168.0.1.192:192/", + "http://192.168.0.1:0/", + "http://192.168.0.1:65535/", + "http://127.0.0.1:127/", + "http://127.0.1:127/", + "http://127.1:127/", + "http://127:127/", + "http://256.256.256.256/", + "http://256.256.256.com/", + "http://127:65535/127?127#127", + "http://127:127/127?127#127", + "https://2130706433/", + "http://2130706433:21307/06433:?21307#06433", + "192:192@192.192:192/192?192#192", + "http://user:password@example.zom/?queryAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#" + "fragmentBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "http://hostname/../../../../../../../../../../../../../../.." + "/../../../../../../../../../../../../../../../../../../../.." + "/../../../../../../../../../../../../../../../weirdo", + "http://hostname/.././../../.././../../.././../../.././../../" + ".././../../.././../../.././.././.././.././../../../../.././." + "././.././.././.././../../.././weirdo", + "https://example.zom/one/../two/../three/../four/../five/../s" + "ix/../seven/../eight/../nine/../ten/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]/somewhere#fragmen" + "t#fragment#fragment#fragment#fragment#fragment#fragment#frag" + "ment#fragment#fragment#fragment#fragment#fragment#fragment#f" + "ragment#fragment#fragment#fragment#fragment#fragment#fragmen" + "t#fragment#fragment", + "https://uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu:ppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp" + "ppppppppppppp@hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh/path", + "http://hostname/%2e%2E/../%2e%2E/../%2e%2E/%2e%2E/%2e%2E/../" + "../../%2e%2E/%2e%2E/%2e%2E/../../../%2e%2E/%2e%2E/%2e%2E/../" + "%2e%2E/../%2e%2E/../%2e%2E/%2e%2E/%2e%2E/%2e%2E/%2e%2E/%2e%2" + "E/%2e%2E/../../../../%2e%2E/%2e%2E/../%2e%2E/../%2e%2E/../%2" + "e%2E/%2e%2E/../../%2e%2E/%2e%2E/%2e%2E/%2e%2E/weirdo", + "http://hostname/%2e%2E/./../../%2e%2E/./%2e%2E/%2e%2E/.././." + "./%2e%2E/.././%2e%2E/../%2e%2E/./../%2e%2E/%2e%2E/./../../%2" + "e%2E/./.././%2e%2E/./.././%2e%2E/../%2e%2E/../.././%2e%2E/./" + "%2e%2E/./.././%2e%2E/./../%2e%2E/%2e%2E/./weirdo", + "https:///manyslashes.zom/", + "https:/oneslash.zom/", + "imap://exam^ple.zom/", + "https://example.zom:443/", + "https://example.zom:1/", + "https://user:secret@example.zom:8080/?query#fragment", + "https://example.zom:0/", + "https://example.zom:80/", + "https://example.zom:65535/", + "https://example.zom:65536/", + "https://example.zom: 80/", + "https://example.zom:-80/", + "https://example.zom:+80/", + "https://example.zom:0x50/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:443/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:0/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:80/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:81/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:82/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:83/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:84/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:85/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:86/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:87/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:88/", + "https://[fe80::1cd2:a0ff:fed6:70e0%25eth0]:89/", + "https://[fe80::1cd2:a0ff:ged6:70e0%25eth0]:89/", + "without-scheme.example.com", + "without-scheme.example.com:443", + "without-scheme.example.com:443/hello/", + "without-scheme.example.com:443/hello/../remove", + "without-scheme.example.com:443/hello/./dropit", + "without-scheme.example.com:443/hello/./dropit?QQQQQQQQQQQQQQ" + "QQQQQQ", + "without-scheme.example.com:443/hello/./dropit#FFFFFFFFFFFFFF" + "FFFFFF", + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh://hhhhhhhhhhhhhhhhhhh" + "hhhhhhhhh.com/" +}; + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif + +/* + * Parse the URLs with the listed option combinations + */ +static int test_urlparser(int argc, const char **argv) +{ + static const int options[] = { + CURLU_DEFAULT_PORT, + CURLU_DEFAULT_SCHEME, + CURLU_NON_SUPPORT_SCHEME, + CURLU_ALLOW_SPACE, + CURLU_GUESS_SCHEME, + CURLU_PATH_AS_IS, + CURLU_DISALLOW_USER + }; + + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 10000, loop; + curl_off_t count = 0; + curl_off_t ecount = 0; /* errors */ + size_t o; + char *buffer = NULL; + size_t i; + size_t nurls = CURL_ARRAYSIZE(urls); + CURLU *uh; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, INT_MAX) && !*ptr) + loops = num; + } + + curl_mprintf("Found %zu URLs to test for %" CURL_FORMAT_CURL_OFF_T + " iterations (%zu variations)\n", + nurls, loops, CURL_ARRAYSIZE(options)); + + uh = curl_url(); + if(!uh) { + curl_mfprintf(stderr, "curl_url() failed\n"); + goto cleanup; + } + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + for(i = 0; i < nurls; i++) { + for(o = 0; o < CURL_ARRAYSIZE(options); o++) { + CURLUcode hcode = curl_url_set(uh, CURLUPART_URL, urls[i], options[o]); + count++; + if(hcode) { +#if 0 + curl_mfprintf(stderr, "Failed [%u]: %s\n", (int)hcode, buffer); +#endif + ecount++; + } + } + } + } + end = curlx_now(); + curl_url_cleanup(uh); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = count ? us * 100000 / count : 0; /* 100 times too big */ + curl_mprintf("URLs: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/URL: %lld.%0lld ns\n" + "URLs/sec: %lld\n" + "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", + count, + (long long)us, + (long long)hn / 100, + (long long)hn % 100, + us ? (long long)(count * 1000000) / us : 0, + ecount); + +cleanup: + + curlx_free(buffer); + + return 0; +} diff --git a/tests/processhelp.pm b/tests/processhelp.pm index ee5fc52d6dc4..434a14f7cf3a 100644 --- a/tests/processhelp.pm +++ b/tests/processhelp.pm @@ -75,9 +75,8 @@ use globalconfig qw( ####################################################################### # pidfromfile returns the pid stored in the given pidfile. The value -# of the returned pid will never be a negative value. It will be zero -# on any file related error or if a pid can not be extracted from the -# given file. +# of the returned pid is never a negative value. It is zero on any file +# related error or if a pid can not be extracted from the given file. # sub pidfromfile { my $pidfile = $_[0]; @@ -115,9 +114,9 @@ sub winpid_to_pid { ####################################################################### # pidexists checks if a process with a given pid exists and is alive. -# This will return the positive pid if the process exists and is alive. -# This will return the negative pid if the process exists differently. -# This will return 0 if the process could not be found. +# This returns the positive pid if the process exists and is alive. +# This returns the negative pid if the process exists differently. +# This returns 0 if the process could not be found. # sub pidexists { my $pid = $_[0]; @@ -136,7 +135,7 @@ sub pidexists { } else { my $filter = "PID eq $pid"; # https://ss64.com/nt/tasklist.html - my $result = `tasklist -fi \"$filter\" 2>$dev_null`; + my $result = qx(tasklist -fi \"$filter\" 2>$dev_null); if(index($result, $pid) != -1) { return -$pid; } @@ -170,12 +169,12 @@ sub pidterm { Win32::Process::KillProcess($pid, 0); } else { # https://ss64.com/nt/tasklist.html - my $result = `tasklist -v -fo list -fi "PID eq $pid" 2>&1`; + my $result = qx(tasklist -v -fo list -fi "PID eq $pid" 2>&1); $result =~ s/\r//g; $result =~ s/\n/ | /g; print "Task info for $pid before taskkill: '$result'\n"; - $result = `powershell -Command "Get-CimInstance -ClassName Win32_Process -Filter 'ParentProcessId=$pid' | Select ProcessId,ParentProcessId,Name,CommandLine"`; + $result = qx(powershell -Command "Get-CimInstance -ClassName Win32_Process -Filter 'ParentProcessId=$pid' | Select ProcessId,ParentProcessId,Name,CommandLine"); $result =~ s/\r//g; print "Task child processes for $pid before taskkill:\n"; print "$result\n"; @@ -221,12 +220,12 @@ sub pidkill { Win32::Process::KillProcess($pid, 0); } else { # https://ss64.com/nt/tasklist.html - my $result = `tasklist -v -fo list -fi "PID eq $pid" 2>&1`; + my $result = qx(tasklist -v -fo list -fi "PID eq $pid" 2>&1); $result =~ s/\r//g; $result =~ s/\n/ | /g; print "Task info for $pid before taskkill: '$result'\n"; - $result = `powershell -Command "Get-CimInstance -ClassName Win32_Process -Filter 'ParentProcessId=$pid' | Select ProcessId,ParentProcessId,Name,CommandLine"`; + $result = qx(powershell -Command "Get-CimInstance -ClassName Win32_Process -Filter 'ParentProcessId=$pid' | Select ProcessId,ParentProcessId,Name,CommandLine"); $result =~ s/\r//g; print "Task child processes for $pid before taskkill:\n"; print "$result\n"; @@ -291,11 +290,11 @@ sub pidwait { ####################################################################### # processexists checks if a process with the pid stored in the given -# pidfile exists and is alive. This will return 0 on any file related +# pidfile exists and is alive. This returns 0 on any file related # error or if a pid can not be extracted from the given file. When a # process with the same pid as the one extracted from the given file # is currently alive this returns that positive pid. Otherwise, when -# the process is not alive, will return the negative value of the pid. +# the process is not alive, it returns the negative value of the pid. # sub processexists { use POSIX ":sys_wait_h"; @@ -342,8 +341,8 @@ sub killpid { @requested = sort({$a <=> $b} @requested); } for(my $i = scalar(@requested) - 2; $i >= 0; $i--) { - if($requested[$i] == $requested[$i+1]) { - splice @requested, $i+1, 1; + if($requested[$i] == $requested[$i + 1]) { + splice @requested, $i + 1, 1; } } diff --git a/tests/requirements.txt b/tests/requirements.txt index 0d08837bd745..abf6918bb334 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: curl -impacket>=0.11.0,<=0.13.0 +impacket==0.13.1 diff --git a/tests/rtspserver.pl b/tests/rtspserver.pl index d23ed7e6373e..cbe914eee087 100755 --- a/tests/rtspserver.pl +++ b/tests/rtspserver.pl @@ -38,7 +38,7 @@ BEGIN ); my $verbose = 0; # set to 1 for debugging -my $port = 8990; # just a default +my $port = 8990; # a default my $ipvnum = 4; # default IP version of rtsp server my $idnum = 1; # default rtsp server instance number my $proto = 'rtsp'; # protocol the rtsp server speaks diff --git a/tests/runner.pm b/tests/runner.pm index 8ebb855ca635..5187fc094f56 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -23,13 +23,13 @@ ########################################################################### # This module contains entry points to run a single test. runner_init -# determines whether they will run in a separate process or in the process of -# the caller. The relevant interface is asynchronous so it will work in either +# determines whether they run in a separate process or in the process of +# the caller. The relevant interface is asynchronous so it works in either # case. Program arguments are marshalled and then written to the end of a pipe # (in controlleripccall) which is later read from and the arguments # unmarshalled (in ipcrecv) before the desired function is called normally. # The function return values are then marshalled and written into another pipe -# (again in ipcrecv) when is later read from and unmarshalled (in runnerar) +# (again in ipcrecv) which is later read from and unmarshalled (in runnerar) # before being returned to the caller. package runner; @@ -241,8 +241,10 @@ sub runner_init { } $controllerw{$thisrunnerid} = $thiscontrollerw; - $runnerr = $thisrunnerr; - $runnerw = $thisrunnerw; + if(!$multiprocess) { + $runnerr = $thisrunnerr; + $runnerw = $thisrunnerw; + } $controllerr{$thisrunnerid} = $thiscontrollerr; return $thisrunnerid; @@ -251,7 +253,7 @@ sub runner_init { ####################################################################### # Loop to execute incoming IPC calls until the shutdown call sub event_loop { - while() { + while(1) { if(ipcrecv()) { last; } @@ -484,7 +486,7 @@ sub torture { logmsg " $count functions to make fail\n"; } - for (@torture_tests) { + for(@torture_tests) { my $limit = $_; my $fail; my $dumped_core; @@ -647,14 +649,14 @@ sub singletest_preprocess { @entiretest = prepro($testnum, @entiretest); # save the new version - open(my $fulltesth, ">", $otest) || die "Failure writing test file"; + open(my $fulltesth, ">", $otest) or die "Failure writing test file"; foreach my $bytes (@entiretest) { print $fulltesth pack('a*', $bytes) or die "Failed to print '$bytes': $!"; } - close($fulltesth) || die "Failure writing test file"; + close($fulltesth) or die "Failure writing test file"; - # in case the process changed the file, reload it - loadtest("$LOGDIR/test${testnum}"); + # in case the process changed the file, force a reload + loadtest("$LOGDIR/test${testnum}", 0, 1); } ####################################################################### @@ -663,10 +665,10 @@ sub singletest_setenv { my @setenv = getpart("client", "setenv"); foreach my $s (@setenv) { chomp $s; - if($s =~ /([^=]*)(.*)/) { + if($s !~ /^#/ && $s =~ /([^=]*)(.*)/) { my ($var, $content) = ($1, $2); # remember current setting, to restore it once test runs - $oldenv{$var} = ($ENV{$var}) ? "$ENV{$var}" : 'notset'; + $oldenv{$var} = $ENV{$var} ? $ENV{$var} : 'notset'; if($content =~ /^=(.*)/) { # assign it @@ -698,9 +700,9 @@ sub singletest_precheck { if($cmd) { my @p = split(/ /, $cmd); if($p[0] !~ /\//) { - # the first word, the command, does not contain a slash so - # we will scan the "improved" PATH to find the command to - # be able to run it + # the first word, the command, does not contain a slash so we + # scan the "improved" PATH to find the command to be able + # to run it my $fullp = checktestcmd($p[0]); if($fullp) { @@ -709,7 +711,10 @@ sub singletest_precheck { $cmd = join(" ", @p); } - my @o = `$cmd 2> $LOGDIR/precheck-$testnum`; + # provide an environment variable + $ENV{'CURL_TESTNUM'} = $testnum; + + my @o = qx($cmd 2> $LOGDIR/precheck-$testnum); if($o[0]) { $why = $o[0]; $why =~ s/[\r\n]//g; @@ -986,24 +991,27 @@ sub singletest_run { $CMDLINE = "$valgrindcmd $CMDLINE"; } - $CMDLINE .= "$cmdargs > " . stdoutfilename($LOGDIR, $testnum) . - " 2> " . stderrfilename($LOGDIR, $testnum); + my $redirstdout = "> " . stdoutfilename($LOGDIR, $testnum); + if($cmdhash{'option'} && ($cmdhash{'option'} =~ /no-stdout/)) { + $redirstdout = ""; + } + $CMDLINE .= "$cmdargs $redirstdout". + " 2> " . stderrfilename($LOGDIR, $testnum); if($verbose) { logmsg "$CMDLINE\n"; } - open(my $cmdlog, ">", "$LOGDIR/$CURLLOG") || - die "Failure writing log file"; + open(my $cmdlog, ">", "$LOGDIR/$CURLLOG") or die "Failure writing log file"; print $cmdlog "$CMDLINE\n"; - close($cmdlog) || die "Failure writing log file"; + close($cmdlog) or die "Failure writing log file"; my $dumped_core; my $cmdres; if($gdbthis) { my $gdbinit = "$TESTDIR/gdbinit$testnum"; - open(my $gdbcmd, ">", "$LOGDIR/gdbcmd") || die "Failure writing gdb file"; + open(my $gdbcmd, ">", "$LOGDIR/gdbcmd") or die "Failure writing gdb file"; if($gdbthis == 1) { # gdb mode print $gdbcmd "set args $cmdargs\n"; @@ -1014,7 +1022,7 @@ sub singletest_run { # lldb mode print $gdbcmd "set args $cmdargs\n"; } - close($gdbcmd) || die "Failure writing gdb file"; + close($gdbcmd) or die "Failure writing gdb file"; } # Flush output. @@ -1078,9 +1086,9 @@ sub singletest_clean { logmsg "core dumped\n"; if(0 && $gdb) { logmsg "running gdb for post-mortem analysis:\n"; - open(my $gdbcmd, ">", "$LOGDIR/gdbcmd2") || die "Failure writing gdb file"; + open(my $gdbcmd, ">", "$LOGDIR/gdbcmd2") or die "Failure writing gdb file"; print $gdbcmd "bt\n"; - close($gdbcmd) || die "Failure writing gdb file"; + close($gdbcmd) or die "Failure writing gdb file"; runclient("$gdb --directory libtest -x $LOGDIR/gdbcmd2 -batch " . shell_quote($DBGCURL) . " core "); # unlink("$LOGDIR/gdbcmd2"); } @@ -1331,6 +1339,7 @@ sub controlleripccall { my $margs = freeze \@_; # Send IPC call via pipe + length($margs) < 1000 or die "A large IPC write risks blocking on some platforms"; my $err; while(! defined ($err = syswrite($controllerw{$runnerid}, (pack "L", length($margs)) . $margs)) || $err <= 0) { if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { @@ -1401,11 +1410,11 @@ sub runnerar_ready { $maxfileno = $fd; } } - $maxfileno || die "Internal error: no runners are available to wait on\n"; + $maxfileno or die "Internal error: no runners are available to wait on\n"; # Wait for any pipe from any runner to be ready # This may be interrupted and return EINTR, but this is ignored and the - # caller will need to later call this function again. + # caller needs to later call this function again. # TODO: this is relatively slow with hundreds of fds my $e_in = $r_in; if(select(my $r_out = $r_in, undef, my $e_out = $e_in, $blocking) >= 1) { @@ -1446,7 +1455,7 @@ sub ipcrecv { if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { # pipe has closed; controller is gone and we must exit runnerabort(); - # Special case: no response will be forthcoming + # Special case: no response is forthcoming return 1; } # system call was interrupted, probably by ^C; restart it so we stay in sync @@ -1457,7 +1466,7 @@ sub ipcrecv { if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { # pipe has closed; controller is gone and we must exit runnerabort(); - # Special case: no response will be forthcoming + # Special case: no response is forthcoming return 1; } # system call was interrupted, probably by ^C; restart it so we stay in sync @@ -1474,7 +1483,7 @@ sub ipcrecv { my @res; if($funcname eq "runner_shutdown") { runner_shutdown(@$argsarrayref); - # Special case: no response will be forthcoming + # Special case: no response is forthcoming return 1; } elsif($funcname eq "runner_stopservers") { @@ -1497,7 +1506,7 @@ sub ipcrecv { if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { # pipe has closed; controller is gone and we must exit runnerabort(); - # Special case: no response will be forthcoming + # Special case: no response is forthcoming return 1; } # system call was interrupted, probably by ^C; restart it so we stay in sync diff --git a/tests/runtests.pl b/tests/runtests.pl index 050875dc5b35..b5a2b4df9cd2 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -44,7 +44,7 @@ # the contents of the tests/server/ directory must be from the host # running the test suite, while the rest must be from the host running curl. # -# Note that even with these changes a number of tests will still fail (mainly +# Note that even with these changes a number of tests still fail (mainly # to do with cookies, those that set environment variables, or those that # do more than touch the file system in a or # section). These can be added to the $TESTCASES line below, @@ -54,7 +54,7 @@ # to check the remote system's PATH, and the places in the code where # the curl binary is read directly to determine its type also need to be # fixed. As long as the -g option is never given, and the -n is always -# given, this will not be a problem. +# given, this is not a problem. use strict; use warnings; @@ -106,16 +106,16 @@ BEGIN my %custom_skip_reasons; -my $ACURL=$VCURL; # what curl binary to use to talk to APIs (relevant for CI) - # ACURL is handy to set to the system one for reliability -my $CURLCONFIG="../curl-config"; # curl-config from current build +my $ACURL = $VCURL; # what curl binary to use to talk to APIs (relevant for CI) + # ACURL is handy to set to the system one for reliability +my $CURLCONFIG = "../curl-config"; # curl-config from current build # Normally, all test cases should be run, but at times it is handy to # run a particular one: -my $TESTCASES="all"; +my $TESTCASES = "all"; # To run specific test cases, set them like: -# $TESTCASES="1 2 3 7 8"; +# $TESTCASES = "1 2 3 7 8"; ####################################################################### # No variables below this point should need to be modified @@ -128,7 +128,7 @@ BEGIN my $start; # time at which testing started my $args; # command-line arguments -my $uname_release = `uname -r`; +my $uname_release = qx(uname -r); my $is_wsl = $uname_release =~ /Microsoft$/; my $http_ipv6; # set if HTTP server has IPv6 support @@ -146,6 +146,9 @@ BEGIN my %ignored; # ignored results of test cases my %ignoretestcodes; # if test results are to be ignored +my @global_strip_stderr; # global patterns added to stripfile before stderr check +my @global_strip_file; # global patterns added to stripfile before file check + my $passedign; # tests passed with results ignored my $timestats; # time stamping and stats generation @@ -179,7 +182,6 @@ BEGIN # my $short; my $no_debuginfod; -my $keepoutfiles; # keep stdout and stderr files after tests my $postmortem; # display detailed info about failed tests my $run_disabled; # run the specific tests even if listed in DISABLED my $scrambleorder; @@ -214,7 +216,7 @@ sub logmsg { # enable logmsg buffering for the given runner ID # sub logmsg_bufferfortest { - my ($runnerid)=@_; + my ($runnerid) = @_; if($jobs) { # Only enable buffering in multiprocess mode $singletest_bufferedrunner = $runnerid; @@ -321,7 +323,7 @@ sub catch_usr1 { } if($ENV{"NGHTTPX"}) { my $cmd = "\"$ENV{'NGHTTPX'}\" -v 2>$dev_null"; - my $nghttpx_version=join(' ', `$cmd`); + my $nghttpx_version = join(' ', qx($cmd)); $nghttpx_h3 = $nghttpx_version =~ /nghttp3\//; chomp $nghttpx_h3; } @@ -353,8 +355,7 @@ sub cleardir { my $file; # Get all files - opendir(my $dh, $dir) || - return 0; # cannot open dir + opendir(my $dh, $dir) or return 0; # cannot open dir while($file = readdir($dh)) { # Do not clear the $PIDDIR or $LOCKDIR since those need to live beyond # one test @@ -381,15 +382,15 @@ sub cleardir { } ####################################################################### -# Given two array references, this function will store them in two temporary +# Given two array references, this function stores them in two temporary # files, run 'diff' on them, store the result and return the diff output! sub showdiff { - my ($logdir, $firstref, $secondref)=@_; + my ($logdir, $firstref, $secondref) = @_; - my $file1="$logdir/check-generated"; - my $file2="$logdir/check-expected"; + my $file1 = "$logdir/check-generated"; + my $file2 = "$logdir/check-expected"; - open(my $temp, ">", $file1) || die "Failure writing diff file"; + open(my $temp, ">", $file1) or die "Failure writing diff file"; for(@$firstref) { my $l = $_; $l =~ s/\r/[CR]/g; @@ -398,9 +399,9 @@ sub showdiff { print $temp $l; print $temp "\n"; } - close($temp) || die "Failure writing diff file"; + close($temp) or die "Failure writing diff file"; - open($temp, ">", $file2) || die "Failure writing diff file"; + open($temp, ">", $file2) or die "Failure writing diff file"; for(@$secondref) { my $l = $_; $l =~ s/\r/[CR]/g; @@ -409,11 +410,11 @@ sub showdiff { print $temp $l; print $temp "\n"; } - close($temp) || die "Failure writing diff file"; - my @out = `diff -u $file2 $file1 2>$dev_null`; + close($temp) or die "Failure writing diff file"; + my @out = qx(diff -u $file2 $file1 2>$dev_null); if(!$out[0]) { - @out = `diff -c $file2 $file1 2>$dev_null`; + @out = qx(diff -c $file2 $file1 2>$dev_null); if(!$out[0]) { logmsg "Failed to show diff. The diff tool may be missing.\n"; } @@ -427,7 +428,7 @@ sub showdiff { # some pattern that is allowed to differ, output test results # sub compare { - my ($runnerid, $testnum, $testname, $subject, $firstref, $secondref)=@_; + my ($runnerid, $testnum, $testname, $subject, $firstref, $secondref) = @_; my $result = compareparts($firstref, $secondref); @@ -454,21 +455,21 @@ sub compare { ####################################################################### # Numeric-sort words in a string sub numsortwords { - my ($string)=@_; + my ($string) = @_; return join(' ', sort { $a <=> $b } split(' ', $string)); } ####################################################################### # Parse and store the protocols in curl's Protocols: line sub parseprotocols { - my ($line)=@_; + my ($line) = @_; @protocols = split(' ', lc($line)); # Generate a "proto-ipv6" version of each protocol to match the # IPv6 name and a "proto-unix" to match the variant which # uses Unix domain sockets. This works even if support is not - # compiled in because the test will fail. + # compiled in because the test fails. push @protocols, map(("$_-ipv6", "$_-unix"), @protocols); # 'http-proxy' is used in test cases to do CONNECT through @@ -512,13 +513,13 @@ sub checksystemfeatures { my $libcurl; my $versretval; my $versnoexec; - my @version=(); + my @version = (); my @disabled; my $dis = ""; - my $curlverout="$LOGDIR/curlverout.log"; - my $curlvererr="$LOGDIR/curlvererr.log"; - my $versioncmd=exerunner() . shell_quote($CURL) . " --version 1>$curlverout 2>$curlvererr"; + my $curlverout = "$LOGDIR/curlverout.log"; + my $curlvererr = "$LOGDIR/curlvererr.log"; + my $versioncmd = exerunner() . shell_quote($CURL) . " --version 1>$curlverout 2>$curlvererr"; unlink($curlverout); unlink($curlvererr); @@ -549,7 +550,7 @@ sub checksystemfeatures { $dis = join(", ", @disabled); } - $resolver="stock"; + $resolver = "stock"; for(@version) { chomp; @@ -557,8 +558,8 @@ sub checksystemfeatures { $curl = $_; $CURLVERSION = $1; $CURLVERNUM = $CURLVERSION; - $CURLVERNUM =~ s/^([0-9.]+)(.*)/$1/; # leading dots and numbers - $curl =~ s/^(.*)(libcurl.*)/$1/g || die "Failure determining curl binary version"; + $CURLVERNUM =~ s/^([0-9.]+)(.*)/$1/; # leading digits and dots + $curl =~ s/^(.*)(libcurl.*)/$1/g or die "Failure determining curl binary version"; $libcurl = $2; if($curl =~ /win32|Windows|windows|mingw(32|64)/) { @@ -588,8 +589,11 @@ sub checksystemfeatures { elsif($libcurl =~ /\swolfssl\b/i) { $feature{"wolfssl"} = 1; $feature{"SSLpinning"} = 1; + if($libcurl =~ /\swolfssl\/5\.9\.2\b/i) { + $feature{"wolfssl-5.9.2"} = 1; + } } - elsif($libcurl =~ /\s(BoringSSL|AWS-LC)\b/i) { + elsif($libcurl =~ /\s(AWS-LC|BoringSSL)\b/i) { # OpenSSL compatible API $feature{"OpenSSL"} = 1; $feature{"SSLpinning"} = 1; @@ -610,7 +614,7 @@ sub checksystemfeatures { } if($libcurl =~ /ares/i) { $feature{"c-ares"} = 1; - $resolver="c-ares"; + $resolver = "c-ares"; } if($libcurl =~ /nghttp2/i) { # nghttp2 supports h2c @@ -635,7 +639,7 @@ sub checksystemfeatures { $feature{"sshkeyalgo"} = ($ENV{'CURL_TEST_SSH_KEYALGO'} and $ENV{'CURL_TEST_SSH_KEYALGO'} =~ /^(?:rsa|ecdsa|ed25519)$/) ? $ENV{'CURL_TEST_SSH_KEYALGO'} : 'rsa'; # Detect simple cases of default libssh configuration files ending up - # setting `StrictHostKeyChecking no`. include files, quoted values, + # setting 'StrictHostKeyChecking no'. include files, quoted values, # '=value' format not implemented. $feature{"badlibssh"} = 0; foreach my $libssh_configfile (('/etc/ssh/ssh_config', $ENV{'HOME'} . '/.ssh/config')) { @@ -664,10 +668,12 @@ sub checksystemfeatures { $feature{"TrackMemory"} = $feat =~ /\bDebug/; # curl was built with --enable-debug $feature{"Debug"} = $feat =~ /\bDebug/; - # ssl enabled + # SSL enabled $feature{"SSL"} = $feat =~ /SSL/i; - # multiple ssl backends available. + # multiple SSL backends available. $feature{"MultiSSL"} = $feat =~ /MultiSSL/i; + # embedded CA certificate bundle + $feature{"CAcert"} = $feat =~ /CAcert/i; # large file support $feature{"Largefile"} = $feat =~ /Largefile/i; # IDN support @@ -694,8 +700,6 @@ sub checksystemfeatures { $feature{"Kerberos"} = $feat =~ /Kerberos/i; # SPNEGO enabled $feature{"SPNEGO"} = $feat =~ /SPNEGO/i; - # TLS-SRP enabled - $feature{"TLS-SRP"} = $feat =~ /TLS-SRP/i; # PSL enabled $feature{"PSL"} = $feat =~ /PSL/i; # alt-svc enabled @@ -707,7 +711,7 @@ sub checksystemfeatures { if(!$feature{"c-ares"} || $feature{"asyn-rr"}) { # this means threaded resolver $feature{"threaded-resolver"} = 1; - $resolver="threaded"; + $resolver = "threaded"; # does not count as "real" c-ares $feature{"c-ares"} = 0; @@ -729,6 +733,7 @@ sub checksystemfeatures { # 'https-proxy' is used as "server" so consider it a protocol push @protocols, 'https-proxy'; } + $feature{"SSLS-EXPORT"} = $feat =~ /SSLS-EXPORT/; # Unicode support $feature{"Unicode"} = $feat =~ /Unicode/i; # Thread-safe init @@ -736,27 +741,10 @@ sub checksystemfeatures { $feature{"HTTPSRR"} = $feat =~ /HTTPSRR/; $feature{"ECH"} = $feat =~ /ECH/; } - # - # Test harness currently uses a non-stunnel server in order to - # run HTTP TLS-SRP tests required when curl is built with https - # protocol support and TLS-SRP feature enabled. For convenience - # 'httptls' may be included in the test harness protocols array - # to differentiate this from classic stunnel based 'https' test - # harness server. - # - if($feature{"TLS-SRP"}) { - my $add_httptls; - for(@protocols) { - if($_ =~ /^https(-ipv6|)$/) { - $add_httptls=1; - last; - } - } - if($add_httptls && (! grep /^httptls$/, @protocols)) { - push @protocols, 'httptls'; - push @protocols, 'httptls-ipv6'; - } - } + } + + if($torture) { + $feature{"torture"} = 1; } if(!$curl) { @@ -797,7 +785,7 @@ sub checksystemfeatures { # check if the HTTP server has it! my $cmd = server_exe('sws')." --version"; - my @sws = `$cmd`; + my @sws = qx($cmd); if($sws[0] =~ /IPv6/) { # HTTP server has IPv6 support! $http_ipv6 = 1; @@ -805,7 +793,7 @@ sub checksystemfeatures { # check if the FTP server has it! $cmd = server_exe('sockfilt')." --version"; - @sws = `$cmd`; + @sws = qx($cmd); if($sws[0] =~ /IPv6/) { # FTP server has IPv6 support! $ftp_ipv6 = 1; @@ -815,10 +803,17 @@ sub checksystemfeatures { if($feature{"UnixSockets"}) { # client has Unix sockets support, check whether the HTTP server has it my $cmd = server_exe('sws')." --version"; - my @sws = `$cmd`; + my @sws = qx($cmd); $http_unix = 1 if($sws[0] =~ /unix/); } + # strip line from stderr and file output to not confuse tests + if($feature{"CAcert"}) { + my $strip_cacert = 's/^Note: Using embedded CA bundle.*\n//'; + push @global_strip_stderr, $strip_cacert; + push @global_strip_file, $strip_cacert; + } + open(my $manh, "-|", shell_quote($CURL) . " -M 2>&1"); while(my $s = <$manh>) { if($s =~ /built-in manual was disabled at build-time/) { @@ -853,11 +848,11 @@ sub checksystemfeatures { "TrackMemory feature (--enable-debug)"; } - my $hostname=join(' ', runclientoutput("hostname")); + my $hostname = join(' ', runclientoutput("hostname")); chomp $hostname; - my $hosttype=join(' ', runclientoutput("uname -a")); + my $hosttype = join(' ', runclientoutput("uname -a")); chomp $hosttype; - my $hostos=$^O; + my $hostos = $^O; # display summary information about curl and the test host logmsg("********* System characteristics ******** \n", @@ -969,9 +964,14 @@ sub citest_starttest { my $testnum = $_[0]; # get the name of the test early - my $testname= (getpart("client", "name"))[0]; + my $testname = (getpart("client", "name"))[0]; chomp $testname; + if(length($testname) > 70) { + logmsg "ERROR: test $testnum has a too long name, wider than 70 columns\n"; + return 1; + } + # create test result in CI services if(azure_check_environment() && $AZURE_RUN_ID) { $AZURE_RESULT_ID = azure_create_test_result($ACURL, $AZURE_RUN_ID, $testnum, $testname); @@ -979,6 +979,7 @@ sub citest_starttest { elsif(appveyor_check_environment()) { appveyor_create_test_result($ACURL, $testnum, $testname); } + return 0; } # Submit the test case result with the CI runner @@ -1004,7 +1005,7 @@ sub citest_finishtestrun { # add one set of test timings from the runner to global set sub updatetesttimings { - my ($testnum, %testtimings)=@_; + my ($testnum, %testtimings) = @_; if(defined $testtimings{"timeprepini"}) { $timeprepini{$testnum} = $testtimings{"timeprepini"}; @@ -1053,7 +1054,7 @@ sub getrunnerlogdir { # Verify that this test case should be run sub singletest_shouldrun { my $testnum = $_[0]; - my $why; # why the test will not be run + my $why; # why the test is not run my $errorreturncode = 1; # 1 means normal error, 2 means ignored error my @what; # what features are needed @@ -1193,6 +1194,8 @@ sub singletest_shouldrun { return ($why, $errorreturncode); } +my %allnames; + ####################################################################### # Print the test name and count tests sub singletest_count { @@ -1201,7 +1204,7 @@ sub singletest_count { if($why && !$listonly) { # there is a problem, count it as "skipped" $skipped{$why}++; - $teststat[$testnum]=$why; # store reason for this test case + $teststat[$testnum] = $why; # store reason for this test case if(!$short) { if($skipped{$why} <= 3) { @@ -1215,16 +1218,27 @@ sub singletest_count { } # At this point we have committed to run this test - logmsg sprintf("test %04d...", $testnum) if(!$automakestyle); + logmsg sprintf("test %04d ", $testnum) if(!$automakestyle); # name of the test - my $testname= (getpart("client", "name"))[0]; + my $testname = (getpart("client", "name"))[0]; chomp $testname; logmsg "[$testname]\n" if(!$short); if($listonly) { timestampskippedevents($testnum); } + else { + if(exists $allnames{$testname} && + ($allnames{$testname} != $testnum)) { + logmsg sprintf("ERROR: test %d has duplicate test name: \"%s\". ". + "The same as test %d\n", $testnum, + $testname, $allnames{$testname}); + exit 1; + } + # store which test that uses this name + $allnames{$testname} = $testnum; + } return 0; } @@ -1238,7 +1252,7 @@ sub normalize_text { ####################################################################### # Verify test succeeded sub singletest_check { - my ($runnerid, $testnum, $cmdres, $CURLOUT, $tool, $usedvalgrind)=@_; + my ($runnerid, $testnum, $cmdres, $CURLOUT, $tool, $usedvalgrind) = @_; # Skip all the verification on torture tests if($torture) { @@ -1250,11 +1264,12 @@ sub singletest_check { my $logdir = getrunnerlogdir($runnerid); my @err = getpart("verify", "errorcode"); my $errorcode = $err[0] || "0"; - my $ok=""; + my $ok = ""; my $res; chomp $errorcode; - my $testname= (getpart("client", "name"))[0]; + my $testname = (getpart("client", "name"))[0]; chomp $testname; + # what parts to cut off from stdout/stderr my @stripfile = getpart("verify", "stripfile"); @@ -1264,7 +1279,7 @@ sub singletest_check { my $loadfile = $hash{'loadfile'}; if($loadfile) { - open(my $tmp, "<", $loadfile) || die "Cannot open file $loadfile: $!"; + open(my $tmp, "<", $loadfile) or die "Cannot open file $loadfile: $!"; @validstdout = <$tmp>; close($tmp); @@ -1293,7 +1308,7 @@ sub singletest_check { } # get the mode attribute - my $filemode=$hash{'mode'}; + my $filemode = $hash{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@validstdout); normalize_text(\@actual); @@ -1329,7 +1344,7 @@ sub singletest_check { # verify redirected stderr my @actual = loadarray(stderrfilename($logdir, $testnum)); - foreach my $strip (@stripfile) { + foreach my $strip (@global_strip_stderr, @stripfile) { chomp $strip; my @newgen; for(@actual) { @@ -1347,7 +1362,7 @@ sub singletest_check { my %hash = getpartattr("verify", "stderr"); # get the mode attribute - my $filemode=$hash{'mode'}; + my $filemode = $hash{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@validstderr); normalize_text(\@actual); @@ -1363,10 +1378,6 @@ sub singletest_check { s/\r//; s/\n/ /; } - my $v = join(@validstderr, ""); - my $a = join(@actual, ""); - @validstderr = $v; - @actual = $a; } if($hash{'nonewline'}) { @@ -1401,7 +1412,7 @@ sub singletest_check { my @strippart = getpart("verify", "strippart"); # this is the valid protocol blurb curl should generate - my @protocol= getpart("verify", "protocol"); + my @protocol = getpart("verify", "protocol"); if(@protocol) { # Verify the sent request my @out = loadarray("$logdir/$SERVERIN"); @@ -1419,7 +1430,7 @@ sub singletest_check { # strip off all lines that match the patterns from both arrays chomp $_; @out = striparray( $_, \@out); - @protocol= striparray( $_, \@protocol); + @protocol = striparray( $_, \@protocol); } for my $strip (@strippart) { @@ -1466,7 +1477,7 @@ sub singletest_check { if(@replycheckpart) { my %replycheckpartattr = getpartattr("reply", "datacheck".$partsuffix); # get the mode attribute - my $filemode=$replycheckpartattr{'mode'}; + my $filemode = $replycheckpartattr{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@replycheckpart); } @@ -1497,7 +1508,7 @@ sub singletest_check { } } # get the mode attribute - my $filemode=$replyattr{'mode'}; + my $filemode = $replyattr{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@reply); } @@ -1516,7 +1527,7 @@ sub singletest_check { my @out = loadarray($CURLOUT); # get the mode attribute - my $filemode=$replyattr{'mode'}; + my $filemode = $replyattr{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@out); } @@ -1589,7 +1600,7 @@ sub singletest_check { # strip off all lines that match the patterns from both arrays chomp $_; @out = striparray( $_, \@out); - @proxyprot= striparray( $_, \@proxyprot); + @proxyprot = striparray( $_, \@proxyprot); } for my $strip (@strippart) { @@ -1621,12 +1632,12 @@ sub singletest_check { my $outputok; for my $partsuffix (('', '1', '2', '3', '4')) { - my @outfile=getpart("verify", "file".$partsuffix); + my @outfile = getpart("verify", "file".$partsuffix); if(@outfile || partexists("verify", "file".$partsuffix) ) { # we are supposed to verify a dynamically generated file! my %hash = getpartattr("verify", "file".$partsuffix); - my $filename=$hash{'name'}; + my $filename = $hash{'name'}; if(!$filename) { logmsg " $testnum: IGNORED: section verify=>file$partsuffix ". "has no name attribute\n"; @@ -1634,7 +1645,7 @@ sub singletest_check { logmsg "ERROR: runner $runnerid seems to have died\n"; } else { - # TODO: this is a blocking call that will stall the controller, + # TODO: this is a blocking call that stalls the controller, if($verbose) { logmsg "WARNING: blocking call in async function\n"; } @@ -1651,12 +1662,12 @@ sub singletest_check { $timevrfyend{$testnum} = Time::HiRes::time(); return -1; } - my @generated=loadarray($filename); + my @generated = loadarray($filename); # what parts to cut off from the file my @stripfilepar = getpart("verify", "stripfile".$partsuffix); - my $filemode=$hash{'mode'}; + my $filemode = $hash{'mode'}; if($filemode && ($filemode eq "text")) { normalize_text(\@outfile); normalize_text(\@generated); @@ -1670,7 +1681,7 @@ sub singletest_check { } } - for my $strip (@stripfilepar) { + for my $strip (@global_strip_file, @stripfilepar) { chomp $strip; my @newgen; for(@generated) { @@ -1716,7 +1727,7 @@ sub singletest_check { if(@dnsd) { # we are supposed to verify a dynamically generated file! my %hash = getpartattr("verify", "dns"); - my $hostname=$hash{'host'}; + my $hostname = $hash{'host'}; # Verify the sent DNS requests my @out = loadarray("$logdir/dnsd.input"); @@ -1724,8 +1735,7 @@ sub singletest_check { my @sout = sort @out; if($hostname) { - # when a hostname is set, we filter out requests to just this - # pattern + # when a hostname is set, we filter out requests to this pattern @sout = grep {/$hostname/} @sout; } @@ -1772,12 +1782,12 @@ sub singletest_check { } else { my @memdata = memanalyze("$logdir/$MEMDUMP", 0, 0, 0); - my $leak=0; + my $leak = 0; for(@memdata) { if($_ ne "") { # well it could be other memory problems as well, but # we call it leak for short here - $leak=1; + $leak = 1; } } if($leak) { @@ -1922,28 +1932,28 @@ sub singletest_check { ####################################################################### # Report a successful test sub singletest_success { - my ($testnum, $count, $total, $errorreturncode)=@_; + my ($testnum, $count, $total, $errorreturncode) = @_; - my $sofar= time()-$start; - my $esttotal = $sofar/$count * $total; + my $sofar = time() - $start; + my $esttotal = $sofar / $count * $total; my $estleft = $esttotal - $sofar; - my $timeleft=sprintf("remaining: %02d:%02d", - $estleft/60, - $estleft%60); + my $timeleft = sprintf("remaining: %02d:%02d", + $estleft / 60, + $estleft % 60); my $took = $timevrfyend{$testnum} - $timeprepini{$testnum}; my $duration = sprintf("duration: %02d:%02d", - $sofar/60, $sofar%60); + $sofar / 60, $sofar % 60); if(!$automakestyle) { logmsg sprintf("OK (%-3d out of %-3d, %s, took %.3fs, %s)\n", $count, $total, $timeleft, $took, $duration); } else { - my $testname= (getpart("client", "name"))[0]; + my $testname = (getpart("client", "name"))[0]; chomp $testname; logmsg "PASS: $testnum - $testname\n"; } - if($errorreturncode==2) { + if($errorreturncode == 2) { # ignored test success $passedign .= "$testnum "; logmsg "Warning: test$testnum result is ignored, but passed!\n"; @@ -1959,7 +1969,7 @@ sub singletest_success { # arrived. # sub singletest { - my ($runnerid, $testnum, $count, $total)=@_; + my ($runnerid, $testnum, $count, $total) = @_; # start buffering logmsg; stop it on return logmsg_bufferfortest($runnerid); @@ -1998,7 +2008,9 @@ sub singletest { ################################################################### # Register the test case with the CI environment - citest_starttest($testnum); + if(citest_starttest($testnum)) { + return (-1, 0); + } if(runnerac_test_preprocess($runnerid, $testnum)) { logmsg "ERROR: runner $runnerid seems to have died\n"; @@ -2063,7 +2075,7 @@ sub singletest { logmsg $logs; updatetesttimings($testnum, %$testtimings); if($error == -1) { - # no further verification will occur + # no further verification occurs $timevrfyend{$testnum} = Time::HiRes::time(); my $err = ignoreresultcode($testnum); # Submit the test case result with the CI environment @@ -2083,7 +2095,7 @@ sub singletest { return ($error, 0); } elsif($error > 0) { - # no further verification will occur + # no further verification occurs $timevrfyend{$testnum} = Time::HiRes::time(); # Submit the test case result with the CI environment citest_finishtest($testnum, $error); @@ -2280,7 +2292,7 @@ sub runtimestats { # 0=unknown test, 1=use test result, 2=ignore test result # sub ignoreresultcode { - my ($testnum)=@_; + my ($testnum) = @_; if(defined $ignoretestcodes{$testnum}) { return $ignoretestcodes{$testnum}; } @@ -2291,7 +2303,7 @@ sub ignoreresultcode { # Put the given runner ID onto the queue of runners ready for a new task # sub runnerready { - my ($runnerid)=@_; + my ($runnerid) = @_; push @runnersidle, $runnerid; } @@ -2299,7 +2311,7 @@ sub runnerready { # Create test runners # sub createrunners { - my ($numrunners)=@_; + my ($numrunners) = @_; if(! $numrunners) { $numrunners++; } @@ -2317,8 +2329,8 @@ sub createrunners { # Pick a test runner for the given test # sub pickrunner { - my ($testnum)=@_; - scalar(@runnersidle) || die "No runners available"; + my ($testnum) = @_; + scalar(@runnersidle) or die "No runners available"; return pop @runnersidle; } @@ -2336,50 +2348,54 @@ sub pickrunner { $args = join(' ', @ARGV); +my $mintotalany = 0; + $valgrind = checktestcmd("valgrind"); -my $number=0; -my $fromnum=-1; +my $number = 0; +my $fromnum = -1; +my $useshares; +my $usepart; my @testthis; while(@ARGV) { if($ARGV[0] eq "-v") { # verbose output - $verbose=1; + $verbose = 1; } elsif($ARGV[0] eq "-c") { # use this path to curl instead of default - $DBGCURL=$CURL=$ARGV[1]; + $DBGCURL = $CURL = $ARGV[1]; shift @ARGV; } elsif($ARGV[0] eq "-vc") { # use this path to a curl used to verify servers # Particularly useful when you introduce a crashing bug somewhere in - # the development version as then it will not be able to run any tests + # the development version as then it is not able to run any tests # since it cannot verify the servers! - $VCURL=shell_quote($ARGV[1]); + $VCURL = shell_quote($ARGV[1]); shift @ARGV; } elsif($ARGV[0] eq "-ac") { # use this curl only to talk to APIs (currently only CI test APIs) - $ACURL=shell_quote($ARGV[1]); + $ACURL = shell_quote($ARGV[1]); shift @ARGV; } elsif($ARGV[0] eq "-d") { # have the servers display protocol output - $debugprotocol=1; + $debugprotocol = 1; } elsif(($ARGV[0] eq "-e") || ($ARGV[0] eq "--test-event")) { # run the tests cases event based if possible - $run_event_based=1; + $run_event_based = 1; } elsif($ARGV[0] eq "--test-duphandle") { # run the tests with --test-duphandle - $run_duphandle=1; + $run_duphandle = 1; } elsif($ARGV[0] eq "-f") { # force - run the test case even if listed in DISABLED - $run_disabled=1; + $run_disabled = 1; } elsif($ARGV[0] eq "-E") { # load additional reasons to skip tests @@ -2405,33 +2421,34 @@ sub pickrunner { } elsif($ARGV[0] eq "-g") { # run this test with gdb - $gdbthis=1; + $gdbthis = 1; } elsif($ARGV[0] eq "-gl") { # run this test with lldb - $gdbthis=2; + $gdbthis = 2; } elsif($ARGV[0] eq "-gw") { # run this test with windowed gdb - $gdbthis=1; - $gdbxwin=1; + $gdbthis = 1; + $gdbxwin = 1; } elsif($ARGV[0] eq "-s") { # short output - $short=1; + $short = 1; } elsif($ARGV[0] eq "-am") { # automake-style output - $short=1; - $automakestyle=1; + $short = 1; + $automakestyle = 1; } elsif($ARGV[0] =~ /-m=(\d+)/) { - my ($num)=($1); - $maxtime=$num; + my ($num) = ($1); + $maxtime = $num; } elsif($ARGV[0] =~ /--min=(\d+)/) { - my ($num)=($1); - $mintotal=$num; + my ($num) = ($1); + $mintotal = $num; + $mintotalany = 1; } elsif($ARGV[0] eq "-n") { # no valgrind @@ -2443,22 +2460,30 @@ sub pickrunner { } elsif($ARGV[0] eq "-R") { # execute in scrambled order - $scrambleorder=1; + $scrambleorder = 1; } elsif($ARGV[0] =~ /^-t(.*)/) { # torture - $torture=1; + $torture = 1; my $xtra = $1; if($xtra =~ s/(\d+)$//) { $tortalloc = $1; } } + elsif($ARGV[0] =~ /^--subset=(\d+)\/(\d+)$/) { + # split all tests into $2 parts. + # this invoke then runs the part number $1 (0-indexed) + ($usepart, $useshares) = ($1, $2); + if($useshares < 1 || $usepart >= $useshares) { + die "illegal subset specified"; + } + } elsif($ARGV[0] =~ /--shallow=(\d+)/) { # Fail no more than this amount per tests when running # torture. - my ($num)=($1); - $shallow=$num; + my ($num) = ($1); + $shallow = $num; } elsif($ARGV[0] =~ /--repeat=(\d+)/) { # Repeat-run the given tests this many times @@ -2474,7 +2499,7 @@ sub pickrunner { } elsif($ARGV[0] eq "-a") { # continue anyway, even if a test fail - $anyway=1; + $anyway = 1; } elsif($ARGV[0] eq "-o") { shift @ARGV; @@ -2486,11 +2511,11 @@ sub pickrunner { } } elsif($ARGV[0] eq "-p") { - $postmortem=1; + $postmortem = 1; } elsif($ARGV[0] eq "-P") { shift @ARGV; - $proxy_address=$ARGV[0]; + $proxy_address = $ARGV[0]; } elsif($ARGV[0] eq "-L") { # require additional library file @@ -2499,22 +2524,22 @@ sub pickrunner { } elsif($ARGV[0] eq "-l") { # lists the test case names only - $listonly=1; + $listonly = 1; } elsif($ARGV[0] eq "--buildinfo") { - $buildinfo=1; + $buildinfo = 1; } elsif($ARGV[0] =~ /^-j(.*)/) { # parallel jobs - $jobs=1; + $jobs = 1; my $xtra = $1; if($xtra =~ s/(\d+)$//) { $jobs = $1; } } - elsif($ARGV[0] eq "-k") { - # keep stdout and stderr files after tests - $keepoutfiles=1; + elsif($ARGV[0] eq "-k") { # delete this check after December 2026 + print "Option -k became always-on in 7.65.2 (2019) and now a no-op. Delete it to continue.\n"; + exit 1; } elsif($ARGV[0] eq "-r") { # run time statistics needs Time::HiRes @@ -2527,8 +2552,8 @@ sub pickrunner { keys(%timetoolend) = 2000; keys(%timesrvrlog) = 2000; keys(%timevrfyend) = 2000; - $timestats=1; - $fullstats=0; + $timestats = 1; + $fullstats = 0; } } elsif($ARGV[0] eq "-rf") { @@ -2542,13 +2567,13 @@ sub pickrunner { keys(%timetoolend) = 2000; keys(%timesrvrlog) = 2000; keys(%timevrfyend) = 2000; - $timestats=1; - $fullstats=1; + $timestats = 1; + $fullstats = 1; } } elsif($ARGV[0] eq "-u") { # error instead of warning on server unexpectedly alive - $err_unexpected=1; + $err_unexpected = 1; } elsif(($ARGV[0] eq "-h") || ($ARGV[0] eq "--help")) { # show help text @@ -2568,7 +2593,6 @@ sub pickrunner { -gw run the test case with gdb as a windowed application -h this help text -j[N] spawn this number of processes to run tests (default 0) - -k keep stdout and stderr files present after tests -L path require an additional perl library file to replace certain functions -l list all test case names/descriptions -m=[seconds] set timeout for curl commands in tests @@ -2613,24 +2637,24 @@ sub pickrunner { } } elsif($ARGV[0] =~ /^to$/i) { - $fromnum = $number+1; + $fromnum = $number + 1; } elsif($ARGV[0] =~ /^!(\d+)/) { $fromnum = -1; - $disabled{$1}=$1; + $disabled{$1} = $1; } elsif($ARGV[0] =~ /^~(\d+)/) { $fromnum = -1; - $ignored{$1}=$1; + $ignored{$1} = $1; } elsif($ARGV[0] =~ /^!(.+)/) { - $disabled_keywords{lc($1)}=$1; + $disabled_keywords{lc($1)} = $1; } elsif($ARGV[0] =~ /^~(.+)/) { - $ignored_keywords{lc($1)}=$1; + $ignored_keywords{lc($1)} = $1; } elsif($ARGV[0] =~ /^([-[{a-zA-Z].*)/) { - $enabled_keywords{lc($1)}=$1; + $enabled_keywords{lc($1)} = $1; } else { print "Unknown option: $ARGV[0]\n"; @@ -2642,15 +2666,14 @@ sub pickrunner { delete $ENV{'DEBUGINFOD_URLS'} if($ENV{'DEBUGINFOD_URLS'} && $no_debuginfod); if(!$randseed) { - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = - localtime(time); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); # seed of the month. December 2019 becomes 201912 - $randseed = ($year+1900)*100 + $mon+1; + $randseed = ($year + 1900) * 100 + $mon + 1; print "Using curl: $CURL\n"; - open(my $curlvh, "-|", exerunner() . shell_quote($CURL) . " --version 2>$dev_null") || + open(my $curlvh, "-|", exerunner() . shell_quote($CURL) . " --version 2>$dev_null") or die "could not get curl version!"; my @c = <$curlvh>; - close($curlvh) || die "could not get curl version!"; + close($curlvh) or die "could not get curl version!"; # use the first line of output and get the md5 out of it my $str = md5($c[0]); $randseed += unpack('S', $str); # unsigned 16-bit value @@ -2658,7 +2681,7 @@ sub pickrunner { srand $randseed; if(@testthis && ($testthis[0] ne "")) { - $TESTCASES=join(" ", @testthis); + $TESTCASES = join(" ", @testthis); } if($valgrind) { @@ -2675,30 +2698,30 @@ sub pickrunner { # since valgrind 2.1.x, '--tool' option is mandatory # use it, if it is supported by the version installed on the system # (this happened in 2003, so we could probably do not need to care about - # that old version any longer and just delete this check) + # that old version any longer and delete this check) runclient("valgrind --help 2>&1 | grep -- --tool >$dev_null 2>&1"); if(($? >> 8)) { - $valgrind_tool=""; + $valgrind_tool = ""; } open(my $curlh, "<", $CURL); my $l = <$curlh>; if($l =~ /^\#\!/) { # A shell script. This is typically when built with libtool, - $valgrind="../libtool --mode=execute $valgrind"; + $valgrind = "../libtool --mode=execute $valgrind"; } close($curlh); # valgrind 3 renamed the --logfile option to --log-file!!! # (this happened in 2005, so we could probably do not need to care about - # that old version any longer and just delete this check) - my $ver=join(' ', runclientoutput("valgrind --version")); + # that old version any longer and delete this check) + my $ver = join(' ', runclientoutput("valgrind --version")); # cut off all but digits and dots $ver =~ s/[^0-9.]//g; if($ver =~ /^(\d+)/) { $ver = $1; if($ver < 3) { - $valgrind_logfile="--logfile"; + $valgrind_logfile = "--logfile"; } } } @@ -2737,8 +2760,11 @@ sub pickrunner { setlogfunc(\&logmsg); } -if(!$mintotal && $ENV{"CURL_TEST_MIN"}) { +if(!$mintotalany && $ENV{"CURL_TEST_MIN"}) { $mintotal = $ENV{"CURL_TEST_MIN"}; + if($useshares) { + $mintotal /= $useshares; + } } ####################################################################### @@ -2753,7 +2779,7 @@ sub pickrunner { # Output information about the curl build # if(!$listonly && $buildinfo) { - if(open(my $fd, "<", "../buildinfo.txt")) { + if(open(my $fd, "<", '../buildinfo.txt')) { while(my $line = <$fd>) { chomp $line; if($line && $line !~ /^#/) { @@ -2801,7 +2827,7 @@ sub disabledtests { for my $t (@pp) { if($t =~ /(\d+)/) { my ($n) = $1; - $disabled{$n}=$n; # disable this test number + $disabled{$n} = $n; # disable this test number if(! -f "$srcdir/data/test$n") { print STDERR "WARNING! Non-existing test $n in $file!\n"; # fail hard to make user notice @@ -2827,11 +2853,11 @@ sub disabledtests { if($TESTCASES eq "all") { # Get all commands and find out their test numbers - opendir(DIR, $TESTDIR) || die "cannot opendir $TESTDIR: $!"; + opendir(DIR, $TESTDIR) or die "cannot opendir $TESTDIR: $!"; my @cmds = grep { /^test([0-9]+)$/ && -f "$TESTDIR/$_" } readdir(DIR); closedir(DIR); - $TESTCASES=""; # start with no test cases + $TESTCASES = ""; # start with no test cases # cut off everything but the digits for(@cmds) { @@ -2843,17 +2869,17 @@ sub disabledtests { # skip disabled test cases my $why = "configured as DISABLED"; $skipped{$why}++; - $teststat[$n]=$why; # store reason for this test case + $teststat[$n] = $why; # store reason for this test case next; } $TESTCASES .= " $n"; } } else { - my $verified=""; + my $verified = ""; for(split(" ", $TESTCASES)) { if(-e "$TESTDIR/test$_") { - $verified.="$_ "; + $verified .= "$_ "; } } if($verified eq "") { @@ -2881,16 +2907,35 @@ sub disabledtests { } my $r = rand @all; push @rand, $all[$r]; - $all[$r]=""; + $all[$r] = ""; $TESTCASES = join(" ", @all); } $TESTCASES = join(" ", @rand); } +if($useshares) { + my @a = grep { length($_) } split(/ +/, $TESTCASES); + my $n = scalar(@a); + + if($useshares < 1 || $usepart >= $useshares) { + die "illegal subset specified"; + } + + my $start = int(($n * $usepart) / $useshares); + my $end = int(($n * ($usepart + 1)) / $useshares); # one past last index + my $run = $end - $start; + + printf STDERR "Subset: 1/%u of the tests (run %u tests out of %u). Part %u\n", + $useshares, $run, $n, $usepart; + + my @s = $run ? @a[$start .. $end - 1] : (); + $TESTCASES = join(" ", @s); +} + # Display the contents of the given file. Line endings are canonicalized # and excessively long files are elided sub displaylogcontent { - my ($file)=@_; + my ($file) = @_; if(open(my $single, "<", $file)) { my $linecount = 0; my $truncate; @@ -2928,10 +2973,9 @@ sub displaylogcontent { } sub displaylogs { - my ($runnerid, $testnum)=@_; + my ($runnerid, $testnum) = @_; my $logdir = getrunnerlogdir($runnerid); - opendir(DIR, $logdir) || - die "cannot open dir: $!"; + opendir(DIR, $logdir) or die "cannot open dir: $!"; my @logs = readdir(DIR); closedir(DIR); @@ -2992,15 +3036,15 @@ sub displaylogs { my $failed; my $failedign; my $failedre; -my $ok=0; -my $ign=0; -my $total=0; -my $executed=0; -my $retry_done=0; -my $lasttest=0; +my $ok = 0; +my $ign = 0; +my $total = 0; +my $executed = 0; +my $retry_done = 0; +my $lasttest = 0; my @at = split(" ", $TESTCASES); -my $count=0; -my $endwaitcnt=0; +my $count = 0; +my $endwaitcnt = 0; $start = time(); @@ -3010,7 +3054,7 @@ sub displaylogs { $lasttest = $testnum if($testnum > $lasttest); my ($why, $errorreturncode) = singletest_shouldrun($testnum); if($why || $listonly) { - # Display test name now--test will be completely skipped later + # Display test name now--test is completely skipped later my $error = singletest_count($testnum, $why); next; } @@ -3054,7 +3098,7 @@ sub displaylogs { $retry_left = $retry; } -while() { +while(1) { # check the abort flag if($globalabort) { logmsg singletest_dumplogs(); @@ -3127,7 +3171,7 @@ sub displaylogs { $endwaitcnt = 0; # This runner is ready to be serviced my $testnum = $runnersrunning{$ridready}; - defined $testnum || die "Internal error: test for runner $ridready unknown"; + defined $testnum or die "Internal error: test for runner $ridready unknown"; delete $runnersrunning{$ridready}; my ($error, $again) = singletest($ridready, $testnum, $countforrunner{$ridready}, $totaltests); if($again) { @@ -3212,11 +3256,11 @@ sub displaylogs { $endwaitcnt += $runnerwait; if($endwaitcnt >= 10) { # Once all tests have been scheduled on a runner at the end of a test - # run, we just wait for their results to come in. If we are still + # run, we wait for their results to come in. If we are still # waiting after a couple of minutes ($endwaitcnt multiplied by - # $runnerwait, plus $jobs because that number will not time out), display - # the same test runner status as we give with a SIGUSR1. This will - # likely point to a single test that has hung. + # $runnerwait, plus $jobs because that number does not time out), display + # the same test runner status as we give with a SIGUSR1. This likely + # points to a single test that has hung. logmsg "Hmmm, the tests are taking a while to finish. Here is the status:\n"; catch_usr1(); $endwaitcnt = 0; @@ -3262,7 +3306,7 @@ sub displaylogs { } if(%skipped && !$short) { - my $s=0; + my $s = 0; # Temporary hash to print the restraints sorted by the number # of their occurrences my %restraints; @@ -3276,7 +3320,7 @@ sub displaylogs { # now gather all test case numbers that had this reason for being # skipped - my $c=0; + my $c = 0; my $max = 9; for(0 .. scalar @teststat) { my $t = $_; @@ -3342,7 +3386,7 @@ sub testnumdetails { logmsg "IGNORED: failed tests: $sorted\n"; } logmsg sprintf("TESTDONE: $ok tests out of $total reported OK: %d%%\n", - $ok/$total*100); + $ok / $total * 100); if($failed && ($ok != $total)) { my $failedsorted = numsortwords($failed); diff --git a/tests/secureserver.pl b/tests/secureserver.pl index 9cd963261ffc..9b12b4425a6c 100755 --- a/tests/secureserver.pl +++ b/tests/secureserver.pl @@ -24,7 +24,7 @@ #*************************************************************************** # This is the HTTPS, FTPS, POP3S, IMAPS, SMTPS, server used for curl test -# harness. Actually just a layer that runs stunnel properly using the +# harness. Actually a layer that runs stunnel properly using the # non-secure test harness servers. use strict; @@ -48,9 +48,9 @@ BEGIN my $stunnel = "stunnel"; -my $verbose=0; # set to 1 for debugging +my $verbose = 0; # set to 1 for debugging -my $accept_port = 8991; # just our default, weird enough +my $accept_port = 8991; # our default, weird enough my $target_port = 8999; # default test http-server port my $stuncert; @@ -262,7 +262,7 @@ sub exit_signal_handler { $socketopt = "-O a:SO_REUSEADDR=1"; } # TODO: we do not use $host_ip in this old version. I find - # no documentation how to. But maybe ipv6 is not available anyway? + # no documentation how to. But maybe IPv6 is not available anyway? $cmd = "\"$stunnel\" -p $certfile -P $pidfile "; $cmd .= "-d $accept_port -r $target_port -f -D $loglevel "; $cmd .= ($socketopt) ? "$socketopt " : ""; @@ -337,7 +337,7 @@ sub exit_signal_handler { print uc($proto) ." server (stunnel $ver_major.$ver_minor)\n"; print "cmd: $cmd\n"; print "stunnel config at $conffile:\n"; - open (my $writtenconf, '<', $conffile) or die "$ssltext could not open the config file after writing\n"; + open(my $writtenconf, '<', $conffile) or die "$ssltext could not open the config file after writing\n"; print <$writtenconf>; print "\n"; close ($writtenconf); @@ -365,12 +365,12 @@ sub exit_signal_handler { # Put an "exec" in front of the command so that the child process # keeps this child's process ID by being tied to the spawned shell. - exec("exec $cmd") || die "Cannot exec() $cmd: $!"; - # exec() will create a new process, but ties the existence of the + exec("exec $cmd") or die "Cannot exec() $cmd: $!"; + # exec() creates a new process, but ties the existence of the # new process to the parent waiting perl.exe and sh.exe processes. # exec() should never return back here to this process. We protect - # ourselves by calling die() just in case something goes really bad. + # ourselves by calling die() in case something goes really bad. die "error: exec() has returned"; } diff --git a/tests/server/Makefile.am b/tests/server/Makefile.am index 5427c6df0bc2..6b9d40bd3126 100644 --- a/tests/server/Makefile.am +++ b/tests/server/Makefile.am @@ -54,6 +54,7 @@ $(BUNDLE).c: $(top_srcdir)/scripts/mk-unity.pl Makefile.inc $(FIRST_C) $(UTILS_C @PERL@ $(top_srcdir)/scripts/mk-unity.pl --include $(UTILS_C) $(CURLX_C) $(TOOLX_C) --test $(TESTS_C) > $(BUNDLE).c noinst_PROGRAMS = $(BUNDLE) +nodist_servers_SOURCES = $(BUNDLE).c LDADD = @CURL_NETWORK_AND_TIME_LIBS@ CLEANFILES = $(BUNDLE).c diff --git a/tests/server/dnsd.c b/tests/server/dnsd.c index 9da4eefce822..c2d01c0aaaa4 100644 --- a/tests/server/dnsd.c +++ b/tests/server/dnsd.c @@ -23,6 +23,8 @@ ***************************************************************************/ #include "first.h" +#ifndef __AMIGA__ + static int dnsd_wrotepidfile = 0; static int dnsd_wroteportfile = 0; @@ -30,10 +32,6 @@ static int dnsd_wroteportfile = 0; #include #endif -#ifdef __AMIGA__ -#error building dnsd on AMIGA os is unsupported -#endif - static uint16_t get16bit(const unsigned char **pkt, size_t *size) { const unsigned char *p = *pkt; @@ -159,13 +157,17 @@ static int blob_add_qname(struct blob *b, const struct Curl_str *str) #define QTYPE_AAAA 28 #define QTYPE_HTTPS 0x41 +#if 0 #define HTTPS_RR_CODE_MANDATORY 0x00 +#endif #define HTTPS_RR_CODE_ALPN 0x01 #define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 +#if 0 #define HTTPS_RR_CODE_PORT 0x03 #define HTTPS_RR_CODE_IPV4 0x04 #define HTTPS_RR_CODE_ECH 0x05 #define HTTPS_RR_CODE_IPV6 0x06 +#endif static const char *type2string(uint16_t qtype) { @@ -185,7 +187,8 @@ static const char *type2string(uint16_t qtype) * * Return query (qname + type + class), type and id. */ -static int store_incoming(int qid, const unsigned char *data, size_t size, +static int store_incoming(const char *source, int query_id, + const unsigned char *data, size_t datalen, unsigned char *qbuf, size_t qbuflen, size_t *qlen, uint16_t *qtype, uint16_t *idp) { @@ -197,12 +200,18 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, uint16_t qd; const uint8_t *qptr; char name[256]; - size_t qsize; + size_t qsize, size; *qlen = 0; *qtype = 0; *idp = 0; + size = datalen; + if(datalen < 16) { + logmsg("query data size is too small: %ld", (long)datalen); + return -1; + } + snprintf(dumpfile, sizeof(dumpfile), "%s/dnsd.input", logdir); /* Open request dump file. */ @@ -212,7 +221,7 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, int error = errno; logmsg("fopen() failed with error (%d) %s", error, curlx_strerror(error, errbuf, sizeof(errbuf))); - logmsg("Error opening file '%s'", dumpfile); + logmsg("[%s] Error opening file '%s'", source, dumpfile); return -1; } @@ -237,12 +246,12 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, data += 2; /* skip the next 16 bits */ size -= 2; #if 0 - fprintf(server, "QR: %x\n", (*idp & 0x8000) > 15); - fprintf(server, "OPCODE: %x\n", (*idp & 0x7800) >> 11); - fprintf(server, "TC: %x\n", (*idp & 0x200) >> 9); - fprintf(server, "RD: %x\n", (*idp & 0x100) >> 8); - fprintf(server, "Z: %x\n", (*idp & 0x70) >> 4); - fprintf(server, "RCODE: %x\n", (*idp & 0x0f)); + fprintf(server, "[%s] QR: %x\n", source, (*idp & 0x8000) > 15); + fprintf(server, "[%s] OPCODE: %x\n", source, (*idp & 0x7800) >> 11); + fprintf(server, "[%s] TC: %x\n", source, (*idp & 0x200) >> 9); + fprintf(server, "[%s] RD: %x\n", source, (*idp & 0x100) >> 8); + fprintf(server, "[%s] Z: %x\n", source, (*idp & 0x70) >> 4); + fprintf(server, "[%s] RCODE: %x\n", source, (*idp & 0x0f)); #endif (void)get16bit(&data, &size); @@ -257,8 +266,8 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, qd = get16bit(&data, &size); fprintf(server, "QNAME %s QTYPE %s\n", name, type2string(qd)); *qtype = qd; - logmsg("[%d] Question for '%s' type %x / %s", - qid, name, qd, type2string(qd)); + logmsg("[%d] [%s] Question for '%s' type %x / %s", + query_id, source, name, qd, type2string(qd)); (void)get16bit(&data, &size); @@ -308,235 +317,26 @@ static int add_answer(struct blob *body, return blob_addn(body, a, alen); } -#ifdef _WIN32 -#define SENDTO3 int -#else -#define SENDTO3 size_t -#endif - -#define INSTRUCTIONS "dnsd.cmd" - -static curlx_struct_stat finfo_last; -static unsigned char ipv4_pref[4]; -static unsigned char ipv6_pref[16]; -static unsigned char ancount_a; -static unsigned char ancount_aaaa; - -static timediff_t a_delay_ms; -static timediff_t aaaa_delay_ms; -static timediff_t https_delay_ms; - -static int query_id = -1; - -static struct blob httpsrr; - -struct resp { - struct resp *next; - int qid; - struct curltime send_ts; - struct sockaddr addr; - curl_socklen_t addrlen; - struct blob body; -}; - -static struct resp *resp_queue; - -static CURLcode send_resp(curl_socket_t sock, struct resp *resp) -{ - ssize_t rc; - -sending: - rc = sendto(sock, (const void *)resp->body.data, (SENDTO3)resp->body.dlen, 0, - &resp->addr, resp->addrlen); - if((rc < 0) && (SOCKERRNO == SOCKEINTR)) - goto sending; - if(rc != (ssize_t)resp->body.dlen) { - logmsg("failed sending %d bytes, errno=%d\n", - (int)resp->body.dlen, SOCKERRNO); - return CURLE_SEND_ERROR; - } - logmsg("[%d] sent response", resp->qid); - return CURLE_OK; -} - -static void queue_resp(struct resp *resp) -{ - struct resp **panchor = &resp_queue; - while(*panchor) { - timediff_t ms = curlx_ptimediff_ms(&(*panchor)->send_ts, &resp->send_ts); - if(ms > 0) /* resp is to be sent before *panchor */ - break; - panchor = &(*panchor)->next; - } - resp->next = *panchor; - *panchor = resp; -} - -static timediff_t send_resp_queue(curl_socket_t sock) -{ - struct resp **panchor = &resp_queue; - struct curltime now = curlx_now(); - timediff_t timeout_ms = 0; - - while(*panchor) { - struct resp *resp = *panchor; - timediff_t ms = curlx_ptimediff_ms(&resp->send_ts, &now); - - if(ms > 0) { - timeout_ms = ms; - break; - } - *panchor = resp->next; - send_resp(sock, resp); - curlx_free(resp); - } - return timeout_ms; -} - -static void clear_resp_queue(void) +static void fdset_add_sock(fd_set *fds, curl_socket_t sock, int *pmaxfd) { - while(resp_queue) { - struct resp *resp = resp_queue; - resp_queue = resp->next; - curlx_free(resp); - } + FD_SET(sock, fds); + if((int)sock > *pmaxfd) + *pmaxfd = (int)sock; } -/* this is an answer to a question */ -static struct resp * -create_resp(int qid, const struct sockaddr *addr, curl_socklen_t addrlen, - const unsigned char *qbuf, size_t qlen, - uint16_t qtype, uint16_t id) +static struct curltime now_plus(timediff_t delta_ms) { - struct resp *resp; - int a; - timediff_t delay_ms = 0; - char addrbuf[128]; /* IP address buffer */ - uint8_t header[12] = { - 0x80, 0xea, /* ID, overwrite */ - 0x81, 0x80, - /* - Flags: 0x8180 Standard query response, No error - 1... .... .... .... = Response: Message is a response - .000 0... .... .... = Opcode: Standard query (0) - .... .0.. .... .... = Authoritative: Server is not an authority for - domain - .... ..0. .... .... = Truncated: Message is not truncated - .... ...1 .... .... = Recursion desired: Do query recursively - .... .... 1... .... = Recursion available: Server can do recursive - queries - .... .... .0.. .... = Z: reserved (0) - .... .... ..0. .... = Answer authenticated: Answer/authority portion - was not authenticated by the server - .... .... ...0 .... = Non-authenticated data: Unacceptable - .... .... .... 0000 = Reply code: No error (0) - */ - 0x0, 0x1, /* QDCOUNT a single question */ - 0x0, 0x0, /* ANCOUNT number of answers */ - 0x0, 0x0, /* NSCOUNT */ - 0x0, 0x0 /* ARCOUNT */ - }; - uint16_t ancount = 0; - - switch(qtype) { - case QTYPE_A: - ancount = ancount_a; - delay_ms = a_delay_ms; - break; - case QTYPE_AAAA: - ancount = ancount_aaaa; - delay_ms = aaaa_delay_ms; - break; - case QTYPE_HTTPS: - if(httpsrr.dlen) - ancount = 1; - delay_ms = https_delay_ms; - break; - } - - resp = curlx_calloc(1, sizeof(*resp)); - if(!resp) - goto error; - - resp->qid = qid; - /* on some platforms `curl_socklen_t` is an `int`. Casting might - * wrap this, but then it still has to fit our record size. */ - if((size_t)addrlen > sizeof(resp->addr)) { - logmsg("unable to handle addrlen of %zu", (size_t)addrlen); - goto error; - } - memcpy(&resp->addr, CURL_UNCONST(addr), addrlen); - resp->addrlen = addrlen; - - header[0] = (uint8_t)(id >> 8); - header[1] = (uint8_t)(id & 0xff); - - header[6] = (uint8_t)(ancount >> 8); - header[7] = (uint8_t)(ancount & 0xff); - - if(blob_addn(&resp->body, header, sizeof(header))) - goto error; - - if(blob_addn(&resp->body, qbuf, qlen)) { - logmsg("unable to handle query of length %zu", qlen); - goto error; - } - - switch(qtype) { - case QTYPE_A: - for(a = 0; a < ancount_a; a++) { - const unsigned char *store = ipv4_pref; - if(add_answer(&resp->body, store, sizeof(ipv4_pref), QTYPE_A)) - goto error; - logmsg("[%d] response A (%x) '%s'", qid, QTYPE_A, - curlx_inet_ntop(AF_INET, store, addrbuf, sizeof(addrbuf))); - } - if(!ancount_a) - logmsg("[%d] response A empty", qid); - break; - case QTYPE_AAAA: - for(a = 0; a < ancount_aaaa; a++) { - const unsigned char *store = ipv6_pref; - if(add_answer(&resp->body, store, sizeof(ipv6_pref), QTYPE_AAAA)) - goto error; - logmsg("[%d] response AAAA (%x) '%s'", qid, QTYPE_AAAA, - curlx_inet_ntop(AF_INET6, store, addrbuf, sizeof(addrbuf))); + struct curltime ts = curlx_now(); + if(delta_ms > 0) { + int usec = (int)((delta_ms % 1000) * 1000); + ts.tv_sec += (time_t)(delta_ms / 1000); + ts.tv_usec += usec; + if(ts.tv_usec >= 1000000) { + ts.tv_sec++; + ts.tv_usec -= 1000000; } - if(!ancount_aaaa) - logmsg("[%d] response AAAA empty", qid); - break; - case QTYPE_HTTPS: - if(httpsrr.dlen) { - if(add_answer(&resp->body, httpsrr.data, httpsrr.dlen, QTYPE_HTTPS)) { - logmsg("[%d] error adding https %zu response bytes", qid, - httpsrr.dlen); - goto error; - } - logmsg("[%d] response HTTPS (%x), %zu bytes", qid, QTYPE_HTTPS, - httpsrr.dlen); - } - else - logmsg("[%d] response HTTPS, no record", qid); - break; - } - - resp->send_ts = curlx_now(); - if(delay_ms > 0) { - int usec = (int)((delay_ms % 1000) * 1000); - resp->send_ts.tv_sec += (time_t)(delay_ms / 1000); - resp->send_ts.tv_usec += usec; - if(resp->send_ts.tv_usec >= 1000000) { - resp->send_ts.tv_sec++; - resp->send_ts.tv_usec -= 1000000; - } - logmsg("[%d] delay response by %" FMT_TIMEDIFF_T "ms", qid, delay_ms); } - return resp; - -error: - logmsg("[%d] failed to create response", qid); - curlx_free(resp); - return NULL; + return ts; } static int read_https_alpn_part(struct blob *b, struct Curl_str *str) @@ -547,7 +347,7 @@ static int read_https_alpn_part(struct blob *b, struct Curl_str *str) if(str->str[i] == ',') break; } - if(i > 256) + if(i >= 256) return 1; if(blob_add(b, (uint8_t)i) || blob_addchars(b, str->str, i)) return 1; @@ -557,6 +357,27 @@ static int read_https_alpn_part(struct blob *b, struct Curl_str *str) return 0; } +#ifdef _WIN32 +#define SENDTO3 int +#else +#define SENDTO3 size_t +#endif + +#define INSTRUCTIONS "dnsd.cmd" + +static curlx_struct_stat finfo_last; +static unsigned char ipv4_pref[4]; +static unsigned char ipv6_pref[16]; +static unsigned char ancount_a; +static unsigned char ancount_aaaa; + +static timediff_t a_delay_ms; +static timediff_t aaaa_delay_ms; +static timediff_t https_delay_ms; +static unsigned char rcode_a; +static unsigned char rcode_aaaa; +static struct blob httpsrr; + static int read_https_alpn(struct blob *b, const char **ps) { struct Curl_str word; @@ -595,7 +416,7 @@ static int read_https(struct blob *b, const char *s) curlx_str_passblanks(&s); if(curlx_str_word(&s, &word, UINT16_MAX)) { - logmsg("https: unable to read target qname, input=%s", s); + logmsg("[CONFIG] https: unable to read target qname, input=%s", s); return 1; } if(blob_add_qname(b, &word)) @@ -648,10 +469,11 @@ static void read_instructions(void) } /* reset defaults */ a_delay_ms = aaaa_delay_ms = https_delay_ms = 0; + rcode_a = rcode_aaaa = 0; blob_reset(&httpsrr); finfo_last = finfo; - logmsg("read instructions from %s", file); + logmsg("[CONFIG] reading from %s", file); f = curlx_fopen(file, FOPEN_READTEXT); if(f) { char buf[256]; @@ -710,69 +532,851 @@ static void read_instructions(void) rc = 1; } } + else if(!strncmp("Rcode-A: ", buf, 9)) { + curl_off_t code; + const char *pc = &buf[9]; + rc = 0; + if(!curlx_str_number(&pc, &code, 15)) { + rcode_a = (unsigned char)code; + rc = 1; + } + } + else if(!strncmp("Rcode-AAAA: ", buf, 12)) { + curl_off_t code; + const char *pc = &buf[12]; + rc = 0; + if(!curlx_str_number(&pc, &code, 15)) { + rcode_aaaa = (unsigned char)code; + rc = 1; + } + } else { /* accept empty line */ rc = buf[0] ? 0 : 1; } if(rc != 1) { - logmsg("Bad line in %s: '%s'\n", file, buf); + logmsg("[CONFIG] Bad line in %s: '%s'", file, buf); } else if(rtype) { - logmsg("added %s record via '%s'", rtype, buf); + logmsg("[CONFIG] added %s record via '%s'", rtype, buf); } } } - logmsg("set delays: A=%" FMT_TIMEDIFF_T "ms AAAA=%" FMT_TIMEDIFF_T - "ms HTTPS=%" FMT_TIMEDIFF_T "ms", + logmsg("[CONFIG] set delays: A=%" FMT_TIMEDIFF_T "ms AAAA=%" + FMT_TIMEDIFF_T "ms HTTPS=%" FMT_TIMEDIFF_T "ms", a_delay_ms, aaaa_delay_ms, https_delay_ms); curlx_fclose(f); } else - logmsg("Error opening file '%s'", file); + logmsg("[CONFIG] Error opening file '%s'", file); } -static int test_dnsd(int argc, const char **argv) +static int last_query_id = -1; + +static int dnsd_make_answer(struct blob *blob, int query_id, + const uint8_t *qbuf, size_t qlen, + uint16_t qtype, uint16_t id, + timediff_t *pdelay_ms) { - srvr_sockaddr_union_t me; - ssize_t n = 0; - int arg = 1; - uint16_t port = 9123; /* UDP */ - curl_socket_t sock = CURL_SOCKET_BAD; - int flag; - int rc; - int error; - char errbuf[STRERROR_LEN]; - int result = 0; - struct resp *resp; + int a; + char addrbuf[128]; /* IP address buffer */ + uint8_t header[12] = { + 0x80, 0xea, /* ID, overwrite */ + 0x81, 0x80, + /* Flags: 0x8180 Standard query response, No error + + 1... .... .... .... = Response: Message is a response + .000 0... .... .... = Opcode: Standard query (0) + .... .0.. .... .... = Authoritative: Server is not an authority for + domain + .... ..0. .... .... = Truncated: Message is not truncated + .... ...1 .... .... = Recursion desired: Do query recursively + .... .... 1... .... = Recursion available: Server can do recursive + queries + .... .... .0.. .... = Z: reserved (0) + .... .... ..0. .... = Answer authenticated: Answer/authority portion + was not authenticated by the server + .... .... ...0 .... = Non-authenticated data: Unacceptable + .... .... .... 0000 = Reply code: No error (0) + */ + 0x0, 0x1, /* QDCOUNT a single question */ + 0x0, 0x0, /* ANCOUNT number of answers */ + 0x0, 0x0, /* NSCOUNT */ + 0x0, 0x0 /* ARCOUNT */ + }; + uint16_t ancount = 0; + unsigned char rcode = 0; + CURLcode result; - pidname = ".dnsd.pid"; - serverlogfile = "log/dnsd.log"; - serverlogslocked = 0; + /* read once per incoming query, which is probably more than one + per test case */ + read_instructions(); - while(argc > arg) { - const char *opt; - curl_off_t num; - if(!strcmp("--verbose", argv[arg])) { - arg++; - /* nothing yet */ + switch(qtype) { + case QTYPE_A: + ancount = ancount_a; + *pdelay_ms = a_delay_ms; + rcode = rcode_a; + break; + case QTYPE_AAAA: + ancount = ancount_aaaa; + *pdelay_ms = aaaa_delay_ms; + rcode = rcode_aaaa; + break; + case QTYPE_HTTPS: + if(httpsrr.dlen) + ancount = 1; + *pdelay_ms = https_delay_ms; + break; + default: + *pdelay_ms = 0; + } + if(rcode) + ancount = 0; + + header[0] = (uint8_t)(id >> 8); + header[1] = (uint8_t)(id & 0xff); + + if(rcode) { + header[3] = (uint8_t)((header[3] & 0xf0) | (rcode & 0x0f)); + logmsg("[%d] response rcode %u", query_id, (unsigned int)rcode); + } + + header[6] = (uint8_t)(ancount >> 8); + header[7] = (uint8_t)(ancount & 0xff); + + if(blob_addn(blob, header, sizeof(header))) + return 1; + + if(blob_addn(blob, qbuf, qlen)) { + logmsg("unable to handle query of length %zu", qlen); + return 1; + } + + switch(qtype) { + case QTYPE_A: + for(a = 0; !rcode && (a < ancount_a); a++) { + const unsigned char *store = ipv4_pref; + if(add_answer(blob, store, sizeof(ipv4_pref), QTYPE_A)) + return 1; + result = curlx_inet_ntop(AF_INET, store, addrbuf, sizeof(addrbuf)); + logmsg("[%d] response A (%x) '%s' (%d)", query_id, + (unsigned int)QTYPE_A, result ? "?" : addrbuf, (int)result); } - else if(!strcmp("--version", argv[arg])) { - printf("dnsd IPv4%s\n", -#ifdef USE_IPV6 - "/IPv6" -#else - "" -#endif - ); - return 0; + if(!ancount_a) + logmsg("[%d] response A empty", query_id); + break; + case QTYPE_AAAA: + for(a = 0; !rcode && (a < ancount_aaaa); a++) { + const unsigned char *store = ipv6_pref; + if(add_answer(blob, store, sizeof(ipv6_pref), QTYPE_AAAA)) + return 1; + result = curlx_inet_ntop(AF_INET6, store, addrbuf, sizeof(addrbuf)); + logmsg("[%d] response AAAA (%x) '%s' (%d)", query_id, + (unsigned int)QTYPE_AAAA, result ? "?" : addrbuf, (int)result); } - else if(!strcmp("--pidfile", argv[arg])) { - arg++; - if(argc > arg) - pidname = argv[arg++]; + if(!ancount_aaaa) + logmsg("[%d] response AAAA empty", query_id); + break; + case QTYPE_HTTPS: + if(httpsrr.dlen) { + if(add_answer(blob, httpsrr.data, httpsrr.dlen, QTYPE_HTTPS)) { + logmsg("[%d] error adding https %zu response bytes", query_id, + httpsrr.dlen); + return 1; + } + logmsg("[%d] response HTTPS (%x), %zu bytes", query_id, + (unsigned int)QTYPE_HTTPS, httpsrr.dlen); } - else if(!strcmp("--portfile", argv[arg])) { - arg++; + else + logmsg("[%d] response HTTPS, no record", query_id); + break; + } + + return 0; +} + +struct udp_resp { + struct udp_resp *next; + int query_id; + struct curltime send_ts; + struct sockaddr addr; + curl_socklen_t addrlen; + struct blob body; +}; + +static struct udp_resp *udp_resp_queue; + +static CURLcode send_udp_resp(curl_socket_t sock, struct udp_resp *resp) +{ + ssize_t rc; + int sockerr = 0; + + do { + rc = sendto(sock, (const void *)resp->body.data, (SENDTO3)resp->body.dlen, + 0, &resp->addr, resp->addrlen); + } while((rc < 0) && ((sockerr = SOCKERRNO) == SOCKEINTR)); + if(rc < 0) { + char errbuf[STRERROR_LEN]; + logmsg("[%d-UDP] failed sending %zu bytes, error: (%d) %s", + resp->query_id, resp->body.dlen, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + return CURLE_SEND_ERROR; + } + else if(rc != (ssize_t)resp->body.dlen) { + logmsg("[%d-UDP] failed sending %zu bytes, sent: %zd", + resp->query_id, resp->body.dlen, rc); + return CURLE_SEND_ERROR; + } + logmsg("[%d-UDP] sent response", resp->query_id); + return CURLE_OK; +} + +static void queue_udp_resp(struct udp_resp *resp) +{ + struct udp_resp **panchor = &udp_resp_queue; + while(*panchor) { + timediff_t ms = curlx_ptimediff_ms(&(*panchor)->send_ts, &resp->send_ts); + if(ms > 0) /* resp is to be sent before *panchor */ + break; + panchor = &(*panchor)->next; + } + resp->next = *panchor; + *panchor = resp; +} + +static timediff_t queue_udp_next_ms(struct curltime *pnow) +{ + timediff_t next_ms = 1000; + struct udp_resp *r; + + for(r = udp_resp_queue; r; r = r->next) { + timediff_t ms = curlx_ptimediff_ms(&r->send_ts, pnow); + if((ms > 0) && (ms < next_ms)) + next_ms = ms; + else if(ms <= 0) + return 0; + } + return next_ms; +} + +static void queue_udp_send(curl_socket_t sock, struct curltime *pnow) +{ + struct udp_resp **panchor = &udp_resp_queue; + + while(*panchor) { + struct udp_resp *resp = *panchor; + timediff_t ms = curlx_ptimediff_ms(&resp->send_ts, pnow); + + /* if not due yet, break as response queue is time sorted */ + if(ms > 0) + break; + *panchor = resp->next; + send_udp_resp(sock, resp); + curlx_free(resp); + } +} + +static void queue_udp_clear(void) +{ + while(udp_resp_queue) { + struct udp_resp *resp = udp_resp_queue; + udp_resp_queue = resp->next; + curlx_free(resp); + } +} + +/* this is an answer to a question */ +static struct udp_resp *udp_resp_create(int query_id, + const struct sockaddr *addr, + curl_socklen_t addrlen, + const unsigned char *qbuf, size_t qlen, + uint16_t qtype, uint16_t id) +{ + struct udp_resp *resp; + timediff_t delay_ms = 0; + + resp = curlx_calloc(1, sizeof(*resp)); + if(!resp) + goto error; + + resp->query_id = query_id; + /* on some platforms `curl_socklen_t` is an `int`. Casting might + * wrap this, but then it still has to fit our record size. */ + if((size_t)addrlen > sizeof(resp->addr)) { + logmsg("[%d-UDP] unable to handle addrlen of %zu", + query_id, (size_t)addrlen); + goto error; + } + memcpy(&resp->addr, CURL_UNCONST(addr), addrlen); + resp->addrlen = addrlen; + + if(dnsd_make_answer(&resp->body, query_id, qbuf, qlen, qtype, id, &delay_ms)) + goto error; + + resp->send_ts = now_plus(delay_ms); + if(delay_ms > 0) + logmsg("[%d-UDP] delay response by %" FMT_TIMEDIFF_T "ms", + query_id, delay_ms); + return resp; + +error: + logmsg("[%d-UDP] failed to create response", query_id); + curlx_free(resp); + return NULL; +} + +static int udp_recv_req(curl_socket_t sock) +{ + srvr_sockaddr_union_t from; + curl_socklen_t fromlen; + uint8_t inbuffer[1500]; + uint8_t qbuf[256]; /* query storage */ + size_t qlen = 0; /* query size */ + struct udp_resp *resp; + uint16_t qtype = 0, id; + ssize_t n; + int result = 0; + + fromlen = sizeof(from); +#ifdef USE_IPV6 + if(socket_domain == AF_INET6) + fromlen = sizeof(from.sa6); + else +#endif + fromlen = sizeof(from.sa4); + + n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0, + &from.sa, &fromlen); + if(got_exit_signal) + goto out; + if(n < 0) { + logmsg("UDP, recvfrom error"); + result = 3; + goto out; + } + + ++last_query_id; + store_incoming("UDP", last_query_id, inbuffer, n, + qbuf, sizeof(qbuf), &qlen, &qtype, &id); + + set_advisor_read_lock(loglockfile); + serverlogslocked = 1; + + resp = udp_resp_create(last_query_id, &from.sa, fromlen, qbuf, + qlen, qtype, id); + if(!resp) + logmsg("[%d-UDP] error creating response", last_query_id); + else + queue_udp_resp(resp); + +out: + return result; +} + +#define MAX_DOH_CONNS 512 +#define MAX_DOH_INBUF_LEN (8 * 1024) +#define MAX_DOH_OUTBUF_LEN (8 * 1024) + +struct doh_conn { + curl_socket_t sock; + int index; + int query_id; + char inbuf[MAX_DOH_INBUF_LEN]; + size_t inblen; + size_t inbody_offset; + size_t inbody_len; + char outbuf[MAX_DOH_OUTBUF_LEN]; + size_t outblen; + struct curltime send_ts; + BIT(want_recv); + BIT(want_send); + BIT(close_pending); +}; + +static struct doh_conn doh_conns[MAX_DOH_CONNS]; + +static void doh_conns_init(void) +{ + int i; + for(i = 0; i < MAX_DOH_CONNS; ++i) { + doh_conns[i].sock = CURL_SOCKET_BAD; + doh_conns[i].index = i; + } +} + +static int doh_conns_add(curl_socket_t sock) +{ + int i; + for(i = 0; i < MAX_DOH_CONNS; ++i) { + if(doh_conns[i].sock == CURL_SOCKET_BAD) { + doh_conns[i].sock = sock; + doh_conns[i].want_recv = TRUE; + logmsg("[x-%d-DOH] accepted new connection, fd=%ld", i, (long)sock); + return 0; + } + } + logmsg("Too many open DoH connections, closing incoming."); + sclose(sock); + return 1; +} + +static void doh_conn_close(size_t i) +{ + if(i >= MAX_DOH_CONNS) + return; + if(doh_conns[i].sock != CURL_SOCKET_BAD) + sclose(doh_conns[i].sock); + doh_conns[i].sock = CURL_SOCKET_BAD; + doh_conns[i].want_recv = FALSE; + doh_conns[i].want_send = FALSE; + logmsg("[x-%d-DOH] connection closed", (int)i); +} + +static void doh_conns_close_all(void) +{ + size_t i; + for(i = 0; i < MAX_DOH_CONNS; ++i) { + doh_conn_close(i); + } +} + +static void doh_conns_fdsets(fd_set *readfds, fd_set *writefds, + struct curltime *pnow, + int *pmaxfd, + timediff_t *ptimeout_ms) +{ + size_t i; + for(i = 0; i < MAX_DOH_CONNS; ++i) { + struct doh_conn *c = &doh_conns[i]; + if((c->sock != CURL_SOCKET_BAD)) { + if(!c->want_send && c->outblen) { /* check delayed send */ + timediff_t ms = curlx_ptimediff_ms(&c->send_ts, pnow); + if(ms <= 0) + c->want_send = TRUE; + else if((ms < *ptimeout_ms) || !*ptimeout_ms) + *ptimeout_ms = ms; + } + if(c->want_send) + FD_SET(c->sock, writefds); + if(c->want_recv) + FD_SET(c->sock, readfds); + + if((FD_ISSET(c->sock, readfds) || FD_ISSET(c->sock, writefds)) && + (int)c->sock > *pmaxfd) + *pmaxfd = (int)c->sock; + } + } +} + +static int doh_conn_send(struct doh_conn *c, struct curltime *pnow) +{ + char errbuf[STRERROR_LEN]; + ssize_t rc; + int sockerr; + + if(c->outblen) { + timediff_t ms = curlx_ptimediff_ms(&c->send_ts, pnow); + size_t n; + + if(ms > 0) { /* not due yet */ + c->want_send = FALSE; + goto out; + } + + rc = swrite(c->sock, c->outbuf, c->outblen); + if(rc < 0) { + sockerr = SOCKERRNO; + if((sockerr == SOCKEINPROGRESS) || SOCK_EAGAIN(sockerr)) + return 0; + sockerr = SOCKERRNO; + logmsg("[%d-%d-DOH] swrite(%ld) failed with error (%d) %s", + c->query_id, c->index, (long)c->sock, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + return 1; + } + n = (size_t)rc; + if(n >= c->outblen) { /* all sent */ + c->outblen = 0; + logmsg("[%d-%d-DOH] last response byte sent", c->query_id, c->index); + } + else { + c->outblen -= n; + memmove(c->outbuf, c->outbuf + n, c->outblen); + } + } + +out: + if(!c->outblen) { + c->want_send = FALSE; + if(c->close_pending) + return 1; + else { + logmsg("[x-%d-DOH] switching to recv", c->index); + c->want_recv = TRUE; + } + } + return 0; +} + +static const char *http_status_descr(int http_status) +{ + switch(http_status) { + case 200: + return "Ok"; + case 400: + return "Bad Request"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + default: + if(http_status < 100) + return "This is wrong"; + if(http_status < 200) + return "Just Kidding"; + if(http_status < 300) + return "Lgtm"; + if(http_status < 400) + return "Follow Me For More Requests"; + if(http_status < 500) + return "You were wrong"; + return "We did something wrong"; + } +} + +static int doh_conn_send_err(struct doh_conn *c, int http_status) +{ + c->inblen = 0; + c->close_pending = TRUE; + c->want_recv = FALSE; + if(c->outblen) { + logmsg("[%d-%d-DOH] error sending response with %zu bytes still outgoing", + c->query_id, c->index, c->outblen); + return 1; + } + + memset(c->outbuf, 0, sizeof(c->outbuf)); + snprintf(c->outbuf, sizeof(c->outbuf) - 1, + "HTTP/1.1 %d %s\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n", + http_status, http_status_descr(http_status)); + c->outblen = strlen(c->outbuf); + c->want_send = TRUE; + logmsg("[%d-%d-DOH] sending HTTP response %d", + c->query_id, c->index, http_status); + return 0; +} + +static int doh_conn_send_answer(struct doh_conn *c, struct blob *body, + timediff_t delay_ms) +{ + if(c->outblen) { /* Should not happen */ + logmsg("[%d-%d-DOH] trying to send an answer with outbuf still having " + "%zu bytes", c->query_id, c->index, c->outblen); + return 1; + } + memset(c->outbuf, 0, sizeof(c->outbuf)); + snprintf(c->outbuf, sizeof(c->outbuf) - 1, + "HTTP/1.1 200 %s\r\n" + "Server: curl/test-dnsd\r\n" + "Date: Thu, 06 Aug 2026 08:42:00 GMT\r\n" + "Content-Type: application/dns-message\r\n" + "Content-Length: %ld\r\n" + "\r\n", + http_status_descr(200), (long)body->dlen); + c->outblen = strlen(c->outbuf); + if((c->outblen + body->dlen) > sizeof(c->outbuf)) { + logmsg("[%d-%d-DOH] response size of %zu too large for outbuf", + c->query_id, c->index, c->outblen + body->dlen); + c->outblen = 0; + return 1; + } + memcpy(c->outbuf + c->outblen, body->data, body->dlen); + c->outblen += body->dlen; + c->send_ts = now_plus(delay_ms); + if(delay_ms > 0) + logmsg("[%d-%d-DOH] delay response by %" FMT_TIMEDIFF_T "ms", + c->query_id, c->index, delay_ms); + else + c->want_send = TRUE; + c->want_recv = FALSE; + logmsg("[%d-%d-DOH] sending HTTP response 200", + c->query_id, c->index); + return 0; +} + +static int doh_req_parse_headers(const char **pstr, + size_t *pcontent_length, + bool *pcomplete) +{ + static const struct Curl_str HD_content_length = { + STRCONST("Content-Length:") + }; + static const struct Curl_str HD_content_type = { + STRCONST("Content-Type:") + }; + static const struct Curl_str HD_wanted_type = { + STRCONST("application/dns-message") + }; + const char *p = *pstr; + bool ct_ok = FALSE; + bool cl_ok = FALSE; + bool eoh = FALSE; + + *pcomplete = FALSE; + *pcontent_length = 0; + while(p[0]) { + const char *nl, *start = p; + struct Curl_str hd_name, hd_val; + + if((p[0] == '\r') && (p[1] == '\n')) { + p += 2; + eoh = TRUE; + break; + } + nl = strchr(p, '\n'); + if(!nl) /* incomplete */ + break; + if(curlx_str_word(&p, &hd_name, 1024) || + curlx_str_singlespace(&p) || + curlx_str_untilnl(&p, &hd_val, 1024) || + curlx_str_newline(&p) || + curlx_str_newline(&p)) { + logmsg("unrecognized request header '%.*s'", + (int)(nl - start), start); + return 1; + } + if(curlx_str_case_equal(&HD_content_type, &hd_name)) { + if(!curlx_str_case_equal(&HD_wanted_type, &hd_val)) { + logmsg("wrong content-type: '%.*s'", (int)hd_val.len, hd_val.str); + return 1; + } + ct_ok = TRUE; + } + else if(curlx_str_case_equal(&HD_content_length, &hd_name)) { + const char *s = hd_val.str; + curl_off_t offt; + if(curlx_str_number(&s, &offt, 4096)) { + logmsg("wrong content-length: '%.*s'", (int)hd_val.len, hd_val.str); + return 1; + } + *pcontent_length = (size_t)offt; + cl_ok = TRUE; + } + else { + /* ignore this header */ + } + } + + *pstr = p; + if(!eoh) + return 0; /* need more */ + if(!ct_ok) { + logmsg("request missing Content-Type"); + return 1; + } + if(!cl_ok) { + logmsg("request missing Content-Length"); + return 1; + } + *pcomplete = TRUE; + return 0; +} + +static int doh_conn_do_req(struct doh_conn *c, bool eos) +{ + static const struct Curl_str DOH_PROTO = { STRCONST("HTTP/1.1") }; + static const struct Curl_str DOH_METHOD = { STRCONST("POST") }; + static const struct Curl_str DOH_PATH = { STRCONST("/") }; + const char *first_nl; + bool complete = FALSE; + + if(!c->inblen && eos) + return 1; + + if(!c->inbody_offset) { + first_nl = strchr(c->inbuf, '\n'); + if(first_nl) { + /* This is a poor man's HTTP/1.1 parser and we should rather have + * one in curlx that we can share. */ + const char *p = c->inbuf; + struct Curl_str method, path, proto; + + if(curlx_str_word(&p, &method, 1024) || curlx_str_singlespace(&p)) { + logmsg("[x-%d-DOH] unrecognized first request line method '%.*s'", + c->index, (int)(first_nl - c->inbuf), c->inbuf); + return 1; + } + if(curlx_str_word(&p, &path, 1024) || curlx_str_singlespace(&p)) { + logmsg("[x-%d-DOH] unrecognized first request line path '%.*s'", + c->index, (int)(first_nl - c->inbuf), c->inbuf); + return 1; + } + if(curlx_str_untilnl(&p, &proto, 1024) || + curlx_str_newline(&p) || + curlx_str_newline(&p)) { + logmsg("[x-%d-DOH] unrecognized first request line proto '%.*s'", + c->index, (int)(first_nl - c->inbuf), c->inbuf); + return 1; + } + if(!curlx_str_case_equal(&DOH_PROTO, &proto)) { + logmsg("[x-%d-DOH] unrecognized request protocol '%.*s'", + c->index, (int)proto.len, proto.str); + return 1; + } + if(!curlx_str_case_equal(&DOH_METHOD, &method)) { + logmsg("[x-%d-DOH] unsupported request method '%.*s'", + c->index, (int)method.len, method.str); + return doh_conn_send_err(c, 405); + } + if(!curlx_str_case_equal(&DOH_PATH, &path)) { + logmsg("[x-%d-DOH] request path not fond '%.*s'", + c->index, (int)path.len, path.str); + return doh_conn_send_err(c, 404); + } + if(doh_req_parse_headers(&p, &c->inbody_len, &complete)) { + return doh_conn_send_err(c, 400); + } + /* Looks ok, remember the start of the body bytes */ + c->inbody_offset = (p - c->inbuf); + } + if(!complete) + return eos ? 1 : 0; /* want more, error if client close */ + } + + if(c->inblen >= (c->inbody_offset + c->inbody_len)) { + /* We have all bytes for processing the request */ + uint8_t qbuf[256]; /* query storage */ + size_t qlen = 0; /* query size */ + size_t rlen = c->inbody_offset + c->inbody_len; /* request size */ + uint16_t qtype = 0, id; + struct blob blob; + timediff_t delay_ms; + + c->query_id = ++last_query_id; + if(store_incoming("DoH", c->query_id, (const uint8_t *)c->inbuf + + c->inbody_offset, c->inbody_len, + qbuf, sizeof(qbuf), &qlen, &qtype, &id)) { + logmsg("[%d-%d-DOH] error storing incoming request", + c->query_id, c->index); + return doh_conn_send_err(c, 400); + } + + /* remove handled request from inbuf */ + if(rlen >= c->inblen) + c->inblen = 0; + else { + memmove(c->inbuf, c->inbuf + rlen, c->inblen - rlen); + c->inblen -= rlen; + } + c->inbody_len = c->inbody_offset = 0; + + memset(&blob, 0, sizeof(blob)); + if(dnsd_make_answer(&blob, c->query_id, qbuf, qlen, qtype, id, + &delay_ms)) { + return doh_conn_send_err(c, 500); + } + return doh_conn_send_answer(c, &blob, delay_ms); + } + return 0; +} + +static int doh_conn_recv(struct doh_conn *c) +{ + char errbuf[STRERROR_LEN]; + ssize_t n; + int sockerr; + + n = sread(c->sock, c->inbuf + c->inblen, sizeof(c->inbuf) - 1 - c->inblen); + if(n < 0) { + sockerr = SOCKERRNO; + if((sockerr == SOCKEINPROGRESS) || SOCK_EAGAIN(sockerr)) + return 0; + sockerr = SOCKERRNO; + logmsg("[x-%d-DOH] sread() failed with error (%d) %s", c->index, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + return 1; + } + else if(n == 0) { + logmsg("[x-%d-DOH] sread() == 0, client closed connection", c->index); + return doh_conn_do_req(c, TRUE); + } + else { + c->inblen += (size_t)n; + c->inbuf[c->inblen] = 0; + logmsg("[x-%d-DOH] sread() == %zu, processing", c->index, (size_t)n); + return doh_conn_do_req(c, FALSE); + } +} + +static int doh_conns_serve(fd_set *readfds, fd_set *writefds) +{ + struct curltime now; + size_t i; + + for(i = 0; i < MAX_DOH_CONNS; ++i) { + struct doh_conn *c = &doh_conns[i]; + if(c->sock != CURL_SOCKET_BAD) { + if(c->want_send && FD_ISSET(c->sock, writefds)) { + now = curlx_now(); + if(doh_conn_send(c, &now)) { + doh_conn_close(i); + continue; + } + } + if(c->want_recv && FD_ISSET(c->sock, readfds)) { + if(doh_conn_recv(c)) { + doh_conn_close(i); + continue; + } + } + } + } + return 0; +} + +static int test_dnsd(int argc, const char **argv) +{ + int arg = 1; + curl_socket_t sock_udp = CURL_SOCKET_BAD; + curl_socket_t sock_tcp_listen = CURL_SOCKET_BAD; + char errbuf[STRERROR_LEN]; + int rc, sockerr; + int result = 0; + + pidname = ".dnsd.pid"; + serverlogfile = "log/dnsd.log"; + serverlogslocked = 0; + server_port = 9123; /* UDP */ + socket_domain = AF_INET; + + while(argc > arg) { + const char *opt; + curl_off_t num; + if(!strcmp("--verbose", argv[arg])) { + arg++; + /* nothing yet */ + } + else if(!strcmp("--version", argv[arg])) { + printf("dnsd IPv4%s\n", +#ifdef USE_IPV6 + "/IPv6" +#else + "" +#endif + ); + return 0; + } + else if(!strcmp("--pidfile", argv[arg])) { + arg++; + if(argc > arg) + pidname = argv[arg++]; + } + else if(!strcmp("--portfile", argv[arg])) { + arg++; if(argc > arg) portname = argv[arg++]; } @@ -787,16 +1391,14 @@ static int test_dnsd(int argc, const char **argv) logdir = argv[arg++]; } else if(!strcmp("--ipv4", argv[arg])) { -#ifdef USE_IPV6 - ipv_inuse = "IPv4"; - use_ipv6 = FALSE; -#endif + socket_type = "IPv4"; + socket_domain = AF_INET; arg++; } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 - ipv_inuse = "IPv6"; - use_ipv6 = TRUE; + socket_type = "IPv6"; + socket_domain = AF_INET6; #endif arg++; } @@ -805,7 +1407,7 @@ static int test_dnsd(int argc, const char **argv) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - port = (uint16_t)num; + server_port = (uint16_t)num; arg++; } } @@ -826,103 +1428,18 @@ static int test_dnsd(int argc, const char **argv) } snprintf(loglockfile, sizeof(loglockfile), "%s/%s/dnsd-%s.lock", - logdir, SERVERLOGS_LOCKDIR, ipv_inuse); + logdir, SERVERLOGS_LOCKDIR, socket_type); -#ifdef USE_IPV6 - if(!use_ipv6) -#endif - sock = socket(AF_INET, SOCK_DGRAM, 0); -#ifdef USE_IPV6 - else - sock = socket(AF_INET6, SOCK_DGRAM, 0); -#endif + install_signal_handlers(FALSE); - if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; - logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; + result = open_stream_sock(&sock_tcp_listen, &server_port); + if(result) goto dnsd_cleanup; - } - - flag = 1; - if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { - error = SOCKERRNO; - logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; - goto dnsd_cleanup; - } + doh_conns_init(); -#ifdef USE_IPV6 - if(!use_ipv6) { -#endif - memset(&me.sa4, 0, sizeof(me.sa4)); - me.sa4.sin_family = AF_INET; - me.sa4.sin_addr.s_addr = INADDR_ANY; - me.sa4.sin_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa4)); -#ifdef USE_IPV6 - } - else { - memset(&me.sa6, 0, sizeof(me.sa6)); - me.sa6.sin6_family = AF_INET6; - me.sa6.sin6_addr = in6addr_any; - me.sa6.sin6_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa6)); - } -#endif /* USE_IPV6 */ - if(rc) { - error = SOCKERRNO; - logmsg("Error binding socket on port %hu (%d) %s", port, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; + result = open_udp_sock(&sock_udp, &server_port); + if(result) goto dnsd_cleanup; - } - - if(!port) { - /* The system was supposed to choose a port number, figure out which - port we actually got and update the listener port value with it. */ - curl_socklen_t la_size; - srvr_sockaddr_union_t localaddr; - memset(&localaddr, 0, sizeof(localaddr)); -#ifdef USE_IPV6 - if(!use_ipv6) -#endif - la_size = sizeof(localaddr.sa4); -#ifdef USE_IPV6 - else - la_size = sizeof(localaddr.sa6); -#endif - if(getsockname(sock, &localaddr.sa, &la_size) < 0) { - error = SOCKERRNO; - logmsg("getsockname() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - sclose(sock); - goto dnsd_cleanup; - } - switch(localaddr.sa.sa_family) { - case AF_INET: - port = ntohs(localaddr.sa4.sin_port); - break; -#ifdef USE_IPV6 - case AF_INET6: - port = ntohs(localaddr.sa6.sin6_port); - break; -#endif - default: - break; - } - if(!port) { - /* Real failure, listener port shall not be zero beyond this point. */ - logmsg("Apparently getsockname() succeeded, with listener port zero."); - logmsg("A valid reason for this failure is a binary built without"); - logmsg("proper network library linkage. This might not be the only"); - logmsg("reason, but double check it before anything else."); - result = 2; - goto dnsd_cleanup; - } - } dnsd_wrotepidfile = write_pidfile(pidname); if(!dnsd_wrotepidfile) { @@ -931,84 +1448,83 @@ static int test_dnsd(int argc, const char **argv) } if(portname) { - dnsd_wroteportfile = write_portfile(portname, port); + dnsd_wroteportfile = write_portfile(portname, server_port); if(!dnsd_wroteportfile) { result = 1; goto dnsd_cleanup; } } - logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); - curlx_nonblock(sock, TRUE); + /* start accepting connections */ + if(listen(sock_tcp_listen, 50)) { + sockerr = SOCKERRNO; + logmsg("listen() failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto dnsd_cleanup; + } + + logmsg("Running %s on port UDP+TCP/%u", socket_type, server_port); + curlx_nonblock(sock_udp, TRUE); + curlx_nonblock(sock_tcp_listen, TRUE); for(;;) { - uint16_t id = 0; - uint8_t inbuffer[1500]; - srvr_sockaddr_union_t from; - curl_socklen_t fromlen; - uint8_t qbuf[256]; /* query storage */ - size_t qlen = 0; /* query size */ - uint16_t qtype = 0; timediff_t timeout_ms = 0; - fromlen = sizeof(from); -#ifdef USE_IPV6 - if(!use_ipv6) -#endif - fromlen = sizeof(from.sa4); -#ifdef USE_IPV6 - else - fromlen = sizeof(from.sa6); -#endif + fd_set readfds, writefds; + struct timeval tv; + int maxfd = 0; + struct curltime now = curlx_now(); - timeout_ms = send_resp_queue(sock); + FD_ZERO(&readfds); + fdset_add_sock(&readfds, sock_udp, &maxfd); + fdset_add_sock(&readfds, sock_tcp_listen, &maxfd); - { - fd_set readfds; - struct timeval tv; - int maxfd = (int)sock; + FD_ZERO(&writefds); + timeout_ms = queue_udp_next_ms(&now); + if(!timeout_ms) + fdset_add_sock(&writefds, sock_udp, &maxfd); - FD_ZERO(&readfds); - FD_SET(sock, &readfds); - if(!timeout_ms || (timeout_ms > 100)) - timeout_ms = 100; + doh_conns_fdsets(&readfds, &writefds, &now, &maxfd, &timeout_ms); - rc = select(maxfd + 1, &readfds, NULL, NULL, - curlx_mstotv(&tv, timeout_ms)); + if(!timeout_ms || (timeout_ms > 100)) + timeout_ms = 100; - if(rc == -1) { - logmsg("error %d returned by select()", SOCKERRNO); - } - else if(!rc) { /* timeout */ - continue; - } + rc = select(maxfd + 1, &readfds, &writefds, NULL, + curlx_mstotv(&tv, timeout_ms)); + + if(rc == -1) { + logmsg("error %d returned by select()", SOCKERRNO); } - n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0, - &from.sa, &fromlen); - if(got_exit_signal) - break; - if(n < 0) { - logmsg("recvfrom"); - result = 3; - break; + else if(!rc) { /* timeout */ + continue; } - /* read once per incoming query, which is probably more than one - per test case */ - read_instructions(); - - ++query_id; - store_incoming(query_id, inbuffer, n, - qbuf, sizeof(qbuf), &qlen, &qtype, &id); + if(FD_ISSET(sock_udp, &writefds)) { + now = curlx_now(); + queue_udp_send(sock_udp, &now); + } + if(FD_ISSET(sock_udp, &readfds)) { + result = udp_recv_req(sock_udp); + if(result) + break; + } - set_advisor_read_lock(loglockfile); - serverlogslocked = 1; + result = doh_conns_serve(&readfds, &writefds); + if(result) + break; - resp = create_resp(query_id, &from.sa, fromlen, qbuf, - qlen, qtype, id); - if(!resp) - logmsg("error creating response"); - else - queue_resp(resp); + if(FD_ISSET(sock_tcp_listen, &readfds)) { + /* Service all queued connections */ + curl_socket_t sock_conn; + while(TRUE) { + sock_conn = accept_connection(sock_tcp_listen); + if(!sock_conn) /* no more connections to accept */ + break; + if(sock_conn == CURL_SOCKET_BAD) + goto dnsd_cleanup; + doh_conns_add(sock_conn); + } + } if(got_exit_signal) break; @@ -1020,14 +1536,12 @@ static int test_dnsd(int argc, const char **argv) } dnsd_cleanup: + if(sock_udp != CURL_SOCKET_BAD) + sclose(sock_udp); + if(sock_tcp_listen != CURL_SOCKET_BAD) + sclose(sock_tcp_listen); -#if 0 - if((peer != sock) && (peer != CURL_SOCKET_BAD)) - sclose(peer); -#endif - - if(sock != CURL_SOCKET_BAD) - sclose(sock); + doh_conns_close_all(); if(got_exit_signal) logmsg("signalled to die"); @@ -1042,20 +1556,18 @@ static int test_dnsd(int argc, const char **argv) clear_advisor_read_lock(loglockfile); } - clear_resp_queue(); - restore_signal_handlers(true); + queue_udp_clear(); - if(got_exit_signal) { - logmsg("========> %s dnsd (port: %d pid: %ld) exits with signal (%d)", - ipv_inuse, (int)port, (long)our_getpid(), exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } + restore_signal_handlers(FALSE); - logmsg("========> dnsd quits"); return result; } +#else +static int test_dnsd(int argc, const char **argv) +{ + (void)argc; + (void)argv; + fprintf(stderr, "dnsd on AmigaOS is unsupported\n"); + return 1; +} +#endif diff --git a/tests/server/first.c b/tests/server/first.c index c57617249885..fa233914b76c 100644 --- a/tests/server/first.c +++ b/tests/server/first.c @@ -25,10 +25,11 @@ #include -int main(int argc, const char **argv) +int main(int argc, const char *argv[]) { entry_func_t entry_func; const char *entry_name; + int result; size_t tmp; if(argc < 2) { @@ -39,7 +40,7 @@ int main(int argc, const char **argv) entry_name = argv[1]; entry_func = NULL; for(tmp = 0; s_entries[tmp].ptr; ++tmp) { - if(strcmp(entry_name, s_entries[tmp].name) == 0) { + if(!strcmp(entry_name, s_entries[tmp].name)) { entry_func = s_entries[tmp].ptr; break; } @@ -55,5 +56,38 @@ int main(int argc, const char **argv) return 2; #endif - return entry_func(argc - 1, argv + 1); + result = entry_func(argc - 1, argv + 1); + + if(serverlogfile && exit_msg) + logmsg("========> exit message: %s", exit_msg); + + if(got_exit_signal) { + char port_str[11]; + const char *location_str = port_str; + snprintf(port_str, sizeof(port_str), "port %hu", server_port); + +#ifdef USE_UNIX_SOCKETS + if(socket_domain == AF_UNIX) + location_str = server_unix_socket ? server_unix_socket + : ""; +#endif + + logmsg("========> %s %s (%s pid: %ld) exits with signal (%d)", + socket_type, entry_name, + location_str, (long)our_getpid(), exit_signal); + +#ifndef _WIN32 + /* + * To properly set the return status of the process we + * must raise the same signal SIGINT or SIGTERM that we + * caught and let the old handler take care of it. + */ + raise(exit_signal); +#endif + } + + if(serverlogfile) + logmsg("========> %s quits", entry_name); + + return result; } diff --git a/tests/server/first.h b/tests/server/first.h index ad5cec41b392..1de9af74b793 100644 --- a/tests/server/first.h +++ b/tests/server/first.h @@ -141,11 +141,14 @@ extern int write_pidfile(const char *filename); extern int write_portfile(const char *filename, int port); extern void set_advisor_read_lock(const char *filename); extern void clear_advisor_read_lock(const char *filename); +extern void storerequest(const char *reqbuf, size_t totalsize, + const char *filename); static volatile int got_exit_signal = 0; static volatile int exit_signal = 0; #ifdef _WIN32 static HANDLE exit_event = NULL; #endif +static volatile const char *exit_msg = NULL; extern void install_signal_handlers(bool keep_sigalrm); extern void restore_signal_handlers(bool keep_sigalrm); #ifdef USE_UNIX_SOCKETS @@ -153,9 +156,17 @@ extern int bind_unix_socket(curl_socket_t sock, const char *unix_socket, struct sockaddr_un *sau); #endif extern curl_socket_t sockdaemon(curl_socket_t sock, - unsigned short *listenport, + uint16_t *listenport, const char *unix_socket, bool bind_only); +extern int open_udp_sock(curl_socket_t *psock, uint16_t *pport); +extern int open_stream_sock(curl_socket_t *psock, uint16_t *pport); +extern curl_socket_t accept_connection(curl_socket_t listen_sock); +extern bool curlx_str_case_equal(const struct Curl_str *s1, + const struct Curl_str *s2); + +/* returns true if the current socket is an IP one */ +extern bool socket_domain_is_ip(void); /* global variables */ static const char *srcpath = "."; /* pointing to the test directory */ @@ -166,11 +177,8 @@ static int serverlogslocked; static const char *configfile = NULL; static const char *logdir = "log"; static char loglockfile[256]; -#ifdef USE_IPV6 -static bool use_ipv6 = FALSE; -#endif -static const char *ipv_inuse = "IPv4"; -static unsigned short server_port = 0; +static const char *server_unix_socket = NULL; +static uint16_t server_port = 0; static const char *socket_type = "IPv4"; static int socket_domain = AF_INET; diff --git a/tests/server/getpart.c b/tests/server/getpart.c index fe3212a15ce7..0cc95f633366 100644 --- a/tests/server/getpart.c +++ b/tests/server/getpart.c @@ -67,7 +67,7 @@ static size_t line_length(const char *buffer, int bytestocheck) * * Calling function may call this multiple times with same 'buffer' * and 'bufsize' pointers to avoid multiple buffer allocations. Buffer - * will be reallocated and 'bufsize' increased until whole line fits in + * is reallocated and 'bufsize' increased until whole line fits in * buffer before returning it. * * Calling function is responsible to free allocated buffer. @@ -132,7 +132,7 @@ static int readline(char **buffer, size_t *bufsize, size_t *length, * decoded data, binary or whatever, to the destination. The source buffer * may not hold binary data, only a null-terminated string is valid content. * - * Destination buffer will be enlarged and relocated as needed. + * Destination buffer is enlarged and relocated as needed. * * Calling function is responsible to provide preallocated destination * buffer and also to deallocate it when no longer needed. @@ -202,7 +202,7 @@ static int decodedata(char **buf, /* dest buffer */ /* * currently there is no way to tell apart an OOM condition in * curlx_base64_decode() from zero length decoded data. For now, - * let's just assume it is an OOM condition, currently we have + * let's assume it is an OOM condition, currently we have * no input for this function that decodes to zero length data. */ free(buf64); @@ -230,8 +230,8 @@ static int decodedata(char **buf, /* dest buffer */ * Data is returned in a dynamically allocated buffer, a pointer to this data * and the size of the data is stored at the addresses that caller specifies. * - * If the returned data is a string the returned size will be the length of - * the string excluding null-termination. Otherwise it will just be the size + * If the returned data is a string the returned size is the length of + * the string excluding null-termination. Otherwise it is the size * of the returned binary data. * * Calling function is responsible to free returned buffer. diff --git a/tests/server/mqttd.c b/tests/server/mqttd.c index 0a3a6ee02499..766b86ca8a32 100644 --- a/tests/server/mqttd.c +++ b/tests/server/mqttd.c @@ -40,6 +40,7 @@ /* #define MQTT_MSG_PUBACK 0x40 */ #define MQTT_MSG_SUBSCRIBE 0x82 #define MQTT_MSG_SUBACK 0x90 +#define MQTT_MSG_PINGRESP 0xd0 #define MQTT_MSG_DISCONNECT 0xe0 struct mqttd_configurable { @@ -49,6 +50,8 @@ struct mqttd_configurable { bool publish_before_suback; bool short_publish; bool excessive_remaining; + bool pingresp_as_connack; /* send PINGRESP with payload instead of CONNACK */ + bool disconnect_malformed; /* DISCONNECT with nonzero remlen */ unsigned char error_connack; unsigned char remlen_connack; }; @@ -65,6 +68,8 @@ static void mqttd_resetdefaults(void) m_config.publish_before_suback = FALSE; m_config.short_publish = FALSE; m_config.excessive_remaining = FALSE; + m_config.pingresp_as_connack = FALSE; + m_config.disconnect_malformed = FALSE; m_config.error_connack = 0; m_config.remlen_connack = 0; m_config.testnum = 0; @@ -98,6 +103,14 @@ static void mqttd_getconfig(void) logmsg("short-PUBLISH set"); m_config.short_publish = TRUE; } + else if(!strcmp(key, "PINGRESP-as-CONNACK")) { + logmsg("PINGRESP-as-CONNACK set"); + m_config.pingresp_as_connack = TRUE; + } + else if(!strcmp(key, "DISCONNECT-malformed")) { + logmsg("DISCONNECT-malformed set"); + m_config.disconnect_malformed = TRUE; + } else if(!strcmp(key, "error-CONNACK")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { @@ -165,18 +178,28 @@ static int connack(FILE *dump, curl_socket_t fd) MQTT_MSG_CONNACK, 0x02, 0x00, 0x00 }; + const char *label = "CONNACK"; ssize_t rc; + if(m_config.pingresp_as_connack) { + /* Send a PINGRESP (0xD0) with remaining_length=2 and payload + mimicking a successful CONNACK. MQTT 3.1.1 s. 3.13.1 requires + PINGRESP to have remaining_length=0, so this is malformed. */ + packet[0] = MQTT_MSG_PINGRESP; + label = "PINGRESP-as-CONNACK"; + logmsg("Sending malformed PINGRESP in place of CONNACK"); + } + if(m_config.remlen_connack) packet[1] = m_config.remlen_connack; packet[3] = m_config.error_connack; rc = swrite(fd, packet, sizeof(packet)); if(rc > 0) { - logmsg("WROTE %zd bytes [CONNACK]", rc); + logmsg("WROTE %zd bytes [%s]", rc, label); loghex(packet, rc); - logprotocol(FROM_SERVER, "CONNACK", packet[1], dump, - packet, sizeof(packet)); + logprotocol(FROM_SERVER, label, packet[1], dump, + packet, rc); } if(rc == sizeof(packet)) { return 0; @@ -222,7 +245,7 @@ static int puback(FILE *dump, curl_socket_t fd, unsigned short packetid) if(rc == sizeof(packet)) { logmsg("WROTE %zd bytes [PUBACK]", rc); loghex(packet, rc); - logprotocol(FROM_SERVER, dump, packet, rc); + logprotocol(FROM_SERVER, "PUBACK", 0, dump, packet, rc); return 0; } logmsg("Failed sending [PUBACK]"); @@ -235,20 +258,35 @@ static int disconnect(FILE *dump, curl_socket_t fd) { unsigned char packet[] = { MQTT_MSG_DISCONNECT, 0x00, + 0x00, 0x00 /* extra bytes for malformed variant */ }; - ssize_t rc = swrite(fd, packet, sizeof(packet)); - if(rc == sizeof(packet)) { - logmsg("WROTE %zd bytes [DISCONNECT]", rc); + const char *label = "DISCONNECT"; + size_t pktlen = 2; + ssize_t rc; + + if(m_config.disconnect_malformed) { + /* Send DISCONNECT with remaining_length=2 (must be 0 per spec) */ + packet[1] = 0x02; + pktlen = 4; + label = "DISCONNECT-malformed"; + logmsg("Sending malformed DISCONNECT with nonzero remaining_length"); + } + + rc = swrite(fd, packet, pktlen); + if(rc > 0) { + logmsg("WROTE %zd bytes [%s]", rc, label); loghex(packet, rc); - logprotocol(FROM_SERVER, "DISCONNECT", 0, dump, packet, rc); + logprotocol(FROM_SERVER, label, packet[1], dump, packet, rc); + } + if(rc == (ssize_t)pktlen) { return 0; } - logmsg("Failed sending [DISCONNECT]"); + logmsg("Failed sending [%s]", label); return 1; } /* - do + do encodedByte = X MOD 128 X = X DIV 128 @@ -256,16 +294,13 @@ static int disconnect(FILE *dump, curl_socket_t fd) // if there are more data to encode, set the top bit of this byte if ( X > 0 ) - encodedByte = encodedByte OR 128 + endif - endif - - 'output' encodedByte + 'output' encodedByte - while ( X > 0 ) - -*/ + while ( X > 0 ) + */ /* return number of bytes used */ static size_t encode_length(size_t packetlen, @@ -378,10 +413,10 @@ static int publish(FILE *dump, static char topic[MAX_TOPIC_LENGTH + 1]; -static int fixedheader(curl_socket_t fd, - unsigned char *bytep, - size_t *remaining_lengthp, - size_t *remaining_length_bytesp) +static bool fixedheader(curl_socket_t fd, + unsigned char *bytep, + size_t *remaining_lengthp, + size_t *remaining_length_bytesp) { /* get the fixed header */ unsigned char buffer[10]; @@ -391,7 +426,7 @@ static int fixedheader(curl_socket_t fd, size_t i; if(rc < 2) { logmsg("READ %zd bytes [SHORT!]", rc); - return 1; /* fail */ + return FALSE; /* fail */ } logmsg("READ %zd bytes", rc); loghex(buffer, rc); @@ -404,20 +439,19 @@ static int fixedheader(curl_socket_t fd, rc = sread(fd, &buffer[i], 1); if(rc != 1) { logmsg("Remaining Length broken"); - return 1; + return FALSE; } } *remaining_lengthp = decode_length(&buffer[1], i, remaining_length_bytesp); logmsg("Remaining Length: %zu [%zu bytes]", *remaining_lengthp, *remaining_length_bytesp); - return 0; + return TRUE; } static curl_socket_t mqttit(curl_socket_t fd) { size_t buff_size = 10 * 1024; unsigned char *buffer = NULL; - ssize_t rc; unsigned char byte; unsigned short packet_id; size_t payload_len; @@ -462,10 +496,10 @@ static curl_socket_t mqttit(curl_socket_t fd) const size_t client_id_offset = 12; size_t start_usr; size_t start_passwd; + ssize_t rc = 0; /* get the fixed header */ - rc = fixedheader(fd, &byte, &remaining_length, &bytes); - if(rc) + if(!fixedheader(fd, &byte, &remaining_length, &bytes)) break; if(remaining_length >= buff_size) { @@ -604,8 +638,8 @@ static curl_socket_t mqttit(curl_socket_t fd) } } else { - const char *def = "this is random payload yes yes it is"; - publish(dump, fd, packet_id, topic, def, strlen(def)); + static const char def[] = "this is random payload yes yes it is"; + publish(dump, fd, packet_id, topic, def, CURL_CSTRLEN(def)); } disconnect(dump, fd); } @@ -647,12 +681,10 @@ static curl_socket_t mqttit(curl_socket_t fd) return CURL_SOCKET_BAD; } -/* - sockfdp is a pointer to an established stream or CURL_SOCKET_BAD +/* sockfdp is a pointer to an established stream or CURL_SOCKET_BAD - if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must - accept() -*/ + if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must + accept() */ static bool mqttd_incoming(curl_socket_t listenfd) { fd_set fds_read; @@ -666,7 +698,7 @@ static bool mqttd_incoming(curl_socket_t listenfd) } #ifdef HAVE_GETPPID - /* As a last resort, quit if socks5 process becomes orphan. */ + /* As a last resort, quit if mqttd process becomes orphan. */ if(getppid() <= 1) { logmsg("process becomes orphan, exiting"); return FALSE; @@ -675,7 +707,7 @@ static bool mqttd_incoming(curl_socket_t listenfd) do { ssize_t rc; - int error = 0; + int sockerr = 0; char errbuf[STRERROR_LEN]; curl_socket_t sockfd = listenfd; int maxfd = (int)sockfd; @@ -694,20 +726,20 @@ static bool mqttd_incoming(curl_socket_t listenfd) logmsg("signalled to die, exiting..."); return FALSE; } - } while((rc == -1) && ((error = SOCKERRNO) == SOCKEINTR)); + } while((rc == -1) && ((sockerr = SOCKERRNO) == SOCKEINTR)); if(rc < 0) { logmsg("select() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return FALSE; } if(FD_ISSET(sockfd, &fds_read)) { curl_socket_t newfd = accept(sockfd, NULL, NULL); if(newfd == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("accept() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); } else { logmsg("====> Client connect, fd %ld. " @@ -732,7 +764,7 @@ static int test_mqttd(int argc, const char *argv[]) int wrotepidfile = 0; int wroteportfile = 0; bool juggle_again; - int error; + int sockerr; char errbuf[STRERROR_LEN]; int arg = 1; @@ -782,17 +814,14 @@ static int test_mqttd(int argc, const char *argv[]) } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 + socket_type = "IPv6"; socket_domain = AF_INET6; - ipv_inuse = "IPv6"; #endif arg++; } else if(!strcmp("--ipv4", argv[arg])) { - /* for completeness, we support this option as well */ -#ifdef USE_IPV6 + socket_type = "IPv4"; socket_domain = AF_INET; - ipv_inuse = "IPv4"; -#endif arg++; } else if(!strcmp("--port", argv[arg])) { @@ -803,7 +832,7 @@ static int test_mqttd(int argc, const char *argv[]) fprintf(stderr, "mqttd: invalid --port argument (%s)\n", argv[arg]); return 0; } - server_port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -823,7 +852,7 @@ static int test_mqttd(int argc, const char *argv[]) } snprintf(loglockfile, sizeof(loglockfile), "%s/%s/mqtt-%s.lock", - logdir, SERVERLOGS_LOCKDIR, ipv_inuse); + logdir, SERVERLOGS_LOCKDIR, socket_type); CURL_BINMODE(stdin); CURL_BINMODE(stdout); @@ -834,9 +863,9 @@ static int test_mqttd(int argc, const char *argv[]) sock = socket(socket_domain, SOCK_STREAM, 0); if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto mqttd_cleanup; } @@ -849,7 +878,7 @@ static int test_mqttd(int argc, const char *argv[]) msgsock = CURL_SOCKET_BAD; /* no stream socket yet */ } - logmsg("Running %s version", ipv_inuse); + logmsg("Running %s version", socket_type); logmsg("Listening on port %hu", server_port); wrotepidfile = write_pidfile(pidname); @@ -881,16 +910,5 @@ static int test_mqttd(int argc, const char *argv[]) restore_signal_handlers(FALSE); - if(got_exit_signal) { - logmsg("============> mqttd exits with signal (%d)", exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } - - logmsg("============> mqttd quits"); return 0; } diff --git a/tests/server/resolve.c b/tests/server/resolve.c index ac72cddd3a8b..43690e7d237d 100644 --- a/tests/server/resolve.c +++ b/tests/server/resolve.c @@ -52,8 +52,8 @@ static int test_resolve(int argc, const char *argv[]) } else if(!strcmp("--ipv6", argv[arg])) { #ifdef CURLRES_IPV6 - ipv_inuse = "IPv6"; - use_ipv6 = TRUE; + socket_type = "IPv6"; + socket_domain = AF_INET6; arg++; #else puts("IPv6 support has been disabled in this program"); @@ -61,11 +61,8 @@ static int test_resolve(int argc, const char *argv[]) #endif } else if(!strcmp("--ipv4", argv[arg])) { - /* for completeness, we support this option as well */ - ipv_inuse = "IPv4"; -#ifdef CURLRES_IPV6 - use_ipv6 = FALSE; -#endif + socket_type = "IPv4"; + socket_domain = AF_INET; arg++; } else { @@ -84,7 +81,7 @@ static int test_resolve(int argc, const char *argv[]) } #ifdef CURLRES_IPV6 - if(use_ipv6) { + if(socket_domain == AF_INET6) { /* Check that the system has IPv6 enabled before checking the resolver */ curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); if(s == CURL_SOCKET_BAD) @@ -101,7 +98,7 @@ static int test_resolve(int argc, const char *argv[]) struct addrinfo hints; memset(&hints, 0, sizeof(hints)); - hints.ai_family = use_ipv6 ? PF_INET6 : PF_INET; + hints.ai_family = socket_domain; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; rc = getaddrinfo(host, "80", &hints, &ai); @@ -123,7 +120,7 @@ static int test_resolve(int argc, const char *argv[]) #endif if(rc) - printf("Resolving %s '%s' did not work\n", ipv_inuse, host); + printf("Resolving %s '%s' did not work\n", socket_type, host); return !!rc; } diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c index 31820008f0cc..44e4f68655a4 100644 --- a/tests/server/rtspd.c +++ b/tests/server/rtspd.c @@ -76,7 +76,7 @@ struct rtspd_httprequest { - skip bytes. */ int rcmd; /* doing a special command, see defines above */ reqprot_t protocol; /* request protocol, HTTP or RTSP */ - int prot_version; /* HTTP or RTSP version (major*10 + minor) */ + int prot_version; /* HTTP or RTSP version (major * 10 + minor) */ bool pipelining; /* true if request is pipelined */ char *rtp_buffer; size_t rtp_buffersize; @@ -96,8 +96,8 @@ struct rtspd_httprequest { #define CMD_AUTH_REQUIRED "auth_required" -/* 'idle' means that it will accept the request fine but never respond - any data. Just keep the connection alive. */ +/* 'idle' means that it accepts the request fine but never responds + any data. Keep the connection alive. */ #define CMD_IDLE "idle" /* 'stream' means to send a never-ending stream of data */ @@ -106,18 +106,18 @@ struct rtspd_httprequest { #define END_OF_HEADERS "\r\n\r\n" /* sent as reply to a QUIT */ -static const char *docquit_rtsp = "HTTP/1.1 200 Goodbye" END_OF_HEADERS; +static const char docquit_rtsp[] = "HTTP/1.1 200 Goodbye" END_OF_HEADERS; /* sent as reply to a CONNECT */ -static const char *docconnect = +static const char docconnect[] = "HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS; /* sent as reply to a "bad" CONNECT */ -static const char *docbadconnect = +static const char docbadconnect[] = "HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS; /* send back this on HTTP 404 file not found */ -static const char *doc404_HTTP = +static const char doc404_HTTP[] = "HTTP/1.1 404 Not Found\r\n" "Server: " RTSPDVERSION "\r\n" "Connection: close\r\n" @@ -133,13 +133,13 @@ static const char *doc404_HTTP = "\n"; /* send back this on RTSP 404 file not found */ -static const char *doc404_RTSP = "RTSP/1.0 404 Not Found\r\n" +static const char doc404_RTSP[] = "RTSP/1.0 404 Not Found\r\n" "Server: " RTSPDVERSION END_OF_HEADERS; /* Default size to send away fake RTP data */ #define RTP_DATA_SIZE 12 -static const char *RTP_DATA = "$_1234\n\0Rsdf"; +static const char RTP_DATA[] = "$_1234\n\0Rsdf"; static int rtspd_ProcessRequest(struct rtspd_httprequest *req) { @@ -267,16 +267,17 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) logmsg("Found a reply-servercmd section!"); do { rtp_size_err = 0; - if(!strncmp(CMD_AUTH_REQUIRED, ptr, strlen(CMD_AUTH_REQUIRED))) { + if(!strncmp(CMD_AUTH_REQUIRED, ptr, + CURL_CSTRLEN(CMD_AUTH_REQUIRED))) { logmsg("instructed to require authorization header"); req->auth_req = TRUE; } - else if(!strncmp(CMD_IDLE, ptr, strlen(CMD_IDLE))) { + else if(!strncmp(CMD_IDLE, ptr, CURL_CSTRLEN(CMD_IDLE))) { logmsg("instructed to idle"); req->rcmd = RCMD_IDLE; req->open = TRUE; } - else if(!strncmp(CMD_STREAM, ptr, strlen(CMD_STREAM))) { + else if(!strncmp(CMD_STREAM, ptr, CURL_CSTRLEN(CMD_STREAM))) { logmsg("instructed to stream"); req->rcmd = RCMD_STREAM; } @@ -372,7 +373,7 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) req->testno = DOCNUMBER_BADCONNECT; else if(!strncmp(doc, "test", 4)) { /* if the hostname starts with test, the port number used in the - CONNECT line will be used as test number! */ + CONNECT line is used as test number! */ const char *portp = strchr(doc, ':'); if(portp && (*(portp + 1) != '\0') && ISDIGIT(*(portp + 1))) { pval = portp + 1; @@ -404,7 +405,7 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) if(req->pipe) /* we do have a full set, advance the checkindex to after the end of the headers, for the pipelining case mostly */ - req->checkindex += (end - line) + strlen(END_OF_HEADERS); + req->checkindex += (end - line) + CURL_CSTRLEN(END_OF_HEADERS); /* **** Persistence **** * @@ -424,10 +425,10 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) if((req->cl == 0) && !CURL_STRNICMP("Content-Length:", line, 15)) { /* If we do not ignore content-length, we read it and we read the whole request including the body before we return. If we have been told to - ignore the content-length, we will return as soon as all headers + ignore the content-length, we return as soon as all headers have been received */ curl_off_t clen; - const char *p = line + strlen("Content-Length:"); + const char *p = line + CURL_CSTRLEN("Content-Length:"); if(curlx_str_numblanks(&p, &clen)) { /* this assumes that a zero Content-Length is valid */ logmsg("Found invalid '%s' in the request", line); @@ -438,11 +439,11 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) logmsg("Found Content-Length: %zu in the request", (size_t)clen); if(req->skip) - logmsg("... but will abort after %zu bytes", req->cl); + logmsg("... but going to abort after %zu bytes", req->cl); break; } else if(!CURL_STRNICMP("Transfer-Encoding: chunked", line, - strlen("Transfer-Encoding: chunked"))) { + CURL_CSTRLEN("Transfer-Encoding: chunked"))) { /* chunked data coming in */ chunked = TRUE; } @@ -506,12 +507,12 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) if(!req->pipe && req->open && req->prot_version >= 11 && - req->reqbuf + req->offset > end + strlen(END_OF_HEADERS) && - (!strncmp(req->reqbuf, "GET", strlen("GET")) || - !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) { + req->reqbuf + req->offset > end + CURL_CSTRLEN(END_OF_HEADERS) && + (!strncmp(req->reqbuf, "GET", CURL_CSTRLEN("GET")) || + !strncmp(req->reqbuf, "HEAD", CURL_CSTRLEN("HEAD")))) { /* If we have a persistent connection, HTTP version >= 1.1 and GET/HEAD request, enable pipelining. */ - req->checkindex = (end - req->reqbuf) + strlen(END_OF_HEADERS); + req->checkindex = (end - req->reqbuf) + CURL_CSTRLEN(END_OF_HEADERS); req->pipelining = TRUE; } @@ -523,7 +524,7 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) end = strstr(line, END_OF_HEADERS); if(!end) break; - req->checkindex += (end - line) + strlen(END_OF_HEADERS); + req->checkindex += (end - line) + CURL_CSTRLEN(END_OF_HEADERS); req->pipe--; } @@ -535,7 +536,8 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) return 1; /* done */ if(req->cl > 0) { - if(req->cl <= req->offset - (end - req->reqbuf) - strlen(END_OF_HEADERS)) + if(req->cl <= req->offset - (end - req->reqbuf) - + CURL_CSTRLEN(END_OF_HEADERS)) return 1; /* done */ else return 0; /* not complete yet */ @@ -544,67 +546,10 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) return 1; /* done */ } -/* store the entire request in a file */ -static void rtspd_storerequest(const char *reqbuf, size_t totalsize) -{ - int res; - int error = 0; - char errbuf[STRERROR_LEN]; - size_t written; - size_t writeleft; - FILE *dump; - char dumpfile[256]; - - snprintf(dumpfile, sizeof(dumpfile), "%s/%s", logdir, REQUEST_DUMP); - - if(!reqbuf) - return; - if(totalsize == 0) - return; - - do { - dump = curlx_fopen(dumpfile, "ab"); - /* !checksrc! disable ERRNOVAR 1 */ - } while(!dump && ((error = errno) == EINTR)); - if(!dump) { - logmsg("Error opening file %s error (%d) %s", dumpfile, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - logmsg("Failed to write request input to %s", dumpfile); - return; - } - - writeleft = totalsize; - do { - written = fwrite(&reqbuf[totalsize - writeleft], 1, writeleft, dump); - if(got_exit_signal) - goto storerequest_cleanup; - if(written > 0) - writeleft -= written; - error = errno; - /* !checksrc! disable ERRNOVAR 1 */ - } while((writeleft > 0) && (error == EINTR)); - - if(writeleft == 0) - logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile); - else if(writeleft > 0) { - logmsg("Error writing file %s error (%d) %s", dumpfile, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s", - totalsize - writeleft, totalsize, dumpfile); - } - -storerequest_cleanup: - - res = curlx_fclose(dump); - if(res) - logmsg("Error closing file %s error (%d) %s", dumpfile, - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); -} - /* return 0 on success, non-zero on failure */ static int rtspd_get_request(curl_socket_t sock, struct rtspd_httprequest *req) { - int error; + int sockerr; char errbuf[STRERROR_LEN]; int fail = 0; int done_processing = 0; @@ -665,15 +610,15 @@ static int rtspd_get_request(curl_socket_t sock, struct rtspd_httprequest *req) fail = 1; } else if(got < 0) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("recv() returned error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); fail = 1; } if(fail) { /* dump the request received so far to the external file */ reqbuf[req->offset] = '\0'; - rtspd_storerequest(reqbuf, req->offset); + storerequest(reqbuf, req->offset, REQUEST_DUMP); return 1; } @@ -708,7 +653,8 @@ static int rtspd_get_request(curl_socket_t sock, struct rtspd_httprequest *req) reqbuf[req->offset] = '\0'; /* dump the request to an external file */ - rtspd_storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset); + storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset, + REQUEST_DUMP); if(got_exit_signal) return 1; @@ -730,7 +676,6 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) size_t responsesize; int error = 0; char errbuf[STRERROR_LEN]; - int res; static char weare[256]; char responsedump[256]; @@ -745,10 +690,10 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) case RCMD_STREAM: { static const char streamthis[] = "a string to stream 01234567890\n"; for(;;) { - written = swrite(sock, streamthis, sizeof(streamthis) - 1); + written = swrite(sock, streamthis, CURL_CSTRLEN(streamthis)); if(got_exit_signal) return -1; - if(written != (ssize_t)(sizeof(streamthis) - 1)) { + if(written != (ssize_t)CURL_CSTRLEN(streamthis)) { logmsg("Stopped streaming"); break; } @@ -795,7 +740,7 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) break; case DOCNUMBER_404: default: - logmsg("Replying to with a 404"); + logmsg("Replying with a 404"); if(req->protocol == RPROT_HTTP) { buffer = doc404_HTTP; } @@ -863,7 +808,7 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) } /* If the word 'swsclose' is present anywhere in the reply chunk, the - connection will be closed after the data has been sent to the requesting + connection is closed after the data has been sent to the requesting client... */ if(strstr(buffer, "swsclose") || !count) { persistent = FALSE; @@ -890,8 +835,8 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) responsesize = count; do { - /* Ok, we send no more than 200 bytes at a time, just to make sure that - larger chunks are split up so that the client will need to do multiple + /* Ok, we send no more than 200 bytes at a time, to make sure that + larger chunks are split up so that the client needs to do multiple recv() calls to get it and thus we exercise that code better */ size_t num = count; if(num > 200) @@ -934,8 +879,7 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) req->rtp_buffersize = 0; } - res = curlx_fclose(dump); - if(res) + if(curlx_fclose(dump)) logmsg("Error closing file %s error (%d) %s", responsedump, errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); @@ -968,16 +912,13 @@ static int rtspd_send_doc(curl_socket_t sock, struct rtspd_httprequest *req) if(!strcmp("wait", command)) { logmsg("Told to sleep for %d seconds", num); quarters = num * 4; - while(quarters > 0) { + while((quarters > 0) && !got_exit_signal) { quarters--; - res = curlx_wait_ms(250); - if(got_exit_signal) - break; - if(res) { + if(curlx_wait_ms(250)) { /* should not happen */ - error = SOCKERRNO; + int sockerr = SOCKERRNO; logmsg("curlx_wait_ms() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); break; } } @@ -1011,10 +952,9 @@ static int test_rtspd(int argc, const char *argv[]) int wrotepidfile = 0; int wroteportfile = 0; int flag; - unsigned short port = 8999; struct rtspd_httprequest req; int rc; - int error; + int sockerr; char errbuf[STRERROR_LEN]; int arg = 1; @@ -1023,6 +963,7 @@ static int test_rtspd(int argc, const char *argv[]) pidname = ".rtsp.pid"; serverlogfile = "log/rtspd.log"; serverlogslocked = 0; + server_port = 8999; while(argc > arg) { const char *opt; @@ -1059,16 +1000,14 @@ static int test_rtspd(int argc, const char *argv[]) logdir = argv[arg++]; } else if(!strcmp("--ipv4", argv[arg])) { -#ifdef USE_IPV6 - ipv_inuse = "IPv4"; - use_ipv6 = FALSE; -#endif + socket_type = "IPv4"; + socket_domain = AF_INET; arg++; } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 - ipv_inuse = "IPv6"; - use_ipv6 = TRUE; + socket_type = "IPv6"; + socket_domain = AF_INET6; #endif arg++; } @@ -1077,7 +1016,7 @@ static int test_rtspd(int argc, const char *argv[]) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -1104,93 +1043,88 @@ static int test_rtspd(int argc, const char *argv[]) } snprintf(loglockfile, sizeof(loglockfile), "%s/%s/rtsp-%s.lock", - logdir, SERVERLOGS_LOCKDIR, ipv_inuse); + logdir, SERVERLOGS_LOCKDIR, socket_type); - install_signal_handlers(false); + install_signal_handlers(FALSE); #ifdef USE_IPV6 - if(!use_ipv6) -#endif - sock = socket(AF_INET, SOCK_STREAM, 0); -#ifdef USE_IPV6 - else + if(socket_domain == AF_INET6) sock = socket(AF_INET6, SOCK_STREAM, 0); + else #endif + sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto server_cleanup; } flag = 1; if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto server_cleanup; } #ifdef USE_IPV6 - if(!use_ipv6) { + if(socket_domain == AF_INET6) { + memset(&me.sa6, 0, sizeof(me.sa6)); + me.sa6.sin6_family = AF_INET6; + me.sa6.sin6_addr = in6addr_any; + me.sa6.sin6_port = htons(server_port); + rc = bind(sock, &me.sa, sizeof(me.sa6)); + } + else #endif + { memset(&me.sa4, 0, sizeof(me.sa4)); me.sa4.sin_family = AF_INET; me.sa4.sin_addr.s_addr = INADDR_ANY; - me.sa4.sin_port = htons(port); + me.sa4.sin_port = htons(server_port); rc = bind(sock, &me.sa, sizeof(me.sa4)); -#ifdef USE_IPV6 - } - else { - memset(&me.sa6, 0, sizeof(me.sa6)); - me.sa6.sin6_family = AF_INET6; - me.sa6.sin6_addr = in6addr_any; - me.sa6.sin6_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa6)); } -#endif /* USE_IPV6 */ if(rc) { - error = SOCKERRNO; - logmsg("Error binding socket on port %hu (%d) %s", port, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr = SOCKERRNO; + logmsg("Error binding socket on port %hu (%d) %s", server_port, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto server_cleanup; } - if(!port) { + if(!server_port) { /* The system was supposed to choose a port number, figure out which port we actually got and update the listener port value with it. */ curl_socklen_t la_size; srvr_sockaddr_union_t localaddr; memset(&localaddr, 0, sizeof(localaddr)); #ifdef USE_IPV6 - if(!use_ipv6) -#endif - la_size = sizeof(localaddr.sa4); -#ifdef USE_IPV6 - else + if(socket_domain == AF_INET6) la_size = sizeof(localaddr.sa6); + else #endif + la_size = sizeof(localaddr.sa4); if(getsockname(sock, &localaddr.sa, &la_size) < 0) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("getsockname() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(sock); goto server_cleanup; } switch(localaddr.sa.sa_family) { case AF_INET: - port = ntohs(localaddr.sa4.sin_port); + server_port = ntohs(localaddr.sa4.sin_port); break; #ifdef USE_IPV6 case AF_INET6: - port = ntohs(localaddr.sa6.sin6_port); + server_port = ntohs(localaddr.sa6.sin6_port); break; #endif default: break; } - if(!port) { + if(!server_port) { /* Real failure, listener port shall not be zero beyond this point. */ logmsg("Apparently getsockname() succeeded, with listener port zero."); logmsg("A valid reason for this failure is a binary built without"); @@ -1200,20 +1134,19 @@ static int test_rtspd(int argc, const char *argv[]) goto server_cleanup; } } - logmsg("Running %s version on port %d", ipv_inuse, (int)port); + logmsg("Running %s version on port %d", socket_type, (int)server_port); /* start accepting connections */ - rc = listen(sock, 5); - if(rc) { - error = SOCKERRNO; + if(listen(sock, 5)) { + sockerr = SOCKERRNO; logmsg("listen() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto server_cleanup; } /* - * As soon as this server writes its pid file the test harness will - * attempt to connect to this server and initiate its verification. + * As soon as this server writes its pid file the test harness attempts + * to connect to this server and initiate its verification. */ wrotepidfile = write_pidfile(pidname); @@ -1221,7 +1154,7 @@ static int test_rtspd(int argc, const char *argv[]) goto server_cleanup; if(portname) { - wroteportfile = write_portfile(portname, port); + wroteportfile = write_portfile(portname, server_port); if(!wroteportfile) goto server_cleanup; } @@ -1232,9 +1165,9 @@ static int test_rtspd(int argc, const char *argv[]) if(got_exit_signal) break; if(msgsock == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("MAJOR ERROR, accept() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); break; } @@ -1343,19 +1276,7 @@ static int test_rtspd(int argc, const char *argv[]) clear_advisor_read_lock(loglockfile); } - restore_signal_handlers(false); - - if(got_exit_signal) { - logmsg("========> %s rtspd (port: %d pid: %ld) exits with signal (%d)", - ipv_inuse, (int)port, (long)our_getpid(), exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } + restore_signal_handlers(FALSE); - logmsg("========> rtspd quits"); return 0; } diff --git a/tests/server/sockfilt.c b/tests/server/sockfilt.c index 94d632425166..cdaff07494b3 100644 --- a/tests/server/sockfilt.c +++ b/tests/server/sockfilt.c @@ -57,25 +57,24 @@ * * This program is intended to be highly portable and as such it must be kept * as simple as possible, due to this the only signal handling mechanisms used - * will be those of ANSI C, and used only in the most basic form which is good + * are those of ANSI C, and used only in the most basic form which is good * enough for the purpose of this program. * * For the above reason and the specific needs of this program signals SIGHUP, - * SIGPIPE and SIGALRM will be ignored on systems where this can be - * done. If possible, signals SIGINT and SIGTERM will be handled by this + * SIGPIPE and SIGALRM are ignored on systems where this can be + * done. If possible, signals SIGINT and SIGTERM are handled by this * program as an indication to cleanup and finish execution as soon as - * possible. This will be achieved with a single signal handler + * possible. This is achieved with a single signal handler * 'exit_signal_handler' for both signals. * * The 'exit_signal_handler' upon the first SIGINT or SIGTERM received signal - * will just set to one the global var 'got_exit_signal' storing in global var + * sets to one the global var 'got_exit_signal' storing in global var * 'exit_signal' the signal that triggered this change. * * Nothing fancy that could introduce problems is used, the program at certain * points in its normal flow checks if var 'got_exit_signal' is set and in - * case this is true it just makes its way out of loops and functions in - * structured and well behaved manner to achieve proper program cleanup and - * termination. + * case this is true it makes its way out of loops and functions in structured + * and well behaved manner to achieve proper program cleanup and termination. * * Even with the above mechanism implemented it is worthwhile to note that * other signals might still be received, or that there might be systems on @@ -91,8 +90,8 @@ static bool verbose = FALSE; static bool s_bind_only = FALSE; -static unsigned short server_connectport = 0; /* if non-zero, - we activate this mode */ +static uint16_t server_connectport = 0; /* if non-zero, + we activate this mode */ enum sockmode { PASSIVE_LISTEN, /* as a server waiting for connections */ @@ -172,16 +171,15 @@ static ssize_t write_wincon(int fd, const void *buf, size_t count) #endif /* On Windows, we sometimes get this for a broken pipe, seemingly - * when the client just closed stdin? */ + * when the client closed stdin? */ #define CURL_WIN32_EPIPE 109 /* - * fullread is a wrapper around the read() function. This will repeat the call + * fullread is a wrapper around the read() function. This repeats the call * to read() until it actually has read the complete number of bytes indicated * in nbytes or it fails with a condition that cannot be handled with a simple * retry of the read call. */ - static ssize_t fullread(int filedes, void *buffer, size_t nbytes) { int error; @@ -230,12 +228,11 @@ static ssize_t fullread(int filedes, void *buffer, size_t nbytes) } /* - * fullwrite is a wrapper around the write() function. This will repeat the + * fullwrite is a wrapper around the write() function. This repeats the * call to write() until it actually has written the complete number of bytes * indicated in nbytes or it fails with a condition that cannot be handled * with a simple retry of the write call. */ - static ssize_t fullwrite(int filedes, const void *buffer, size_t nbytes) { int error; @@ -280,11 +277,10 @@ static ssize_t fullwrite(int filedes, const void *buffer, size_t nbytes) /* * read_stdin tries to read from stdin nbytes into the given buffer. This is a - * blocking function that will only return TRUE when nbytes have actually been + * blocking function that only returns TRUE when nbytes have actually been * read or FALSE when an unrecoverable error has been detected. Failure of this * function is an indication that the sockfilt process should terminate. */ - static bool read_stdin(void *buffer, size_t nbytes) { ssize_t nread = fullread(fileno(stdin), buffer, nbytes); @@ -297,11 +293,10 @@ static bool read_stdin(void *buffer, size_t nbytes) /* * write_stdout tries to write to stdio nbytes from the given buffer. This is a - * blocking function that will only return TRUE when nbytes have actually been + * blocking function that only returns TRUE when nbytes have actually been * written or FALSE when an unrecoverable error has been detected. Failure of * this function is an indication that the sockfilt process should terminate. */ - static bool write_stdout(const void *buffer, size_t nbytes) { ssize_t nwrite; @@ -436,7 +431,7 @@ static DWORD WINAPI select_ws_wait_thread(void *lpParameter) switch(type) { case FILE_TYPE_DISK: /* The handle represents a file on disk, this means: - * - WaitForMultipleObjectsEx will always be signalled for it. + * - WaitForMultipleObjectsEx is always signalled for it. * - comparison of current position in file and total size of * the file can be used to check if we reached the end yet. * @@ -473,7 +468,7 @@ static DWORD WINAPI select_ws_wait_thread(void *lpParameter) case FILE_TYPE_CHAR: /* The handle represents a character input, this means: - * - WaitForMultipleObjectsEx will be signalled on any kind of input, + * - WaitForMultipleObjectsEx is signalled on any kind of input, * including mouse and window size events we do not care about. * * Approach: Loop till either the internal event is signalled @@ -502,7 +497,7 @@ static DWORD WINAPI select_ws_wait_thread(void *lpParameter) case FILE_TYPE_PIPE: /* The handle represents an anonymous or named pipe, this means: - * - WaitForMultipleObjectsEx will always be signalled for it. + * - WaitForMultipleObjectsEx is always signalled for it. * - peek into the pipe and retrieve the amount of data available. * * Approach: Loop till either the internal event is signalled @@ -711,8 +706,7 @@ static int select_ws(int nfds, fd_set *readfds, fd_set *writefds, if(wsaevents.lNetworkEvents & FD_WRITE) { swrite(wsasock, NULL, 0); /* reset FD_WRITE */ } - if(WSAEventSelect(wsasock, wsaevent, wsaevents.lNetworkEvents) - == 0) { + if(!WSAEventSelect(wsasock, wsaevent, wsaevents.lNetworkEvents)) { handles[nfd] = (HANDLE)wsaevent; data[nws].wsasock = wsasock; data[nws].wsaevent = wsaevent; @@ -897,7 +891,7 @@ static bool disc_handshake(void) return FALSE; } else if(!memcmp("QUIT", buffer, 4)) { - /* just die */ + /* die */ logmsg("quits"); return FALSE; } @@ -916,12 +910,10 @@ static bool disc_handshake(void) return TRUE; } -/* - sockfdp is a pointer to an established stream or CURL_SOCKET_BAD +/* sockfdp is a pointer to an established stream or CURL_SOCKET_BAD - if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must - accept() -*/ + if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must + accept() */ static bool juggle(curl_socket_t *sockfdp, curl_socket_t listenfd, enum sockmode *mode) @@ -933,7 +925,7 @@ static bool juggle(curl_socket_t *sockfdp, curl_socket_t sockfd = CURL_SOCKET_BAD; int maxfd = -99; ssize_t rc; - int error = 0; + int sockerr = 0; char errbuf[STRERROR_LEN]; unsigned char buffer[BUFFER_SIZE]; @@ -945,7 +937,7 @@ static bool juggle(curl_socket_t *sockfdp, } #ifdef HAVE_GETPPID - /* As a last resort, quit if sockfilt process becomes orphan. Just in case + /* As a last resort, quit if sockfilt process becomes orphan. In case parent ftpserver process has died without killing its sockfilt children */ if(getppid() <= 1) { logmsg("process becomes orphan, exiting"); @@ -1019,11 +1011,11 @@ static bool juggle(curl_socket_t *sockfdp, logmsg("signalled to die, exiting..."); return FALSE; } - } while((rc == -1) && ((error = SOCKERRNO) == SOCKEINTR)); + } while((rc == -1) && ((sockerr = SOCKERRNO) == SOCKEINTR)); if(rc < 0) { logmsg("select() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return FALSE; } @@ -1046,7 +1038,7 @@ static bool juggle(curl_socket_t *sockfdp, Commands: DATA - plain pass-through data - */ + */ if(!read_stdin(buffer, 5)) return FALSE; @@ -1055,7 +1047,7 @@ static bool juggle(curl_socket_t *sockfdp, buffer[0], buffer[1], buffer[2], buffer[3]); if(!memcmp("PING", buffer, 4)) { - /* send reply on stdout, just proving we are alive */ + /* send reply on stdout, proving we are alive */ if(!write_stdout("PONG\n", 5)) return FALSE; } @@ -1064,7 +1056,7 @@ static bool juggle(curl_socket_t *sockfdp, /* Question asking us what PORT number we are listening to. Replies to PORT with "IPv[num]/[port]" */ snprintf((char *)buffer, sizeof(buffer), "%s/%hu\n", - ipv_inuse, server_port); + socket_type, server_port); buffer_len = (ssize_t)strlen((const char *)buffer); snprintf(data, sizeof(data), "PORT\n%04x\n", (unsigned int)buffer_len); if(!write_stdout(data, 10)) @@ -1073,7 +1065,7 @@ static bool juggle(curl_socket_t *sockfdp, return FALSE; } else if(!memcmp("QUIT", buffer, 4)) { - /* just die */ + /* die */ logmsg("quits"); return FALSE; } @@ -1125,9 +1117,9 @@ static bool juggle(curl_socket_t *sockfdp, client connecting. */ curl_socket_t newfd = accept(sockfd, NULL, NULL); if(newfd == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("accept() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); } else { logmsg("====> Client connect"); @@ -1179,7 +1171,7 @@ static int test_sockfilt(int argc, const char *argv[]) int wroteportfile = 0; bool juggle_again; int rc; - int error; + int sockerr; char errbuf[STRERROR_LEN]; int arg = 1; enum sockmode mode = PASSIVE_LISTEN; /* default */ @@ -1223,17 +1215,14 @@ static int test_sockfilt(int argc, const char *argv[]) } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 + socket_type = "IPv6"; socket_domain = AF_INET6; - ipv_inuse = "IPv6"; #endif arg++; } else if(!strcmp("--ipv4", argv[arg])) { - /* for completeness, we support this option as well */ -#ifdef USE_IPV6 + socket_type = "IPv4"; socket_domain = AF_INET; - ipv_inuse = "IPv4"; -#endif arg++; } else if(!strcmp("--bindonly", argv[arg])) { @@ -1245,7 +1234,7 @@ static int test_sockfilt(int argc, const char *argv[]) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - server_port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -1260,7 +1249,7 @@ static int test_sockfilt(int argc, const char *argv[]) argv[arg]); return 0; } - server_connectport = (unsigned short)num; + server_connectport = (uint16_t)num; arg++; } } @@ -1293,14 +1282,14 @@ static int test_sockfilt(int argc, const char *argv[]) CURL_BINMODE(stdout); CURL_BINMODE(stderr); - install_signal_handlers(false); + install_signal_handlers(FALSE); sock = socket(socket_domain, SOCK_STREAM, 0); if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); write_stdout("FAIL\n", 5); goto sockfilt_cleanup; } @@ -1336,9 +1325,9 @@ static int test_sockfilt(int argc, const char *argv[]) rc = 1; } if(rc) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("Error connecting to port %hu (%d) %s", server_connectport, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); write_stdout("FAIL\n", 5); goto sockfilt_cleanup; } @@ -1355,7 +1344,7 @@ static int test_sockfilt(int argc, const char *argv[]) msgsock = CURL_SOCKET_BAD; /* no stream socket yet */ } - logmsg("Running %s version", ipv_inuse); + logmsg("Running %s version", socket_type); if(server_connectport) logmsg("Connected to port %hu", server_connectport); @@ -1394,18 +1383,7 @@ static int test_sockfilt(int argc, const char *argv[]) if(wroteportfile) unlink(portname); - restore_signal_handlers(false); - - if(got_exit_signal) { - logmsg("============> sockfilt exits with signal (%d)", exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } + restore_signal_handlers(FALSE); - logmsg("============> sockfilt quits"); return 0; } diff --git a/tests/server/socksd.c b/tests/server/socksd.c index 8a4840ce69b4..29fe56264e28 100644 --- a/tests/server/socksd.c +++ b/tests/server/socksd.c @@ -44,7 +44,7 @@ * "password [string]" - the password that must match (if method is 2) * "backend [IPv4]" - numerical IPv4 address of backend to connect to * "backendport [number:0]" - TCP port of backend to connect to. 0 means use - the client's specified port number. + * the client's specified port number. * "method [number: 0]" - connect method to respond with: * 0 - no auth * 1 - GSSAPI (not supported) @@ -57,7 +57,7 @@ /* based on sockfilt.c */ static const char *backendaddr = "127.0.0.1"; -static unsigned short backendport = 0; /* default is use client's */ +static uint16_t backendport = 0; /* default is use client's */ struct socksd_configurable { unsigned char version; /* initial version byte in the request must match @@ -68,7 +68,7 @@ struct socksd_configurable { unsigned char responsemethod; unsigned char reqcmd; unsigned char connectrep; - unsigned short port; /* backend port */ + uint16_t port; /* backend port */ char addr[32]; /* backend IPv4 numerical */ char user[256]; char password[256]; @@ -102,9 +102,9 @@ static void socksd_resetdefaults(void) curlx_strcopy(s_config.addr, sizeof(s_config.addr), CONFIG_ADDR, strlen(CONFIG_ADDR)); curlx_strcopy(s_config.user, sizeof(s_config.user), - "user", strlen("user")); + "user", CURL_CSTRLEN("user")); curlx_strcopy(s_config.password, sizeof(s_config.password), - "password", strlen("password")); + "password", CURL_CSTRLEN("password")); } static void socksd_getconfig(void) @@ -149,7 +149,7 @@ static void socksd_getconfig(void) else if(!strcmp(key, "backendport")) { pval = value; if(!curlx_str_number(&pval, &num, 0xffff)) { - s_config.port = (unsigned short)num; + s_config.port = (uint16_t)num; logmsg("backendport [%d] set", s_config.port); } } @@ -164,10 +164,10 @@ static void socksd_getconfig(void) logmsg("password [%s] set", s_config.password); } /* Methods: - o X'00' NO AUTHENTICATION REQUIRED - o X'01' GSSAPI - o X'02' USERNAME/PASSWORD - */ + o 0x00 NO AUTHENTICATION REQUIRED + o 0x01 GSSAPI + o 0x02 USERNAME/PASSWORD + */ else if(!strcmp(key, "method")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { @@ -210,10 +210,9 @@ static void socksd_getconfig(void) #define SOCKS4_DSTPORT 2 /* connect to a given IPv4 address, not the one asked for */ -static curl_socket_t socksconnect(unsigned short connectport, +static curl_socket_t socksconnect(uint16_t connectport, const char *connectaddr) { - int rc; srvr_sockaddr_union_t me; curl_socket_t sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == CURL_SOCKET_BAD) @@ -224,13 +223,11 @@ static curl_socket_t socksconnect(unsigned short connectport, me.sa4.sin_addr.s_addr = INADDR_ANY; curlx_inet_pton(AF_INET, connectaddr, &me.sa4.sin_addr); - rc = connect(sock, &me.sa, sizeof(me.sa4)); - - if(rc) { + if(connect(sock, &me.sa, sizeof(me.sa4))) { char errbuf[STRERROR_LEN]; - int error = SOCKERRNO; + int sockerr = SOCKERRNO; logmsg("Failed connecting to %s:%hu (%d) %s", connectaddr, connectport, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return CURL_SOCKET_BAD; } logmsg("Connected fine to %s:%d", connectaddr, connectport); @@ -244,7 +241,7 @@ static curl_socket_t socks4(curl_socket_t fd, unsigned char response[256 + 16]; curl_socket_t connfd; unsigned char cd; - unsigned short s4port; + uint16_t s4port; if(buffer[SOCKS4_CD] != 1) { logmsg("SOCKS4 CD is not 1: %d", buffer[SOCKS4_CD]); @@ -255,8 +252,8 @@ static curl_socket_t socks4(curl_socket_t fd, return CURL_SOCKET_BAD; } if(!s_config.port) - s4port = (unsigned short)((buffer[SOCKS4_DSTPORT] << 8) | - (buffer[SOCKS4_DSTPORT + 1])); + s4port = (uint16_t)((buffer[SOCKS4_DSTPORT] << 8) | + buffer[SOCKS4_DSTPORT + 1]); else s4port = s_config.port; @@ -300,9 +297,9 @@ static curl_socket_t sockit(curl_socket_t fd) unsigned char type; unsigned char rep = 0; const unsigned char *address; - unsigned short socksport; + uint16_t socksport; curl_socket_t connfd = CURL_SOCKET_BAD; - unsigned short s5port; + uint16_t s5port; socksd_getconfig(); @@ -369,7 +366,7 @@ static curl_socket_t sockit(curl_socket_t fd) +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 | +----+------+----------+------+----------+ - */ + */ unsigned char ulen; unsigned char plen; bool login = TRUE; @@ -441,10 +438,10 @@ static curl_socket_t sockit(curl_socket_t fd) return CURL_SOCKET_BAD; } /* ATYP: - o IP V4 address: X'01' - o DOMAINNAME: X'03' - o IP V6 address: X'04' - */ + o IPv4 address: 0x01 + o domain name: 0x03 + o IPv6 address: 0x04 + */ type = buffer[SOCKS5_ATYP]; address = &buffer[SOCKS5_DSTADDR]; switch(type) { @@ -503,7 +500,7 @@ static curl_socket_t sockit(curl_socket_t fd) if(!s_config.port) { const unsigned char *portp = &buffer[SOCKS5_DSTADDR + len]; - s5port = (unsigned short)((portp[0] << 8) | (portp[1])); + s5port = (uint16_t)((portp[0] << 8) | portp[1]); } else s5port = s_config.port; @@ -521,19 +518,18 @@ static curl_socket_t sockit(curl_socket_t fd) response[SOCKS5_VERSION] = s_config.responseversion; - /* - o REP Reply field: - o X'00' succeeded - o X'01' general SOCKS server failure - o X'02' connection not allowed by ruleset - o X'03' Network unreachable - o X'04' Host unreachable - o X'05' Connection refused - o X'06' TTL expired - o X'07' Command not supported - o X'08' Address type not supported - o X'09' to X'FF' unassigned - */ + /* o REP Reply field: + o 0x00 succeeded + o 0x01 general SOCKS server failure + o 0x02 connection not allowed by ruleset + o 0x03 Network unreachable + o 0x04 Host unreachable + o 0x05 Connection refused + o 0x06 TTL expired + o 0x07 Command not supported + o 0x08 Address type not supported + o 0x09 to 0xFF unassigned + */ response[SOCKS5_REP] = rep; response[SOCKS5_RESERVED] = 0; /* must be zero */ response[SOCKS5_ATYP] = type; /* address type */ @@ -605,12 +601,10 @@ static int tunnel(struct perclient *cp, fd_set *fds) return 0; } -/* - sockfdp is a pointer to an established stream or CURL_SOCKET_BAD +/* sockfdp is a pointer to an established stream or CURL_SOCKET_BAD - if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must - accept() -*/ + if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must + accept() */ static bool socksd_incoming(curl_socket_t listenfd) { fd_set fds_read; @@ -636,7 +630,7 @@ static bool socksd_incoming(curl_socket_t listenfd) do { int i; ssize_t rc; - int error = 0; + int sockerr = 0; char errbuf[STRERROR_LEN]; curl_socket_t sockfd = listenfd; int maxfd = (int)sockfd; @@ -668,20 +662,20 @@ static bool socksd_incoming(curl_socket_t listenfd) logmsg("signalled to die, exiting..."); return FALSE; } - } while((rc == -1) && ((error = SOCKERRNO) == SOCKEINTR)); + } while((rc == -1) && ((sockerr = SOCKERRNO) == SOCKEINTR)); if(rc < 0) { logmsg("select() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return FALSE; } if((clients < 2) && FD_ISSET(sockfd, &fds_read)) { curl_socket_t newfd = accept(sockfd, NULL, NULL); if(newfd == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("accept() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); } else { curl_socket_t remotefd; @@ -732,13 +726,10 @@ static int test_socksd(int argc, const char *argv[]) int wrotepidfile = 0; int wroteportfile = 0; bool juggle_again; - int error; char errbuf[STRERROR_LEN]; int arg = 1; - - const char *unix_socket = NULL; #ifdef USE_UNIX_SOCKETS - bool unlink_socket = false; + bool unlink_socket = FALSE; #endif pidname = ".socksd.pid"; @@ -784,7 +775,7 @@ static int test_socksd(int argc, const char *argv[]) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - backendport = (unsigned short)num; + backendport = (uint16_t)num; arg++; } } @@ -800,16 +791,14 @@ static int test_socksd(int argc, const char *argv[]) } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 - socket_domain = AF_INET6; socket_type = "IPv6"; + socket_domain = AF_INET6; #endif arg++; } else if(!strcmp("--ipv4", argv[arg])) { - /* for completeness, we support this option as well */ -#ifdef USE_IPV6 socket_type = "IPv4"; -#endif + socket_domain = AF_INET; arg++; } else if(!strcmp("--unix-socket", argv[arg])) { @@ -817,15 +806,15 @@ static int test_socksd(int argc, const char *argv[]) if(argc > arg) { #ifdef USE_UNIX_SOCKETS struct sockaddr_un sau; - unix_socket = argv[arg]; - if(strlen(unix_socket) >= sizeof(sau.sun_path)) { + server_unix_socket = argv[arg]; + if(strlen(server_unix_socket) >= sizeof(sau.sun_path)) { fprintf(stderr, "socksd: socket path must be shorter than %u chars: %s\n", - (unsigned int)sizeof(sau.sun_path), unix_socket); + (unsigned int)sizeof(sau.sun_path), server_unix_socket); return 0; } - socket_domain = AF_UNIX; socket_type = "unix"; + socket_domain = AF_UNIX; #endif arg++; } @@ -835,7 +824,7 @@ static int test_socksd(int argc, const char *argv[]) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - server_port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -861,25 +850,25 @@ static int test_socksd(int argc, const char *argv[]) CURL_BINMODE(stdout); CURL_BINMODE(stderr); - install_signal_handlers(false); + install_signal_handlers(FALSE); sock = socket(socket_domain, SOCK_STREAM, 0); if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; + int sockerr = SOCKERRNO; logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto socks5_cleanup; } { /* passive daemon style */ - sock = sockdaemon(sock, &server_port, unix_socket, FALSE); + sock = sockdaemon(sock, &server_port, server_unix_socket, FALSE); if(sock == CURL_SOCKET_BAD) { goto socks5_cleanup; } #ifdef USE_UNIX_SOCKETS - unlink_socket = true; + unlink_socket = TRUE; #endif msgsock = CURL_SOCKET_BAD; /* no stream socket yet */ } @@ -888,7 +877,7 @@ static int test_socksd(int argc, const char *argv[]) #ifdef USE_UNIX_SOCKETS if(socket_domain == AF_UNIX) - logmsg("Listening on Unix socket %s", unix_socket); + logmsg("Listening on Unix socket %s", server_unix_socket); else #endif logmsg("Listening on port %hu", server_port); @@ -918,11 +907,10 @@ static int test_socksd(int argc, const char *argv[]) sclose(sock); #ifdef USE_UNIX_SOCKETS - if(unlink_socket && socket_domain == AF_UNIX && unix_socket) { - error = unlink(unix_socket); - logmsg("unlink(%s) = %d (%s)", unix_socket, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - } + if(unlink_socket && socket_domain == AF_UNIX && server_unix_socket && + unlink(server_unix_socket)) + logmsg("unlink(%s): %d (%s)", server_unix_socket, + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); #endif if(wrotepidfile) @@ -930,18 +918,7 @@ static int test_socksd(int argc, const char *argv[]) if(wroteportfile) unlink(portname); - restore_signal_handlers(false); - - if(got_exit_signal) { - logmsg("============> socksd exits with signal (%d)", exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } + restore_signal_handlers(FALSE); - logmsg("============> socksd quits"); return 0; } diff --git a/tests/server/sws.c b/tests/server/sws.c index c2f62384b8df..5d7087927d9b 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -53,7 +53,7 @@ static bool sws_prevbounce = FALSE; /* instructs the server to override the struct sws_httprequest { char reqbuf[2 * 1024 * 1024]; /* buffer area for the incoming request */ bool connect_request; /* if a CONNECT */ - unsigned short connect_port; /* the port number CONNECT used */ + uint16_t connect_port; /* the port number CONNECT used */ size_t checkindex; /* where to start checking of the request */ size_t offset; /* size of the incoming request */ long testno; /* test number found in the request */ @@ -100,6 +100,9 @@ static size_t num_sockets = 0; #define REQUEST_PROXY_DUMP "proxy.input" #define RESPONSE_PROXY_DUMP "proxy.response" +#define REQUEST_DUMP_FILENAME \ + (is_proxy ? REQUEST_PROXY_DUMP : REQUEST_DUMP) + /* file in which additional instructions may be found */ static const char *cmdfile = "log/server.cmd"; @@ -112,14 +115,14 @@ static const char *cmdfile = "log/server.cmd"; #define CMD_AUTH_REQUIRED "auth_required" -/* 'idle' means that it will accept the request fine but never respond - any data. Just keep the connection alive. */ +/* 'idle' means that it accepts the request fine but never responds + any data. Keep the connection alive. */ #define CMD_IDLE "idle" /* 'stream' means to send a never-ending stream of data */ #define CMD_STREAM "stream" -/* 'connection-monitor' will output when a server/proxy connection gets +/* 'connection-monitor' outputs when a server/proxy connection gets disconnected as for some cases it is important that it gets done at the proper point - like with NTLM */ #define CMD_CONNECTIONMONITOR "connection-monitor" @@ -138,10 +141,10 @@ static const char *cmdfile = "log/server.cmd"; static const char *end_of_headers = END_OF_HEADERS; /* sent as reply to a QUIT */ -static const char *docquit_sws = "HTTP/1.1 200 Goodbye" END_OF_HEADERS; +static const char docquit_sws[] = "HTTP/1.1 200 Goodbye" END_OF_HEADERS; /* send back this on 404 file not found */ -static const char *doc404 = +static const char doc404[] = "HTTP/1.1 404 Not Found\r\n" "Server: " SWSVERSION "\r\n" "Connection: close\r\n" @@ -186,23 +189,6 @@ static char *data_to_hex(const char *data, size_t len) /* work around for handling trailing headers */ static int already_recv_zeroed_chunk = FALSE; -#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED) -/* returns true if the current socket is an IP one */ -static bool socket_domain_is_ip(void) -{ - switch(socket_domain) { - case AF_INET: -#ifdef USE_IPV6 - case AF_INET6: -#endif - return true; - default: - /* case AF_UNIX: */ - return false; - } -} -#endif - /* parse the file on disk that might have a test number for us */ static int parse_cmdfile(struct sws_httprequest *req) { @@ -259,29 +245,29 @@ static int sws_parse_servercmd(struct sws_httprequest *req) while(cmd && cmdsize) { const char *check; - if(!strncmp(CMD_AUTH_REQUIRED, cmd, strlen(CMD_AUTH_REQUIRED))) { + if(!strncmp(CMD_AUTH_REQUIRED, cmd, CURL_CSTRLEN(CMD_AUTH_REQUIRED))) { logmsg("instructed to require authorization header"); req->auth_req = TRUE; } - else if(!strncmp(CMD_IDLE, cmd, strlen(CMD_IDLE))) { + else if(!strncmp(CMD_IDLE, cmd, CURL_CSTRLEN(CMD_IDLE))) { logmsg("instructed to idle"); req->rcmd = RCMD_IDLE; req->open = TRUE; } - else if(!strncmp(CMD_STREAM, cmd, strlen(CMD_STREAM))) { + else if(!strncmp(CMD_STREAM, cmd, CURL_CSTRLEN(CMD_STREAM))) { logmsg("instructed to stream"); req->rcmd = RCMD_STREAM; } else if(!strncmp(CMD_CONNECTIONMONITOR, cmd, - strlen(CMD_CONNECTIONMONITOR))) { + CURL_CSTRLEN(CMD_CONNECTIONMONITOR))) { logmsg("enabled connection monitoring"); req->connmon = TRUE; } - else if(!strncmp(CMD_UPGRADE, cmd, strlen(CMD_UPGRADE))) { + else if(!strncmp(CMD_UPGRADE, cmd, CURL_CSTRLEN(CMD_UPGRADE))) { logmsg("enabled upgrade"); req->upgrade = TRUE; } - else if(!strncmp(CMD_SWSCLOSE, cmd, strlen(CMD_SWSCLOSE))) { + else if(!strncmp(CMD_SWSCLOSE, cmd, CURL_CSTRLEN(CMD_SWSCLOSE))) { logmsg("swsclose: close this connection after response"); req->close = TRUE; } @@ -289,7 +275,7 @@ static int sws_parse_servercmd(struct sws_httprequest *req) logmsg("instructed to skip this number of bytes %d", num); req->skip = num; } - else if(!strncmp(CMD_NOEXPECT, cmd, strlen(CMD_NOEXPECT))) { + else if(!strncmp(CMD_NOEXPECT, cmd, CURL_CSTRLEN(CMD_NOEXPECT))) { logmsg("instructed to reject Expect: 100-continue"); req->noexpect = TRUE; } @@ -304,7 +290,7 @@ static int sws_parse_servercmd(struct sws_httprequest *req) else { logmsg("Unknown instruction found: %s", cmd); } - /* try to deal with CRLF or just LF */ + /* try to deal with CRLF or LF */ check = strchr(cmd, '\r'); if(!check) check = strchr(cmd, '\n'); @@ -484,9 +470,9 @@ static int sws_ProcessRequest(struct sws_httprequest *req) (num <= 0) || (num > 65535)) logmsg("Invalid CONNECT port received"); else - req->connect_port = (unsigned short)num; + req->connect_port = (uint16_t)num; } - logmsg("Port number: %d, test case number: %ld", + logmsg("Port number: %hu, test case number: %ld", req->connect_port, req->testno); } } @@ -585,10 +571,10 @@ static int sws_ProcessRequest(struct sws_httprequest *req) if((req->cl == 0) && !CURL_STRNICMP("Content-Length:", line, 15)) { /* If we do not ignore content-length, we read it and we read the whole request including the body before we return. If we have been told to - ignore the content-length, we will return as soon as all headers + ignore the content-length, we return as soon as all headers have been received */ curl_off_t clen; - const char *p = line + strlen("Content-Length:"); + const char *p = line + CURL_CSTRLEN("Content-Length:"); if(curlx_str_numblanks(&p, &clen)) { /* this assumes that a zero Content-Length is valid */ logmsg("Found invalid '%s' in the request", line); @@ -602,15 +588,16 @@ static int sws_ProcessRequest(struct sws_httprequest *req) logmsg("Found Content-Length: %zu in the request", (size_t)clen); if(req->skip) - logmsg("... but will abort after %zu bytes", req->cl); + logmsg("... but going to abort after %zu bytes", req->cl); } else if(!CURL_STRNICMP("Transfer-Encoding: chunked", line, - strlen("Transfer-Encoding: chunked"))) { + CURL_CSTRLEN("Transfer-Encoding: chunked"))) { /* chunked data coming in */ chunked = TRUE; } - else if(req->noexpect && !CURL_STRNICMP("Expect: 100-continue", line, - strlen("Expect: 100-continue"))) { + else if(req->noexpect && + !CURL_STRNICMP("Expect: 100-continue", line, + CURL_CSTRLEN("Expect: 100-continue"))) { if(req->cl) req->cl = 0; req->skipall = TRUE; @@ -706,8 +693,8 @@ static int sws_ProcessRequest(struct sws_httprequest *req) req->prot_version >= 11 && req->reqbuf + req->offset > end + strlen(end_of_headers) && !req->cl && - (!strncmp(req->reqbuf, "GET", strlen("GET")) || - !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) { + (!strncmp(req->reqbuf, "GET", CURL_CSTRLEN("GET")) || + !strncmp(req->reqbuf, "HEAD", CURL_CSTRLEN("HEAD")))) { /* If we have a persistent connection, HTTP version >= 1.1 and GET/HEAD request, enable pipelining. */ req->checkindex = (end - req->reqbuf) + strlen(end_of_headers); @@ -718,7 +705,7 @@ static int sws_ProcessRequest(struct sws_httprequest *req) test case send a rejection before any such data has been sent. Test case 154 uses this.*/ if(req->auth_req && !req->auth) { - logmsg("Return early due to auth requested by none provided"); + logmsg("Return early due to auth requested but none provided"); return 1; /* done */ } @@ -739,64 +726,6 @@ static int sws_ProcessRequest(struct sws_httprequest *req) return 1; /* done */ } -/* store the entire request in a file */ -static void sws_storerequest(const char *reqbuf, size_t totalsize) -{ - int res; - int error = 0; - char errbuf[STRERROR_LEN]; - size_t written; - size_t writeleft; - FILE *dump; - char dumpfile[256]; - - snprintf(dumpfile, sizeof(dumpfile), "%s/%s", - logdir, is_proxy ? REQUEST_PROXY_DUMP : REQUEST_DUMP); - - if(!reqbuf) - return; - if(totalsize == 0) - return; - - do { - dump = curlx_fopen(dumpfile, "ab"); - /* !checksrc! disable ERRNOVAR 1 */ - } while(!dump && ((error = errno) == EINTR)); - if(!dump) { - logmsg("[2] Error opening file %s error (%d) %s", dumpfile, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - logmsg("Failed to write request input "); - return; - } - - writeleft = totalsize; - do { - written = fwrite(&reqbuf[totalsize - writeleft], 1, writeleft, dump); - if(got_exit_signal) - goto storerequest_cleanup; - if(written > 0) - writeleft -= written; - error = errno; - /* !checksrc! disable ERRNOVAR 1 */ - } while((writeleft > 0) && (error == EINTR)); - - if(writeleft == 0) - logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile); - else if(writeleft > 0) { - logmsg("Error writing file %s error (%d) %s", dumpfile, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s", - totalsize - writeleft, totalsize, dumpfile); - } - -storerequest_cleanup: - - res = curlx_fclose(dump); - if(res) - logmsg("Error closing file %s error (%d) %s", dumpfile, - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); -} - /* returns -1 on failure */ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) { @@ -813,7 +742,6 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) size_t responsesize; int error = 0; char errbuf[STRERROR_LEN]; - int res; static char weare[256]; char responsedump[256]; @@ -827,10 +755,10 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) case RCMD_STREAM: { static const char streamthis[] = "a string to stream 01234567890\n"; for(;;) { - written = swrite(sock, streamthis, sizeof(streamthis) - 1); + written = swrite(sock, streamthis, CURL_CSTRLEN(streamthis)); if(got_exit_signal) return -1; - if(written != (ssize_t)(sizeof(streamthis) - 1)) { + if(written != (ssize_t)CURL_CSTRLEN(streamthis)) { logmsg("Stopped streaming"); break; } @@ -869,7 +797,7 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) break; case DOCNUMBER_404: default: - logmsg("Replying to with a 404"); + logmsg("Replying with a 404"); buffer = doc404; break; } @@ -940,12 +868,20 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) } /* If the word 'swsclose' is present anywhere in the reply chunk, the - connection will be closed after the data has been sent to the requesting + connection is closed after the data has been sent to the requesting client... */ - if(strstr(buffer, "swsclose") || !count || req->close) { + if(strstr(buffer, "swsclose")) { persistent = FALSE; logmsg("connection close instruction \"swsclose\" found in response"); } + else if(!count) { + persistent = FALSE; + logmsg("connection closed because of empty response"); + } + else if(req->close) { + persistent = FALSE; + logmsg("connection closed because of close instruction in servercmd"); + } if(strstr(buffer, "swsbounce")) { sws_prevbounce = TRUE; logmsg("enable \"swsbounce\" in the next request"); @@ -966,8 +902,8 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) responsesize = count; do { - /* Ok, we send no more than N bytes at a time, just to make sure that - larger chunks are split up so that the client will need to do multiple + /* Ok, we send no more than N bytes at a time, to make sure that + larger chunks are split up so that the client needs to do multiple recv() calls to get it and thus we exercise that code better */ size_t num = count; if(num > 20) @@ -976,7 +912,7 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) retry: written = swrite(sock, buffer, num); if(written < 0) { - if((SOCKEWOULDBLOCK == SOCKERRNO) || (EAGAIN == SOCKERRNO)) { + if(SOCK_EAGAIN(SOCKERRNO)) { curlx_wait_ms(10); goto retry; } @@ -1007,8 +943,7 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) } } while((count > 0) && !got_exit_signal); - res = curlx_fclose(dump); - if(res) + if(curlx_fclose(dump)) logmsg("Error closing file %s error (%d) %s", responsedump, errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); @@ -1045,12 +980,11 @@ static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) quarters = num * 4; while((quarters > 0) && !got_exit_signal) { quarters--; - res = curlx_wait_ms(250); - if(res) { + if(curlx_wait_ms(250)) { /* should not happen */ - error = SOCKERRNO; + int sockerr = SOCKERRNO; logmsg("curlx_wait_ms() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); break; } } @@ -1119,7 +1053,7 @@ static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) /* dump the request received so far to the external file */ reqbuf[req->offset] = '\0'; - sws_storerequest(reqbuf, req->offset); + storerequest(reqbuf, req->offset, REQUEST_DUMP_FILENAME); req->offset = 0; /* read websocket traffic */ @@ -1133,8 +1067,7 @@ static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) logmsg("Got %zd bytes from client", got); } - if((got == -1) && - ((SOCKERRNO == EAGAIN) || (SOCKERRNO == SOCKEWOULDBLOCK))) { + if((got == -1) && SOCK_EAGAIN(SOCKERRNO)) { int rc; fd_set input; fd_set output; @@ -1163,7 +1096,7 @@ static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) logmsg("log the websocket traffic"); /* dump the incoming websocket traffic to the external file */ reqbuf[req->offset] = '\0'; - sws_storerequest(reqbuf, req->offset); + storerequest(reqbuf, req->offset, REQUEST_DUMP_FILENAME); req->offset = 0; } init_httprequest(req); @@ -1193,19 +1126,19 @@ static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) } else if(got < 0) { char errbuf[STRERROR_LEN]; - int error = SOCKERRNO; - if(EAGAIN == error || SOCKEWOULDBLOCK == error) { + int sockerr = SOCKERRNO; + if(SOCK_EAGAIN(sockerr)) { /* nothing to read at the moment */ return 0; } logmsg("recv() returned error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); fail = 1; } if(fail) { /* dump the request received so far to the external file */ reqbuf[req->offset] = '\0'; - sws_storerequest(reqbuf, req->offset); + storerequest(reqbuf, req->offset, REQUEST_DUMP_FILENAME); return -1; } @@ -1236,18 +1169,18 @@ static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) /* at the end of a request dump it to an external file */ if(fail || req->done_processing) - sws_storerequest(reqbuf, req->offset); + storerequest(reqbuf, req->offset, REQUEST_DUMP_FILENAME); if(got_exit_signal) return -1; return fail ? -1 : 1; } -static curl_socket_t connect_to(const char *ipaddr, unsigned short port) +static curl_socket_t connect_to(const char *ipaddr, uint16_t port) { srvr_sockaddr_union_t serveraddr; curl_socket_t serverfd; - int error; + int sockerr; char errbuf[STRERROR_LEN]; int rc = 0; const char *op_br = ""; @@ -1267,9 +1200,9 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) serverfd = socket(socket_domain, SOCK_STREAM, 0); if(serverfd == CURL_SOCKET_BAD) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("Error creating socket for server connection (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return CURL_SOCKET_BAD; } @@ -1287,9 +1220,9 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) * Windows has an internal retry logic that may lead to long * timeouts if the peer is not listening. */ if(curlx_nonblock(serverfd, TRUE)) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("curlx_nonblock(TRUE) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(serverfd); return CURL_SOCKET_BAD; } @@ -1334,8 +1267,8 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) } if(rc) { - error = SOCKERRNO; - if((error == SOCKEINPROGRESS) || (error == SOCKEWOULDBLOCK)) { + sockerr = SOCKERRNO; + if((sockerr == SOCKEINPROGRESS) || SOCK_EAGAIN(sockerr)) { fd_set output; struct timeval timeout = { 0 }; timeout.tv_sec = 1; /* 1000 ms */ @@ -1344,16 +1277,17 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) FD_SET(serverfd, &output); while(1) { rc = select((int)serverfd + 1, NULL, &output, NULL, &timeout); - if(rc < 0 && SOCKERRNO != SOCKEINTR) + sockerr = SOCKERRNO; + if(rc < 0 && sockerr != SOCKEINTR) goto error; else if(rc > 0) { - curl_socklen_t errSize = sizeof(error); + curl_socklen_t errSize = sizeof(sockerr); if(getsockopt(serverfd, SOL_SOCKET, SO_ERROR, - (void *)&error, &errSize)) - error = SOCKERRNO; - if((error == 0) || (SOCKEISCONN == error)) + (void *)&sockerr, &errSize)) + sockerr = SOCKERRNO; + if((sockerr == 0) || (SOCKEISCONN == sockerr)) goto success; - else if((error != SOCKEINPROGRESS) && (error != SOCKEWOULDBLOCK)) + else if((sockerr != SOCKEINPROGRESS) && !SOCK_EAGAIN(sockerr)) goto error; } else if(!rc) { @@ -1365,7 +1299,7 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) } error: logmsg("Error connecting to server port %hu (%d) %s", port, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(serverfd); return CURL_SOCKET_BAD; } @@ -1374,9 +1308,9 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) op_br, ipaddr, cl_br, port); if(curlx_nonblock(serverfd, FALSE)) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("curlx_nonblock(FALSE) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(serverfd); return CURL_SOCKET_BAD; } @@ -1392,7 +1326,7 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) * either end. * * When doing FTP through a CONNECT proxy, we expect that the data connection - * will be setup while the first connect is still being kept up. Therefore we + * is setup while the first connect is still being kept up. Therefore we * must accept a new connection and deal with it appropriately. */ @@ -1404,7 +1338,7 @@ static curl_socket_t connect_to(const char *ipaddr, unsigned short port) static void http_connect(curl_socket_t *infdp, curl_socket_t rootfd, const char *ipaddr, - unsigned short ipport, + uint16_t ipport, int keepalive_secs) { curl_socket_t serverfd[2] = { CURL_SOCKET_BAD, CURL_SOCKET_BAD }; @@ -1794,96 +1728,10 @@ static void http_connect(curl_socket_t *infdp, static void http_upgrade(struct sws_httprequest *req) { (void)req; - logmsg("Upgraded to ... %u", req->upgrade_request); + logmsg("Upgraded to ... %d", (int)req->upgrade_request); /* left to implement */ } -/* returns a socket handle, or 0 if there are no more waiting sockets, - or < 0 if there was an error */ -static curl_socket_t accept_connection(curl_socket_t sock) -{ - curl_socket_t msgsock = CURL_SOCKET_BAD; - int error; - char errbuf[STRERROR_LEN]; - int flag = 1; - - if(MAX_SOCKETS == num_sockets) { - logmsg("Too many open sockets!"); - return CURL_SOCKET_BAD; - } - - msgsock = accept(sock, NULL, NULL); - - if(got_exit_signal) { - if(msgsock != CURL_SOCKET_BAD) - sclose(msgsock); - return CURL_SOCKET_BAD; - } - - if(msgsock == CURL_SOCKET_BAD) { - error = SOCKERRNO; - if(EAGAIN == error || SOCKEWOULDBLOCK == error) { - /* nothing to accept */ - return 0; - } - logmsg("MAJOR ERROR, accept() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - return CURL_SOCKET_BAD; - } - - if(curlx_nonblock(msgsock, TRUE)) { - error = SOCKERRNO; - logmsg("curlx_nonblock failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - sclose(msgsock); - return CURL_SOCKET_BAD; - } - -#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) - if(socket_domain != AF_UNIX) { -#endif - if(setsockopt(msgsock, SOL_SOCKET, SO_KEEPALIVE, - (void *)&flag, sizeof(flag))) { - error = SOCKERRNO; - logmsg("setsockopt(SO_KEEPALIVE) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - sclose(msgsock); - return CURL_SOCKET_BAD; - } -#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) - } -#endif - - /* - * As soon as this server accepts a connection from the test harness it - * must set the server logs advisor read lock to indicate that server - * logs should not be read until this lock is removed by this server. - */ - - if(!serverlogslocked) - set_advisor_read_lock(loglockfile); - serverlogslocked += 1; - - logmsg("====> Client connect"); - - all_sockets[num_sockets] = msgsock; - num_sockets += 1; - -#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED) - if(socket_domain_is_ip()) { - /* - * Disable the Nagle algorithm to make it easier to send out a large - * response in many small segments to torture the clients more. - */ - if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY, - (void *)&flag, sizeof(flag))) - logmsg("====> TCP_NODELAY failed"); - } -#endif - - return msgsock; -} - /* returns 1 if the connection should be serviced again immediately, 0 if there is no data waiting, or < 0 if it should be closed */ static int service_connection(curl_socket_t *msgsock, @@ -1951,11 +1799,11 @@ static int service_connection(curl_socket_t *msgsock, /* if we got a CONNECT, loop and get another request as well! */ if(req->open) { - logmsg("=> persistent connection request ended, awaits new request\n"); + logmsg("=> persistent connection request ended, awaits new request"); return 1; } else { - logmsg("=> NOT a persistent connection, close close CLOSE\n"); + logmsg("=> NOT a persistent connection, close close CLOSE"); } return -1; @@ -1963,19 +1811,15 @@ static int service_connection(curl_socket_t *msgsock, static int test_sws(int argc, const char *argv[]) { - srvr_sockaddr_union_t me; curl_socket_t sock = CURL_SOCKET_BAD; int wrotepidfile = 0; int wroteportfile = 0; - int flag; - unsigned short port = 8999; #ifdef USE_UNIX_SOCKETS - const char *unix_socket = NULL; - bool unlink_socket = false; + bool unlink_socket = FALSE; #endif struct sws_httprequest *req = NULL; int rc = 0; - int error; + int sockerr; char errbuf[STRERROR_LEN]; int arg = 1; const char *connecthost = "127.0.0.1"; @@ -1983,6 +1827,7 @@ static int test_sws(int argc, const char *argv[]) const char *location_str = port_str; int keepalive_secs = 5; const char *protocol_type = "HTTP"; + int result = 0; /* a default CONNECT port is pointless, but still ... */ size_t socket_idx; @@ -1991,6 +1836,7 @@ static int test_sws(int argc, const char *argv[]) portname = ".http.port"; serverlogfile = "log/sws.log"; serverlogslocked = 0; + server_port = 8999; while(argc > arg) { const char *opt; @@ -2055,16 +1901,17 @@ static int test_sws(int argc, const char *argv[]) arg++; if(argc > arg) { #ifdef USE_UNIX_SOCKETS - unix_socket = argv[arg]; - if(strlen(unix_socket) >= sizeof(me.sau.sun_path)) { + srvr_sockaddr_union_t me; + server_unix_socket = argv[arg]; + if(strlen(server_unix_socket) >= sizeof(me.sau.sun_path)) { fprintf(stderr, "sws: socket path must be shorter than %u chars: %s\n", - (unsigned int)sizeof(me.sau.sun_path), unix_socket); + (unsigned int)sizeof(me.sau.sun_path), server_unix_socket); return 0; } socket_type = "unix"; socket_domain = AF_UNIX; - location_str = unix_socket; + location_str = server_unix_socket; #endif arg++; } @@ -2077,7 +1924,7 @@ static int test_sws(int argc, const char *argv[]) fprintf(stderr, "sws: invalid --port argument (%s)\n", argv[arg]); return 0; } - port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -2094,7 +1941,7 @@ static int test_sws(int argc, const char *argv[]) opt = argv[arg]; if(curlx_str_number(&opt, &num, 0xffff)) { fprintf(stderr, "sws: invalid --keepalive argument (%s), must " - "be number of seconds\n", argv[arg]); + "be a number of seconds\n", argv[arg]); return 0; } keepalive_secs = (unsigned short)num; @@ -2102,7 +1949,7 @@ static int test_sws(int argc, const char *argv[]) } } else if(!strcmp("--connect", argv[arg])) { - /* The connect host IP number that the proxy will connect to no matter + /* The connect host IP number that the proxy connects to no matter what the client asks for, but also use this as a hint that we run as a proxy and do a few different internal choices */ arg++; @@ -2135,155 +1982,50 @@ static int test_sws(int argc, const char *argv[]) logdir, SERVERLOGS_LOCKDIR, protocol_type, is_proxy ? "-proxy" : "", socket_type); - install_signal_handlers(false); + install_signal_handlers(FALSE); req = calloc(1, sizeof(*req)); if(!req) goto sws_cleanup; - sock = socket(socket_domain, SOCK_STREAM, 0); + result = open_stream_sock(&sock, &server_port); + if(result) + goto sws_cleanup; all_sockets[0] = sock; num_sockets = 1; - if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; - logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - goto sws_cleanup; - } - -#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) - if(socket_domain != AF_UNIX) { -#endif - flag = 1; - if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (void *)&flag, sizeof(flag))) { - error = SOCKERRNO; - logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - goto sws_cleanup; - } -#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) - } -#endif - if(curlx_nonblock(sock, TRUE)) { - error = SOCKERRNO; - logmsg("curlx_nonblock failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - goto sws_cleanup; - } - - switch(socket_domain) { - case AF_INET: - memset(&me.sa4, 0, sizeof(me.sa4)); - me.sa4.sin_family = AF_INET; - me.sa4.sin_addr.s_addr = INADDR_ANY; - me.sa4.sin_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa4)); - break; -#ifdef USE_IPV6 - case AF_INET6: - memset(&me.sa6, 0, sizeof(me.sa6)); - me.sa6.sin6_family = AF_INET6; - me.sa6.sin6_addr = in6addr_any; - me.sa6.sin6_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa6)); - break; -#endif /* USE_IPV6 */ -#ifdef USE_UNIX_SOCKETS - case AF_UNIX: - rc = bind_unix_socket(sock, unix_socket, &me.sau); -#endif /* USE_UNIX_SOCKETS */ - } - if(rc) { - error = SOCKERRNO; -#ifdef USE_UNIX_SOCKETS - if(socket_domain == AF_UNIX) - logmsg("Error binding socket on path %s (%d) %s", unix_socket, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - else -#endif - logmsg("Error binding socket on port %hu (%d) %s", port, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - goto sws_cleanup; - } - - if(!port) { - /* The system was supposed to choose a port number, figure out which - port we actually got and update the listener port value with it. */ - curl_socklen_t la_size; - srvr_sockaddr_union_t localaddr; - memset(&localaddr, 0, sizeof(localaddr)); -#ifdef USE_IPV6 - if(socket_domain != AF_INET6) -#endif - la_size = sizeof(localaddr.sa4); -#ifdef USE_IPV6 - else - la_size = sizeof(localaddr.sa6); -#endif - if(getsockname(sock, &localaddr.sa, &la_size) < 0) { - error = SOCKERRNO; - logmsg("getsockname() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - sclose(sock); - goto sws_cleanup; - } - switch(localaddr.sa.sa_family) { - case AF_INET: - port = ntohs(localaddr.sa4.sin_port); - break; -#ifdef USE_IPV6 - case AF_INET6: - port = ntohs(localaddr.sa6.sin6_port); - break; -#endif - default: - break; - } - if(!port) { - /* Real failure, listener port shall not be zero beyond this point. */ - logmsg("Apparently getsockname() succeeded, with listener port zero."); - logmsg("A valid reason for this failure is a binary built without"); - logmsg("proper network library linkage. This might not be the only"); - logmsg("reason, but double check it before anything else."); - sclose(sock); - goto sws_cleanup; - } - } #ifdef USE_UNIX_SOCKETS if(socket_domain != AF_UNIX) #endif - snprintf(port_str, sizeof(port_str), "port %hu", port); + snprintf(port_str, sizeof(port_str), "port %hu", server_port); logmsg("Running %s %s version on %s", protocol_type, socket_type, location_str); /* start accepting connections */ - rc = listen(sock, 50); - if(rc) { - error = SOCKERRNO; + if(listen(sock, 50)) { + sockerr = SOCKERRNO; logmsg("listen() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto sws_cleanup; } #ifdef USE_UNIX_SOCKETS /* listen succeeds, so let's assume a valid listening Unix socket */ - unlink_socket = true; + unlink_socket = TRUE; #endif /* - * As soon as this server writes its pid file the test harness will - * attempt to connect to this server and initiate its verification. + * As soon as this server writes its pid file the test harness attempts + * to connect to this server and initiate its verification. */ wrotepidfile = write_pidfile(pidname); if(!wrotepidfile) goto sws_cleanup; - wroteportfile = write_portfile(portname, port); + wroteportfile = write_portfile(portname, server_port); if(!wroteportfile) goto sws_cleanup; @@ -2329,17 +2071,18 @@ static int test_sws(int argc, const char *argv[]) if(got_exit_signal) goto sws_cleanup; + sockerr = 0; do { rc = select((int)maxfd + 1, &input, &output, NULL, &timeout); - } while(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal); + } while(rc < 0 && ((sockerr = SOCKERRNO) == SOCKEINTR && + !got_exit_signal)); if(got_exit_signal) goto sws_cleanup; if(rc < 0) { - error = SOCKERRNO; logmsg("select() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); goto sws_cleanup; } @@ -2354,11 +2097,19 @@ static int test_sws(int argc, const char *argv[]) /* Service all queued connections */ curl_socket_t msgsock; do { + if(MAX_SOCKETS == num_sockets) { + logmsg("Too many open sockets!"); + goto sws_cleanup; + } msgsock = accept_connection(sock); + if(!msgsock) + break; logmsg("accept_connection %ld returned %ld", (long)sock, (long)msgsock); if(msgsock == CURL_SOCKET_BAD) goto sws_cleanup; + all_sockets[num_sockets] = msgsock; + num_sockets += 1; if(req->delay) curlx_wait_ms(req->delay); } while(msgsock > 0); @@ -2383,9 +2134,9 @@ static int test_sws(int argc, const char *argv[]) logmsg("====> Client disconnect %d", req->connmon); if(req->connmon) { - const char *keepopen = "[DISCONNECT]\n"; - sws_storerequest(keepopen, strlen(keepopen)); - req->connmon = FALSE; + static const char keepopen[] = "[DISCONNECT]\n"; + storerequest(keepopen, CURL_CSTRLEN(keepopen), + REQUEST_DUMP_FILENAME); } if(!req->open) @@ -2415,12 +2166,12 @@ static int test_sws(int argc, const char *argv[]) * 2) the socket is still open, and * 3) (stale) data is still available (or about to be available) * on that socket - * In that case, this loop will run once more and treat that stale + * In that case, this loop runs once more and treat that stale * data (in service_connection()) as the first data received on * this new HTTP request and report "** Unusual request" (skipall * would have otherwise caused that data to be ignored). Normally, - * that socket will be closed by the client and there will not be - * any stale data to cause this, but stranger things have happened + * that socket is closed by the client and there is no stale data + * to cause this, but stranger things have happened * (see issue #11678). */ init_httprequest(req); @@ -2443,11 +2194,10 @@ static int test_sws(int argc, const char *argv[]) sclose(sock); #ifdef USE_UNIX_SOCKETS - if(unlink_socket && socket_domain == AF_UNIX && unix_socket) { - rc = unlink(unix_socket); - logmsg("unlink(%s) = %d (%s)", unix_socket, - rc, curlx_strerror(rc, errbuf, sizeof(errbuf))); - } + if(unlink_socket && socket_domain == AF_UNIX && server_unix_socket && + unlink(server_unix_socket)) + logmsg("unlink(%s): %d (%s)", server_unix_socket, + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); #endif free(req); @@ -2465,19 +2215,7 @@ static int test_sws(int argc, const char *argv[]) clear_advisor_read_lock(loglockfile); } - restore_signal_handlers(false); + restore_signal_handlers(FALSE); - if(got_exit_signal) { - logmsg("========> %s sws (%s pid: %ld) exits with signal (%d)", - socket_type, location_str, (long)our_getpid(), exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } - - logmsg("========> sws quits"); - return 0; + return result; } diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 44e7976f1cd4..0ca6aca83e7d 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -186,7 +186,7 @@ static struct bf bfs[2]; static int nextone; /* index of next buffer to use */ static int current; /* index of buffer in use */ - /* control flags for crlf conversions */ + /* control flags for CRLF conversions */ static int newline = 0; /* fillbuf: in middle of newline expansion */ static int prevchar = -1; /* putbuf: previous char (cr check) */ @@ -287,7 +287,7 @@ static void nak(int error) memcpy(tp->th_msg, pe->e_msg, length + 1); length += 5; if(swrite(peer, &trsbuf.storage[0], length) != length) - logmsg("nak: fail\n"); + logmsg("nak: fail"); } /* @@ -296,7 +296,7 @@ static void nak(int error) */ static struct tftphdr *rw_init(int x) { - newline = 0; /* init crlf flag */ + newline = 0; /* init CRLF flag */ prevchar = -1; bfs[0].counter = BF_ALLOC; /* pass out the first buffer */ current = 0; @@ -413,7 +413,7 @@ static ssize_t write_behind(struct testcase *test, int convert) b = &bfs[nextone]; if(b->counter < -1) /* anything to flush? */ - return 0; /* just nop if nothing to do */ + return 0; /* nop if nothing to do */ if(!test->ofile) { char outfile[256]; @@ -453,11 +453,11 @@ static ssize_t write_behind(struct testcase *test, int convert) while(ct--) { /* loop over the buffer */ c = (unsigned char)*p++; /* pick up a character */ if(prevchar == '\r') { /* if prev char was cr */ - if(c == '\n') /* if have cr,lf then just */ + if(c == '\n') /* if have cr,lf then */ curl_lseek(test->ofile, -1, SEEK_CUR); /* smash lf on top of the cr */ else if(c == '\0') /* if have cr,nul then */ - goto skipit; /* just skip over the putc */ - /* else just fall through and allow it */ + goto skipit; /* skip over the putc */ + /* else fall through and allow it */ } /* formerly putc(c, file); */ @@ -488,7 +488,7 @@ static int writeit(struct testcase *test, struct tftphdr * volatile *dpp, * synch. Ie: that what I think is the other side's response to packet N is * really their response to packet N-1. * - * So, to try to prevent that, we flush all the input queued up for us on the + * To try to prevent that, we flush all the input queued up for us on the * network connection on our host. * * We return the number of packets we flushed (mostly for reporting when trace @@ -516,13 +516,11 @@ static int synchnet(curl_socket_t f /* socket to flush */) if(i) { j++; #ifdef USE_IPV6 - if(!use_ipv6) -#endif - fromaddrlen = sizeof(fromaddr.sa4); -#ifdef USE_IPV6 - else + if(socket_domain == AF_INET6) fromaddrlen = sizeof(fromaddr.sa6); + else #endif + fromaddrlen = sizeof(fromaddr.sa4); (void)recvfrom(f, rbuf, sizeof(rbuf), 0, &fromaddr.sa, &fromaddrlen); } else @@ -570,7 +568,7 @@ static int tftpd_parse_servercmd(struct testcase *req) else { logmsg("Unknown instruction found: %s", cmd); } - /* try to deal with CRLF or just LF */ + /* try to deal with CRLF or LF */ check = strchr(cmd, '\r'); if(!check) check = strchr(cmd, '\n'); @@ -858,7 +856,7 @@ static void recvtftp(struct testcase *test, const struct formats *pf) rap->th_block = htons(recvblock); (void)swrite(peer, &ackbuf.storage[0], 4); #if defined(HAVE_ALARM) && defined(SIGALRM) - mysignal(SIGALRM, justtimeout); /* just abort read on timeout */ + mysignal(SIGALRM, justtimeout); /* abort read on timeout */ alarm(rexmtval); #endif /* normally times out and quits */ @@ -918,10 +916,10 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) cp = (char *)&tp->th_stuff; filename = cp; do { - bool endofit = true; + bool endofit = TRUE; while(cp < &trsbuf.storage[size]) { if(*cp == '\0') { - endofit = false; + endofit = FALSE; break; } cp++; @@ -970,7 +968,7 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) curlx_fclose(server); for(pf = formata; pf->f_mode; pf++) - if(strcmp(pf->f_mode, mode) == 0) + if(!strcmp(pf->f_mode, mode)) break; if(!pf->f_mode) { nak(TFTP_EBADOP); @@ -1007,16 +1005,10 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) static int test_tftpd(int argc, const char **argv) { - srvr_sockaddr_union_t me; struct tftphdr *tp; ssize_t n = 0; int arg = 1; - unsigned short port = 8999; /* UDP */ curl_socket_t sock = CURL_SOCKET_BAD; - int flag; - int rc; - int error; - char errbuf[STRERROR_LEN]; struct testcase test; int result = 0; srvr_sockaddr_union_t from; @@ -1027,6 +1019,7 @@ static int test_tftpd(int argc, const char **argv) pidname = ".tftpd.pid"; serverlogfile = "log/tftpd.log"; serverlogslocked = 0; + server_port = 8999; /* UDP */ while(argc > arg) { const char *opt; @@ -1062,16 +1055,14 @@ static int test_tftpd(int argc, const char **argv) logdir = argv[arg++]; } else if(!strcmp("--ipv4", argv[arg])) { -#ifdef USE_IPV6 - ipv_inuse = "IPv4"; - use_ipv6 = FALSE; -#endif + socket_type = "IPv4"; + socket_domain = AF_INET; arg++; } else if(!strcmp("--ipv6", argv[arg])) { #ifdef USE_IPV6 - ipv_inuse = "IPv6"; - use_ipv6 = TRUE; + socket_type = "IPv6"; + socket_domain = AF_INET6; #endif arg++; } @@ -1080,7 +1071,7 @@ static int test_tftpd(int argc, const char **argv) if(argc > arg) { opt = argv[arg]; if(!curlx_str_number(&opt, &num, 0xffff)) - port = (unsigned short)num; + server_port = (uint16_t)num; arg++; } } @@ -1107,105 +1098,13 @@ static int test_tftpd(int argc, const char **argv) } snprintf(loglockfile, sizeof(loglockfile), "%s/%s/tftp-%s.lock", - logdir, SERVERLOGS_LOCKDIR, ipv_inuse); - - install_signal_handlers(true); + logdir, SERVERLOGS_LOCKDIR, socket_type); -#ifdef USE_IPV6 - if(!use_ipv6) -#endif - sock = socket(AF_INET, SOCK_DGRAM, 0); -#ifdef USE_IPV6 - else - sock = socket(AF_INET6, SOCK_DGRAM, 0); -#endif + install_signal_handlers(TRUE); - if(sock == CURL_SOCKET_BAD) { - error = SOCKERRNO; - logmsg("Error creating socket (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; + result = open_udp_sock(&sock, &server_port); + if(result) goto tftpd_cleanup; - } - - flag = 1; - if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { - error = SOCKERRNO; - logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; - goto tftpd_cleanup; - } - -#ifdef USE_IPV6 - if(!use_ipv6) { -#endif - memset(&me.sa4, 0, sizeof(me.sa4)); - me.sa4.sin_family = AF_INET; - me.sa4.sin_addr.s_addr = INADDR_ANY; - me.sa4.sin_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa4)); -#ifdef USE_IPV6 - } - else { - memset(&me.sa6, 0, sizeof(me.sa6)); - me.sa6.sin6_family = AF_INET6; - me.sa6.sin6_addr = in6addr_any; - me.sa6.sin6_port = htons(port); - rc = bind(sock, &me.sa, sizeof(me.sa6)); - } -#endif /* USE_IPV6 */ - if(rc) { - error = SOCKERRNO; - logmsg("Error binding socket on port %hu (%d) %s", port, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - result = 1; - goto tftpd_cleanup; - } - - if(!port) { - /* The system was supposed to choose a port number, figure out which - port we actually got and update the listener port value with it. */ - curl_socklen_t la_size; - srvr_sockaddr_union_t localaddr; - memset(&localaddr, 0, sizeof(localaddr)); -#ifdef USE_IPV6 - if(!use_ipv6) -#endif - la_size = sizeof(localaddr.sa4); -#ifdef USE_IPV6 - else - la_size = sizeof(localaddr.sa6); -#endif - if(getsockname(sock, &localaddr.sa, &la_size) < 0) { - error = SOCKERRNO; - logmsg("getsockname() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); - sclose(sock); - goto tftpd_cleanup; - } - switch(localaddr.sa.sa_family) { - case AF_INET: - port = ntohs(localaddr.sa4.sin_port); - break; -#ifdef USE_IPV6 - case AF_INET6: - port = ntohs(localaddr.sa6.sin6_port); - break; -#endif - default: - break; - } - if(!port) { - /* Real failure, listener port shall not be zero beyond this point. */ - logmsg("Apparently getsockname() succeeded, with listener port zero."); - logmsg("A valid reason for this failure is a binary built without"); - logmsg("proper network library linkage. This might not be the only"); - logmsg("reason, but double check it before anything else."); - result = 2; - goto tftpd_cleanup; - } - } tftpd_wrotepidfile = write_pidfile(pidname); if(!tftpd_wrotepidfile) { @@ -1214,25 +1113,23 @@ static int test_tftpd(int argc, const char **argv) } if(portname) { - tftpd_wroteportfile = write_portfile(portname, port); + tftpd_wroteportfile = write_portfile(portname, server_port); if(!tftpd_wroteportfile) { result = 1; goto tftpd_cleanup; } } - logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); + logmsg("Running %s version on port UDP/%d", socket_type, (int)server_port); for(;;) { fromlen = sizeof(from); #ifdef USE_IPV6 - if(!use_ipv6) -#endif - fromlen = sizeof(from.sa4); -#ifdef USE_IPV6 - else + if(socket_domain == AF_INET6) fromlen = sizeof(from.sa6); + else #endif + fromlen = sizeof(from.sa4); n = (ssize_t)recvfrom(sock, &trsbuf.storage[0], sizeof(trsbuf.storage), 0, &from.sa, &fromlen); if(got_exit_signal) @@ -1247,37 +1144,36 @@ static int test_tftpd(int argc, const char **argv) serverlogslocked = 1; #ifdef USE_IPV6 - if(!use_ipv6) { -#endif - from.sa4.sin_family = AF_INET; - peer = socket(AF_INET, SOCK_DGRAM, 0); + if(socket_domain == AF_INET6) { + from.sa6.sin6_family = AF_INET6; + peer = socket(AF_INET6, SOCK_DGRAM, 0); if(peer == CURL_SOCKET_BAD) { logmsg("socket"); result = 2; break; } - if(connect(peer, &from.sa, sizeof(from.sa4)) < 0) { + if(connect(peer, &from.sa, sizeof(from.sa6)) < 0) { logmsg("connect: fail"); result = 1; break; } -#ifdef USE_IPV6 } - else { - from.sa6.sin6_family = AF_INET6; - peer = socket(AF_INET6, SOCK_DGRAM, 0); + else +#endif + { + from.sa4.sin_family = AF_INET; + peer = socket(AF_INET, SOCK_DGRAM, 0); if(peer == CURL_SOCKET_BAD) { logmsg("socket"); result = 2; break; } - if(connect(peer, &from.sa, sizeof(from.sa6)) < 0) { + if(connect(peer, &from.sa, sizeof(from.sa4)) < 0) { logmsg("connect: fail"); result = 1; break; } } -#endif maxtimeout = 5 * TIMEOUT; @@ -1327,19 +1223,7 @@ static int test_tftpd(int argc, const char **argv) clear_advisor_read_lock(loglockfile); } - restore_signal_handlers(true); - - if(got_exit_signal) { - logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)", - ipv_inuse, (int)port, (long)our_getpid(), exit_signal); - /* - * To properly set the return status of the process we - * must raise the same signal SIGINT or SIGTERM that we - * caught and let the old handler take care of it. - */ - raise(exit_signal); - } + restore_signal_handlers(TRUE); - logmsg("========> tftpd quits"); return result; } diff --git a/tests/server/util.c b/tests/server/util.c index 3a76d2769755..2e1dfb6b71b7 100644 --- a/tests/server/util.c +++ b/tests/server/util.c @@ -33,6 +33,10 @@ #include #endif +#if defined(CURL_WINDOWS_UWP) && !defined(CURL_DEBUG_NO_WIN32_WND) +#define CURL_DEBUG_NO_WIN32_WND +#endif + void loghex(const unsigned char *buffer, ssize_t len) { char data[12000]; @@ -42,7 +46,7 @@ void loghex(const unsigned char *buffer, ssize_t len) ssize_t width = 0; int left = sizeof(data); - for(i = 0; i < len && (left >= 0); i++) { + for(i = 0; i < len && (left > 2); i++) { snprintf(optr, left, "%02x", ptr[i]); width += 2; optr += 2; @@ -66,7 +70,7 @@ void logmsg(const char *msg, ...) static int known_offset; if(!serverlogfile) { - fprintf(stderr, "Serverlogfile not set error\n"); + fprintf(stderr, "Error: Server log file not set\n"); return; } @@ -120,8 +124,7 @@ static void win32_cleanup(void) WSACleanup(); #endif - /* flush buffers of all streams regardless of their mode */ - _flushall(); + _flushall(); /* flush buffers of all streams regardless of their mode */ } int win32_init(void) @@ -129,30 +132,16 @@ int win32_init(void) curlx_now_init(); #ifdef USE_WINSOCK { - WORD wVersionRequested; - WSADATA wsaData; - int err; - char buffer[STRERROR_LEN]; - - wVersionRequested = MAKEWORD(2, 2); - err = WSAStartup(wVersionRequested, &wsaData); - if(err) { + WSADATA wsa; + if(WSAStartup(MAKEWORD(2, 2), &wsa)) { + char buffer[STRERROR_LEN]; curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)); fprintf(stderr, "Winsock init failed: %s\n", buffer); - logmsg("Error initialising Winsock -- aborting"); - return 1; - } - - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { - WSACleanup(); - curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)); - fprintf(stderr, "Winsock init failed: %s\n", buffer); - logmsg("No suitable winsock.dll found -- aborting"); + logmsg("Error initializing Winsock -- aborting"); return 1; } } -#endif /* USE_WINSOCK */ +#endif atexit(win32_cleanup); return 0; } @@ -230,7 +219,6 @@ void set_advisor_read_lock(const char *filename) FILE *lockfile; int error = 0; char errbuf[STRERROR_LEN]; - int res; do { lockfile = curlx_fopen(filename, "wb"); @@ -242,8 +230,7 @@ void set_advisor_read_lock(const char *filename) return; } - res = curlx_fclose(lockfile); - if(res) + if(curlx_fclose(lockfile)) logmsg("Error closing lock file %s error (%d) %s", filename, errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); } @@ -251,7 +238,7 @@ void set_advisor_read_lock(const char *filename) void clear_advisor_read_lock(const char *filename) { int error = 0; - int res; + int rc; /* * Log all removal failures. Even those due to file not existing. @@ -260,159 +247,206 @@ void clear_advisor_read_lock(const char *filename) */ do { - res = unlink(filename); + rc = unlink(filename); /* !checksrc! disable ERRNOVAR 1 */ - } while(res && ((error = errno) == EINTR)); - if(res) { + } while(rc && ((error = errno) == EINTR)); + if(rc) { char errbuf[STRERROR_LEN]; logmsg("Error removing lock file %s error (%d) %s", filename, error, curlx_strerror(error, errbuf, sizeof(errbuf))); } } -/* vars used to keep around previous signal handlers */ +/* store the entire request in a file */ +void storerequest(const char *reqbuf, size_t totalsize, const char *filename) +{ + int error = 0; + char errbuf[STRERROR_LEN]; + size_t written; + size_t writeleft; + FILE *dump; + char dumpfile[256]; -typedef void (*SIGHANDLER_T)(int); + snprintf(dumpfile, sizeof(dumpfile), "%s/%s", logdir, filename); + + if(!reqbuf) + return; + if(totalsize == 0) + return; + + do { + dump = curlx_fopen(dumpfile, "ab"); + /* !checksrc! disable ERRNOVAR 1 */ + } while(!dump && ((error = errno) == EINTR)); + if(!dump) { + logmsg("storerequest: Error opening file %s error (%d) %s", dumpfile, + error, curlx_strerror(error, errbuf, sizeof(errbuf))); + return; + } + + writeleft = totalsize; + do { + written = fwrite(&reqbuf[totalsize - writeleft], 1, writeleft, dump); + if(got_exit_signal) + goto storerequest_cleanup; + if(written > 0) + writeleft -= written; + error = errno; + /* !checksrc! disable ERRNOVAR 1 */ + } while((writeleft > 0) && (error == EINTR)); + + if(writeleft == 0) + logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile); + else if(writeleft > 0) { + logmsg("Error writing file %s error (%d) %s", dumpfile, + error, curlx_strerror(error, errbuf, sizeof(errbuf))); + logmsg("Wrote only (%zu bytes) of (%zu bytes) request input to %s", + totalsize - writeleft, totalsize, dumpfile); + } + +storerequest_cleanup: + + if(curlx_fclose(dump)) + logmsg("Error closing file %s error (%d) %s", dumpfile, + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); +} -#if defined(_MSC_VER) && (_MSC_VER <= 1700) -/* Workaround for warning C4306: - 'type cast' : conversion from 'int' to 'void (__cdecl *)(int)' */ -#undef SIG_ERR -#define SIG_ERR ((SIGHANDLER_T)(size_t)-1) +static bool initiate_exit(int signum) /* keep signal-safe */ +{ + if(got_exit_signal == 0) { + got_exit_signal = 1; + exit_signal = signum; + } +#ifdef _WIN32 + if(!exit_event) + return FALSE; + (void)SetEvent(exit_event); #endif + return TRUE; +} + +#ifndef _WIN32 + +/* vars used to keep around previous signal handlers */ + +typedef void (*SIGHANDLER_T)(int); #ifdef SIGHUP static SIGHANDLER_T old_sighup_handler = SIG_ERR; #endif - #ifdef SIGPIPE static SIGHANDLER_T old_sigpipe_handler = SIG_ERR; #endif - #ifdef SIGALRM static SIGHANDLER_T old_sigalrm_handler = SIG_ERR; #endif - #ifdef SIGINT static SIGHANDLER_T old_sigint_handler = SIG_ERR; #endif - #ifdef SIGTERM static SIGHANDLER_T old_sigterm_handler = SIG_ERR; #endif -#if defined(SIGBREAK) && defined(_WIN32) -static SIGHANDLER_T old_sigbreak_handler = SIG_ERR; -#endif - -#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) -static DWORD thread_main_id = 0; -static HANDLE thread_main_window = NULL; -static HWND hidden_main_window = NULL; -#endif - -/* signal handler that will be triggered to indicate that the program +/* signal handler that is triggered to indicate that the program * should finish its execution in a controlled manner as soon as possible. - * The first time this is called it will set got_exit_signal to one and - * store in exit_signal the signal that triggered its execution. - */ -/* + * The first time this is called it sets got_exit_signal to 1 and + * stores in exit_signal the signal that triggered its execution. + * * Only call signal-safe functions from the signal handler, as required by * the POSIX specification: - * https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html - * Hence, do not call 'logmsg()', and instead use 'open/write/close' to - * log errors. + * https://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html#tag_02_04_03 + * https://iafisher.com/2026/08/restart + * https://iafisher.com/2026/08/safe-signals + * https://lwn.net/Articles/414618/ */ -static void exit_signal_handler(int signum) +static void exit_signal_handler(int signum) /* keep signal-safe */ { int old_errno = errno; - if(!serverlogfile) { - static const char msg[] = "exit_signal_handler: serverlogfile not set\n"; - (void)!write(STDERR_FILENO, msg, sizeof(msg) - 1); - } - else { - int fd = -1; -#ifdef _WIN32 - if(!_sopen_s(&fd, serverlogfile, _O_WRONLY | _O_CREAT | _O_APPEND, - _SH_DENYNO, _S_IREAD | _S_IWRITE) && - fd != -1) { + exit_msg = "exit_signal_handler(): triggered"; + (void)initiate_exit(signum); +#if !(defined(HAVE_SIGACTION) && defined(SA_RESTART)) + (void)signal(signum, exit_signal_handler); +#endif + errno = old_errno; +} + +static SIGHANDLER_T set_signal(int signum, SIGHANDLER_T handler, int norestart) +{ +#if defined(HAVE_SIGACTION) && defined(SA_RESTART) + struct sigaction sa, oldsa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, signum); + sa.sa_flags = norestart ? 0 : SA_RESTART; + + if(sigaction(signum, &sa, &oldsa)) + return SIG_ERR; + + return oldsa.sa_handler; +#else + SIGHANDLER_T oldhdlr = signal(signum, handler); + +#ifdef HAVE_SIGINTERRUPT + if(oldhdlr != SIG_ERR) + siginterrupt(signum, norestart); #else - /* !checksrc! disable BANNEDFUNC 1 */ - fd = open(serverlogfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); - if(fd != -1) { + (void)norestart; #endif - static const char msg[] = "exit_signal_handler: called\n"; - (void)!write(fd, msg, sizeof(msg) - 1); - curlx_close(fd); - } - else { - static const char msg[] = "exit_signal_handler: failed opening "; - (void)!write(STDERR_FILENO, msg, sizeof(msg) - 1); - (void)!write(STDERR_FILENO, serverlogfile, strlen(serverlogfile)); - (void)!write(STDERR_FILENO, "\n", 1); - } - } - if(got_exit_signal == 0) { - got_exit_signal = 1; - exit_signal = signum; -#ifdef _WIN32 - if(exit_event) - (void)SetEvent(exit_event); + + return oldhdlr; #endif - } - (void)signal(signum, exit_signal_handler); - errno = old_errno; } -#ifdef _WIN32 -/* CTRL event handler for Windows Console applications to simulate - * SIGINT, SIGTERM and SIGBREAK on CTRL events and trigger signal handler. +#else /* _WIN32 */ + +/* CTRL event handler for Windows Console applications to handle exit events. * * Background information from MSDN: - * SIGINT is not supported for any Win32 application. When a CTRL+C - * interrupt occurs, Win32 operating systems generate a new thread - * to specifically handle that interrupt. This can cause a single-thread + * When a CTRL+C interrupt occurs, Win32 operating systems generate a new + * thread to specifically handle that interrupt. This can cause a single-thread * application, such as one in UNIX, to become multi-threaded and cause * unexpected behavior. - * [...] - * The SIGKILL and SIGTERM signals are not generated under Windows. - * They are included for ANSI compatibility. Therefore, you can set - * signal handlers for these signals by using signal, and you can also - * explicitly generate these signals by calling raise. Source: - * https://learn.microsoft.com/cpp/c-runtime-library/reference/signal */ -static BOOL WINAPI ctrl_event_handler(DWORD dwCtrlType) +static BOOL WINAPI ctrl_event_handler(DWORD dwCtrlType) /* keep signal-safe */ { + static const char msgU[] = "ctrl_event_handler(): unhandled\n"; + static const char msgH[] = "ctrl_event_handler(): handled\n"; + static const char msgF[] = "ctrl_event_handler(): failed to handle\n"; + HANDLE out = GetStdHandle(STD_ERROR_HANDLE); + DWORD dwWritten; int signum = 0; - logmsg("ctrl_event_handler: %lu", dwCtrlType); switch(dwCtrlType) { -#ifdef SIGINT case CTRL_C_EVENT: signum = SIGINT; break; -#endif -#ifdef SIGTERM case CTRL_CLOSE_EVENT: signum = SIGTERM; break; -#endif -#ifdef SIGBREAK case CTRL_BREAK_EVENT: signum = SIGBREAK; break; -#endif default: + WriteFile(out, msgU, CURL_CSTRLEN(msgU), &dwWritten, NULL); + exit_msg = msgU; return FALSE; } - if(signum) { - logmsg("ctrl_event_handler: %lu -> %d", dwCtrlType, signum); - raise(signum); + if(!initiate_exit(signum)) { + WriteFile(out, msgF, CURL_CSTRLEN(msgF), &dwWritten, NULL); + exit_msg = msgF; + return FALSE; } + WriteFile(out, msgH, CURL_CSTRLEN(msgH), &dwWritten, NULL); + exit_msg = msgH; return TRUE; } -#endif -#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) +#ifndef CURL_DEBUG_NO_WIN32_WND +static DWORD thread_main_id = 0; +static HANDLE thread_main_window = NULL; +static HWND hidden_main_window = NULL; + /* Window message handler for Windows applications to add support * for graceful process termination via taskkill (without /f) which * sends WM_CLOSE to all Windows of a process (even hidden ones). @@ -423,43 +457,41 @@ static BOOL WINAPI ctrl_event_handler(DWORD dwCtrlType) static LRESULT CALLBACK main_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - int signum = 0; if(hwnd == hidden_main_window) { switch(uMsg) { -#ifdef SIGTERM - case WM_CLOSE: - signum = SIGTERM; + case WM_CLOSE: { + static const char msg[] = "main_window_proc(): WM_CLOSE -> SIGTERM\n"; + DWORD dwWritten; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, CURL_CSTRLEN(msg), + &dwWritten, NULL); + exit_msg = msg; + initiate_exit(SIGTERM); break; -#endif + } case WM_DESTROY: PostQuitMessage(0); break; } - if(signum) { - logmsg("main_window_proc: %u -> %d", uMsg, signum); - raise(signum); - } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } -/* Window message queue loop for hidden main window, details see above. - */ + +/* Window message queue loop for hidden main window, details see above. */ static DWORD WINAPI main_window_loop(void *lpParameter) { WNDCLASS wc; BOOL ret; MSG msg; - DWORD err; - char buffer[WINAPI_ERROR_LEN]; + DWORD dwWritten; ZeroMemory(&wc, sizeof(wc)); wc.lpfnWndProc = (WNDPROC)main_window_proc; wc.hInstance = (HINSTANCE)lpParameter; wc.lpszClassName = TEXT("MainWClass"); if(!RegisterClass(&wc)) { - err = GetLastError(); - curlx_winapi_strerror(err, buffer, sizeof(buffer)); - fprintf(stderr, "RegisterClass failed: %s\n", buffer); + static const char str[] = "RegisterClass() failed\n"; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), str, CURL_CSTRLEN(str), + &dwWritten, NULL); return (DWORD)-1; } @@ -471,18 +503,18 @@ static DWORD WINAPI main_window_loop(void *lpParameter) (HWND)NULL, (HMENU)NULL, wc.hInstance, NULL); if(!hidden_main_window) { - err = GetLastError(); - curlx_winapi_strerror(err, buffer, sizeof(buffer)); - fprintf(stderr, "CreateWindowEx failed: (0x%08lx) - %s\n", err, buffer); + static const char str[] = "CreateWindowEx() failed\n"; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), str, CURL_CSTRLEN(str), + &dwWritten, NULL); return (DWORD)-1; } do { ret = GetMessage(&msg, NULL, 0, 0); if(ret == -1) { - err = GetLastError(); - curlx_winapi_strerror(err, buffer, sizeof(buffer)); - fprintf(stderr, "GetMessage failed: (0x%08lx) - %s\n", err, buffer); + static const char str[] = "GetMessage() failed\n"; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), str, CURL_CSTRLEN(str), + &dwWritten, NULL); return (DWORD)-1; } else if(ret) { @@ -498,58 +530,25 @@ static DWORD WINAPI main_window_loop(void *lpParameter) hidden_main_window = NULL; return (DWORD)msg.wParam; } -#endif - -static SIGHANDLER_T set_signal(int signum, SIGHANDLER_T handler, - bool restartable) -{ -#if defined(HAVE_SIGACTION) && defined(SA_RESTART) - struct sigaction sa, oldsa; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = handler; - sigemptyset(&sa.sa_mask); - sigaddset(&sa.sa_mask, signum); - sa.sa_flags = restartable ? SA_RESTART : 0; - - if(sigaction(signum, &sa, &oldsa)) - return SIG_ERR; - - return oldsa.sa_handler; -#else - SIGHANDLER_T oldhdlr = signal(signum, handler); - -#ifdef HAVE_SIGINTERRUPT - if(oldhdlr != SIG_ERR) - siginterrupt(signum, (int)restartable); -#else - (void)restartable; -#endif - - return oldhdlr; -#endif -} +#endif /* !CURL_DEBUG_NO_WIN32_WND */ +#endif /* !_WIN32 */ void install_signal_handlers(bool keep_sigalrm) { char errbuf[STRERROR_LEN]; (void)errbuf; -#ifdef _WIN32 - /* setup Windows exit event before any signal can trigger */ - exit_event = CreateEvent(NULL, TRUE, FALSE, NULL); - if(!exit_event) - logmsg("cannot create exit event"); -#endif + (void)keep_sigalrm; +#ifndef _WIN32 #ifdef SIGHUP /* ignore SIGHUP signal */ - old_sighup_handler = set_signal(SIGHUP, SIG_IGN, FALSE); + old_sighup_handler = set_signal(SIGHUP, SIG_IGN, 0); if(old_sighup_handler == SIG_ERR) logmsg("cannot install SIGHUP handler: (%d) %s", errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); #endif #ifdef SIGPIPE /* ignore SIGPIPE signal */ - old_sigpipe_handler = set_signal(SIGPIPE, SIG_IGN, FALSE); + old_sigpipe_handler = set_signal(SIGPIPE, SIG_IGN, 0); if(old_sigpipe_handler == SIG_ERR) logmsg("cannot install SIGPIPE handler: (%d) %s", errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); @@ -557,81 +556,72 @@ void install_signal_handlers(bool keep_sigalrm) #ifdef SIGALRM if(!keep_sigalrm) { /* ignore SIGALRM signal */ - old_sigalrm_handler = set_signal(SIGALRM, SIG_IGN, FALSE); + old_sigalrm_handler = set_signal(SIGALRM, SIG_IGN, 0); if(old_sigalrm_handler == SIG_ERR) logmsg("cannot install SIGALRM handler: (%d) %s", errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); } -#else - (void)keep_sigalrm; #endif #ifdef SIGINT /* handle SIGINT signal with our exit_signal_handler */ - old_sigint_handler = set_signal(SIGINT, exit_signal_handler, TRUE); + old_sigint_handler = set_signal(SIGINT, exit_signal_handler, 1); if(old_sigint_handler == SIG_ERR) logmsg("cannot install SIGINT handler: (%d) %s", errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); #endif #ifdef SIGTERM /* handle SIGTERM signal with our exit_signal_handler */ - old_sigterm_handler = set_signal(SIGTERM, exit_signal_handler, TRUE); + old_sigterm_handler = set_signal(SIGTERM, exit_signal_handler, 1); if(old_sigterm_handler == SIG_ERR) logmsg("cannot install SIGTERM handler: (%d) %s", errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); #endif -#if defined(SIGBREAK) && defined(_WIN32) - /* handle SIGBREAK signal with our exit_signal_handler */ - old_sigbreak_handler = set_signal(SIGBREAK, exit_signal_handler, TRUE); - if(old_sigbreak_handler == SIG_ERR) - logmsg("cannot install SIGBREAK handler: (%d) %s", - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); -#endif -#ifdef _WIN32 +#else /* _WIN32 */ + /* setup Windows exit event before any signal can trigger */ + exit_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!exit_event) + logmsg("cannot create exit event"); if(!SetConsoleCtrlHandler(ctrl_event_handler, TRUE)) logmsg("cannot install CTRL event handler"); -#ifndef CURL_WINDOWS_UWP +#ifndef CURL_DEBUG_NO_WIN32_WND thread_main_window = CreateThread(NULL, 0, &main_window_loop, GetModuleHandle(NULL), 0, &thread_main_id); if(!thread_main_window || !thread_main_id) logmsg("cannot start main window loop"); #endif -#endif +#endif /* !_WIN32 */ } void restore_signal_handlers(bool keep_sigalrm) { + (void)keep_sigalrm; +#ifndef _WIN32 #ifdef SIGHUP - if(SIG_ERR != old_sighup_handler) - (void)set_signal(SIGHUP, old_sighup_handler, FALSE); + if(old_sighup_handler != SIG_ERR) + (void)set_signal(SIGHUP, old_sighup_handler, 0); #endif #ifdef SIGPIPE - if(SIG_ERR != old_sigpipe_handler) - (void)set_signal(SIGPIPE, old_sigpipe_handler, FALSE); + if(old_sigpipe_handler != SIG_ERR) + (void)set_signal(SIGPIPE, old_sigpipe_handler, 0); #endif #ifdef SIGALRM if(!keep_sigalrm) { - if(SIG_ERR != old_sigalrm_handler) - (void)set_signal(SIGALRM, old_sigalrm_handler, FALSE); + if(old_sigalrm_handler != SIG_ERR) + (void)set_signal(SIGALRM, old_sigalrm_handler, 0); } -#else - (void)keep_sigalrm; #endif #ifdef SIGINT - if(SIG_ERR != old_sigint_handler) - (void)set_signal(SIGINT, old_sigint_handler, FALSE); + if(old_sigint_handler != SIG_ERR) + (void)set_signal(SIGINT, old_sigint_handler, 0); #endif #ifdef SIGTERM - if(SIG_ERR != old_sigterm_handler) - (void)set_signal(SIGTERM, old_sigterm_handler, FALSE); -#endif -#if defined(SIGBREAK) && defined(_WIN32) - if(SIG_ERR != old_sigbreak_handler) - (void)set_signal(SIGBREAK, old_sigbreak_handler, FALSE); + if(old_sigterm_handler != SIG_ERR) + (void)set_signal(SIGTERM, old_sigterm_handler, 0); #endif -#ifdef _WIN32 +#else /* _WIN32 */ (void)SetConsoleCtrlHandler(ctrl_event_handler, FALSE); -#ifndef CURL_WINDOWS_UWP +#ifndef CURL_DEBUG_NO_WIN32_WND if(thread_main_window && thread_main_id) { if(PostThreadMessage(thread_main_id, WM_APP, 0, 0)) { if(WaitForSingleObjectEx(thread_main_window, INFINITE, TRUE)) { @@ -642,13 +632,10 @@ void restore_signal_handlers(bool keep_sigalrm) } } } - if(exit_event) { - if(CloseHandle(exit_event)) { - exit_event = NULL; - } - } -#endif #endif + if(exit_event && CloseHandle(exit_event)) + exit_event = NULL; +#endif /* !_WIN32 */ } #ifdef USE_UNIX_SOCKETS @@ -656,7 +643,6 @@ void restore_signal_handlers(bool keep_sigalrm) int bind_unix_socket(curl_socket_t sock, const char *unix_socket, struct sockaddr_un *sau) { - int error; char errbuf[STRERROR_LEN]; int rc; size_t len; @@ -677,47 +663,45 @@ int bind_unix_socket(curl_socket_t sock, const char *unix_socket, curlx_strcopy(sau->sun_path, sizeof(sau->sun_path), unix_socket, len); rc = bind(sock, (struct sockaddr *)sau, sizeof(struct sockaddr_un)); if(rc && SOCKERRNO == SOCKEADDRINUSE) { - curlx_struct_stat statbuf; + int sockerr; /* socket already exists. Perhaps it is stale? */ curl_socket_t unixfd = socket(AF_UNIX, SOCK_STREAM, 0); if(unixfd == CURL_SOCKET_BAD) { + sockerr = SOCKERRNO; logmsg("Failed to create socket at %s (%d) %s", unix_socket, - SOCKERRNO, curlx_strerror(SOCKERRNO, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return -1; } /* check whether the server is alive */ rc = connect(unixfd, (struct sockaddr*)sau, sizeof(struct sockaddr_un)); - error = SOCKERRNO; + sockerr = SOCKERRNO; sclose(unixfd); - if(rc && error != SOCKECONNREFUSED) { + if(rc && sockerr != SOCKECONNREFUSED) { logmsg("Failed to connect to %s (%d) %s", unix_socket, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); return rc; } +#if !defined(_WIN32) && defined(S_IFSOCK) /* No lstat(), S_IFSOCK on Windows */ /* socket server is not alive, now check if it was actually a socket. */ -#ifdef _WIN32 - /* Windows does not have lstat function. */ - rc = curlx_stat(unix_socket, &statbuf); -#else - rc = lstat(unix_socket, &statbuf); -#endif - if(rc) { - logmsg("Error binding socket, failed to stat %s (%d) %s", unix_socket, - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); - return rc; - } -#ifdef S_IFSOCK - if((statbuf.st_mode & S_IFSOCK) != S_IFSOCK) { - logmsg("Error binding socket, failed to stat %s", unix_socket); - return -1; + { + curlx_struct_stat statbuf; + rc = lstat(unix_socket, &statbuf); + if(rc && errno != ENOENT) { + logmsg("Error binding socket, failed to stat %s (%d) %s", unix_socket, + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); + return -1; + } + if(!rc && (statbuf.st_mode & S_IFMT) != S_IFSOCK) { + logmsg("Error binding socket, %s is not a socket", unix_socket); + return -1; + } } #endif /* dead socket, cleanup and retry bind */ - rc = unlink(unix_socket); - if(rc) { - logmsg("Error binding socket, failed to unlink %s (%d) %s", unix_socket, + if(unlink(unix_socket) && errno != ENOENT) { + logmsg("Error binding socket, failed to unlink %s: %d (%s)", unix_socket, errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); - return rc; + return -1; } /* stale socket is gone, retry bind */ rc = bind(sock, (struct sockaddr *)sau, sizeof(struct sockaddr_un)); @@ -727,7 +711,7 @@ int bind_unix_socket(curl_socket_t sock, const char *unix_socket, #endif curl_socket_t sockdaemon(curl_socket_t sock, - unsigned short *listenport, + uint16_t *listenport, const char *unix_socket, bool bind_only) { @@ -739,7 +723,7 @@ curl_socket_t sockdaemon(curl_socket_t sock, int maxretr = 10; int delay = 20; int attempt = 0; - int error = 0; + int sockerr = 0; char errbuf[STRERROR_LEN]; #ifndef USE_UNIX_SOCKETS @@ -755,16 +739,15 @@ curl_socket_t sockdaemon(curl_socket_t sock, rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag)); if(rc) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); if(maxretr) { - rc = curlx_wait_ms(delay); - if(rc) { + if(curlx_wait_ms(delay)) { /* should not happen */ - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("curlx_wait_ms() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(sock); return CURL_SOCKET_BAD; } @@ -782,7 +765,7 @@ curl_socket_t sockdaemon(curl_socket_t sock, if(rc) { logmsg("setsockopt(SO_REUSEADDR) failed %d times in %d ms. " "Error (%d) %s", attempt, totdelay, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); logmsg("Continuing anyway..."); } #if defined(_WIN32) && defined(USE_UNIX_SOCKETS) @@ -819,15 +802,15 @@ curl_socket_t sockdaemon(curl_socket_t sock, } if(rc) { - error = SOCKERRNO; + sockerr = SOCKERRNO; #ifdef USE_UNIX_SOCKETS if(socket_domain == AF_UNIX) logmsg("Error binding socket on path %s (%d) %s", unix_socket, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); else #endif logmsg("Error binding socket on port %hu (%d) %s", *listenport, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(sock); return CURL_SOCKET_BAD; } @@ -849,9 +832,9 @@ curl_socket_t sockdaemon(curl_socket_t sock, #endif la_size = sizeof(localaddr.sa4); if(getsockname(sock, &localaddr.sa, &la_size) < 0) { - error = SOCKERRNO; + sockerr = SOCKERRNO; logmsg("getsockname() failed with error (%d) %s", - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(sock); return CURL_SOCKET_BAD; } @@ -885,14 +868,351 @@ curl_socket_t sockdaemon(curl_socket_t sock, } /* start accepting connections */ - rc = listen(sock, 5); - if(rc) { - error = SOCKERRNO; + if(listen(sock, 5)) { + sockerr = SOCKERRNO; logmsg("listen(%ld, 5) failed with error (%d) %s", (long)sock, - error, curlx_strerror(error, errbuf, sizeof(errbuf))); + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); sclose(sock); return CURL_SOCKET_BAD; } return sock; } + +int open_udp_sock(curl_socket_t *psock, uint16_t *pport) +{ + srvr_sockaddr_union_t me; + curl_socket_t sock = CURL_SOCKET_BAD; + uint16_t port = *pport; + int sockerr, flag, rc; + char errbuf[STRERROR_LEN]; + int result = 0; + + sock = socket(socket_domain, SOCK_DGRAM, 0); + + if(sock == CURL_SOCKET_BAD) { + sockerr = SOCKERRNO; + logmsg("Error creating socket (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + + flag = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { + sockerr = SOCKERRNO; + logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + +#ifdef USE_IPV6 + if(socket_domain == AF_INET6) { + memset(&me.sa6, 0, sizeof(me.sa6)); + me.sa6.sin6_family = AF_INET6; + me.sa6.sin6_addr = in6addr_any; + me.sa6.sin6_port = htons(port); + rc = bind(sock, &me.sa, sizeof(me.sa6)); + } + else +#endif + { + memset(&me.sa4, 0, sizeof(me.sa4)); + me.sa4.sin_family = AF_INET; + me.sa4.sin_addr.s_addr = INADDR_ANY; + me.sa4.sin_port = htons(port); + rc = bind(sock, &me.sa, sizeof(me.sa4)); + } + if(rc) { + sockerr = SOCKERRNO; + logmsg("Error binding socket on port %hu (%d) %s", port, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + + if(!port) { + /* The system was supposed to choose a port number, figure out which + port we actually got and update the listener port value with it. */ + curl_socklen_t la_size; + srvr_sockaddr_union_t localaddr; + memset(&localaddr, 0, sizeof(localaddr)); +#ifdef USE_IPV6 + if(socket_domain == AF_INET6) + la_size = sizeof(localaddr.sa6); + else +#endif + la_size = sizeof(localaddr.sa4); + if(getsockname(sock, &localaddr.sa, &la_size) < 0) { + sockerr = SOCKERRNO; + logmsg("getsockname() failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + switch(localaddr.sa.sa_family) { + case AF_INET: + port = ntohs(localaddr.sa4.sin_port); + break; +#ifdef USE_IPV6 + case AF_INET6: + port = ntohs(localaddr.sa6.sin6_port); + break; +#endif + default: + break; + } + if(!port) { + /* Real failure, listener port shall not be zero beyond this point. */ + logmsg("Apparently getsockname() succeeded, with listener port zero."); + logmsg("A valid reason for this failure is a binary built without"); + logmsg("proper network library linkage. This might not be the only"); + logmsg("reason, but double check it before anything else."); + result = 2; + goto out; + } + } + +out: + if(result) { + if(sock != CURL_SOCKET_BAD) + sclose(sock); + sock = CURL_SOCKET_BAD; + port = 0; + } + *psock = sock; + *pport = port; + return result; +} + +bool socket_domain_is_ip(void) +{ + switch(socket_domain) { + case AF_INET: +#ifdef USE_IPV6 + case AF_INET6: +#endif + return TRUE; + default: + /* case AF_UNIX: */ + return FALSE; + } +} + +int open_stream_sock(curl_socket_t *psock, uint16_t *pport) +{ + srvr_sockaddr_union_t me; + char errbuf[STRERROR_LEN]; + int flag, sockerr, rc = 0; + curl_socket_t sock = CURL_SOCKET_BAD; + uint16_t port = *pport; + int result = 0; + + sock = socket(socket_domain, SOCK_STREAM, 0); + + if(sock == CURL_SOCKET_BAD) { + sockerr = SOCKERRNO; + logmsg("Error creating socket (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + +#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) + if(socket_domain != AF_UNIX) { +#endif + flag = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void *)&flag, sizeof(flag))) { + sockerr = SOCKERRNO; + logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } +#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) + } +#endif + if(curlx_nonblock(sock, TRUE)) { + sockerr = SOCKERRNO; + logmsg("curlx_nonblock failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + + switch(socket_domain) { + case AF_INET: + memset(&me.sa4, 0, sizeof(me.sa4)); + me.sa4.sin_family = AF_INET; + me.sa4.sin_addr.s_addr = INADDR_ANY; + me.sa4.sin_port = htons(port); + rc = bind(sock, &me.sa, sizeof(me.sa4)); + break; +#ifdef USE_IPV6 + case AF_INET6: + memset(&me.sa6, 0, sizeof(me.sa6)); + me.sa6.sin6_family = AF_INET6; + me.sa6.sin6_addr = in6addr_any; + me.sa6.sin6_port = htons(port); + rc = bind(sock, &me.sa, sizeof(me.sa6)); + break; +#endif /* USE_IPV6 */ +#ifdef USE_UNIX_SOCKETS + case AF_UNIX: + rc = bind_unix_socket(sock, server_unix_socket, &me.sau); +#endif /* USE_UNIX_SOCKETS */ + } + if(rc) { + sockerr = SOCKERRNO; +#ifdef USE_UNIX_SOCKETS + if(socket_domain == AF_UNIX) + logmsg("Error binding socket on path %s (%d) %s", server_unix_socket, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + else +#endif + logmsg("Error binding socket on port %hu (%d) %s", port, + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + + if(!port) { + /* The system was supposed to choose a port number, figure out which + port we actually got and update the listener port value with it. */ + curl_socklen_t la_size; + srvr_sockaddr_union_t localaddr; + memset(&localaddr, 0, sizeof(localaddr)); +#ifdef USE_IPV6 + if(socket_domain == AF_INET6) + la_size = sizeof(localaddr.sa6); + else +#endif + la_size = sizeof(localaddr.sa4); + if(getsockname(sock, &localaddr.sa, &la_size) < 0) { + sockerr = SOCKERRNO; + logmsg("getsockname() failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + result = 1; + goto out; + } + + switch(localaddr.sa.sa_family) { + case AF_INET: + port = ntohs(localaddr.sa4.sin_port); + break; +#ifdef USE_IPV6 + case AF_INET6: + port = ntohs(localaddr.sa6.sin6_port); + break; +#endif + default: + break; + } + if(!port) { + /* Real failure, listener port shall not be zero beyond this point. */ + logmsg("Apparently getsockname() succeeded, with listener port zero."); + logmsg("A valid reason for this failure is a binary built without"); + logmsg("proper network library linkage. This might not be the only"); + logmsg("reason, but double check it before anything else."); + result = 1; + goto out; + } + } +out: + if(result) { + if(sock != CURL_SOCKET_BAD) + sclose(sock); + sock = CURL_SOCKET_BAD; + port = 0; + } + *psock = sock; + *pport = port; + return result; +} + +/* returns a socket handle, or 0 if there are no more waiting sockets, + or < 0 if there was an error */ +curl_socket_t accept_connection(curl_socket_t listen_sock) +{ + curl_socket_t msgsock = CURL_SOCKET_BAD; + int sockerr; + char errbuf[STRERROR_LEN]; + int flag = 1; + + msgsock = accept(listen_sock, NULL, NULL); + + if(got_exit_signal) { + if(msgsock != CURL_SOCKET_BAD) + sclose(msgsock); + return CURL_SOCKET_BAD; + } + + if(msgsock == CURL_SOCKET_BAD) { + sockerr = SOCKERRNO; + if(SOCK_EAGAIN(sockerr)) { + /* nothing to accept */ + return 0; + } + logmsg("MAJOR ERROR, accept() failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + return CURL_SOCKET_BAD; + } + + if(curlx_nonblock(msgsock, TRUE)) { + sockerr = SOCKERRNO; + logmsg("curlx_nonblock failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + sclose(msgsock); + return CURL_SOCKET_BAD; + } + +#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) + if(socket_domain != AF_UNIX) { +#endif + if(setsockopt(msgsock, SOL_SOCKET, SO_KEEPALIVE, + (void *)&flag, sizeof(flag))) { + sockerr = SOCKERRNO; + logmsg("setsockopt(SO_KEEPALIVE) failed with error (%d) %s", + sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); + sclose(msgsock); + return CURL_SOCKET_BAD; + } +#if defined(_WIN32) && defined(USE_UNIX_SOCKETS) + } +#endif + + /* + * As soon as this server accepts a connection from the test harness it + * must set the server logs advisor read lock to indicate that server + * logs should not be read until this lock is removed by this server. + */ + + if(!serverlogslocked) + set_advisor_read_lock(loglockfile); + serverlogslocked += 1; + + logmsg("====> Client connect"); + +#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED) + if(socket_domain_is_ip()) { + /* + * Disable the Nagle algorithm to make it easier to send out a large + * response in many small segments to torture the clients more. + */ + if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY, + (void *)&flag, sizeof(flag))) + logmsg("====> TCP_NODELAY failed"); + } +#endif + + return msgsock; +} + +bool curlx_str_case_equal(const struct Curl_str *s1, + const struct Curl_str *s2) +{ + return ((s1->len == s2->len) && + !CURL_STRNICMP(s1->str, s2->str, s1->len)); +} diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index 3f345ad74a44..ac451cb04b7f 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -67,18 +67,16 @@ use testutil qw( our $logfile; # server log filename, for logmsg #*************************************************************************** -# Just for convenience, test harness uses 'https' and 'httptls' literals as -# values for 'proto' variable in order to differentiate different servers. -# 'https' literal is used for stunnel based https test servers, and 'httptls' -# is used for non-stunnel https test servers. +# For convenience, test harness uses 'https' literal as values for 'proto' +# variable in order to differentiate different servers. 'https' literal is +# used for stunnel based https test servers. #********************************************************************** # logmsg is general message logging subroutine for our test servers. # sub logmsg { my ($seconds, $usec) = Time::HiRes::gettimeofday(); - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = - localtime($seconds); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($seconds); my $now = sprintf("%02d:%02d:%02d.%06d ", $hour, $min, $sec, $usec); # we see warnings on Windows run that $logfile is used uninitialized # TODO: not found yet where this comes from @@ -106,7 +104,7 @@ sub serverfactors { $ipvnum = ($4 && ($4 =~ /6$/)) ? 6 : 4; } elsif($server =~ - /^(dns|tftp|sftp|socks|ssh|rtsp|gopher|httptls)(\d*)(-ipv6|)$/) { + /^(dns|tftp|sftp|socks|ssh|rtsp|gopher)(\d*)(-ipv6|)$/) { $proto = $1; $idnum = ($2 && ($2 > 1)) ? $2 : 1; $ipvnum = ($3 && ($3 =~ /6$/)) ? 6 : 4; @@ -125,7 +123,7 @@ sub servername_str { $proto = uc($proto) if($proto); die "unsupported protocol: '$proto'" unless($proto && - ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT|MQTTS))$/)); + ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|DICT|SMB|SMBS|TELNET|MQTT|MQTTS))$/)); $ipver = (not $ipver) ? 'ipv4' : lc($ipver); die "unsupported IP version: '$ipver'" unless($ipver && diff --git a/tests/servers.pm b/tests/servers.pm index dba3c3e1d1e1..99db988124e1 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -96,7 +96,6 @@ use sshhelp qw( find_sshd find_ssh find_sftp - find_httptlssrv sshversioninfo ); @@ -119,32 +118,31 @@ use testutil qw( my %serverpidfile; # all server pid filenames, identified by server id my %serverportfile;# all server port filenames, identified by server id -my $sshdvernum; # for socks server, ssh daemon version number -my $sshdverstr; # for socks server, ssh daemon version string -my $sshderror; # for socks server, ssh daemon version error -my %doesntrun; # servers that do not work, identified by pidfile +my $sshdvernum; # for socks server, ssh daemon version number +my $sshdverstr; # for socks server, ssh daemon version string +my $sshderror; # for socks server, ssh daemon version error +my %doesntrun; # servers that do not work, identified by pidfile my %PORT = (nolisten => 47); # port we use for a local non-listening service -my $server_response_maxtime=13; -my $httptlssrv = find_httptlssrv(); -my %run; # running server -my %runcert; # cert file currently in use by an ssl running server -my $CLIENTIP="127.0.0.1"; # address which curl uses for incoming connections -my $CLIENT6IP="[::1]"; # address which curl uses for incoming connections +my $server_response_maxtime = 13; +my %run; # running server +my %runcert; # cert file currently in use by an SSL running server +my $CLIENTIP = "127.0.0.1"; # address which curl uses for incoming connections +my $CLIENT6IP = "[::1]"; # address which curl uses for incoming connections my $posix_pwd = build_sys_abs_path($pwd); # current working directory in POSIX format -my $h2cver = "h2c"; # this version is decided by the nghttp2 lib being used -my $HOSTIP="127.0.0.1"; # address on which the test server listens -my $HOST6IP="[::1]"; # address on which the test server listens -my $HTTPUNIXPATH; # HTTP server Unix domain socket path -my $SOCKSUNIXPATH; # socks server Unix domain socket path +my $h2cver = "h2c"; # this version is decided by the nghttp2 lib being used +my $HOSTIP = "127.0.0.1"; # address on which the test server listens +my $HOST6IP = "[::1]"; # address on which the test server listens +my $HTTPUNIXPATH; # HTTP server Unix domain socket path +my $SOCKSUNIXPATH; # socks server Unix domain socket path my $SSHSRVMD5 = "[uninitialized]"; # MD5 of ssh server public key my $SSHSRVSHA256 = "[uninitialized]"; # SHA256 of ssh server public key -my $USER; # name of the current user -my $sshdid; # for socks server, ssh daemon version id -my $ftpchecktime=1; # time it took to verify our test FTP server +my $USER; # name of the current user +my $sshdid; # for socks server, ssh daemon version id +my $ftpchecktime = 1; # time it took to verify our test FTP server my $SERVER_TIMEOUT_SEC = 15; # time for a server to spin up # Variables shared with runtests.pl -our $SOCKSIN="socksd-request.log"; # what curl sent to the SOCKS proxy +our $SOCKSIN = "socksd-request.log"; # what curl sent to the SOCKS proxy our $err_unexpected; # error instead of warning on server unexpectedly alive our $debugprotocol; # nonzero for verbose server logs our $stunnel; # path to stunnel command @@ -153,15 +151,15 @@ our $stunnel; # path to stunnel command # Check for a command in the PATH of the test server. # sub checkcmd { - my ($cmd, @extrapaths)=@_; + my ($cmd, @extrapaths) = @_; my @paths; if($^O eq 'MSWin32' || $^O eq 'dos' || $^O eq 'os2') { # PATH separator is different - @paths=(split(';', $ENV{'PATH'}), @extrapaths); + @paths = (split(';', $ENV{'PATH'}), @extrapaths); } else { - @paths=(split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin", - "/sbin", "/usr/bin", "/usr/local/bin", @extrapaths); + @paths = (split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin", + "/sbin", "/usr/bin", "/usr/local/bin", @extrapaths); } for(@paths) { if(-x "$_/$cmd" . exe_ext('SYS') && ! -d "$_/$cmd" . exe_ext('SYS')) { @@ -206,11 +204,11 @@ sub initserverconfig { $USER = $ENV{LOGNAME}; # Some Unix (I think) } if(!$USER) { - $USER = `whoami`; + $USER = qx(whoami); chomp $USER; } if(!$USER) { - $USER = `id -un`; + $USER = qx(id -un); chomp $USER; } init_serverpidfile_hash(); @@ -236,7 +234,7 @@ sub init_serverpidfile_hash { } } } - for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'httptls', + for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'mqtts', 'https-mtls', 'dns')) { for my $ipvnum ((4, 6)) { @@ -265,7 +263,7 @@ sub init_serverpidfile_hash { } ####################################################################### -# Check if a given child process has just died. Reaps it if so. +# Check if a given child process has died. Reaps it if so. # sub checkdied { my $pid = $_[0]; @@ -291,7 +289,7 @@ sub checkdied { # 4 for an unsupported server type # sub serverfortest { - my (@what)=@_; + my (@what) = @_; for(my $i = scalar(@what) - 1; $i >= 0; $i--) { my $srvrline = $what[$i]; @@ -301,10 +299,6 @@ sub serverfortest { my $server = "${1}"; my $lnrest = "${2}"; my $tlsext; - if($server =~ /^(httptls)(\+)(ext|srp)(\d*)(-ipv6|)$/) { - $server = "${1}${4}${5}"; - $tlsext = uc("TLS-${3}"); - } my @lprotocols = @protocols; @@ -332,7 +326,7 @@ sub serverfortest { # Return the pids (yes plural) of the new child process to the parent. # sub startnew { - my ($cmd, $pidfile, $timeout, $fakepidfile)=@_; + my ($cmd, $pidfile, $timeout, $fakepidfile) = @_; logmsg "startnew: $cmd\n" if($verbose); @@ -340,7 +334,7 @@ sub startnew { if(not defined $child) { logmsg "startnew: fork() failure detected\n"; - return (-1,-1); + return (-1, -1); } if(0 == $child) { @@ -351,10 +345,10 @@ sub startnew { # Put an "exec" in front of the command so that the child process # keeps this child's process ID. - exec("exec $cmd") || die "Cannot exec() $cmd: $!"; + exec("exec $cmd") or die "Cannot exec() $cmd: $!"; # exec() should never return back here to this process. We protect - # ourselves by calling die() just in case something goes really bad. + # ourselves by calling die() in case something goes really bad. die "error: exec() has returned"; } @@ -362,7 +356,7 @@ sub startnew { if($fakepidfile) { if(open(my $out, ">", $pidfile)) { print $out $child . "\n"; - close($out) || die "Failure writing pidfile"; + close($out) or die "Failure writing pidfile"; logmsg "startnew: $pidfile faked with pid=$child\n" if($verbose); } else { @@ -372,7 +366,7 @@ sub startnew { Time::HiRes::sleep($timeout); if(checkdied($child)) { logmsg "startnew: child process has failed to start\n" if($verbose); - return (-1,-1); + return (-1, -1); } } @@ -382,17 +376,17 @@ sub startnew { $pid2 = pidfromfile($pidfile, 0); if(($pid2 > 0) && pidexists($pid2)) { # if $pid2 is valid, then make sure this pid is alive, as - # otherwise it is just likely to be the _previous_ pidfile or + # otherwise it is likely to be the _previous_ pidfile or # similar! last; } if(checkdied($child)) { logmsg "startnew: child process has died, server might start up\n" if($verbose); - # We cannot just abort waiting for the server with a - # return (-1,-1); + # We cannot abort waiting for the server with a + # return (-1, -1); # because the server might have forked and could still start - # up normally. Instead, just reduce the amount of time we remain + # up normally. Instead, reduce the amount of time we remain # waiting. $count >>= 2; } @@ -435,11 +429,11 @@ sub stopserver { # my @killservers; if($server =~ /^(ftp|http|imap|pop3|smtp)s((\d*)(-ipv6|-unix|))$/) { - # given a stunnel based ssl server, also kill non-ssl underlying one + # given a stunnel based SSL server, also kill non-SSL underlying one push @killservers, "${1}${2}"; } elsif($server =~ /^(ftp|http|imap|pop3|smtp)((\d*)(-ipv6|-unix|))$/) { - # given a non-ssl server, also kill stunnel based ssl piggybacking one + # given a non-SSL server, also kill stunnel based SSL piggybacking one push @killservers, "${1}s${2}"; } elsif($server =~ /^(socks)((\d*)(-ipv6|))$/) { @@ -515,7 +509,7 @@ sub getexternalproxyflags { sub verifyhttp { my ($proto, $ipvnum, $idnum, $ip, $port_or_path, $do_http3) = @_; my $server = servername_id($proto, $ipvnum, $idnum); - my $bonus=""; + my $bonus = ""; # $port_or_path contains a path for Unix sockets, sws ignores the port my $port = ($ipvnum eq "unix") ? 80 : $port_or_path; my $infix = ($do_http3) ? "_h3" : ""; @@ -530,7 +524,7 @@ sub verifyhttp { if($proto eq "gopher") { # gopher is funny - $bonus="1/"; + $bonus = "1/"; } my $flags = "--max-time $server_response_maxtime "; @@ -602,8 +596,8 @@ sub verifyhttp { sub verifyftp { my ($proto, $ipvnum, $idnum, $ip, $port) = @_; my $server = servername_id($proto, $ipvnum, $idnum); - my $time=time(); - my $extra=""; + my $time = time(); + my $extra = ""; my $verifylog = "$LOGDIR/". servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; @@ -789,88 +783,6 @@ sub verifysftp { return $verified; } -####################################################################### -# Verify that the non-stunnel HTTP TLS extensions capable server that runs -# on $ip, $port is our server. This also implies that we can speak with it, -# as there might be occasions when the server runs fine but we cannot talk -# to it ("Failed to connect to ::1: Cannot assign requested address") -# -sub verifyhttptls { - my ($proto, $ipvnum, $idnum, $ip, $port) = @_; - my $server = servername_id($proto, $ipvnum, $idnum); - my $pidfile = server_pidfilename("$LOGDIR/$PIDDIR", $proto, $ipvnum, - $idnum); - - my $verifyout = "$LOGDIR/". - servername_canon($proto, $ipvnum, $idnum) .'_verify.out'; - unlink($verifyout) if(-f $verifyout); - - my $verifylog = "$LOGDIR/". - servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; - unlink($verifylog) if(-f $verifylog); - - my $flags = "--max-time $server_response_maxtime "; - $flags .= "--output $verifyout "; - $flags .= "--verbose "; - $flags .= "--globoff "; - $flags .= "--insecure "; - $flags .= "--tlsauthtype SRP "; - $flags .= "--tlsuser jsmith "; - $flags .= "--tlspassword abc "; - if($proxy_address) { - $flags .= getexternalproxyflags(); - } - $flags .= "\"https://$ip:$port/verifiedserver\""; - - my $cmd = exerunner() . "$VCURL $flags 2>$verifylog"; - - # verify if our/any server is running on this port - logmsg "RUN: $cmd\n" if($verbose); - my $res = runclient($cmd); - - $res >>= 8; # rotate the result - if($res & 128) { - logmsg "RUN: curl command died with a coredump\n"; - return -1; - } - - if($res && $verbose) { - logmsg "RUN: curl command returned $res\n"; - if(open(my $file, "<", $verifylog)) { - while(my $string = <$file>) { - logmsg "RUN: $string" if($string !~ /^([ \t]*)$/); - } - close($file); - } - } - - my $data; - if(open(my $file, "<", $verifyout)) { - while(my $string = <$file>) { - $data .= $string; - } - close($file); - } - - my $pid = 0; - if($data && ($data =~ /(GNUTLS|GnuTLS)/) && ($pid = processexists($pidfile))) { - if($pid < 0) { - logmsg "RUN: $server server has died after starting up\n"; - } - return $pid; - } - elsif($res == 6) { - # curl: (6) Could not resolve hostname '::1' - logmsg "RUN: failed to resolve host (https://$ip:$port/verifiedserver)\n"; - return -1; - } - elsif($data || ($res && ($res != 7))) { - logmsg "RUN: Unknown server on our $server port: $port ($res)\n"; - return -1; - } - return $pid; -} - ####################################################################### # For verifying mqtt and socks # @@ -894,8 +806,8 @@ sub verifypid { sub verifysmb { my ($proto, $ipvnum, $idnum, $ip, $port) = @_; my $server = servername_id($proto, $ipvnum, $idnum); - my $time=time(); - my $extra=""; + my $time = time(); + my $extra = ""; my $verifylog = "$LOGDIR/". servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; @@ -954,8 +866,8 @@ sub verifysmb { sub verifytelnet { my ($proto, $ipvnum, $idnum, $ip, $port) = @_; my $server = servername_id($proto, $ipvnum, $idnum); - my $time=time(); - my $extra=""; + my $time = time(); + my $extra = ""; my $verifylog = "$LOGDIR/". servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; @@ -1012,10 +924,9 @@ sub verifytelnet { # particular can take a long time to start if it needs to generate # keys on a slow or loaded host. # -# Just for convenience, test harness uses 'https' and 'httptls' literals +# For convenience, test harness uses 'https' literal # as values for 'proto' variable in order to differentiate different -# servers. 'https' literal is used for stunnel based https test servers, -# and 'httptls' is used for non-stunnel https test servers. +# servers. 'https' literal is used for stunnel based https test servers. # my %protofunc = ('http' => \&verifyhttp, @@ -1038,7 +949,6 @@ my %protofunc = ('http' => \&verifyhttp, 'socks' => \&verifypid, 'socks5unix' => \&verifypid, 'gopher' => \&verifyhttp, - 'httptls' => \&verifyhttptls, 'dict' => \&verifyftp, 'smb' => \&verifysmb, 'telnet' => \&verifytelnet); @@ -1164,7 +1074,7 @@ sub runhttpserver { # sub runhttp2server { my ($verb) = @_; - my $proto="http/2"; + my $proto = "http/2"; my $ipvnum = 4; my $idnum = 0; my $exe = "$perl " . shell_quote("$srcdir/http2-server.pl"); @@ -1225,7 +1135,7 @@ sub runhttp2server { # sub runhttp3server { my ($verb, $cert) = @_; - my $proto="http/3"; + my $proto = "http/3"; my $ipvnum = 4; my $idnum = 0; my $exe = "$perl " . shell_quote("$srcdir/http3-server.pl"); @@ -1348,7 +1258,7 @@ sub runhttpsserver { if($httpspid <= 0 || !pidexists($httpspid)) { # it is NOT alive - # do not call stopserver since that will also kill the dependent + # do not call stopserver since that also kills the dependent # server that has already been started properly $doesntrun{$pidfile} = 1; $httpspid = $pid2 = 0; @@ -1367,66 +1277,6 @@ sub runhttpsserver { return (0+!$httpspid, $httpspid, $pid2, $port); } -####################################################################### -# start the non-stunnel HTTP TLS extensions capable server -# -sub runhttptlsserver { - my ($verb, $ipv6) = @_; - my $proto = "httptls"; - my $ip = ($ipv6 && ($ipv6 =~ /6$/)) ? $HOST6IP : $HOSTIP; - my $ipvnum = ($ipv6 && ($ipv6 =~ /6$/)) ? 6 : 4; - my $idnum = 1; - - if(!$httptlssrv) { - return (4, 0, 0); - } - - my $server = servername_id($proto, $ipvnum, $idnum); - - my $pidfile = $serverpidfile{$server}; - - # do not retry if the server does not work - if($doesntrun{$pidfile}) { - return (2, 0, 0, 0); - } - - my $pid = processexists($pidfile); - if($pid > 0) { - stopserver($server, $pid); - } - unlink($pidfile) if(-f $pidfile); - - my $srvrname = servername_str($proto, $ipvnum, $idnum); - my $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); - - my $flags = ""; - $flags .= "--http "; - $flags .= "--debug 1 " if($debugprotocol); - $flags .= "--priority NORMAL:+SRP "; - $flags .= "--srppasswd $srcdir/certs/srp-verifier-db "; - $flags .= "--srppasswdconf $srcdir/certs/srp-verifier-conf"; - - my $port = getfreeport($ipvnum); - my $allflags = "--port $port $flags"; - my $cmd = "$httptlssrv $allflags > $logfile 2>&1"; - my ($httptlspid, $pid2) = startnew($cmd, $pidfile, 10, 1); - - if($httptlspid <= 0 || !pidexists($httptlspid)) { - # it is NOT alive - stopserver($server, $pid2); - $doesntrun{$pidfile} = 1; - $httptlspid = $pid2 = 0; - logmsg "RUN: failed to start the $srvrname server\n"; - return (3, 0, 0, 0); - } - $doesntrun{$pidfile} = 0; - - if($verb) { - logmsg "RUN: $srvrname server PID $httptlspid port $port\n"; - } - return (0+!$httptlspid, $httptlspid, $pid2, $port); -} - ####################################################################### # start the pingpong server (FTP, POP3, IMAP, SMTP) # @@ -1552,7 +1402,7 @@ sub runsecureserver { if($protospid <= 0 || !pidexists($protospid)) { # it is NOT alive - # do not call stopserver since that will also kill the dependent + # do not call stopserver since that also kills the dependent # server that has already been started properly $doesntrun{$pidfile} = 1; $protospid = $pid2 = 0; @@ -1676,7 +1526,7 @@ sub rundnsserver { my $portfile = $serverportfile{$server}; my $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); - my $cmd=server_exe('dnsd'); + my $cmd = server_exe('dnsd'); $cmd .= " --port 0"; $cmd .= " --verbose" if($debugprotocol); $cmd .= " --pidfile \"$pidfile\""; @@ -1800,7 +1650,7 @@ sub sshkeyalgostr { # sub runsshserver { my ($id, $verb, $ipv6) = @_; - my $ip=$HOSTIP; + my $ip = $HOSTIP; my $proto = 'ssh'; my $ipvnum = 4; my $idnum = ($id && ($id =~ /^(\d+)$/) && ($id > 1)) ? $id : 1; @@ -1821,7 +1671,7 @@ sub runsshserver { my $sshd = find_sshd(); if($sshd) { - ($sshdid,$sshdvernum,$sshdverstr,$sshderror) = sshversioninfo($sshd); + ($sshdid, $sshdvernum, $sshdverstr, $sshderror) = sshversioninfo($sshd); logmsg $sshderror if($sshderror); } @@ -1925,7 +1775,7 @@ sub runsshserver { # sub runmqttserver { my ($id, $verb, $ipv6) = @_; - my $ip=$HOSTIP; + my $ip = $HOSTIP; my $proto = 'mqtt'; my $port = protoport($proto); my $ipvnum = 4; @@ -1951,7 +1801,7 @@ sub runmqttserver { unlink($portfile); # need to see a new one # start our MQTT server - on a random port! - my $cmd=server_exe('mqttd'). + my $cmd = server_exe('mqttd'). " --port 0". " --pidfile $pidfile". " --portfile $portfile". @@ -1988,7 +1838,7 @@ sub runmqttserver { # sub runsocksserver { my ($id, $verb, $ipv6, $is_unix) = @_; - my $ip=$HOSTIP; + my $ip = $HOSTIP; my $proto = 'socks'; my $ipvnum = 4; my $idnum = ($id && ($id =~ /^(\d+)$/) && ($id > 1)) ? $id : 1; @@ -2014,9 +1864,9 @@ sub runsocksserver { unlink($portfile); # need to see a new one # start our socks server, get commands from the FTP cmd file - my $cmd=""; + my $cmd = ""; if($is_unix) { - $cmd=server_exe('socksd'). + $cmd = server_exe('socksd'). " --pidfile $pidfile". " --reqfile $LOGDIR/$SOCKSIN". " --logfile $logfile". @@ -2025,7 +1875,7 @@ sub runsocksserver { " --config $LOGDIR/$SERVERCMD"; $portfile = "none"; } else { - $cmd=server_exe('socksd'). + $cmd = server_exe('socksd'). " --port 0". " --pidfile $pidfile". " --portfile $portfile". @@ -2372,27 +2222,6 @@ sub responsive_dns_server { return &responsiveserver($proto, $ipvnum, $idnum, $ip, $port); } -####################################################################### -# Single shot non-stunnel HTTP TLS extensions capable server -# responsiveness test. This should only be used to verify that a -# server present in %run hash is still functional -# -sub responsive_httptls_server { - my ($verb, $ipv6) = @_; - my $ipvnum = ($ipv6 && ($ipv6 =~ /6$/)) ? 6 : 4; - my $proto = "httptls"; - my $port = protoport($proto); - my $ip = $HOSTIP; - my $idnum = 1; - - if($ipvnum == 6) { - $port = protoport("httptls6"); - $ip = $HOST6IP; - } - - return &responsiveserver($proto, $ipvnum, $idnum, $ip, $port); -} - ####################################################################### # startservers() starts all the named servers # @@ -2433,7 +2262,7 @@ sub startservers { return ("failed starting ". uc($what) ." server", $serr); } logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2) if($verbose); - $run{$what}="$pid $pid2"; + $run{$what} = "$pid $pid2"; } } elsif($what eq "ftp-ipv6") { @@ -2450,7 +2279,7 @@ sub startservers { } logmsg sprintf("* pid ftp-ipv6 => %d %d\n", $pid, $pid2) if($verbose); - $run{'ftp-ipv6'}="$pid $pid2"; + $run{'ftp-ipv6'} = "$pid $pid2"; } } elsif($what eq "gopher") { @@ -2469,7 +2298,7 @@ sub startservers { } logmsg sprintf ("* pid gopher => %d %d\n", $pid, $pid2) if($verbose); - $run{'gopher'}="$pid $pid2"; + $run{'gopher'} = "$pid $pid2"; } } elsif($what eq "gopher-ipv6") { @@ -2488,7 +2317,7 @@ sub startservers { } logmsg sprintf("* pid gopher-ipv6 => %d %d\n", $pid, $pid2) if($verbose); - $run{'gopher-ipv6'}="$pid $pid2"; + $run{'gopher-ipv6'} = "$pid $pid2"; } } elsif($what eq "http") { @@ -2507,7 +2336,7 @@ sub startservers { } logmsg sprintf ("* pid http => %d %d\n", $pid, $pid2) if($verbose); - $run{'http'}="$pid $pid2"; + $run{'http'} = "$pid $pid2"; } } elsif($what eq "http-proxy") { @@ -2526,7 +2355,7 @@ sub startservers { } logmsg sprintf ("* pid http-proxy => %d %d\n", $pid, $pid2) if($verbose); - $run{'http-proxy'}="$pid $pid2"; + $run{'http-proxy'} = "$pid $pid2"; } } elsif($what eq "http-ipv6") { @@ -2545,7 +2374,7 @@ sub startservers { } logmsg sprintf("* pid http-ipv6 => %d %d\n", $pid, $pid2) if($verbose); - $run{'http-ipv6'}="$pid $pid2"; + $run{'http-ipv6'} = "$pid $pid2"; } } elsif($what eq "rtsp") { @@ -2561,7 +2390,7 @@ sub startservers { return ("failed starting RTSP server", $serr); } logmsg sprintf("* pid rtsp => %d %d\n", $pid, $pid2) if($verbose); - $run{'rtsp'}="$pid $pid2"; + $run{'rtsp'} = "$pid $pid2"; } } elsif($what eq "rtsp-ipv6") { @@ -2578,7 +2407,7 @@ sub startservers { } logmsg sprintf("* pid rtsp-ipv6 => %d %d\n", $pid, $pid2) if($verbose); - $run{'rtsp-ipv6'}="$pid $pid2"; + $run{'rtsp-ipv6'} = "$pid $pid2"; } } elsif($what =~ /^(ftp|imap|pop3|smtp)s$/) { @@ -2605,7 +2434,7 @@ sub startservers { return ("failed starting $cproto server", $serr); } logmsg sprintf("* pid $cproto => %d %d\n", $pid, $pid2) if($verbose); - $run{$cproto}="$pid $pid2"; + $run{$cproto} = "$pid $pid2"; } if(!$run{$what}) { ($serr, $pid, $pid2, $PORT{$what}) = @@ -2616,7 +2445,7 @@ sub startservers { } logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2) if($verbose); - $run{$what}="$pid $pid2"; + $run{$what} = "$pid $pid2"; } } elsif($what eq "file") { @@ -2663,7 +2492,7 @@ sub startservers { return ("failed starting HTTP server (for https/https-mtls)", $serr); } logmsg sprintf("* pid http => %d %d\n", $pid, $pid2) if($verbose); - $run{'http'}="$pid $pid2"; + $run{'http'} = "$pid $pid2"; } if(!$run{$what}) { ($serr, $pid, $pid2, $PORT{$what}) = @@ -2673,7 +2502,7 @@ sub startservers { } logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2) if($verbose); - $run{$what}="$pid $pid2"; + $run{$what} = "$pid $pid2"; } } elsif($what eq "http/2") { @@ -2704,7 +2533,7 @@ sub startservers { return ("failed starting HTTP server (for http/2)", $serr); } logmsg sprintf("* pid http => %d %d\n", $pid, $pid2) if($verbose); - $run{'http'}="$pid $pid2"; + $run{'http'} = "$pid $pid2"; } if(!$run{'http/2'}) { ($serr, $pid, $pid2, $PORT{"http2"}, $PORT{"http2tls"}) = @@ -2714,7 +2543,7 @@ sub startservers { } logmsg sprintf ("* pid http/2 => %d %d\n", $pid, $pid2) if($verbose); - $run{'http/2'}="$pid $pid2"; + $run{'http/2'} = "$pid $pid2"; } } elsif($what eq "http/3") { @@ -2745,7 +2574,7 @@ sub startservers { return ("failed starting HTTP server (for http/3)", $serr); } logmsg sprintf("* pid http => %d %d\n", $pid, $pid2) if($verbose); - $run{'http'}="$pid $pid2"; + $run{'http'} = "$pid $pid2"; } if(!$run{'http/3'}) { ($serr, $pid, $pid2, $PORT{"http3"}) = runhttp3server($verbose); @@ -2754,7 +2583,7 @@ sub startservers { } logmsg sprintf ("* pid http/3 => %d %d\n", $pid, $pid2) if($verbose); - $run{'http/3'}="$pid $pid2"; + $run{'http/3'} = "$pid $pid2"; } } elsif($what eq "gophers") { @@ -2777,15 +2606,14 @@ sub startservers { } if(!$run{'gopher'}) { my $port; - ($serr, $pid, $pid2, $port) = - runhttpserver("gopher", $verbose, 0); + ($serr, $pid, $pid2, $port) = runhttpserver("gopher", $verbose, 0); $PORT{'gopher'} = $port; if($pid <= 0) { return ("failed starting GOPHER server", $serr); } logmsg sprintf("* pid gopher => %d %d\n", $pid, $pid2) if($verbose); logmsg "GOPHERPORT => $port\n" if($verbose); - $run{'gopher'}="$pid $pid2"; + $run{'gopher'} = "$pid $pid2"; } if(!$run{'gophers'}) { my $port; @@ -2798,7 +2626,7 @@ sub startservers { logmsg sprintf("* pid gophers => %d %d\n", $pid, $pid2) if($verbose); logmsg "GOPHERSPORT => $port\n" if($verbose); - $run{'gophers'}="$pid $pid2"; + $run{'gophers'} = "$pid $pid2"; } } elsif($what eq "https-proxy") { @@ -2829,51 +2657,7 @@ sub startservers { } logmsg sprintf("* pid https-proxy => %d %d\n", $pid, $pid2) if($verbose); - $run{'https-proxy'}="$pid $pid2"; - } - } - elsif($what eq "httptls") { - if(!$httptlssrv) { - # for now, we cannot run http TLS-EXT tests without gnutls-serv - return ("no gnutls-serv (with SRP support)", 4); - } - if($run{'httptls'} && - !responsive_httptls_server($verbose, "IPv4")) { - if(stopserver('httptls')) { - return ("failed stopping unresponsive HTTPTLS server", 3); - } - } - if(!$run{'httptls'}) { - ($serr, $pid, $pid2, $PORT{'httptls'}) = - runhttptlsserver($verbose, "IPv4"); - if($pid <= 0) { - return ("failed starting HTTPTLS server (gnutls-serv)", $serr); - } - logmsg sprintf("* pid httptls => %d %d\n", $pid, $pid2) - if($verbose); - $run{'httptls'}="$pid $pid2"; - } - } - elsif($what eq "httptls-ipv6") { - if(!$httptlssrv) { - # for now, we cannot run http TLS-EXT tests without gnutls-serv - return ("no gnutls-serv", 4); - } - if($run{'httptls-ipv6'} && - !responsive_httptls_server($verbose, "ipv6")) { - if(stopserver('httptls-ipv6')) { - return ("failed stopping unresponsive HTTPTLS-IPv6 server", 3); - } - } - if(!$run{'httptls-ipv6'}) { - ($serr, $pid, $pid2, $PORT{"httptls6"}) = - runhttptlsserver($verbose, "ipv6"); - if($pid <= 0) { - return ("failed starting HTTPTLS-IPv6 server (gnutls-serv)", $serr); - } - logmsg sprintf("* pid httptls-ipv6 => %d %d\n", $pid, $pid2) - if($verbose); - $run{'httptls-ipv6'}="$pid $pid2"; + $run{'https-proxy'} = "$pid $pid2"; } } elsif($what eq "dns") { @@ -2890,7 +2674,7 @@ sub startservers { return ("failed starting DNS server", $serr); } logmsg sprintf("* pid dns => %d %d\n", $pid, $pid2) if($verbose); - $run{'dns'}="$pid $pid2"; + $run{'dns'} = "$pid $pid2"; } } elsif($what eq "tftp") { @@ -2907,7 +2691,7 @@ sub startservers { return ("failed starting TFTP server", $serr); } logmsg sprintf("* pid tftp => %d %d\n", $pid, $pid2) if($verbose); - $run{'tftp'}="$pid $pid2"; + $run{'tftp'} = "$pid $pid2"; } } elsif($what eq "tftp-ipv6") { @@ -2924,7 +2708,7 @@ sub startservers { return ("failed starting TFTP-IPv6 server", $serr); } logmsg sprintf("* pid tftp-ipv6 => %d %d\n", $pid, $pid2) if($verbose); - $run{'tftp-ipv6'}="$pid $pid2"; + $run{'tftp-ipv6'} = "$pid $pid2"; } } elsif($what eq "sftp" || $what eq "scp") { @@ -2934,7 +2718,7 @@ sub startservers { return ("failed starting SSH server", $serr); } logmsg sprintf("* pid ssh => %d %d\n", $pid, $pid2) if($verbose); - $run{'ssh'}="$pid $pid2"; + $run{'ssh'} = "$pid $pid2"; } } elsif($what eq "socks4" || $what eq "socks5" ) { @@ -2944,7 +2728,7 @@ sub startservers { return ("failed starting socks server", $serr); } logmsg sprintf("* pid socks => %d %d\n", $pid, $pid2) if($verbose); - $run{'socks'}="$pid $pid2"; + $run{'socks'} = "$pid $pid2"; } } elsif($what eq "socks5unix") { @@ -2954,7 +2738,7 @@ sub startservers { return ("failed starting socks5unix server", $serr); } logmsg sprintf("* pid socks5unix => %d %d\n", $pid, $pid2) if($verbose); - $run{'socks5unix'}="$pid $pid2"; + $run{'socks5unix'} = "$pid $pid2"; } } elsif($what eq "mqtt" ) { @@ -2970,7 +2754,7 @@ sub startservers { return ("failed starting mqtt server", $serr); } logmsg sprintf("* pid mqtt => %d %d\n", $pid, $pid2) if($verbose); - $run{'mqtt'}="$pid $pid2"; + $run{'mqtt'} = "$pid $pid2"; } } elsif($what eq "mqtts" ) { @@ -2990,7 +2774,7 @@ sub startservers { return ("failed starting mqtt server", $serr); } logmsg sprintf("* pid mqtt => %d %d\n", $pid, $pid2) if($verbose); - $run{'mqtt'}="$pid $pid2"; + $run{'mqtt'} = "$pid $pid2"; } if(!$run{$what}) { ($serr, $pid, $pid2, $PORT{$what}) = @@ -3000,7 +2784,7 @@ sub startservers { } logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2) if($verbose); - $run{$what}="$pid $pid2"; + $run{$what} = "$pid $pid2"; } } elsif($what eq "http-unix") { @@ -3019,7 +2803,7 @@ sub startservers { } logmsg sprintf("* pid http-unix => %d %d\n", $pid, $pid2) if($verbose); - $run{'http-unix'}="$pid $pid2"; + $run{'http-unix'} = "$pid $pid2"; } } elsif($what eq "dict") { @@ -3030,7 +2814,7 @@ sub startservers { } logmsg sprintf ("* pid DICT => %d %d\n", $pid, $pid2) if($verbose); - $run{'dict'}="$pid $pid2"; + $run{'dict'} = "$pid $pid2"; } } elsif($what eq "smb") { @@ -3041,7 +2825,7 @@ sub startservers { } logmsg sprintf ("* pid SMB => %d %d\n", $pid, $pid2) if($verbose); - $run{'smb'}="$pid $pid2"; + $run{'smb'} = "$pid $pid2"; } } elsif($what eq "telnet") { @@ -3053,7 +2837,7 @@ sub startservers { } logmsg sprintf ("* pid neg TELNET => %d %d\n", $pid, $pid2) if($verbose); - $run{'telnet'}="$pid $pid2"; + $run{'telnet'} = "$pid $pid2"; } } else { @@ -3138,7 +2922,7 @@ sub subvariables { 'FTP', 'FTP6', 'FTPS', 'GOPHER', 'GOPHER6', 'GOPHERS', 'HTTP', 'HTTP6', 'HTTPS', 'HTTPS-MTLS', - 'HTTPSPROXY', 'HTTPTLS', 'HTTPTLS6', + 'HTTPSPROXY', 'HTTP2', 'HTTP2TLS', 'HTTP3', 'IMAP', 'IMAP6', 'IMAPS', diff --git a/tests/smbserver.py b/tests/smbserver.py index c2942079882e..d14e7ff1d3cd 100755 --- a/tests/smbserver.py +++ b/tests/smbserver.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -25,6 +24,7 @@ import argparse import configparser +import importlib.util import logging import os import signal @@ -36,9 +36,7 @@ from util import ClosingFileHandler, TestData # impacket needs to be installed in the Python environment -try: - import impacket # noqa: F401 -except ImportError: +if importlib.util.find_spec('impacket') is None: sys.stderr.write( 'Warning: Python package impacket is required for smb testing; ' 'use pip or your package manager to install it\n') @@ -66,7 +64,7 @@ class ShutdownHandler(threading.Thread): """ def __init__(self, server): - super(ShutdownHandler, self).__init__() + super().__init__() self.server = server self.shutdown_event = threading.Event() @@ -76,7 +74,7 @@ def __enter__(self): signal.signal(signal.SIGTERM, self._sighandler) def __exit__(self, *_): - # Call for shutdown just in case it was not done already + # Call for shutdown in case it was not done already self.shutdown_event.set() # Wait for thread, and therefore also the server, to finish self.join() @@ -125,7 +123,7 @@ def smbserver(options): smb_config.set("SERVER", "share type", "0") smb_config.set("SERVER", "path", SERVER_MAGIC) - # Have a share for tests. These files will be autogenerated from the + # Have a share for tests. These files are auto-generated from the # test input. smb_config.add_section("TESTS") smb_config.set("TESTS", "comment", "tests") @@ -146,7 +144,7 @@ def smbserver(options): # Start a thread that cleanly shuts down the server on a signal with ShutdownHandler(smb_server): - # This will block until smb_server.shutdown() is called + # This blocks until smb_server.shutdown() is called smb_server.serve_forever() return 0 @@ -154,8 +152,9 @@ def smbserver(options): class TestSmbServer(imp_smbserver.SMBSERVER): """ - Test server for SMB which subclasses the impacket SMBSERVER and provides - test functionality. + Test server for SMB. + + It subclasses the impacket SMBSERVER and provides test functionality. """ def __init__(self, @@ -177,9 +176,10 @@ def __init__(self, def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): """ - Our version of smbComNtCreateAndX looks for special test files and - fools the rest of the framework into opening them as if they were - normal files. + Our version of smbComNtCreateAndX. + + It looks for special test files and fools the rest of the framework + into opening them as if they were normal files. """ conn_data = smb_server.getConnectionData(conn_id) @@ -199,8 +199,7 @@ def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): # Currently we only support reading files. if disposition != imp_smb.FILE_OPEN: - raise SmbError(STATUS_ACCESS_DENIED, - "Only support reading files") + raise SmbError(STATUS_ACCESS_DENIED, "Only support reading files") # Check to see if the path we were given is actually a # magic path which needs generating on the fly. @@ -213,8 +212,7 @@ def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): flags2 = recv_packet["Flags2"] ncax_data = imp_smb.SMBNtCreateAndX_Data(flags=flags2, - data=smb_command[ - "Data"]) + data=smb_command["Data"]) requested_file = imp_smbserver.decodeSMBString( flags2, ncax_data["FileName"]) @@ -235,7 +233,7 @@ def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): if len(conn_data["OpenedFiles"]) == 0: fakefid = 1 else: - fakefid = conn_data["OpenedFiles"].keys()[-1] + 1 + fakefid = max(conn_data["OpenedFiles"].keys()) + 1 resp_params["Fid"] = fakefid resp_params["CreateAction"] = disposition @@ -294,30 +292,26 @@ def get_share_path(self, conn_data, root_fid, tid): if root_fid > 0: # If we have a rootFid, the path is relative to that fid path = conn_data["OpenedFiles"][root_fid]["FileName"] - log.debug("RootFid present %s!" % path) - else: - if "path" in conn_shares[tid]: - path = conn_shares[tid]["path"] - else: - raise SmbError(STATUS_ACCESS_DENIED, - "Connection share had no path") - else: - raise SmbError(imp_smbserver.STATUS_SMB_BAD_TID, - "TID was invalid") + log.debug(f'RootFid present {path}!') + return path + + if "path" in conn_shares[tid]: + return conn_shares[tid]["path"] - return path + raise SmbError(STATUS_ACCESS_DENIED, "Connection share had no path") + raise SmbError(imp_smbserver.STATUS_SMB_BAD_TID, "TID was invalid") def get_server_path(self, requested_filename): log.debug("[SMB] Get server path '%s'", requested_filename) - if requested_filename not in [VERIFIED_REQ]: + if requested_filename != VERIFIED_REQ: raise SmbError(STATUS_NO_SUCH_FILE, "Could not find the file") fid, filename = tempfile.mkstemp() log.debug("[SMB] Created %s (%d) for storing '%s'", filename, fid, requested_filename) - contents = "" + contents = b'' if requested_filename == VERIFIED_REQ: log.debug("[SMB] Verifying server is alive") @@ -357,7 +351,7 @@ def get_test_path(self, requested_filename): class SmbError(Exception): def __init__(self, error_code, error_message): - super(SmbError, self).__init__(error_message) + super().__init__(error_message) self.error_code = error_code @@ -377,9 +371,9 @@ def get_options(): parser = argparse.ArgumentParser() parser.add_argument("--port", action="store", default=9017, - type=int, help="port to listen on") + type=int, help="port to listen on") parser.add_argument("--host", action="store", default="127.0.0.1", - help="host to listen on") + help="host to listen on") parser.add_argument("--verbose", action="store", type=int, default=0, help="verbose output") parser.add_argument("--pidfile", action="store", diff --git a/tests/sshhelp.pm b/tests/sshhelp.pm index 41a21662618f..2481de3278de 100644 --- a/tests/sshhelp.pm +++ b/tests/sshhelp.pm @@ -342,7 +342,7 @@ sub find_sshkeygen { sub find_httptlssrv { my $p = find_exe_file_hpath($httptlssrvexe); if($p) { - my @o = `"$p" -l`; + my @o = qx("$p" -l); my $found; for(@o) { if(/Key exchange: SRP/) { diff --git a/tests/sshserver.pl b/tests/sshserver.pl index c4d0285782ab..1aacbb53b766 100755 --- a/tests/sshserver.pl +++ b/tests/sshserver.pl @@ -612,7 +612,7 @@ sub logmsg { } if(($sshdid =~ /OpenSSH/) && ($sshdvernum >= 880) && ($keyalgo eq 'rsa')) { push @cfgarr, 'HostKeyAlgorithms +ssh-rsa'; - push @cfgarr, 'PubkeyAcceptedKeyTypes +ssh-rsa'; + push @cfgarr, 'PubkeyAcceptedAlgorithms +ssh-rsa'; # named PubkeyAcceptedKeyTypes in OpenSSH <8.5 } push @cfgarr, '#'; push @cfgarr, "Port $port"; @@ -677,7 +677,7 @@ sub sshd_supports_opt { ($sshdid =~ /SunSSH/)) { # ssh daemon supports command line options -t -f and -o $err = grep /((Unsupported)|(Bad configuration)|(Deprecated)) option.*$option/, - `\"$sshd\" -t -f $sshdconfig_abs -o \"$option=$value\" 2>&1`; + qx(\"$sshd\" -t -f $sshdconfig_abs -o \"$option=$value\" 2>&1); return !$err; } if(($sshdid =~ /OpenSSH/) && ($sshdvernum >= 299)) { @@ -688,7 +688,7 @@ sub sshd_supports_opt { return 0; } $err = grep /((Unsupported)|(Bad configuration)|(Deprecated)) option.*$option/, - `\"$sshd\" -t -f $sshdconfig_abs 2>&1`; + qx(\"$sshd\" -t -f $sshdconfig_abs 2>&1); unlink $sshdconfig; return !$err; } @@ -698,33 +698,33 @@ sub sshd_supports_opt { #*************************************************************************** # Kerberos Authentication support may have not been built into sshd # -if(sshd_supports_opt('KerberosAuthentication','no')) { +if(sshd_supports_opt('KerberosAuthentication', 'no')) { push @cfgarr, 'KerberosAuthentication no'; } -if(sshd_supports_opt('KerberosGetAFSToken','no')) { +if(sshd_supports_opt('KerberosGetAFSToken', 'no')) { push @cfgarr, 'KerberosGetAFSToken no'; } -if(sshd_supports_opt('KerberosOrLocalPasswd','no')) { +if(sshd_supports_opt('KerberosOrLocalPasswd', 'no')) { push @cfgarr, 'KerberosOrLocalPasswd no'; } -if(sshd_supports_opt('KerberosTgtPassing','no')) { +if(sshd_supports_opt('KerberosTgtPassing', 'no')) { push @cfgarr, 'KerberosTgtPassing no'; } -if(sshd_supports_opt('KerberosTicketCleanup','yes')) { +if(sshd_supports_opt('KerberosTicketCleanup', 'yes')) { push @cfgarr, 'KerberosTicketCleanup yes'; } #*************************************************************************** # Andrew File System support may have not been built into sshd # -if(sshd_supports_opt('AFSTokenPassing','no')) { +if(sshd_supports_opt('AFSTokenPassing', 'no')) { push @cfgarr, 'AFSTokenPassing no'; } #*************************************************************************** # S/Key authentication support may have not been built into sshd # -if(sshd_supports_opt('SkeyAuthentication','no')) { +if(sshd_supports_opt('SkeyAuthentication', 'no')) { push @cfgarr, 'SkeyAuthentication no'; } @@ -732,23 +732,23 @@ sub sshd_supports_opt { # GSSAPI Authentication support may have not been built into sshd # my $sshd_builtwith_GSSAPI; -if(sshd_supports_opt('GSSAPIAuthentication','no')) { +if(sshd_supports_opt('GSSAPIAuthentication', 'no')) { push @cfgarr, 'GSSAPIAuthentication no'; $sshd_builtwith_GSSAPI = 1; } -if(sshd_supports_opt('GSSAPICleanupCredentials','yes')) { +if(sshd_supports_opt('GSSAPICleanupCredentials', 'yes')) { push @cfgarr, 'GSSAPICleanupCredentials yes'; } -if(sshd_supports_opt('GSSAPIKeyExchange','no')) { +if(sshd_supports_opt('GSSAPIKeyExchange', 'no')) { push @cfgarr, 'GSSAPIKeyExchange no'; } -if(sshd_supports_opt('GSSAPIStoreDelegatedCredentials','no')) { +if(sshd_supports_opt('GSSAPIStoreDelegatedCredentials', 'no')) { push @cfgarr, 'GSSAPIStoreDelegatedCredentials no'; } -if(sshd_supports_opt('GSSCleanupCreds','yes')) { +if(sshd_supports_opt('GSSCleanupCreds', 'yes')) { push @cfgarr, 'GSSCleanupCreds yes'; } -if(sshd_supports_opt('GSSUseSessionCredCache','no')) { +if(sshd_supports_opt('GSSUseSessionCredCache', 'no')) { push @cfgarr, 'GSSUseSessionCredCache no'; } push @cfgarr, '#'; @@ -760,54 +760,54 @@ sub sshd_supports_opt { # Address family must be specified before ListenAddress splice @cfgarr, 11, 0, 'AddressFamily any'; } -if(sshd_supports_opt('Compression','no')) { +if(sshd_supports_opt('Compression', 'no')) { push @cfgarr, 'Compression no'; } -if(sshd_supports_opt('KbdInteractiveAuthentication','no')) { +if(sshd_supports_opt('KbdInteractiveAuthentication', 'no')) { push @cfgarr, 'KbdInteractiveAuthentication no'; } -if(sshd_supports_opt('KeepAlive','no')) { +if(sshd_supports_opt('KeepAlive', 'no')) { push @cfgarr, 'KeepAlive no'; } -if(sshd_supports_opt('LookupClientHostnames','no')) { +if(sshd_supports_opt('LookupClientHostnames', 'no')) { push @cfgarr, 'LookupClientHostnames no'; } if(sshd_supports_opt('MaxAuthTries','10')) { push @cfgarr, 'MaxAuthTries 10'; } -if(sshd_supports_opt('PAMAuthenticationViaKbdInt','no')) { +if(sshd_supports_opt('PAMAuthenticationViaKbdInt', 'no')) { push @cfgarr, 'PAMAuthenticationViaKbdInt no'; } -if(sshd_supports_opt('PermitTunnel','no')) { +if(sshd_supports_opt('PermitTunnel', 'no')) { push @cfgarr, 'PermitTunnel no'; } -if(sshd_supports_opt('PermitUserEnvironment','no')) { +if(sshd_supports_opt('PermitUserEnvironment', 'no')) { push @cfgarr, 'PermitUserEnvironment no'; } -if(sshd_supports_opt('RhostsAuthentication','no')) { +if(sshd_supports_opt('RhostsAuthentication', 'no')) { push @cfgarr, 'RhostsAuthentication no'; } -if(sshd_supports_opt('TCPKeepAlive','no')) { +if(sshd_supports_opt('TCPKeepAlive', 'no')) { push @cfgarr, 'TCPKeepAlive no'; } -if(sshd_supports_opt('UseDNS','no')) { +if(sshd_supports_opt('UseDNS', 'no')) { push @cfgarr, 'UseDNS no'; } -if(sshd_supports_opt('UsePAM','no')) { +if(sshd_supports_opt('UsePAM', 'no')) { push @cfgarr, 'UsePAM no'; } if($sshdid =~ /OpenSSH/) { # http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id=6492415 - if(sshd_supports_opt('UsePrivilegeSeparation','no')) { + if(sshd_supports_opt('UsePrivilegeSeparation', 'no')) { push @cfgarr, 'UsePrivilegeSeparation no'; } } -if(sshd_supports_opt('VerifyReverseMapping','no')) { +if(sshd_supports_opt('VerifyReverseMapping', 'no')) { push @cfgarr, 'VerifyReverseMapping no'; } -if(sshd_supports_opt('X11UseLocalhost','yes')) { +if(sshd_supports_opt('X11UseLocalhost', 'yes')) { push @cfgarr, 'X11UseLocalhost yes'; } push @cfgarr, '#'; @@ -1185,12 +1185,12 @@ sub sshd_supports_opt { # Put an "exec" in front of the command so that the child process # keeps this child's process ID by being tied to the spawned shell. - exec("exec $cmd") || die "Cannot exec() $cmd: $!"; - # exec() will create a new process, but ties the existence of the + exec("exec $cmd") or die "Cannot exec() $cmd: $!"; + # exec() creates a new process, but ties the existence of the # new process to the parent waiting perl.exe and sh.exe processes. # exec() should never return back here to this process. We protect - # ourselves by calling die() just in case something goes really bad. + # ourselves by calling die() in case something goes really bad. die "error: exec() has returned"; } diff --git a/tests/test1119.pl b/tests/test1119.pl index 9004696f6f55..a1b09d059457 100755 --- a/tests/test1119.pl +++ b/tests/test1119.pl @@ -48,25 +48,25 @@ } # we may get the directory root pointed out -my $root=$ARGV[0] || "."; +my $root = $ARGV[0] || "."; # need an include directory when building out-of-tree my $i = ($ARGV[1]) ? "-I$ARGV[1] " : ''; -my $verbose=0; -my $summary=0; -my $misses=0; +my $verbose = 0; +my $summary = 0; +my $misses = 0; my @manrefs; my @syms; my %doc; my %rem; -# scanenum runs the preprocessor on curl.h so it will process all enums +# scanenum runs the preprocessor on curl.h so it processes all enums # included by it, which *should* be all headers sub scanenum { my ($file) = @_; - open my $h_in, "-|", "$Cpreprocessor $i$file" || die "Cannot preprocess $file"; + open(my $h_in, "-|", "$Cpreprocessor $i$file") or die "Cannot preprocess $file"; while(<$h_in>) { if(/enum\s+(\S+\s+)?{/ .. /}/) { s/^\s+//; @@ -76,11 +76,11 @@ sub scanenum { push @syms, $_; } } - close $h_in || die "Error preprocessing $file"; + close $h_in or die "Error preprocessing $file"; } sub scanheader { - my ($f)=@_; + my ($f) = @_; open(my $h, "<", $f); while(<$h>) { if(/^#define ((LIB|)CURL[A-Za-z0-9_]*)/) { @@ -92,8 +92,7 @@ sub scanheader { sub scanallheaders { my $d = "$root/include/curl"; - opendir(my $dh, $d) || - die "Cannot opendir: $!"; + opendir(my $dh, $d) or die "Cannot opendir: $!"; my @headers = grep { /.h\z/ } readdir($dh); closedir $dh; foreach my $h (@headers) { @@ -130,8 +129,7 @@ sub checkmanpage { sub scanman_md_dir { my ($d) = @_; - opendir(my $dh, $d) || - die "Cannot opendir: $!"; + opendir(my $dh, $d) or die "Cannot opendir: $!"; my @mans = grep { /.md\z/ } readdir($dh); closedir $dh; for my $m (@mans) { @@ -146,26 +144,26 @@ sub scanman_md_dir { open(my $s, "<", "$root/docs/libcurl/symbols-in-versions"); while(<$s>) { if(/(^[^ \n]+) +(.*)/) { - my ($sym, $rest)=($1, $2); + my ($sym, $rest) = ($1, $2); if($doc{$sym}) { print "Detected duplicate symbol: $sym\n"; $misses++; next; } - $doc{$sym}=$sym; - my @a=split(/ +/, $rest); + $doc{$sym} = $sym; + my @a = split(/ +/, $rest); if($a[2]) { # this symbol is documented to have been present the last time # in this release - $rem{$sym}=$a[2]; + $rem{$sym} = $a[2]; } } } close $s; -my $ignored=0; +my $ignored = 0; for my $e (sort @syms) { - # OBSOLETE - names that are just placeholders for a position where we + # OBSOLETE - names that are placeholders for a position where we # previously had a name, that is now removed. The OBSOLETE names should # never be used for anything. # @@ -176,7 +174,7 @@ sub scanman_md_dir { # # CURL_TEMP_ - are defined and *undefined* again within the file # - # *_LAST and *_LASTENTRY are just suffix for the placeholders used for the + # *_LAST and *_LASTENTRY are suffix for the placeholders used for the # last entry in many enum series. # @@ -188,7 +186,7 @@ sub scanman_md_dir { if($verbose) { print $e."\n"; } - $doc{$e}="used"; + $doc{$e} = "used"; next; } else { @@ -223,7 +221,7 @@ sub scanman_md_dir { my %warned; for my $r (@manrefs) { if($r =~ /^([^:]+):(.*)/) { - my ($sym, $file)=($1, $2); + my ($sym, $file) = ($1, $2); if(!$doc{$sym} && !$warned{$sym, $file}) { print "$file: $sym is not a public symbol\n"; $warned{$sym, $file} = 1; diff --git a/tests/test1135.pl b/tests/test1135.pl index 40f18aaa2293..ceb313cf8c2e 100755 --- a/tests/test1135.pl +++ b/tests/test1135.pl @@ -51,7 +51,7 @@ } $root = "$root/include/curl"; -opendir(D, $root) || die "Cannot open directory $root: $!\n"; +opendir(D, $root) or die "Cannot open directory $root: $!\n"; my @dir = readdir(D); closedir(D); @@ -62,13 +62,13 @@ } } -my $verbose=0; -my $summary=0; -my $misses=0; +my $verbose = 0; +my $summary = 0; +my $misses = 0; my @out; foreach my $f (@incs) { - open H, "<$f" || die; + open(H, "<", $f) or die; my $first = ""; while() { s/CURL_DEPRECATED\(.*"\)//; diff --git a/tests/test1139.pl b/tests/test1139.pl index a11c9d03a3ba..ac09d4f59021 100755 --- a/tests/test1139.pl +++ b/tests/test1139.pl @@ -42,11 +42,11 @@ use warnings; # we may get the directory roots pointed out -my $root=$ARGV[0] || "."; -my $buildroot=$ARGV[1] || "."; +my $root = $ARGV[0] || "."; +my $buildroot = $ARGV[1] || "."; my $syms = "$root/docs/libcurl/symbols-in-versions"; my $curlh = "$root/include/curl/curl.h"; -my $errors=0; +my $errors = 0; # the prepopulated alias list is the CURLINFO_* defines that are used for the # debug function callback and the fact that they use the same prefix as the @@ -67,8 +67,7 @@ sub scanmdpage { my ($file, @words) = @_; - open(my $mh, "<", $file) || - die "could not open $file"; + open(my $mh, "<", $file) or die "could not open $file"; my @m; while(<$mh>) { if($_ =~ /^## (.*)/) { @@ -101,11 +100,10 @@ sub scanmdpage { my $r; # check for define aliases -open($r, "<", $curlh) || - die "no curl.h"; +open($r, "<", $curlh) or die "no curl.h"; while(<$r>) { if(/^\#define (CURL(OPT|INFO|MOPT)_\w+) (.*)/) { - $alias{$1}=$3; + $alias{$1} = $3; } } close($r); @@ -113,11 +111,10 @@ sub scanmdpage { my @curlopt; my @curlinfo; my @curlmopt; -open($r, "<", $syms) || - die "no input file"; +open($r, "<", $syms) or die "no input file"; while(<$r>) { chomp; - my $l= $_; + my $l = $_; if($l =~ /(CURL(OPT|INFO|MOPT)_\w+) *([0-9.]*) *([0-9.-]*) *([0-9.]*)/) { my ($opt, $type, $add, $dep, $rem) = ($1, $2, $3, $4, $5); @@ -187,8 +184,7 @@ sub scanmdpage { ######################################################################### # parse the curl code that parses the command line arguments! -open($r, "<", "$root/src/tool_getparam.c") || - die "no input file"; +open($r, "<", "$root/src/tool_getparam.c") or die "no input file"; my $list; my @getparam; # store all parsed parameters @@ -198,11 +194,11 @@ sub scanmdpage { $no++; chomp; if(/struct LongShort aliases/) { - $list=1; + $list = 1; } elsif($list) { if(/^ \{(\"[^,]*\").*\'(.)\',/) { - my ($l, $s)=($1, $2); + my ($l, $s) = ($1, $2); my $sh; my $lo; my $title; @@ -212,12 +208,12 @@ sub scanmdpage { if($l =~ /\"(.*)\"/) { # long option $lo = $1; - $title="--$lo"; + $title = "--$lo"; } if($s ne " ") { # a short option $sh = $s; - $title="-$sh, $title"; + $title = "-$sh, $title"; } push @getparam, $title; $opts{$title} |= 1; @@ -230,12 +226,13 @@ sub scanmdpage { ######################################################################### # parse the curl.1 man page, extract all documented command line options # The man page may or may not be rebuilt, so check both possible locations -open($r, "<", "$buildroot/docs/cmdline-opts/curl.1") || open($r, "<", "$root/docs/cmdline-opts/curl.1") || +open($r, "<", "$buildroot/docs/cmdline-opts/curl.1") or + open($r, "<", "$root/docs/cmdline-opts/curl.1") or die "failed getting curl.1"; my @manpage; # store all parsed parameters while(<$r>) { chomp; - my $l= $_; + my $l = $_; $l =~ s/\\-/-/g; if($l =~ /^\.IP \"(-[^\"]*)\"/) { my $str = $1; @@ -258,14 +255,13 @@ sub scanmdpage { ######################################################################### # parse the curl code that outputs the curl -h list -open($r, "<", "$root/src/tool_listhelp.c") || - die "no input file"; +open($r, "<", "$root/src/tool_listhelp.c") or die "no input file"; my @toolhelp; # store all parsed parameters while(<$r>) { chomp; - my $l= $_; + my $l = $_; if(/^ \{ \" *(.*)/) { - my $str=$1; + my $str = $1; my $combo; if($str =~ /^-(.), --([a-z0-9.-]*)/) { # figure out the -short, --long combo @@ -295,10 +291,10 @@ sub scanmdpage { my $exists; my $missing; if($where & 1) { - $exists=" tool_getparam.c"; + $exists = " tool_getparam.c"; } else { - $missing=" tool_getparam.c"; + $missing = " tool_getparam.c"; } if($where & 2) { $exists.= " curl.1"; diff --git a/tests/test1140.pl b/tests/test1140.pl index 45da989ea667..320f9f94392b 100755 --- a/tests/test1140.pl +++ b/tests/test1140.pl @@ -50,7 +50,7 @@ sub manpresent { elsif(-r "$docsroot/$man" || -r "$docsroot/libcurl/$man" || -r "$docsroot/libcurl/opts/$man") { - $manp{$man}=1; + $manp{$man} = 1; return 1; } return 0; @@ -58,14 +58,13 @@ sub manpresent { sub file { my ($f) = @_; - open(my $fh, "<", $f) || - die "test1140.pl could not open $f"; + open(my $fh, "<", $f) or die "test1140.pl could not open $f"; my $line = 1; while(<$fh>) { chomp; my $l = $_; while($l =~ s/\\f(.)([^ ]*)\\f(.)//) { - my ($pre, $str, $post)=($1, $2, $3); + my ($pre, $str, $post) = ($1, $2, $3); if($str =~ /^\\f[ib]/i) { print "error: $f:$line: double-highlight\n"; $errors++; @@ -92,7 +91,7 @@ sub file { $errors++; } if($l =~ /^\.BR (.*)/) { - my $i= $1; + my $i = $1; while($i =~ s/((lib|)curl([^ ]*)) *\"\(3\)(,|) *\" *//i ) { my $man = "$1.3"; $man =~ s/\\//g; # cut off backslashes diff --git a/tests/test1165.pl b/tests/test1165.pl index bcf8b50fb713..259e13cc8783 100755 --- a/tests/test1165.pl +++ b/tests/test1165.pl @@ -38,15 +38,15 @@ my %docs; # we may get the directory root pointed out -my $root=$ARGV[0] || "."; -my $DOCS="CURL-DISABLE.md"; +my $root = $ARGV[0] || "."; +my $DOCS = "CURL-DISABLE.md"; sub scanconf { - my ($f)=@_; - open S, "<$f"; + my ($f) = @_; + open(S, "<", $f); while() { if(/(CURL_DISABLE_[A-Z0-9_]+)/g) { - my ($sym)=($1); + my ($sym) = ($1); if(not $sym =~ /^(CURL_DISABLE_TYPECHECK)$/) { $disable{$sym} = 1; } @@ -56,7 +56,7 @@ sub scanconf { } sub scan_configure { - opendir(my $m, "$root/m4") || die "Cannot opendir $root/m4: $!"; + opendir(my $m, "$root/m4") or die "Cannot opendir $root/m4: $!"; my @m4 = grep { /\.m4$/ } readdir($m); closedir $m; scanconf("$root/configure.ac"); @@ -67,12 +67,12 @@ sub scan_configure { } sub scanconf_cmake { - my ($hashr, $f)=@_; - open S, "<$f"; + my ($hashr, $f) = @_; + open(S, "<", $f); while() { if(/(CURL_DISABLE_[A-Z0-9_]+)/g) { - my ($sym)=($1); - if(not $sym =~ /^(CURL_DISABLE_INSTALL|CURL_DISABLE_SRP|CURL_DISABLE_TYPECHECK)$/) { + my ($sym) = ($1); + if(not $sym =~ /^(CURL_DISABLE_INSTALL|CURL_DISABLE_TYPECHECK)$/) { $hashr->{$sym} = 1; } } @@ -94,11 +94,11 @@ sub scan_cmake_config_h { ); sub scan_file { - my ($source)=@_; - open F, "<$source"; + my ($source) = @_; + open(F, "<", $source); while() { while(s/(CURL_DISABLE_[A-Z0-9_]+)//) { - my ($sym)=($1); + my ($sym) = ($1); if(!$whitelisted{$sym}) { $file{$sym} = $source; @@ -109,8 +109,8 @@ sub scan_file { } sub scan_dir { - my ($dir)=@_; - opendir(my $dh, $dir) || die "Cannot opendir $dir: $!"; + my ($dir) = @_; + opendir(my $dh, $dir) or die "Cannot opendir $dir: $!"; my @cfiles = grep { /\.[ch]\z/ && -f "$dir/$_" } readdir($dh); closedir $dh; for my $f (sort @cfiles) { @@ -127,12 +127,12 @@ sub scan_sources { } sub scan_docs { - open F, "<$root/docs/$DOCS"; + open(F, "<", "$root/docs/$DOCS"); my $line = 0; while() { $line++; if(/^## `(CURL_DISABLE_[A-Z0-9_]+)`/g) { - my ($sym)=($1); + my ($sym) = ($1); if(not $sym =~ /^(CURL_DISABLE_TYPECHECK)$/) { $docs{$sym} = $line; } diff --git a/tests/test1167.pl b/tests/test1167.pl index dd0097e3716a..a0bdb385db91 100755 --- a/tests/test1167.pl +++ b/tests/test1167.pl @@ -47,32 +47,32 @@ $Cpreprocessor = 'cpp'; } -my $verbose=0; +my $verbose = 0; # verbose mode when -v is the first argument if($ARGV[0] eq "-v") { - $verbose=1; + $verbose = 1; shift; } # we may get the directory root pointed out -my $root=$ARGV[0] || "."; +my $root = $ARGV[0] || "."; # need an include directory when building out-of-tree my $i = ($ARGV[1]) ? "-I$ARGV[1] " : ''; my $incdir = "$root/include/curl"; -my $summary=0; -my $misses=0; +my $summary = 0; +my $misses = 0; my @syms; sub scanenums { - my ($file)=@_; + my ($file) = @_; my $skipit = 0; - open H_IN, "-|", "$Cpreprocessor -DCURL_DISABLE_DEPRECATION $i$file" || + open(H_IN, "-|", "$Cpreprocessor -DCURL_DISABLE_DEPRECATION $i$file") or die "Cannot preprocess $file"; while() { my ($line, $linenum) = ($_, $.); @@ -113,11 +113,11 @@ sub scanenums { } } } - close H_IN || die "Error preprocessing $file"; + close H_IN or die "Error preprocessing $file"; } sub scanheader { - my ($f)=@_; + my ($f) = @_; scanenums($f); open(H, '<', $f); while() { @@ -134,7 +134,7 @@ sub scanheader { close H; } -opendir(my $dh, $incdir) || die "Cannot opendir $incdir: $!"; +opendir(my $dh, $incdir) or die "Cannot opendir $incdir: $!"; my @hfiles = grep { /\.h$/ } readdir($dh); closedir $dh; diff --git a/tests/test1173.pl b/tests/test1173.pl index 6e2c9141afca..14894c625bd0 100755 --- a/tests/test1173.pl +++ b/tests/test1173.pl @@ -32,10 +32,10 @@ use File::Basename; # get the filename first -my $symbolsinversions=shift @ARGV; +my $symbolsinversions = shift @ARGV; # we may get the directory roots pointed out -my @manpages=@ARGV; +my @manpages = @ARGV; my $errors = 0; my %docsdirs; @@ -79,15 +79,14 @@ CURLOPT_RANDOM_FILE => 1, ); sub allsymbols { - open(my $f, "<", $symbolsinversions) || - die "$symbolsinversions: $|"; + open(my $f, "<", $symbolsinversions) or die "$symbolsinversions: $|"; while(<$f>) { if($_ =~ /^([^ ]*) +(.*)/) { my ($name, $info) = ($1, $2); - $symbol{$name}=$name; + $symbol{$name} = $name; if($info =~ /([0-9.]+) +([0-9.]+)/) { - $deprecated{$name}=$info; + $deprecated{$name} = $info; } } } @@ -98,7 +97,7 @@ sub allsymbols { 'curl.1' => 1 ); sub checkref { - my ($f, $sec, $file, $line)=@_; + my ($f, $sec, $file, $line) = @_; my $present = 0; #print STDERR "check $f.$sec\n"; if($ref{"$f.$sec"}) { @@ -108,7 +107,7 @@ sub checkref { foreach my $d (keys %docsdirs) { if(-f "$d/$f.$sec") { $present = 1; - $ref{"$f.$sec"}=1; + $ref{"$f.$sec"} = 1; last; } } @@ -140,12 +139,11 @@ sub scanmanpage { my $shc = 0; my $optpage = 0; # option or function my @sh; - my $SH=""; + my $SH = ""; my @separators; my @sepline; - open(my $m, "<", $file) || - die "test1173.pl could not open $file"; + open(my $m, "<", $file) or die "test1173.pl could not open $file"; if($file =~ /[\/\\](CURL|curl_)([^\/\\]*).3/) { # This is a man page for libcurl. It requires an example unless it is # considered deprecated. @@ -158,7 +156,7 @@ sub scanmanpage { while(<$m>) { chomp; if($_ =~ /^.so /) { - # this man page is just a referral + # this man page is a referral close($m); return; } diff --git a/tests/test1175.pl b/tests/test1175.pl index 54e0a1fe762e..06db7c7d4c13 100755 --- a/tests/test1175.pl +++ b/tests/test1175.pl @@ -41,7 +41,7 @@ sub getdocserrors { ; } else { - $docs{$symbol}=1; + $docs{$symbol} = 1; } } } @@ -52,12 +52,12 @@ sub getincludeerrors { open(my $f, "<", "$root/docs/libcurl/symbols-in-versions"); while(<$f>) { if($_ =~ /^(CURL[EM]_[^ \t]*)[ \t]*([0-9.]+)[ \t]*(.*)/) { - my ($symbol, $added, $rest) = ($1,$2,$3); + my ($symbol, $added, $rest) = ($1, $2, $3); if($rest =~ /^([0-9.]+)/) { # removed! } else { - $error{$symbol}=$added; + $error{$symbol} = $added; } } } diff --git a/tests/test1177.pl b/tests/test1177.pl index 32377986a585..bca20fb37d43 100755 --- a/tests/test1177.pl +++ b/tests/test1177.pl @@ -30,14 +30,14 @@ use strict; use warnings; -my $manpage=$ARGV[0]; -my $header=$ARGV[1]; -my $source=$ARGV[2]; +my $manpage = $ARGV[0]; +my $header = $ARGV[1]; +my $source = $ARGV[2]; my %manversion; my %headerversion; my %manname; my %sourcename; -my $error=0; +my $error = 0; open(my $m, "<", $manpage); while(<$m>) { @@ -66,6 +66,7 @@ } close($s); $sourcename{'NTLM_WB'}++; # deprecated, fake its presence in code +$sourcename{'TLS-SRP'}++; # deprecated, fake its presence in code for my $h (keys %headerversion) { if(!$manversion{$h}) { diff --git a/tests/test1222.pl b/tests/test1222.pl index 96f6d60b23e2..0d6a5ffc2cd6 100755 --- a/tests/test1222.pl +++ b/tests/test1222.pl @@ -54,7 +54,7 @@ # Scan header file for public function and enum values. Flag them with # the version they are deprecated in, if some. sub scan_header { - my ($f)=@_; + my ($f) = @_; my $line = ""; my $incomment = 0; my $inenum = 0; @@ -147,7 +147,7 @@ sub scan_header { # Each option has to be declared as ".IP