#!/usr/bin/env bash
# Shared helpers. Sourced by every demo script.
AS=http://localhost:9000
RS=http://localhost:8090
# Kill by main class, never by a pattern that could match this script's own command line.
# `pkill -f spring-boot` matches the shell running it and takes the shell with it.
kill_app() {
local mainclass="$1" port="${2:-}"
for p in $(ps -eo pid,cmd | grep "[${mainclass:0:1}]${mainclass:1}" | awk '{print $1}'); do
kill -9 "$p" 2>/dev/null || true
done
# Then wait for the port to actually close. `ss -lptn` often reports the socket with no
# PID, so a port-based kill can silently do nothing while the old process keeps serving -
# which looks exactly like your config change having had no effect. Waiting for the
# listener to disappear is the only reliable signal that the restart is real.
if [ -n "$port" ]; then
for _ in $(seq 1 30); do
curl -s -o /dev/null --max-time 1 "http://localhost:$port/" || return 0
sleep 1
done
echo "WARNING: something is still listening on :$port after kill_app $mainclass" >&2
return 1
fi
sleep 1
}
wait_for() {
local url="$1" tries="${2:-90}"
for _ in $(seq 1 "$tries"); do
if curl -s -o /dev/null --max-time 2 "$url"; then return 0; fi
sleep 1
done
echo "timed out waiting for $url - check the app log" >&2
return 1
}
hr() { printf '%s\n' "------------------------------------------------------------------"; }
section() { echo; hr; echo "== $*"; hr; }
# Decode a JWS payload without verifying it. Debug only - never do this to decide anything.
jwt_payload() {
python3 - "$1" <<'PY'
import base64, json, sys
part = sys.argv[1].split('.')[1]
part += '=' * (-len(part) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(part)), indent=2, sort_keys=True))
PY
}
jwt_header() {
python3 - "$1" <<'PY'
import base64, json, sys
part = sys.argv[1].split('.')[0]
part += '=' * (-len(part) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(part)), indent=2, sort_keys=True))
PY
}
# Pull a hidden input's value out of a page. Attribute order is not fixed - Spring
# Security's default login page renders name before value, Thymeleaf renders value before
# name - so a naive grep for name="x" value="y" works on one and silently returns empty on
# the other. Empty CSRF token, HTTP 403, and an hour lost.
form_value() {
python3 - "$1" "$2" <<'PY'
import re, sys
html = open(sys.argv[1], encoding='utf-8', errors='replace').read()
want = sys.argv[2]
for tag in re.findall(r']*>', html, re.I):
attrs = dict((m.group(1).lower(), m.group(2))
for m in re.finditer(r'([\w:-]+)\s*=\s*"([^"]*)"', tag))
if attrs.get('name') == want:
print(attrs.get('value', ''))
break
PY
}
# Every value of a repeated input (the scope checkboxes on the consent page).
form_values() {
python3 - "$1" "$2" <<'PY'
import re, sys
html = open(sys.argv[1], encoding='utf-8', errors='replace').read()
want = sys.argv[2]
for tag in re.findall(r']*>', html, re.I):
attrs = dict((m.group(1).lower(), m.group(2))
for m in re.finditer(r'([\w:-]+)\s*=\s*"([^"]*)"', tag))
if attrs.get('name') == want and 'disabled' not in tag.lower():
print(attrs.get('value', ''))
PY
}