#!/bin/bash

# SQL Injection Test Suite for C-Relay
# Comprehensive testing of SQL injection vulnerabilities across all filter types

set -e

# Configuration
RELAY_HOST="127.0.0.1"
RELAY_PORT="8888"
TEST_TIMEOUT=10

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Test counters
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0

# Function to send WebSocket message and check for SQL injection success
test_sql_injection() {
    local description="$1"
    local message="$2"

    TOTAL_TESTS=$((TOTAL_TESTS + 1))

    echo -n "Testing $description... "

    # Send message via websocat and capture response
    local response
    response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')

    # Check if the response indicates successful query execution (which would be bad)
    # Look for signs that SQL injection worked (like database errors or unexpected results)
    if [[ "$response" == "TIMEOUT" ]]; then
        echo -e "${YELLOW}UNCERTAIN${NC} - Connection timeout (may indicate crash)"
        FAILED_TESTS=$((FAILED_TESTS + 1))
        return 1
    elif [[ -z "$response" ]]; then
        # Empty response - relay silently rejected malformed input
        echo -e "${GREEN}PASSED${NC} - SQL injection blocked (silently rejected)"
        PASSED_TESTS=$((PASSED_TESTS + 1))
        return 0
    elif [[ "$response" == *"NOTICE"* ]] && [[ "$response" == *"error:"* ]]; then
        # Relay properly rejected the input with a NOTICE error message
        echo -e "${GREEN}PASSED${NC} - SQL injection blocked (rejected with error)"
        PASSED_TESTS=$((PASSED_TESTS + 1))
        return 0
    elif [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"COUNT"* ]] || [[ "$response" == *"EVENT"* ]]; then
        # Query completed normally - this is expected for properly sanitized input
        echo -e "${GREEN}PASSED${NC} - SQL injection blocked (query sanitized)"
        PASSED_TESTS=$((PASSED_TESTS + 1))
        return 0
    elif [[ "$response" == *"SQL"* ]] || [[ "$response" == *"syntax"* ]]; then
        # Database error leaked - potential vulnerability
        echo -e "${RED}FAILED${NC} - SQL error leaked: $response"
        FAILED_TESTS=$((FAILED_TESTS + 1))
        return 1
    else
        # Unknown response
        echo -e "${YELLOW}UNCERTAIN${NC} - Unexpected response: $response"
        FAILED_TESTS=$((FAILED_TESTS + 1))
        return 1
    fi
}

# Function to test valid message (should work normally)
test_valid_query() {
    local description="$1"
    local message="$2"

    TOTAL_TESTS=$((TOTAL_TESTS + 1))

    echo -n "Testing $description... "

    local response
    response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')

    if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]]; then
        echo -e "${GREEN}PASSED${NC} - Valid query works"
        PASSED_TESTS=$((PASSED_TESTS + 1))
        return 0
    else
        echo -e "${RED}FAILED${NC} - Valid query failed: $response"
        FAILED_TESTS=$((FAILED_TESTS + 1))
        return 1
    fi
}

echo "=========================================="
echo "C-Relay SQL Injection Test Suite"
echo "=========================================="
echo "Testing against relay at ws://$RELAY_HOST:$RELAY_PORT"
echo

# Test basic connectivity first
echo "=== Basic Connectivity Test ==="
test_valid_query "Basic connectivity" '["REQ","basic_test",{}]'
echo

# SQL Injection Payloads
SQL_PAYLOADS=(
    # Classic SQL injection
    "'; DROP TABLE events; --"
    "1' OR '1'='1"
    "1; SELECT * FROM sqlite_master; --"
    "' OR 1=1 --"
    "admin'--"
    "1' UNION SELECT password FROM users --"

    # Time-based blind SQL injection
    "1' AND SLEEP(5) --"
    "1' AND 1=1 WAITFOR DELAY '0:0:5' --"

    # Error-based SQL injection
    "1' AND 1=CAST((SELECT version()) AS INT) --"
    "1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --"

    # Union-based injection
    "' UNION SELECT NULL,NULL,NULL --"
    "' UNION SELECT 1,2,3 --"
    "' UNION ALL SELECT NULL,NULL,NULL --"

    # Stacked queries
    "'; SELECT * FROM events; --"
    "'; DELETE FROM events; --"
    "'; UPDATE events SET content='hacked' WHERE 1=1; --"

    # Comment injection
    "/*"
    "*/"
    "/**/"
    "--"
    "#"

    # Hex encoded injection
    "0x53514C5F494E4A454354494F4E"  # SQL_INJECTION in hex

    # Base64 encoded injection
    "J1NSTCBJTkpFQ1RJT04gLS0="  # 'SQL INJECTION -- in base64

    # Nested injection
    "'))); DROP TABLE events; --"
    "')) UNION SELECT NULL; --"

    # Boolean-based blind injection
    "' AND 1=1 --"
    "' AND 1=2 --"
    "' AND (SELECT COUNT(*) FROM events) > 0 --"

    # Out-of-band injection (if supported)
    "'; EXEC master..xp_cmdshell 'net user' --"
    "'; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --"
)

echo "=== Authors Filter SQL Injection Tests ==="
for payload in "${SQL_PAYLOADS[@]}"; do
    test_sql_injection "Authors filter with payload: $payload" "[\"REQ\",\"sql_test_authors_$RANDOM\",{\"authors\":[\"$payload\"]}]"
done
echo

echo "=== IDs Filter SQL Injection Tests ==="
for payload in "${SQL_PAYLOADS[@]}"; do
    test_sql_injection "IDs filter with payload: $payload" "[\"REQ\",\"sql_test_ids_$RANDOM\",{\"ids\":[\"$payload\"]}]"
done
echo

echo "=== Kinds Filter SQL Injection Tests ==="
# Test numeric kinds with SQL injection attempts (these will fail JSON parsing, which is expected)
test_sql_injection "Kinds filter with string injection" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[\"1' OR '1'='1\"]}]"
test_sql_injection "Kinds filter with negative value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[-1]}]"
test_sql_injection "Kinds filter with very large value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[999999999]}]"
echo

echo "=== Search Filter SQL Injection Tests ==="
for payload in "${SQL_PAYLOADS[@]}"; do
    test_sql_injection "Search filter with payload: $payload" "[\"REQ\",\"sql_test_search_$RANDOM\",{\"search\":\"$payload\"}]"
done
echo

echo "=== Tag Filter SQL Injection Tests ==="
TAG_PREFIXES=("#e" "#p" "#t" "#r" "#d")
for prefix in "${TAG_PREFIXES[@]}"; do
    for payload in "${SQL_PAYLOADS[@]}"; do
        test_sql_injection "$prefix tag filter with payload: $payload" "[\"REQ\",\"sql_test_tag_$RANDOM\",{\"$prefix\":[\"$payload\"]}]"
    done
done
echo

echo "=== Timestamp Filter SQL Injection Tests ==="
# Test since/until parameters
test_sql_injection "Since parameter injection" "[\"REQ\",\"sql_test_since_$RANDOM\",{\"since\":\"1' OR '1'='1\"}]"
test_sql_injection "Until parameter injection" "[\"REQ\",\"sql_test_until_$RANDOM\",{\"until\":\"1; DROP TABLE events; --\"}]"
echo

echo "=== Limit Parameter SQL Injection Tests ==="
test_sql_injection "Limit parameter injection" "[\"REQ\",\"sql_test_limit_$RANDOM\",{\"limit\":\"1' OR '1'='1\"}]"
test_sql_injection "Limit with UNION" "[\"REQ\",\"sql_test_limit_$RANDOM\",{\"limit\":\"0 UNION SELECT password FROM users\"}]"
echo

echo "=== Complex Multi-Filter SQL Injection Tests ==="
# Test combinations that might bypass validation
test_sql_injection "Multi-filter with authors injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"authors\":[\"admin'--\"],\"kinds\":[1],\"search\":\"anything\"}]"
test_sql_injection "Multi-filter with search injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"authors\":[\"valid\"],\"search\":\"'; DROP TABLE events; --\"}]"
test_sql_injection "Multi-filter with tag injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"#e\":[\"'; SELECT * FROM sqlite_master; --\"],\"limit\":10}]"
echo

echo "=== COUNT Message SQL Injection Tests ==="
# Test COUNT messages which might have different code paths
for payload in "${SQL_PAYLOADS[@]}"; do
    test_sql_injection "COUNT with authors payload: $payload" "[\"COUNT\",\"sql_count_authors_$RANDOM\",{\"authors\":[\"$payload\"]}]"
    test_sql_injection "COUNT with search payload: $payload" "[\"COUNT\",\"sql_count_search_$RANDOM\",{\"search\":\"$payload\"}]"
done
echo

echo "=== Edge Case SQL Injection Tests ==="
# Test edge cases that might bypass validation
test_sql_injection "Empty string injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"\"]}]"
test_sql_injection "Null byte injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"admin\\x00' OR '1'='1\"]}]"
test_sql_injection "Unicode injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"admin' OR '1'='1' -- 💣\"]}]"
test_sql_injection "Very long injection payload" "[\"REQ\",\"sql_edge_$RANDOM\",{\"search\":\"$(printf 'a%.0s' {1..1000})' OR '1'='1\"}]"
echo

echo "=== Subscription ID SQL Injection Tests ==="
# Test if subscription IDs can be used for injection
test_sql_injection "Subscription ID injection" "[\"REQ\",\"'; DROP TABLE subscriptions; --\",{}]"
test_sql_injection "Subscription ID with quotes" "[\"REQ\",\"sub\"'; SELECT * FROM events; --\",{}]"
echo

echo "=== CLOSE Message SQL Injection Tests ==="
# Test CLOSE messages
test_sql_injection "CLOSE with injection" "[\"CLOSE\",\"'; DROP TABLE subscriptions; --\"]"
echo

echo "=== Test Results ==="
echo "Total tests: $TOTAL_TESTS"
echo -e "Passed: ${GREEN}$PASSED_TESTS${NC}"
echo -e "Failed: ${RED}$FAILED_TESTS${NC}"

if [[ $FAILED_TESTS -eq 0 ]]; then
    echo -e "${GREEN}✓ All SQL injection tests passed!${NC}"
    echo "The relay appears to be protected against SQL injection attacks."
    exit 0
else
    echo -e "${RED}✗ SQL injection vulnerabilities detected!${NC}"
    echo "The relay may be vulnerable to SQL injection attacks."
    echo "Failed tests: $FAILED_TESTS"
    exit 1
fi