#!/usr/bin/env bash
# SPV CURL CONTAINMENT UNIT · every Grok / external desk curl must enter here
# SDF place · isolated from other memory · no free-roam curl
# LAW: BGS→BGF→BGL→SDF · SPV free · Grok curl only via this unit · free thrift
#
#   spv-curl-contain.sh curl [curl-args…]     # contained curl
#   spv-curl-contain.sh grok [krtx-grok-args] # force contain env for krtx-grok
#   spv-curl-contain.sh status
#   spv-curl-contain.sh allowlist
#
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$ROOT/out/spv_contain"
LOG="$OUT/contain.log"
ALLOW_HOSTS_FILE="${SPV_CURL_ALLOW:-$ROOT/Build/spv-curl-allow.list}"
mkdir -p "$OUT"
chmod 700 "$OUT" 2>/dev/null || true
export SPV_CONTAIN=1
export SPV_CONTAIN_SDF="place:shell_curl_unit"
export SPV_CONTAIN_ROOT="$ROOT"
# keep unit memory out of other trees
export TMPDIR="$OUT/tmp"
mkdir -p "$TMPDIR"
chmod 700 "$TMPDIR" 2>/dev/null || true

ts() { date -Iseconds; }
say() { echo "[$(ts)] $*" | tee -a "$LOG" >/dev/null; echo "$*"; }

# default allowlist if missing
if [[ ! -f "$ALLOW_HOSTS_FILE" ]]; then
  cat >"$ALLOW_HOSTS_FILE" <<'EOF'
# SPV curl contain allowlist · one host per line
api.x.ai
127.0.0.1
localhost
public-api.wordpress.com
biggrinrtx.com
public.api.bsky.app
bsky.social
EOF
  chmod 600 "$ALLOW_HOSTS_FILE" 2>/dev/null || true
fi

allowlist() {
  grep -vE '^\s*#|^\s*$' "$ALLOW_HOSTS_FILE" 2>/dev/null || true
}

host_allowed() {
  local url=$1 host
  # extract host from URL
  host=$(printf '%s' "$url" | sed -E 's#^[a-zA-Z]+://##' | sed -E 's#/.*##' | sed -E 's#:[0-9]+$##' | tr 'A-Z' 'a-z')
  [[ -z "$host" ]] && return 1
  while IFS= read -r a; do
    a=$(echo "$a" | tr 'A-Z' 'a-z')
    [[ "$host" == "$a" || "$host" == *".$a" ]] && return 0
  done < <(allowlist)
  return 1
}

extract_urls() {
  # pull http(s) URLs from args
  local a
  for a in "$@"; do
    if [[ "$a" == http://* || "$a" == https://* ]]; then
      printf '%s\n' "$a"
    fi
  done
}

cmd_status() {
  local n=0
  [[ -f "$OUT/last.json" ]] && n=$(wc -c <"$OUT/last.json" | tr -d ' ')
  cat <<JSON
{
  "word": "SPV_CURL_CONTAIN",
  "unit": "SPV containment",
  "sdf": "place · shell_curl_unit · isolated memory TMPDIR=$TMPDIR",
  "spv_contain": true,
  "allowlist": $(allowlist | python3 -c 'import sys,json; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))'),
  "log": "$LOG",
  "last_bytes": $n,
  "law": "EVERY Grok curl through this unit · no free curl · SDF away from other memory"
}
JSON
}

cmd_curl() {
  # refuse if no containment mark (we set it)
  local urls url
  mapfile -t urls < <(extract_urls "$@")
  if ((${#urls[@]} == 0)); then
    # relative / path only local ok if --url later; scan all args loosely
    :
  fi
  for url in "${urls[@]+"${urls[@]}"}"; do
    if ! host_allowed "$url"; then
      say "REFUSE host not in allowlist: $url"
      echo "{\"ok\":false,\"error\":\"host_not_allowed\",\"url\":\"$url\",\"unit\":\"spv-curl-contain\"}" >&2
      return 3
    fi
  done
  # force safe curl flags
  local -a args=(
    --silent --show-error
    --max-time "${SPV_CURL_MAX_TIME:-120}"
    --connect-timeout "${SPV_CURL_CONNECT:-15}"
    --proto-redir '=https,http'
    --path-as-is
  )
  # no .netrc bleed · no global config if possible
  args+=(--netrc-optional)
  # run
  local stamp="$OUT/run-$(date +%Y%m%d-%H%M%S)-$$.meta"
  {
    echo "ts=$(ts)"
    echo "sdf=$SPV_CONTAIN_SDF"
    echo "args=$*"
  } >"$stamp"
  chmod 600 "$stamp" 2>/dev/null || true
  # never log Authorization line values
  say "CONTAIN curl · urls=${urls[*]:-none} · sdf=$SPV_CONTAIN_SDF"
  # execute real curl
  if command -v curl >/dev/null 2>&1; then
    curl "${args[@]}" "$@" 
    local rc=$?
    echo "rc=$rc" >>"$stamp"
    return $rc
  fi
  echo "curl missing" >&2
  return 2
}

cmd_grok() {
  # run krtx-grok inside contain env so its curl path uses SPV_CURL wrapper if patched
  export PATH="$ROOT/Build/spv_contain_bin:$PATH"
  export SPV_CURL_WRAPPER="$ROOT/Build/spv-curl-contain.sh"
  local grok="$ROOT/KateRTX/bin/krtx-grok"
  [[ -x "$grok" ]] || grok="$ROOT/bin/krtx-grok"
  if [[ ! -x "$grok" ]]; then
    echo "krtx-grok missing · build KateRTX" >&2
    return 2
  fi
  say "CONTAIN grok · $*"
  exec "$grok" "$@"
}

# thin real curl in PATH for child processes
install_shim() {
  local d="$ROOT/Build/spv_contain_bin"
  mkdir -p "$d"
  cat >"$d/curl" <<EOF
#!/usr/bin/env bash
exec "$ROOT/Build/spv-curl-contain.sh" curl "\$@"
EOF
  chmod 755 "$d/curl"
}

case "${1:-status}" in
  status) cmd_status ;;
  allowlist) allowlist ;;
  curl) shift; cmd_curl "$@" ;;
  grok) shift; install_shim; cmd_grok "$@" ;;
  install-shim) install_shim; echo "shim $ROOT/Build/spv_contain_bin/curl" ;;
  *)
    echo "usage: $0 status|allowlist|curl …|grok …|install-shim" >&2
    exit 2
    ;;
esac
