#!/usr/bin/env bash
# This file is in the public domain.
#
# Tests that a /challenge upload with a field that is not valid UTF-8
# is rejected with 400 and does NOT take the daemon down.
#
# Regression test: json_string() returns NULL for invalid UTF-8, and the
# resulting json_object_set_new() failure used to be caught by a
# GNUNET_assert(), so a single unauthenticated POST aborted
# challenger-httpd.

set -eu

# Exit, with status code "skip" (no 'real' failure)
function exit_skip() {
    echo " SKIP: $1"
    exit 77
}

# Exit, with error message (hard failure)
function exit_fail() {
    echo " FAIL: $@"
    exit 1
}

# Cleanup to run whenever we exit
function cleanup()
{
    for n in $(jobs -p)
    do
        kill $n 2> /dev/null || true
    done
    rm -f "$LAST_RESPONSE" "$FILENAME"
    wait
}

LAST_RESPONSE=$(mktemp responseXXXXXX.log)
FILENAME="test-challenger-badutf8.txt"

# Install cleanup handler (except for kill -9)
trap cleanup EXIT

export PATH="$PATH:."

echo -n "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
echo " FOUND"
echo -n "Testing for curl"
curl -h > /dev/null || exit_skip "curl required"
echo " FOUND"
echo -n "Testing for wget"
wget -h > /dev/null || exit_skip "wget required"
echo " FOUND"
echo -n "Testing for challenger-httpd ..."
challenger-httpd -h > /dev/null || exit_skip "challenger-httpd required"
echo " FOUND"

CONF="test-challenger.conf"
BURL="http://localhost:9967"
REDIRECT_URI="http://client.example.com/"

echo -n "Initialize challenger database ..."
challenger-dbinit -r -c "${CONF}" &> dbinit.log
echo " OK"

echo -n "Add challenger client ..."
CLIENT_SECRET="secret-token:secret"
challenger-admin -c "${CONF}" -a "${CLIENT_SECRET}" "${REDIRECT_URI}" &> admin.log
echo " OK"
# We just reset the DB, thus the client ID must be 1 here:
CLIENT_ID=1

echo -n "Start challenger-httpd ..."
challenger-httpd -L INFO -c "${CONF}" &> httpd.log &

# Wait for challenger to be available
for n in $(seq 1 50)
do
    echo -n "."
    sleep 0.2
    OK=0
    wget --tries=1 --timeout=1 "${BURL}/config" -o /dev/null -O /dev/null >/dev/null || continue
    OK=1
    break
done
if [ 1 != $OK ]
then
    exit_skip "Failed to launch challenger service"
fi
echo " OK"

echo -n "Setup new validation process..."
STATUS=$(curl "${BURL}/setup/${CLIENT_ID}" \
    -H "Authorization: Bearer ${CLIENT_SECRET}" \
    -d '' \
    -w "%{http_code}" -s -o $LAST_RESPONSE)

if [ "$STATUS" != "200" ]
then
    exit_fail "Expected 200 OK. Got: $STATUS" $(cat $LAST_RESPONSE)
fi
NONCE=$(jq -r .nonce < "$LAST_RESPONSE")
echo " OK"

# Note: '--data' (not '--data-urlencode') so that the '%FF' reaches the
# server verbatim and is URL-decoded into a lone 0xFF byte, which is not
# valid UTF-8.
echo -n "Submitting address with a non-UTF-8 value..."
STATUS=$(curl "${BURL}/challenge/${NONCE}" \
    -X POST \
    -H "Accept: application/json" \
    --data "filename=%FF" \
    -w "%{http_code}" -s -o $LAST_RESPONSE || echo "000")

if [ "$STATUS" != "400" ]
then
    exit_fail "Expected 400 Bad Request. Got: $STATUS" $(cat $LAST_RESPONSE)
fi
echo " OK"

echo -n "Checking that challenger-httpd is still alive..."
wget --tries=1 --timeout=5 "${BURL}/config" -o /dev/null -O /dev/null \
    || exit_fail "challenger-httpd died on a non-UTF-8 upload"
echo " OK"

echo -n "Submitting address with a non-UTF-8 field name..."
STATUS=$(curl "${BURL}/challenge/${NONCE}" \
    -X POST \
    -H "Accept: application/json" \
    --data "file%FFname=x" \
    -w "%{http_code}" -s -o $LAST_RESPONSE || echo "000")

if [ "$STATUS" != "400" ]
then
    exit_fail "Expected 400 Bad Request. Got: $STATUS" $(cat $LAST_RESPONSE)
fi
echo " OK"

echo -n "Checking that challenger-httpd is still alive..."
wget --tries=1 --timeout=5 "${BURL}/config" -o /dev/null -O /dev/null \
    || exit_fail "challenger-httpd died on a non-UTF-8 field name"
echo " OK"

# Same class of bug via the JSON upload path: the address is echoed back
# with GNUNET_JSON_pack_object_incref(), which GNUNET_assert()s on a
# non-object, so a JSON body that is not an object used to abort the
# daemon.
for BODY in '[1,2]' '"a string"' 'null'
do
    echo -n "Submitting a JSON address that is not an object (${BODY})..."
    STATUS=$(curl "${BURL}/challenge/${NONCE}" \
        -X POST \
        -H "Accept: application/json" \
        -H "Content-Type: application/json" \
        -d "${BODY}" \
        -w "%{http_code}" -s -o $LAST_RESPONSE) \
        || exit_fail "challenger-httpd died on a non-object JSON address ${BODY}"
    if [ "$STATUS" != "400" ]
    then
        exit_fail "Expected 400 Bad Request. Got: $STATUS" $(cat $LAST_RESPONSE)
    fi
    echo " OK"

    echo -n "Checking that challenger-httpd is still alive..."
    wget --tries=1 --timeout=5 "${BURL}/config" -o /dev/null -O /dev/null \
        || exit_fail "challenger-httpd died on a non-object JSON address ${BODY}"
    echo " OK"
done

# The rejected requests must not have consumed anything: a well-formed
# address is still accepted afterwards.
echo -n "Submitting a well-formed address..."
STATUS=$(curl "${BURL}/challenge/${NONCE}" \
    -X POST \
    -H "Accept: application/json" \
    --data-urlencode "filename=${FILENAME}" \
    -w "%{http_code}" -s -o $LAST_RESPONSE)

if [ "$STATUS" != "200" ]
then
    exit_fail "Expected 200 OK. Got: $STATUS" $(cat $LAST_RESPONSE)
fi
echo " OK"

exit 0
