#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
CODING_DIR="$ROOT/coding"
LOG_DIR="$CODING_DIR/verify_logs"
mkdir -p "$LOG_DIR"

WORKDIR="/tmp/cve-2025-0520-coding-verify"
REPO_DIR="$WORKDIR/showdoc_repo"
BASE_SRC="$WORKDIR/showdoc_e1cd_base"
PATCHED_SRC="$WORKDIR/showdoc_e1cd_patched"
TARGET_COMMIT="e1cd02a3f98bb227c0599e7fa6b803ab1097597f"

BASE_IMAGE="showdoc-coding-0520:e1cd-base"
PATCHED_IMAGE="showdoc-coding-0520:e1cd-patched"
BASE_CTR="showdoc-coding-0520-base"
PATCHED_CTR="showdoc-coding-0520-patched"

LOGIN_URL='http://127.0.0.1/server/index.php?s=/Api/User/login'
UPLOAD_URL='http://127.0.0.1/server/index.php?s=/Api/Page/uploadImg'

for cmd in docker git patch curl grep sed awk; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "[!] Missing required command: $cmd" >&2
    exit 2
  fi
done

docker ps >/dev/null

cleanup() {
  docker rm -f "$BASE_CTR" "$PATCHED_CTR" >/dev/null 2>&1 || true
}
trap cleanup EXIT
cleanup

mkdir -p "$WORKDIR"
if [[ ! -d "$REPO_DIR/.git" ]]; then
  git clone https://github.com/star7th/showdoc.git "$REPO_DIR" >"$LOG_DIR/git_clone.log" 2>&1
else
  git -C "$REPO_DIR" fetch --tags --force >"$LOG_DIR/git_fetch.log" 2>&1
fi

rm -rf "$BASE_SRC" "$PATCHED_SRC"
cp -a "$REPO_DIR" "$BASE_SRC"
cp -a "$REPO_DIR" "$PATCHED_SRC"

git -C "$BASE_SRC" checkout -f "$TARGET_COMMIT" >"$LOG_DIR/git_checkout_base.log" 2>&1
git -C "$PATCHED_SRC" checkout -f "$TARGET_COMMIT" >"$LOG_DIR/git_checkout_patched.log" 2>&1

patch -p1 -d "$PATCHED_SRC" < "$CODING_DIR/proposed_fix.diff" >"$LOG_DIR/patch_apply.log" 2>&1

build_image() {
  local image="$1"
  local src_dir="$2"
  local build_log="$3"
  docker build -t "$image" -f - "$src_dir" >"$build_log" 2>&1 <<'DOCKERFILE'
FROM php:7.4-apache
RUN apt-get update && apt-get install -y libsqlite3-dev \
    && docker-php-ext-install pdo pdo_sqlite \
    && a2enmod rewrite \
    && rm -rf /var/lib/apt/lists/*
COPY . /var/www/html/
RUN mkdir -p /var/www/html/Public/Uploads /var/www/html/server/Application/Runtime \
    && chmod -R 777 /var/www/html
DOCKERFILE
}

seed_item_if_needed() {
  local ctr="$1"
  docker exec "$ctr" bash -lc 'cat > /tmp/coding_seed_item.php <<"PHP"
<?php
try {
  $db = new PDO("sqlite:/var/www/html/Sqlite/showdoc.db.php");
  $stmt = $db->query("SELECT name FROM sqlite_master WHERE type=\"table\" AND name=\"item\"");
  if (!$stmt || !$stmt->fetchColumn()) {
    echo "no_item_table\n";
    exit(0);
  }
  $count = (int)$db->query("SELECT count(*) FROM item")->fetchColumn();
  if ($count < 1) {
    $n = time();
    $ins = $db->prepare("INSERT INTO item(item_name,item_description,uid,username,password,addtime,last_update_time,item_domain,item_type,is_archived,is_del) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
    $ins->execute(array("coding-seed","seed",1,"showdoc","",$n,$n,"",1,0,0));
    echo "seeded\n";
  } else {
    echo "already_seeded\n";
  }
} catch (Exception $e) {
  echo "seed_error:".$e->getMessage()."\n";
}
?>
PHP
php /tmp/coding_seed_item.php'
}

run_case() {
  local image="$1"
  local ctr="$2"
  local outdir="$3"

  rm -rf "$outdir"
  mkdir -p "$outdir"

  docker rm -f "$ctr" >/dev/null 2>&1 || true
  docker run -d --name "$ctr" "$image" >"$outdir/container_id.txt"

  local http_code=""
  for _ in $(seq 1 60); do
    http_code=$(docker exec "$ctr" bash -lc 'curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/index.php || true')
    if [[ "$http_code" == "200" || "$http_code" == "302" ]]; then
      break
    fi
    sleep 1
  done
  echo "$http_code" >"$outdir/health_http_code.txt"
  if [[ "$http_code" != "200" && "$http_code" != "302" ]]; then
    echo "[!] container did not become ready" >&2
    return 1
  fi

  if docker exec "$ctr" bash -lc 'test -f /var/www/html/install/non_interactive.php'; then
    docker exec "$ctr" bash -lc 'cd /var/www/html/install && php non_interactive.php' >"$outdir/install_output.txt" 2>&1 || true
  else
    echo "no_non_interactive" >"$outdir/install_output.txt"
  fi

  seed_item_if_needed "$ctr" >"$outdir/seed_item_output.txt" 2>&1 || true

  docker exec "$ctr" bash -lc "curl -i -sS -c /tmp/coding_cookie.txt -X POST '$LOGIN_URL' -d 'username=showdoc&password=123456'" >"$outdir/login_response.txt"
  if ! grep -q '"error_code":0' "$outdir/login_response.txt"; then
    echo "[!] login failed in $ctr" >&2
    return 1
  fi

  docker exec "$ctr" bash -lc 'cat > /tmp/coding_payload.php <<"PHP"
<?php echo "CODING_VERIFY_VARIANT"; ?>
PHP'

  docker exec "$ctr" bash -lc "curl -i -sS -b /tmp/coding_cookie.txt -X POST '$UPLOAD_URL' -F 'editormd-image-file=@/tmp/coding_payload.php;filename=verify_shell.phtml;type=text/plain'" >"$outdir/upload_response.txt"

  docker exec "$ctr" bash -lc 'find /var/www/html/Public/Uploads -type f | sort' >"$outdir/all_uploaded_files.txt"
  grep -E '\.phtml$' "$outdir/all_uploaded_files.txt" >"$outdir/uploaded_phtml_files.txt" || true

  local phtml_count
  phtml_count=$(wc -l < "$outdir/uploaded_phtml_files.txt" 2>/dev/null || echo 0)
  echo "$phtml_count" >"$outdir/phtml_count.txt"

  docker logs "$ctr" >"$outdir/container.log" 2>&1 || true
  docker rm -f "$ctr" >/dev/null 2>&1 || true

  echo "$phtml_count"
}

build_image "$BASE_IMAGE" "$BASE_SRC" "$LOG_DIR/build_base.log"
build_image "$PATCHED_IMAGE" "$PATCHED_SRC" "$LOG_DIR/build_patched.log"

base_count=$(run_case "$BASE_IMAGE" "$BASE_CTR" "$LOG_DIR/base")
patched_count=$(run_case "$PATCHED_IMAGE" "$PATCHED_CTR" "$LOG_DIR/patched")

cat > "$LOG_DIR/summary.txt" <<EOF
Verification summary for coding/proposed_fix.diff
=================================================
Target commit: $TARGET_COMMIT
Exploit path: authenticated API upload /Api/Page/uploadImg with filename verify_shell.phtml

Results:
- Baseline (unpatched e1cd) uploaded .phtml count: $base_count
- Patched uploaded .phtml count: $patched_count

Pass criteria:
- baseline count > 0
- patched count == 0
EOF

if [[ "$base_count" -gt 0 && "$patched_count" -eq 0 ]]; then
  echo "[+] PASS: patch blocks .phtml upload on API endpoint while baseline remains vulnerable"
  exit 0
fi

echo "[-] FAIL: expected baseline vulnerable and patched blocked" >&2
exit 1
