Browse Source

tests: add HTTP/3 test case, custom location for proper nghttpx

- adding support for HTTP/3 test cases via a nghttpx server that is
  build with ngtcp2 and nghttp3.
- test2500 is the first test case, performing a simple GET.
- nghttpx is checked for support and the 'feature' nghttpx-h3
  is set accordingly. test2500 will only run, when supported.
- a specific nghttpx location can be given in the environment
  variable NGHTTPX or via the configure option
    --with-test-nghttpx=<path>

Extend NGHTTPX config to H2 tests as well

* use $ENV{NGHTTPX} and the configured default also in http2 server starts
* always provide the empty test/nghttpx.conf to nghttpx. as it defaults to
  reading /etc/nghttpx/nghttpx.conf otherwise.

Added nghttpx to CI ngtcp2 jobs to run h3 tests.

Closes #9031
Stefan Eissing 1 year ago
parent
commit
ca15b7512e

+ 34 - 7
.github/workflows/ngtcp2-gnutls.yml

@@ -28,9 +28,21 @@ jobs:
       matrix:
         build:
         - name: gnutls
-          install: nettle-dev libp11-kit-dev libtspi-dev libunistring-dev guile-2.2-dev libtasn1-bin libtasn1-6-dev libidn2-0-dev gawk gperf libtss2-dev dns-root-data bison gtk-doc-tools texinfo texlive texlive-extra-utils autopoint libev-dev
-          configure: LDFLAGS="-Wl,-rpath,$HOME/all/lib" --with-gnutls=$HOME/all --enable-debug
-          gnutls-configure: --with-included-libtasn1 --with-included-unistring --disable-guile --disable-doc --disable-tests
+          install: >-
+            libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libev-dev libc-ares-dev
+            nettle-dev libp11-kit-dev libtspi-dev libunistring-dev guile-2.2-dev libtasn1-bin
+            libtasn1-6-dev libidn2-0-dev gawk gperf libtss2-dev dns-root-data bison gtk-doc-tools
+            texinfo texlive texlive-extra-utils autopoint libev-dev
+          configure: >-
+            PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" LDFLAGS="-Wl,-rpath,$HOME/all/lib"
+            --with-ngtcp2=$HOME/all --enable-warnings --enable-werror --enable-debug
+            --with-test-nghttpx="$HOME/all/bin/nghttpx"
+          ngtcp2-configure: >-
+            --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
+          gnutls-configure: >-
+            PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" LDFLAGS="-Wl,-rpath,$HOME/all/lib -L$HOME/all/lib"
+            --with-included-libtasn1 --with-included-unistring
+            --disable-guile --disable-doc --disable-tests --disable-tools
 
     steps:
     - run: |
@@ -39,6 +51,13 @@ jobs:
         sudo python3 -m pip install impacket
       name: 'install prereqs and impacket'
 
+    - run: |
+        git clone --depth=1 -b openssl-3.0.7+quic https://github.com/quictls/openssl
+        cd openssl
+        ./config --prefix=$HOME/all --libdir=$HOME/all/lib
+        make install_sw
+      name: 'install quictls'
+
     - run: |
         git clone --depth=1 https://gitlab.com/gnutls/nettle.git
         cd nettle
@@ -51,7 +70,7 @@ jobs:
         git clone --depth=1 -b 3.7.7 https://github.com/gnutls/gnutls.git
         cd gnutls
         ./bootstrap
-        ./configure PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" LDFLAGS="-Wl,-rpath,$HOME/all/lib -L$HOME/all/lib" --prefix=$HOME/all ${{ matrix.build.gnutls-configure }} --disable-tools
+        ./configure ${{ matrix.build.gnutls-configure }} --prefix=$HOME/all
         make install
       name: 'install gnutls'
 
@@ -59,7 +78,7 @@ jobs:
         git clone --depth=1 https://github.com/ngtcp2/nghttp3
         cd nghttp3
         autoreconf -fi
-        ./configure --prefix=$HOME/all --enable-lib-only
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
         make install
       name: 'install nghttp3'
 
@@ -67,16 +86,24 @@ jobs:
         git clone --depth=1 https://github.com/ngtcp2/ngtcp2
         cd ngtcp2
         autoreconf -fi
-        ./configure PKG_CONFIG_PATH=$HOME/all/lib/pkgconfig LDFLAGS="-Wl,-rpath,$HOME/all/lib" --prefix=$HOME/all --enable-lib-only --with-gnutls=$HOME/all
+        ./configure ${{ matrix.build.ngtcp2-configure }} --with-openssl --with-gnutls
         make install
       name: 'install ngtcp2'
 
+    - run: |
+        git clone --depth=1 https://github.com/nghttp2/nghttp2
+        cd nghttp2
+        autoreconf -fi
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-http3
+        make install
+      name: 'install nghttp2'
+
     - uses: actions/checkout@v3
 
     - run: autoreconf -fi
       name: 'autoreconf'
 
-    - run: ./configure --enable-warnings --enable-werror ${{ matrix.build.configure }}
+    - run: ./configure --with-gnutls=$HOME/all ${{ matrix.build.configure }}
       name: 'configure'
 
     - run: make V=1

+ 98 - 0
.github/workflows/ngtcp2-quictls.yml

@@ -0,0 +1,98 @@
+# Copyright (C) 2000 - 2022 Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# SPDX-License-Identifier: curl
+
+name: ngtcp2
+
+on:
+  push:
+    branches:
+    - master
+    - '*/ci'
+  pull_request:
+    branches:
+    - master
+
+concurrency:
+  # Hardcoded workflow filename as workflow name above is just Linux again
+  group: ngtcp2-openssl-${{ github.event.pull_request.number || github.sha }}
+  cancel-in-progress: true
+
+jobs:
+  autotools:
+    name: ${{ matrix.build.name }}
+    runs-on: 'ubuntu-latest'
+    timeout-minutes: 60
+    strategy:
+      fail-fast: false
+      matrix:
+        build:
+        - name: quictls
+          install: >-
+            libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libev-dev libc-ares-dev
+          configure: >-
+            PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" LDFLAGS="-Wl,-rpath,$HOME/all/lib"
+            --with-ngtcp2=$HOME/all --enable-warnings --enable-werror --enable-debug
+            --with-test-nghttpx="$HOME/all/bin/nghttpx"
+          ngtcp2-configure: >-
+            --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
+
+    steps:
+    - run: |
+        sudo apt-get update
+        sudo apt-get install libtool autoconf automake pkg-config stunnel4 ${{ matrix.build.install }}
+        sudo python3 -m pip install impacket
+      name: 'install prereqs and impacket'
+
+    - run: |
+        git clone --depth=1 -b openssl-3.0.7+quic https://github.com/quictls/openssl
+        cd openssl
+        ./config --prefix=$HOME/all --libdir=$HOME/all/lib
+        make install_sw
+      name: 'install quictls'
+
+    - run: |
+        git clone --depth=1 https://github.com/ngtcp2/nghttp3
+        cd nghttp3
+        autoreconf -fi
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
+        make install
+      name: 'install nghttp3'
+
+    - run: |
+        git clone --depth=1 https://github.com/ngtcp2/ngtcp2
+        cd ngtcp2
+        autoreconf -fi
+        ./configure ${{ matrix.build.ngtcp2-configure }} --with-openssl
+        make install
+      name: 'install ngtcp2'
+
+    - run: |
+        git clone --depth=1 https://github.com/nghttp2/nghttp2
+        cd nghttp2
+        autoreconf -fi
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-http3
+        make install
+      name: 'install nghttp2'
+
+    - uses: actions/checkout@v3
+
+    - run: autoreconf -fi
+      name: 'autoreconf'
+
+    - run: ./configure --with-openssl=$HOME/all ${{ matrix.build.configure }}
+      name: 'configure'
+
+    - run: make V=1
+      name: 'make'
+
+    - run: make V=1 examples
+      name: 'make examples'
+
+    - run: make V=1 -C tests
+      name: 'make tests'
+
+    - run: make V=1 test-ci
+      name: 'run tests'
+      env:
+        TFLAGS: "${{ matrix.build.tflags }}"

+ 31 - 8
.github/workflows/ngtcp2-wolfssl.yml

@@ -28,9 +28,17 @@ jobs:
       matrix:
         build:
         - name: wolfssl
-          install:
-          configure: LDFLAGS="-Wl,-rpath,$HOME/all/lib" --with-wolfssl=$HOME/all --enable-debug
-          wolfssl-configure: --enable-quic --enable-session-ticket --enable-earlydata --enable-psk --enable-harden --enable-altcertchains
+          install: >-
+            libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libev-dev libc-ares-dev
+          configure: >-
+            PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" LDFLAGS="-Wl,-rpath,$HOME/all/lib"
+            --with-ngtcp2=$HOME/all --enable-warnings --enable-werror --enable-debug
+            --with-test-nghttpx="$HOME/all/bin/nghttpx"
+          ngtcp2-configure: >-
+            --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
+          wolfssl-configure: >-
+            --enable-quic --enable-session-ticket --enable-earlydata --enable-psk
+            --enable-harden --enable-altcertchains
 
     steps:
     - run: |
@@ -48,27 +56,42 @@ jobs:
       name: 'install wolfssl'
 
     - run: |
-        git clone https://github.com/ngtcp2/nghttp3
+        git clone --depth=1 -b openssl-3.0.7+quic https://github.com/quictls/openssl
+        cd openssl
+        ./config --prefix=$HOME/all --libdir=$HOME/all/lib
+        make install_sw
+      name: 'install quictls'
+
+    - run: |
+        git clone --depth=1 https://github.com/ngtcp2/nghttp3
         cd nghttp3
         autoreconf -fi
-        ./configure --prefix=$HOME/all --enable-lib-only
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-lib-only
         make install
       name: 'install nghttp3'
 
     - run: |
-        git clone https://github.com/ngtcp2/ngtcp2
+        git clone --depth=1 https://github.com/ngtcp2/ngtcp2
         cd ngtcp2
         autoreconf -fi
-        ./configure PKG_CONFIG_PATH=$HOME/all/lib/pkgconfig LDFLAGS="-Wl,-rpath,$HOME/all/lib" --prefix=$HOME/all --enable-lib-only --with-wolfssl=$HOME/all
+        ./configure ${{ matrix.build.ngtcp2-configure }} --with-openssl  --with-wolfssl
         make install
       name: 'install ngtcp2'
 
+    - run: |
+        git clone --depth=1 https://github.com/nghttp2/nghttp2
+        cd nghttp2
+        autoreconf -fi
+        ./configure --prefix=$HOME/all PKG_CONFIG_PATH="$HOME/all/lib/pkgconfig" --enable-http3
+        make install
+      name: 'install nghttp2'
+
     - uses: actions/checkout@v3
 
     - run: autoreconf -fi
       name: 'autoreconf'
 
-    - run: ./configure --enable-warnings --enable-werror ${{ matrix.build.configure }}
+    - run: ./configure --with-wolfssl=$HOME/all ${{ matrix.build.configure }}
       name: 'configure'
 
     - run: make V=1

+ 2 - 0
.gitignore

@@ -64,3 +64,5 @@ curl_fuzzer
 curl_fuzzer_seed_corpus.zip
 libstandaloneengine.a
 tests/string
+tests/config
+

+ 11 - 0
configure.ac

@@ -301,6 +301,16 @@ AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the inst
   fi
 )
 
+TEST_NGHTTPX=nghttpx
+AC_ARG_WITH(test-nghttpx,dnl
+AS_HELP_STRING([--with-test-nghttpx=PATH],[where to find nghttpx for testing]),
+  TEST_NGHTTPX=$withval
+  if test X"$OPT_TEST_NGHTTPX" = "Xno" ; then
+      TEST_NGHTTPX=""
+  fi
+)
+AC_SUBST(TEST_NGHTTPX)
+
 dnl If no TLS choice has been made, check if it was explicitly disabled or
 dnl error out to force the user to decide.
 if test -z "$TLSCHOICE"; then
@@ -4568,6 +4578,7 @@ AC_CONFIG_FILES([Makefile \
            lib/libcurl.vers \
            lib/libcurl.plist \
            tests/Makefile \
+           tests/config \
            tests/certs/Makefile \
            tests/certs/scripts/Makefile \
            tests/data/Makefile \

+ 1 - 0
tests/.gitignore

@@ -24,3 +24,4 @@ runtests.pdf
 testcurl.html
 testcurl.pdf
 *.port
+config

+ 6 - 1
tests/README.md

@@ -16,7 +16,7 @@ SPDX-License-Identifier: curl
   - diff (when a test fails, a diff is shown)
   - stunnel (for HTTPS and FTPS tests)
   - OpenSSH or SunSSH (for SCP, SFTP and SOCKS4/5 tests)
-  - nghttpx (for HTTP/2 tests)
+  - nghttpx (for HTTP/2 and HTTP/3 tests)
   - nroff (for --manual tests)
   - An available `en_US.UTF-8` locale
 
@@ -69,6 +69,11 @@ SPDX-License-Identifier: curl
 
   The HTTP server supports listening on a Unix domain socket, the default
   location is 'http.sock'.
+  
+  For HTTP/2 and HTTP/3 testing an installed `nghttpx` is used. HTTP/3
+  tests check if nghttpx supports the protocol. To override the nghttpx
+  used, set the environment variable `NGHTTPX`. The default can also be
+  changed by specifying `--with-test-nghttpx=<path>` as argument to `configure`.
 
 ### Run
 

+ 24 - 0
tests/config.in

@@ -0,0 +1,24 @@
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+NGHTTPX: @TEST_NGHTTPX@

+ 2 - 0
tests/data/Makefile.inc

@@ -243,6 +243,8 @@ test2200 test2201 test2202 test2203 test2204 test2205 \
 \
 test2300 test2301 test2302 test2303 \
 \
+test2500 \
+\
 test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
 test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
 test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \

+ 78 - 0
tests/data/test2500

@@ -0,0 +1,78 @@
+<testcase http-crlf="yes">
+<info>
+<keywords>
+HTTP
+HTTP GET
+HTTP/3
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+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
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+http/3
+nghttpx-h3
+</features>
+<server>
+http
+http/3
+</server>
+ <name>
+HTTP/3 GET:
+ </name>
+ <command>
+--cacert %SRCDIR/certs/EdelCurlRoot-ca.crt --http3 --resolve localhost:%HTTP3PORT:%HOSTIP https://localhost:%HTTP3PORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^X-Forwarded-Proto:.*
+^Via:.*
+</strip>
+<protocol>
+GET https://localhost:%HTTP3PORT/%TESTNUMBER HTTP/1.1
+Host: localhost:%HTTP3PORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+</protocol>
+<stdout crlf="yes">
+HTTP/3 200 
+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-
+</stdout>
+<stripfile>
+s/^server: nghttpx.*\r?\n//
+</stripfile>
+</verify>
+</testcase>

+ 15 - 0
tests/getpart.pm

@@ -41,6 +41,21 @@ sub decode_hex {
     return $s;
 }
 
+sub testcaseattr {
+    my %hash;
+    for(@xml) {
+        if(($_ =~ /^ *\<testcase ([^>]*)/)) {
+            my $attr=$1;
+            while($attr =~ s/ *([^=]*)= *(\"([^\"]*)\"|([^\> ]*))//) {
+                my ($var, $cont)=($1, $2);
+                $cont =~ s/^\"(.*)\"$/$1/;
+                $hash{$var}=$cont;
+            }
+        }
+    }
+    return %hash;
+}
+
 sub getpartattr {
     # if $part is undefined (ie only one argument) then
     # return the attributes of the section

+ 8 - 0
tests/http2-server.pl

@@ -31,6 +31,7 @@ my $logfile = "log/http2.log";
 my $nghttpx = "nghttpx";
 my $listenport = 9015;
 my $connect = "127.0.0.1,8990";
+my $conf = "nghttpx.conf";
 
 #***************************************************************************
 # Process command line options
@@ -70,6 +71,12 @@ while(@ARGV) {
             shift @ARGV;
         }
     }
+    elsif($ARGV[0] eq '--conf') {
+        if($ARGV[1]) {
+            $conf = $ARGV[1];
+            shift @ARGV;
+        }
+    }
     else {
         print STDERR "\nWarning: http2-server.pl unknown parameter: $ARGV[0]\n";
     }
@@ -80,6 +87,7 @@ my $cmdline="$nghttpx --backend=$connect ".
     "--frontend=\"*,$listenport;no-tls\" ".
     "--log-level=INFO ".
     "--pid-file=$pidfile ".
+    "--conf=$conf ".
     "--errorlog-file=$logfile";
 print "RUN: $cmdline\n" if($verbose);
 system("$cmdline 2>/dev/null");

+ 111 - 0
tests/http3-server.pl

@@ -0,0 +1,111 @@
+#!/usr/bin/env perl
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2016 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+#***************************************************************************
+
+# This script invokes nghttpx properly to have it serve HTTP/3 for us.
+# nghttpx runs as a proxy in front of our "actual" HTTP/1 server.
+
+use Cwd;
+use Cwd 'abs_path';
+
+my $pidfile = "log/nghttpx.pid";
+my $logfile = "log/http3.log";
+my $nghttpx = "nghttpx";
+my $listenport = 9015;
+my $connect = "127.0.0.1,8990";
+my $cert = "Server-localhost-sv";
+my $conf = "nghttpx.conf";
+
+#***************************************************************************
+# Process command line options
+#
+while(@ARGV) {
+    if($ARGV[0] eq '--verbose') {
+        $verbose = 1;
+    }
+    elsif($ARGV[0] eq '--pidfile') {
+        if($ARGV[1]) {
+            $pidfile = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--nghttpx') {
+        if($ARGV[1]) {
+            $nghttpx = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--port') {
+        if($ARGV[1]) {
+            $listenport = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--connect') {
+        if($ARGV[1]) {
+            $connect = $ARGV[1];
+            $connect =~ s/:/,/;
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--cert') {
+        if($ARGV[1]) {
+            $cert = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--logfile') {
+        if($ARGV[1]) {
+            $logfile = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    elsif($ARGV[0] eq '--conf') {
+        if($ARGV[1]) {
+            $conf = $ARGV[1];
+            shift @ARGV;
+        }
+    }
+    else {
+        print STDERR "\nWarning: http3-server.pl unknown parameter: $ARGV[0]\n";
+    }
+    shift @ARGV;
+}
+
+my $path   = getcwd();
+my $srcdir = $path;
+$certfile = "$srcdir/certs/$cert.pem";
+$keyfile = "$srcdir/certs/$cert.key";
+$certfile = abs_path($certfile);
+$keyfile = abs_path($keyfile);
+
+my $cmdline="$nghttpx --http2-proxy --backend=$connect ".
+    "--frontend=\"*,$listenport;quic\" ".
+    "--log-level=INFO ".
+    "--pid-file=$pidfile ".
+    "--errorlog-file=$logfile ".
+    "--conf=$conf ".
+    "$keyfile $certfile";
+print "RUN: $cmdline\n" if($verbose);
+system("$cmdline 2>/dev/null");

+ 26 - 0
tests/nghttpx.conf

@@ -0,0 +1,26 @@
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+
+# nghttpx reads /etc/nghttpx/nghttpx.conf if we do not give it another one.
+# This is what this otherwise empty file is for.

+ 134 - 7
tests/runtests.pl

@@ -159,6 +159,7 @@ my $HTTPTLSPORT=$noport; # HTTP TLS (non-stunnel) server port
 my $HTTPTLS6PORT=$noport; # HTTP TLS (non-stunnel) IPv6 server port
 my $HTTPPROXYPORT=$noport; # HTTP proxy port, when using CONNECT
 my $HTTP2PORT=$noport;   # HTTP/2 server port
+my $HTTP3PORT=$noport;   # HTTP/3 server port
 my $DICTPORT=$noport;    # DICT server port
 my $SMBPORT=$noport;     # SMB server port
 my $SMBSPORT=$noport;    # SMBS server port
@@ -262,6 +263,7 @@ my $has_charconv;   # set if libcurl is built with CharConv support
 my $has_tls_srp;    # set if libcurl is built with TLS-SRP support
 my $has_http2;      # set if libcurl is built with HTTP2 support
 my $has_h2c;        # set if libcurl is built with h2c support
+my $has_http3;      # set if libcurl is built with HTTP3 support
 my $has_httpsproxy; # set if libcurl is built with HTTPS-proxy support
 my $has_crypto;     # set if libcurl is built with cryptographic support
 my $has_cares;      # set if built with c-ares
@@ -315,7 +317,7 @@ my %ignored_keywords;   # key words of tests to ignore results
 my %enabled_keywords;   # key words of tests to run
 my %disabled;           # disabled test cases
 my %ignored;            # ignored results of test cases
-
+my $crlf_http = 0;      # always convert HTTP heaaders to cr+lf
 my $sshdid;      # for socks server, ssh daemon version id
 my $sshdvernum;  # for socks server, ssh daemon version number
 my $sshdverstr;  # for socks server, ssh daemon version string
@@ -438,12 +440,36 @@ delete $ENV{'SSL_CERT_DIR'} if($ENV{'SSL_CERT_DIR'});
 delete $ENV{'SSL_CERT_PATH'} if($ENV{'SSL_CERT_PATH'});
 delete $ENV{'CURL_CA_BUNDLE'} if($ENV{'CURL_CA_BUNDLE'});
 
+# provide defaults from our config file for ENV vars not explicitly
+# set by the caller
+if (open(my $fd, "< config")) {
+    while(my $line = <$fd>) {
+        next if ($line =~ /^#/);
+        chomp $line;
+        my ($name, $val) = split(/\s*:\s*/, $line, 2);
+        $ENV{$name} = $val if(!$ENV{$name});
+    }
+    close($fd);
+}
+
+# Check if we have nghttpx available and if it talks http/3
+my $nghttpx_h3 = 0;
+if (!$ENV{"NGHTTPX"}) {
+    $ENV{"NGHTTPX"} = checktestcmd("nghttpx");
+}
+if ($ENV{"NGHTTPX"}) {
+    my $nghttpx_version=join(' ', runclientoutput("$ENV{'NGHTTPX'} -v"));
+    $nghttpx_h3 = $nghttpx_version =~ /nghttp3\//;
+    logmsg "nghttpx_h3=$nghttpx_h3, output=$nghttpx_version\n";
+}
+
+
 #######################################################################
 # Load serverpidfile and serverportfile hashes with file names for all
 # possible servers.
 #
 sub init_serverpidfile_hash {
-  for my $proto (('ftp', 'gopher', 'http', 'imap', 'pop3', 'smtp', 'http/2')) {
+  for my $proto (('ftp', 'gopher', 'http', 'imap', 'pop3', 'smtp', 'http/2', 'http/3')) {
     for my $ssl (('', 's')) {
       for my $ipvnum ((4, 6)) {
         for my $idnum ((1, 2, 3)) {
@@ -468,7 +494,7 @@ sub init_serverpidfile_hash {
       }
     }
   }
-  for my $proto (('http', 'imap', 'pop3', 'smtp', 'http/2')) {
+  for my $proto (('http', 'imap', 'pop3', 'smtp', 'http/2', 'http/3')) {
     for my $ssl (('', 's')) {
       my $serv = servername_id("$proto$ssl", "unix", 1);
       my $pidf = server_pidfilename("$proto$ssl", "unix", 1);
@@ -840,10 +866,11 @@ sub stopserver {
         push @killservers, "socks${2}";
     }
     if($server eq "http") {
-        # since the http2 server is a proxy that needs to know about the
+        # since the http2+3 server is a proxy that needs to know about the
         # dynamic http port it too needs to get restarted when the http server
         # is killed
         push @killservers, "http/2";
+        push @killservers, "http/3";
     }
     push @killservers, $server;
     #
@@ -1528,6 +1555,7 @@ sub runhttp2server {
 
     $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
 
+    $flags .= "--nghttpx \"$ENV{'NGHTTPX'}\" ";
     $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
     $flags .= "--connect $HOSTIP:$HTTPPORT ";
     $flags .= $verbose_flag if($debugprotocol);
@@ -1561,6 +1589,76 @@ sub runhttp2server {
     return ($http2pid, $pid2, $port);
 }
 
+#######################################################################
+# start the http3 server
+#
+sub runhttp3server {
+    my ($verbose, $cert) = @_;
+    my $server;
+    my $srvrname;
+    my $pidfile;
+    my $logfile;
+    my $flags = "";
+    my $proto="http/3";
+    my $ipvnum = 4;
+    my $idnum = 0;
+    my $exe = "$perl $srcdir/http3-server.pl";
+    my $verbose_flag = "--verbose ";
+
+    $server = servername_id($proto, $ipvnum, $idnum);
+
+    $pidfile = $serverpidfile{$server};
+
+    # don't retry if the server doesn't work
+    if ($doesntrun{$pidfile}) {
+        return (0, 0, 0);
+    }
+
+    my $pid = processexists($pidfile);
+    if($pid > 0) {
+        stopserver($server, "$pid");
+    }
+    unlink($pidfile) if(-f $pidfile);
+
+    $srvrname = servername_str($proto, $ipvnum, $idnum);
+
+    $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
+
+    $flags .= "--nghttpx \"$ENV{'NGHTTPX'}\" ";
+    $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
+    $flags .= "--connect $HOSTIP:$HTTPPORT ";
+    $flags .= "--cert \"$cert\" " if($cert);
+    $flags .= $verbose_flag if($debugprotocol);
+
+    my ($http3pid, $pid3);
+    my $port = 24113;
+    for(1 .. 10) {
+        $port += int(rand(900));
+        my $aflags = "--port $port $flags";
+
+        my $cmd = "$exe $aflags";
+        ($http3pid, $pid3) = startnew($cmd, $pidfile, 15, 0);
+
+        if($http3pid <= 0 || !pidexists($http3pid)) {
+            # it is NOT alive
+            stopserver($server, "$pid3");
+            $doesntrun{$pidfile} = 1;
+            $http3pid = $pid3 = 0;
+            next;
+        }
+        $doesntrun{$pidfile} = 0;
+
+        if($verbose) {
+            logmsg "RUN: $srvrname server PID $http3pid port $port\n";
+        }
+        last;
+    }
+
+    logmsg "RUN: failed to start the $srvrname server\n" if(!$http3pid);
+
+    return ($http3pid, $pid3, $port);
+}
+
 #######################################################################
 # start the http server
 #
@@ -2899,6 +2997,7 @@ sub setupfeatures {
     $feature{"h2c"} = $has_h2c;
     $feature{"HSTS"} = $has_hsts;
     $feature{"http/2"} = $has_http2;
+    $feature{"http/3"} = $has_http3;
     $feature{"https-proxy"} = $has_httpsproxy;
     $feature{"hyper"} = $has_hyper;
     $feature{"idn"} = $has_idn;
@@ -2959,6 +3058,8 @@ sub setupfeatures {
     $feature{"wakeup"} = 1;
     $feature{"headers-api"} = 1;
     $feature{"xattr"} = 1;
+    $feature{"nghttpx"} = !!$ENV{'NGHTTPX'};
+    $feature{"nghttpx-h3"} = !!$nghttpx_h3;
 }
 
 #######################################################################
@@ -3213,6 +3314,12 @@ sub checksystem {
 
                 push @protocols, 'http/2';
             }
+            if($feat =~ /HTTP3/) {
+                # http3 enabled
+                $has_http3=1;
+
+                push @protocols, 'http/3';
+            }
             if($feat =~ /HTTPS-proxy/) {
                 $has_httpsproxy=1;
 
@@ -3400,6 +3507,7 @@ sub subVariables {
     $$thing =~ s/${prefix}HTTPSPORT/$HTTPSPORT/g;
     $$thing =~ s/${prefix}HTTPSPROXYPORT/$HTTPSPROXYPORT/g;
     $$thing =~ s/${prefix}HTTP2PORT/$HTTP2PORT/g;
+    $$thing =~ s/${prefix}HTTP3PORT/$HTTP3PORT/g;
     $$thing =~ s/${prefix}HTTPPORT/$HTTPPORT/g;
     $$thing =~ s/${prefix}PROXYPORT/$HTTPPROXYPORT/g;
     $$thing =~ s/${prefix}MQTTPORT/$MQTTPORT/g;
@@ -3510,7 +3618,8 @@ sub subNewlines {
     # as well, all test comparisons will survive without knowing about this
     # little quirk.
 
-    if(($$thing =~ /^HTTP\/(1.1|1.0|2) [1-5][^\x0d]*\z/) ||
+    if(($$thing =~ /^HTTP\/(1.1|1.0|2|3) [1-5][^\x0d]*\z/) ||
+       ($$thing =~ /^(GET|POST|PUT|DELETE) \S+ HTTP\/\d+(\.\d+)?/) ||
        (($$thing =~ /^[a-z0-9_-]+: [^\x0d]*\z/i) &&
         # skip curl error messages
         ($$thing !~ /^curl: \(\d+\) /))) {
@@ -3587,6 +3696,8 @@ sub prepro {
     my (@entiretest) = @_;
     my $show = 1;
     my @out;
+    my $crlf_header = ($crlf_http || ($has_hyper && ($keywords{"HTTP"}
+                                                     || $keywords{"HTTPS"})));
     for my $s (@entiretest) {
         my $f = $s;
         if($s =~ /^ *%if (.*)/) {
@@ -3612,8 +3723,7 @@ sub prepro {
         if($show) {
             subVariables(\$s, $testnum, "%");
             subBase64(\$s);
-            subNewlines(\$s) if($has_hyper && ($keywords{"HTTP"} ||
-                                               $keywords{"HTTPS"}));
+            subNewlines(\$s) if($crlf_header);
             push @out, $s;
         }
     }
@@ -3807,6 +3917,12 @@ sub singletest {
         $why = serverfortest($testnum);
     }
 
+    $crlf_http = 0;
+    my %hash = testcaseattr();
+    if($hash{'http-crlf'}) {
+        $crlf_http = 1;
+    }
+
     # Save a preprocessed version of the entire test file. This allows more
     # "basic" test case readers to enjoy variable replacements.
     my @entiretest = fulltest();
@@ -4958,6 +5074,17 @@ sub startservers {
                 $run{'gopher-ipv6'}="$pid $pid2";
             }
         }
+        elsif($what eq "http/3") {
+            if(!$run{'http/3'}) {
+                ($pid, $pid2, $HTTP3PORT) = runhttp3server($verbose);
+                if($pid <= 0) {
+                    return "failed starting HTTP/3 server";
+                }
+                logmsg sprintf ("* pid http/3 => %d %d\n", $pid, $pid2)
+                    if($verbose);
+                $run{'http/3'}="$pid $pid2";
+            }
+        }
         elsif($what eq "http/2") {
             if(!$run{'http/2'}) {
                 ($pid, $pid2, $HTTP2PORT) = runhttp2server($verbose);

+ 1 - 1
tests/serverhelp.pm

@@ -108,7 +108,7 @@ sub servername_str {
 
     $proto = uc($proto) if($proto);
     die "unsupported protocol: '$proto'" unless($proto &&
-        ($proto =~ /^(((FTP|HTTP|HTTP\/2|IMAP|POP3|GOPHER|SMTP|HTTP-PIPE)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/));
+        ($proto =~ /^(((FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTP-PIPE)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/));
 
     $ipver = (not $ipver) ? 'ipv4' : lc($ipver);
     die "unsupported IP version: '$ipver'" unless($ipver &&