#!/usr/bin/env bash
#
# onx-modsec-test-request — bir HTTP isteğini CRS kurallarına karşı dene.
# DRIVER-AWARE + format-agnostic (v3.41).
#
# Aktif driver'ın dinlediği port'u (443→80) ve audit log yolunu tespit eder,
# benzersiz marker'lı istek atar, yeni audit kayıtlarından (JSON veya native serial)
# eşleşen kuralları çıkarır.
#
# Input:  {"method","uri","headers","body","driver?"}
# Output: {ok, matched_rules:[...], anomaly_score, action, would_block, driver, http_response_code}

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

input="$(cat 2>/dev/null || echo '{}')"
echo "$input" | jq -e 'type == "object"' >/dev/null 2>&1 || input='{}'
method=$(echo "$input"  | jq -r '.method // "GET"')
uri=$(echo "$input"     | jq -r '.uri // "/"')
headers=$(echo "$input" | jq -r '.headers // ""')
body=$(echo "$input"    | jq -r '.body // ""')
req_driver=$(echo "$input" | jq -r '.driver // "auto"')

DRIVER=$(onx_ws_resolve_driver "$req_driver")
LOG_FILE=$(onx_modsec_audit_log "$DRIVER")

marker="ONOX-TEST-$(date +%s%N)-$RANDOM"

# ── Dinlenen port/scheme tespit (443 → 80) ───────────────────────────────────
TARGET_SCHEME="http"; TARGET_PORT="80"; TARGET_K=""
for cand in "https:443:-k" "http:80:"; do
    sc="${cand%%:*}"; rest="${cand#*:}"; pt="${rest%%:*}"; kf="${rest##*:}"
    if curl ${kf:+$kf} --connect-timeout 2 -s -o /dev/null "${sc}://127.0.0.1:${pt}/" 2>/dev/null; then
        TARGET_SCHEME="$sc"; TARGET_PORT="$pt"; TARGET_K="$kf"; break
    fi
done

# ── curl argümanları ─────────────────────────────────────────────────────────
declare -a curl_args=(-s -o /dev/null -w '%{http_code}' --max-time 10
    -H "User-Agent: $marker" -H "X-ONOX-Test: 1" -X "$method")
[[ -n "$TARGET_K" ]] && curl_args+=("$TARGET_K")
if [[ -n "$headers" ]]; then
    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        curl_args+=(-H "$line")
    done <<<"$headers"
fi
if [[ -n "$body" ]] && [[ "$method" == "POST" || "$method" == "PUT" || "$method" == "PATCH" ]]; then
    curl_args+=(--data-binary "$body")
fi

# ── Audit log boyutu (öncesi) ────────────────────────────────────────────────
log_size_before=0
[[ -n "$LOG_FILE" && -r "$LOG_FILE" ]] && log_size_before=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)

response_code=$(curl "${curl_args[@]}" "${TARGET_SCHEME}://127.0.0.1:${TARGET_PORT}${uri}" 2>/dev/null || echo "000")
sleep 0.6   # log flush

# ── Yeni audit kayıtlarını marker'a göre parse et ────────────────────────────
matched_rules='[]'
anomaly_score=0
if [[ -n "$LOG_FILE" && -r "$LOG_FILE" && $log_size_before -gt 0 ]]; then
    new_entries=$(tail -c +"$((log_size_before + 1))" "$LOG_FILE" 2>/dev/null || echo "")

    # Format detect (yeni bölgenin ilk { → JSON)
    fmt="native"
    if printf '%s' "$new_entries" | grep -m1 -v '^[[:space:]]*$' | head -c 1 | grep -q '{' 2>/dev/null; then
        fmt="json"
    fi

    if [[ "$fmt" == "json" ]]; then
        matched_rules=$(printf '%s\n' "$new_entries" | grep -F "$marker" | jq -cs '
            def sev_score($s): ($s|tostring|ascii_upcase) as $u |
                if   $u=="CRITICAL" or $u=="2" then 5
                elif $u=="ERROR"    or $u=="3" then 4
                elif $u=="WARNING"  or $u=="4" then 3
                elif $u=="NOTICE"   or $u=="5" then 2
                else 1 end;
            [ .[] | (.transaction.messages // [])[]
              | {rule_id:((.details.ruleId // "")|tostring),
                 category:(.message // ""),
                 severity:((.details.severity // "")|tostring|ascii_upcase),
                 score: sev_score(.details.severity)} ]
        ' 2>/dev/null || echo '[]')
    else
        # native — marker içeren bloğu bul (tolerant boundary), H section'dan kural çek
        test_block=$(printf '%s\n' "$new_entries" | awk -v marker="$marker" '
            function is_boundary(line) { return (line ~ /^-+[A-Za-z0-9]+-+[A-Z]-+$/) }
            function sect_letter(line) { if (match(line, /[A-Z]-+$/)) return substr(line, RSTART, 1); return "" }
            {
                if (is_boundary($0)) {
                    L = sect_letter($0)
                    if (L == "A") { if (capture && buf!="") print buf; capture=0; buf=$0 "\n"; next }
                    if (L == "Z") { buf=buf $0 "\n"; if (capture) print buf; capture=0; buf=""; next }
                }
                buf = buf $0 "\n"
                if (index($0, marker) > 0) capture = 1
            }
            END { if (capture && buf!="") print buf }
        ')
        if [[ -n "$test_block" ]]; then
            matched_rules=$(printf '%s\n' "$test_block" \
                | grep -oE '\[id "[0-9]+"\][^[]*\[msg "[^"]*"\][^[]*\[severity "[A-Za-z0-9]+"\]' \
                | while IFS= read -r m; do
                    rid=$(echo "$m" | grep -oE 'id "[0-9]+"' | grep -oE '[0-9]+')
                    msg=$(echo "$m" | grep -oE 'msg "[^"]+"' | sed 's/msg "//;s/"$//')
                    sev=$(echo "$m" | grep -oE 'severity "[A-Za-z0-9]+"' | sed 's/severity "//;s/"$//')
                    sev=$(onx_modsec_sev_word "$sev")
                    case "$sev" in
                        CRITICAL) score=5 ;; ERROR) score=4 ;; WARNING) score=3 ;; NOTICE) score=2 ;; *) score=1 ;;
                    esac
                    jq -nc --arg rid "$rid" --arg msg "$msg" --arg sev "$sev" --argjson score "$score" \
                        '{rule_id:$rid, category:$msg, severity:$sev, score:$score}'
                done | jq -cs '.' 2>/dev/null || echo '[]')
        fi
    fi
    [[ -z "$matched_rules" ]] && matched_rules='[]'
    anomaly_score=$(echo "$matched_rules" | jq '[.[].score] | add // 0' 2>/dev/null || echo 0)
fi

# ── Action / would_block ─────────────────────────────────────────────────────
action="allow"; would_block=false
if [[ "$response_code" == "403" || "$response_code" == "406" ]]; then
    action="block"; would_block=true
elif [[ "${anomaly_score:-0}" =~ ^[0-9]+$ ]] && [[ "$anomaly_score" -ge 5 ]]; then
    action="block"; would_block=true
fi

jq -nc \
    --argjson matched_rules "$matched_rules" \
    --argjson anomaly_score "${anomaly_score:-0}" \
    --arg action "$action" \
    --argjson would_block "$would_block" \
    --arg response_code "$response_code" \
    --arg driver "$DRIVER" \
    '{ok:true, matched_rules:$matched_rules, anomaly_score:$anomaly_score, action:$action,
      would_block:$would_block, http_response_code:$response_code, driver:$driver}'
