From 7292a15d8a7dc2b4d813b17ae9520eb2d004abcd Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Tue, 18 Aug 2026 16:03:59 +0900 Subject: [PATCH 1/2] parenthesize _rotl macro arguments Silences clang's -Wshift-op-parentheses; the grouping was already what operator precedence produced, so no behavior change. --- utils.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.cxx b/utils.cxx index 4150673..b4360a4 100644 --- a/utils.cxx +++ b/utils.cxx @@ -1078,7 +1078,7 @@ std::map parse_querystring(const std::string& query_st #endif #ifndef _WIN32 -#define _rotl(x, y) ((x<>(32-y))) +#define _rotl(x, y) (((x)<<(y))|((x)>>(32-(y)))) #endif #define F1(X, Y, Z) ((Z) ^ ((X) & ((Y) ^ (Z)))) From f64f12f95834dae3ca8ffa8d21d86b03f509cbb4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Tue, 18 Aug 2026 16:03:59 +0900 Subject: [PATCH 2/2] add GitHub Actions CI with build and smoke test Builds with g++ and clang++ via autotools and runs t/smoke.sh, which exercises static files, directory listing, content types, CGI GET/POST (including a 300KB body), keep-alive, a 1MB transfer and clean SIGTERM shutdown. --- .github/workflows/ci.yml | 27 ++++++++++++++ t/smoke.sh | 80 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100755 t/smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c137c2c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + compiler: [g++, clang++] + steps: + - uses: actions/checkout@v4 + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake + - name: Build + run: | + ./autogen.sh + ./configure CXX=${{ matrix.compiler }} + make + - name: Smoke test + run: bash t/smoke.sh ./tthttpd diff --git a/t/smoke.sh b/t/smoke.sh new file mode 100755 index 0000000..0977e10 --- /dev/null +++ b/t/smoke.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# smoke test for tthttpd: static files, directory listing, CGI, keep-alive. +# usage: t/smoke.sh [path-to-tthttpd] +set -u + +BIN=${1:-./tthttpd} +PORT=${TTHTTPD_TEST_PORT:-18080} +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +DOCROOT="$WORK/docroot" +mkdir -p "$DOCROOT/subdir" +echo "hello world" > "$DOCROOT/index.html" +echo "plain text file" > "$DOCROOT/subdir/file.txt" +head -c 1000000 /dev/urandom > "$DOCROOT/big.bin" + +cat > "$DOCROOT/test.cgi" <<'EOF' +#!/usr/bin/perl +read(STDIN, my $body, $ENV{CONTENT_LENGTH} || 0); +print "Content-Type: text/plain\r\n\r\n"; +print "method=$ENV{REQUEST_METHOD} qs=$ENV{QUERY_STRING} len=" . length($body) . "\n"; +EOF +chmod +x "$DOCROOT/test.cgi" + +"$BIN" -p "$PORT" -d "$DOCROOT" & +PID=$! +trap 'kill -9 $PID 2>/dev/null; rm -rf "$WORK"' EXIT + +for i in $(seq 1 50); do + curl -s -o /dev/null "http://127.0.0.1:$PORT/index.html" && break + sleep 0.1 +done + +fail=0 +check() { # name expected actual + if [ "$2" != "$3" ]; then + echo "FAIL: $1: expected [$2] got [$3]" + fail=1 + else + echo "ok: $1" + fi +} + +check "static file" "hello world" "$(curl -s http://127.0.0.1:$PORT/index.html)" +check "default page" "hello world" "$(curl -s http://127.0.0.1:$PORT/)" +check "subdir file" "plain text file" "$(curl -s http://127.0.0.1:$PORT/subdir/file.txt)" +check "404" "404" "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:$PORT/nope.txt)" +check "content type" "200 text/html;" "$(curl -s -o /dev/null -w '%{http_code} %{content_type}' http://127.0.0.1:$PORT/index.html)" +check "dir listing" "2" "$(curl -s http://127.0.0.1:$PORT/subdir/ | grep -o 'file.txt' | wc -l | tr -d ' ')" +check "head" "200" "$(curl -s -I -o /dev/null -w '%{http_code}' http://127.0.0.1:$PORT/index.html)" +check "keepalive" "hello world hello world" "$(curl -s http://127.0.0.1:$PORT/index.html http://127.0.0.1:$PORT/index.html | tr '\n' ' ' | sed 's/ $//')" + +a=$(md5sum < "$DOCROOT/big.bin" | cut -d' ' -f1) +b=$(curl -s http://127.0.0.1:$PORT/big.bin | md5sum | cut -d' ' -f1) +check "1MB file" "$a" "$b" + +if [ -x /usr/bin/perl ]; then + check "cgi get" "method=GET qs=a=1 len=0" "$(curl -s "http://127.0.0.1:$PORT/test.cgi?a=1")" + for i in $(seq 1 10); do + check "cgi post ($i)" "method=POST qs= len=4" "$(curl -s -d 'x=42' http://127.0.0.1:$PORT/test.cgi)" + done + head -c 300000 /dev/zero | tr '\0' 'a' > "$WORK/bigbody" + check "cgi big post" "method=POST qs= len=300000" "$(curl -s --data-binary @"$WORK/bigbody" http://127.0.0.1:$PORT/test.cgi)" +else + echo "skip: cgi tests (no /usr/bin/perl)" +fi + +kill -TERM $PID +for i in $(seq 1 50); do + kill -0 $PID 2>/dev/null || break + sleep 0.1 +done +if kill -0 $PID 2>/dev/null; then + echo "FAIL: server did not exit on SIGTERM" + fail=1 +else + echo "ok: clean shutdown" +fi + +exit $fail