#!/bin/sh
#
# Copyright (C) 2026 Nikos Mavrogiannopoulos
#
# This file is part of ocserv.
#
# ocserv is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# ocserv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GnuTLS; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# Verify that HTTP request-boundary confusion is not possible.
# When two requests arrive in the same TLS read buffer, the parser must
# stop at the first message_complete boundary (via llhttp_pause) and
# dispatch only the first request.  The second request must not reach
# any handler in that same keep-alive cycle.

SERV="${SERV:-../src/ocserv}"
srcdir=${srcdir:-.}
NO_NEED_ROOT=1

. `dirname $0`/common.sh

eval "${GETPORT}"

if ! which gnutls-cli > /dev/null 2>&1; then
	echo "gnutls-cli not found, needed for this test"
	exit 1
fi

echo "Testing HTTP request smuggling prevention..."

update_config test-user-cert.config
launch_simple_sr_server -d 1 -f -c ${CONFIG}
PID=$!
wait_server $PID

TMPFILE=test-http-smuggling.$$.tmp

# Send raw bytes over TLS via gnutls-cli and capture the full server
# response.  Because stdin is a pipe (not a terminal) gnutls-cli delivers
# all data to gnutls_record_send() in a single call, placing both requests
# in the same TLS record — the exact scenario that triggers the bug.
# tls_send takes a printf(1) format string with \r\n escape sequences and
# sends the result over TLS via gnutls-cli.  The argument is intentionally
# NOT passed through $() to avoid shell command substitution stripping
# trailing newlines, which would drop the blank line that terminates HTTP
# headers.
#
# The "sleep 1" keeps the pipe open after the request bytes are sent so
# that gnutls-cli stays in its select loop long enough to receive the
# server response before stdin EOF triggers a TLS close_notify.
tls_send() {
	{ printf '%b' "$1"; sleep 1; } | \
		LD_PRELOAD=libsocket_wrapper.so timeout 5 \
		gnutls-cli \
		--insecure --x509cafile="${srcdir}/certs/ca.pem" \
		$ADDRESS --port $PORT \
		--sni-hostname localhost > "$TMPFILE" || true
}

# --- Positive: single GET must still return a response ------------------
echo -n "Single GET /cert.pem returns a response ... "
tls_send 'GET /cert.pem HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
grep -q "^HTTP/1\." "$TMPFILE" ||
	fail $PID "Single GET /cert.pem produced no HTTP response"
echo "ok"

# --- Negative: second pipelined request must not be dispatched ----------
# Strategy: second URL is /cert.pem (→ 200).  Before the fix the second
# request's URL overwrote the first and the server returned 200.  After
# the fix the server detects pipelined data and closes the connection
# immediately — neither request gets a response, but /cert.pem is never
# served.
echo -n "Pipelined GET: /cert.pem not served ... "
tls_send 'GET /BOGUS_SENTINEL HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\nGET /cert.pem HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
if grep -q "^HTTP/1.1 200" "$TMPFILE"; then
	fail $PID "Second pipelined request was dispatched: got 200 for /cert.pem"
fi
echo "ok"

# --- Negative: GET with body bytes + pipelined GET ----------------------
# The body bytes of the first GET must not bleed into the second request's
# URL parsing.
echo -n "GET with body + pipelined GET: first request dispatched ... "
tls_send 'GET /BOGUS_SENTINEL HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\nConnection: keep-alive\r\n\r\nabcdeGET /cert.pem HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
if grep -q "^HTTP/1.1 200" "$TMPFILE"; then
	fail $PID "Pipelined request after GET body was dispatched: expected 404, got 200 for /cert.pem"
fi
echo "ok"

# --- Negative: GET with Content-Length: 0 + pipelined GET ---------------
echo -n "GET Content-Length:0 + pipelined GET: first request dispatched ... "
tls_send 'GET /BOGUS_SENTINEL HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\nGET /cert.pem HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
if grep -q "^HTTP/1.1 200" "$TMPFILE"; then
	fail $PID "Pipelined request after CL:0 GET was dispatched: expected 404, got 200 for /cert.pem"
fi
echo "ok"

rm -f "$TMPFILE"
cleanup
exit 0
