monitoring: QR form + qrencode/zbarimg roundtrip (www.qr)
Harvest taler:// and payto:// (plus app-store links) from landings and mint JSON; validate shape; re-encode PNG and decode with zbarimg.
This commit is contained in:
parent
026f3ac04c
commit
f7856525f3
5 changed files with 529 additions and 2 deletions
|
|
@ -1193,4 +1193,232 @@ else
|
|||
warn "landing merchant shop assets" "${_ma}/2 present (soft)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# QR payloads — form + encode/decode roundtrip (qrencode + zbarimg)
|
||||
# Collect taler:// + payto:// (+ app-store https from data-qr-url) from landings
|
||||
# and bank mint JSON; re-encode PNG; decode; require exact payload match.
|
||||
# Env: QR_CHECK=0 to skip; QR_ECC=M (default) for qrencode -l
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "${QR_CHECK:-1}" = "1" ] && [ "${CHECK_LANDING:-1}" = "1" ]; then
|
||||
set_group qr
|
||||
section "www · QR payloads (form + encode/decode · taler:// payto://)"
|
||||
|
||||
QR_ECC="${QR_ECC:-M}"
|
||||
qr_dir="$tmp/qr-check"
|
||||
mkdir -p "$qr_dir"
|
||||
_qr_tools=1
|
||||
if ! command -v qrencode >/dev/null 2>&1; then
|
||||
warn "qr tools" "qrencode missing — form checks only (apt install qrencode)"
|
||||
_qr_tools=0
|
||||
fi
|
||||
if ! command -v zbarimg >/dev/null 2>&1; then
|
||||
warn "qr tools" "zbarimg missing — form checks only (apt install zbar-tools)"
|
||||
_qr_tools=0
|
||||
fi
|
||||
if [ "$_qr_tools" = "1" ]; then
|
||||
ok "qr tools" "qrencode + zbarimg"
|
||||
fi
|
||||
|
||||
# Fetch sources (HTML landings + mint JSON when present)
|
||||
_qr_args=()
|
||||
for pair in \
|
||||
"bank-intro|$BANK_PUBLIC/intro/" \
|
||||
"merchant-intro|$MERCHANT_PUBLIC/intro/" \
|
||||
"exchange-intro|$EXCHANGE_PUBLIC/intro/"
|
||||
do
|
||||
_qn="${pair%%|*}"
|
||||
_qu="${pair#*|}"
|
||||
_qf="$qr_dir/${_qn}.html"
|
||||
_qc=$(http_body "$_qu" "$_qf" 2>/dev/null || echo 000)
|
||||
if [ "$_qc" = "200" ] && [ -s "$_qf" ]; then
|
||||
_qr_args+=("$_qn" "$_qf")
|
||||
fi
|
||||
done
|
||||
for pair in \
|
||||
"demo-withdraw|$BANK_PUBLIC/intro/demo-withdraw.json" \
|
||||
"auto-account|$BANK_PUBLIC/intro/auto-account.json"
|
||||
do
|
||||
_qn="${pair%%|*}"
|
||||
_qu="${pair#*|}"
|
||||
_qf="$qr_dir/${_qn}.json"
|
||||
_qc=$(http_body "$_qu" "$_qf" 2>/dev/null || echo 000)
|
||||
if [ "$_qc" = "200" ] && [ -s "$_qf" ]; then
|
||||
_qr_args+=("$_qn" "$_qf")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#_qr_args[@]}" -eq 0 ]; then
|
||||
warn "qr sources" "no landing HTML / mint JSON fetched — skip payload checks"
|
||||
else
|
||||
_qr_tsv="$qr_dir/payloads.tsv"
|
||||
if ! python3 "$ROOT/check_qr_payloads.py" "${_qr_args[@]}" >"$_qr_tsv" 2>"$qr_dir/harvest.err"; then
|
||||
warn "qr harvest" "collector failed · $(tr '\n' ' ' <"$qr_dir/harvest.err" | head -c 160)"
|
||||
fi
|
||||
|
||||
_qr_n=0
|
||||
_qr_form_ok=0
|
||||
_qr_form_bad=0
|
||||
_qr_rt_ok=0
|
||||
_qr_rt_bad=0
|
||||
_qr_taler=0
|
||||
_qr_payto=0
|
||||
_qr_bad_sample=""
|
||||
_qr_i=0
|
||||
|
||||
while IFS=$'\t' read -r src kind uri status detail || [ -n "${src:-}" ]; do
|
||||
[ -n "${src:-}" ] || continue
|
||||
_qr_n=$((_qr_n + 1))
|
||||
case "$kind" in
|
||||
withdraw|pay|pay-template|refund|other) _qr_taler=$((_qr_taler + 1)) ;;
|
||||
payto) _qr_payto=$((_qr_payto + 1)) ;;
|
||||
esac
|
||||
|
||||
if [ "$status" = "ok" ]; then
|
||||
_qr_form_ok=$((_qr_form_ok + 1))
|
||||
else
|
||||
_qr_form_bad=$((_qr_form_bad + 1))
|
||||
_qr_bad_sample="${_qr_bad_sample}${_qr_bad_sample:+; }form/${kind} ${uri:0:56}"
|
||||
# taler:// and payto:// form errors are hard on local/GOA
|
||||
case "$kind" in
|
||||
withdraw|pay|pay-template|payto)
|
||||
if [ "${LOCAL_STACK:-1}" = "1" ] || [ "${EXPECT_CURRENCY:-}" = "GOA" ]; then
|
||||
fail "qr form ${kind}" "${src} · ${detail} · ${uri:0:80}"
|
||||
else
|
||||
warn "qr form ${kind}" "${src} · ${detail} · ${uri:0:80}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
warn "qr form ${kind}" "${src} · ${detail} · ${uri:0:80}"
|
||||
;;
|
||||
esac
|
||||
continue
|
||||
fi
|
||||
|
||||
# Encode → PNG → zbarimg (exact match)
|
||||
if [ "$_qr_tools" = "1" ] && [ -n "$uri" ]; then
|
||||
_qr_i=$((_qr_i + 1))
|
||||
_png="$qr_dir/p-${_qr_i}.png"
|
||||
# ECC: H for wallet-style long URIs, L for long https store links, else QR_ECC
|
||||
_ecc="$QR_ECC"
|
||||
case "$kind" in
|
||||
https) _ecc=L ;;
|
||||
withdraw|pay|pay-template) _ecc=M ;;
|
||||
payto) _ecc=M ;;
|
||||
esac
|
||||
if ! qrencode -l "$_ecc" -s 4 -m 2 -o "$_png" "$uri" 2>/dev/null; then
|
||||
_qr_rt_bad=$((_qr_rt_bad + 1))
|
||||
fail "qr encode ${kind}" "qrencode failed · ${uri:0:72}"
|
||||
continue
|
||||
fi
|
||||
_got=$(zbarimg --raw -q "$_png" 2>/dev/null | head -n1 | tr -d '\r' || true)
|
||||
if [ "$_got" = "$uri" ]; then
|
||||
_qr_rt_ok=$((_qr_rt_ok + 1))
|
||||
else
|
||||
_qr_rt_bad=$((_qr_rt_bad + 1))
|
||||
_qr_bad_sample="${_qr_bad_sample}${_qr_bad_sample:+; }decode/${kind}"
|
||||
fail "qr decode ${kind}" "zbar mismatch · want ${uri:0:48}… · got ${_got:0:48}"
|
||||
fi
|
||||
fi
|
||||
done <"$_qr_tsv"
|
||||
|
||||
# Download any obvious static QR images from bank intro and decode (soft)
|
||||
if [ "$_qr_tools" = "1" ] && [ -f "$qr_dir/bank-intro.html" ]; then
|
||||
python3 - "$qr_dir/bank-intro.html" "$BANK_PUBLIC" "$qr_dir" <<'PY' >"$qr_dir/img-urls.txt" 2>/dev/null || true
|
||||
import re, sys
|
||||
from urllib.parse import urljoin
|
||||
html = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
||||
base = sys.argv[2].rstrip("/") + "/"
|
||||
out = sys.argv[3]
|
||||
seen = set()
|
||||
for m in re.finditer(r'''(?:src|href)=["']([^"']+\.(?:png|jpe?g|gif|svg))["']''', html, re.I):
|
||||
u = m.group(1)
|
||||
if "qr" not in u.lower() and "withdraw" not in u.lower():
|
||||
continue
|
||||
if u.startswith("data:"):
|
||||
continue
|
||||
absu = urljoin(base + "intro/", u)
|
||||
if absu in seen:
|
||||
continue
|
||||
seen.add(absu)
|
||||
print(absu)
|
||||
PY
|
||||
_img_n=0
|
||||
_img_ok=0
|
||||
while read -r _img_url; do
|
||||
[ -n "$_img_url" ] || continue
|
||||
_img_n=$((_img_n + 1))
|
||||
_imgf="$qr_dir/img-${_img_n}.bin"
|
||||
_ic=$(http_body "$_img_url" "$_imgf" 2>/dev/null || echo 000)
|
||||
if [ "$_ic" != "200" ] || [ ! -s "$_imgf" ]; then
|
||||
warn "qr image fetch" "HTTP ${_ic} · ${_img_url}"
|
||||
continue
|
||||
fi
|
||||
# zbarimg needs image format; skip non-raster
|
||||
if ! file "$_imgf" 2>/dev/null | grep -qiE 'PNG|JPEG|PBM|PGM|PPM|GIF'; then
|
||||
info "qr image skip" "not raster · ${_img_url}"
|
||||
continue
|
||||
fi
|
||||
_dec=$(zbarimg --raw -q "$_imgf" 2>/dev/null | head -n1 | tr -d '\r' || true)
|
||||
if [ -z "$_dec" ]; then
|
||||
warn "qr image decode" "no barcode · ${_img_url}"
|
||||
continue
|
||||
fi
|
||||
_img_ok=$((_img_ok + 1))
|
||||
# validate decoded payload form
|
||||
printf '%s\n' "$_dec" >"$qr_dir/img-decoded.txt"
|
||||
_line=$(python3 "$ROOT/check_qr_payloads.py" "img" "$qr_dir/img-decoded.txt" 2>/dev/null | head -1 || true)
|
||||
if [ -n "$_line" ]; then
|
||||
_st=$(printf '%s' "$_line" | cut -f4)
|
||||
_kd=$(printf '%s' "$_line" | cut -f2)
|
||||
if [ "$_st" = "ok" ]; then
|
||||
ok "qr image ${_kd}" "${_dec:0:64}"
|
||||
else
|
||||
case "$_kd" in
|
||||
withdraw|pay|pay-template|payto)
|
||||
fail "qr image ${_kd}" "bad form · ${_dec:0:72}"
|
||||
;;
|
||||
*)
|
||||
warn "qr image ${_kd}" "bad form · ${_dec:0:72}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
ok "qr image decode" "${_dec:0:64}"
|
||||
fi
|
||||
done <"$qr_dir/img-urls.txt"
|
||||
if [ "$_img_n" -gt 0 ]; then
|
||||
info "qr images" "fetched ${_img_n} · decoded ${_img_ok}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summaries
|
||||
if [ "$_qr_n" -eq 0 ]; then
|
||||
warn "qr payloads" "none found in landings/mint JSON"
|
||||
else
|
||||
if [ "$_qr_form_bad" -eq 0 ]; then
|
||||
ok "qr form" "${_qr_form_ok}/${_qr_n} ok · taler=${_qr_taler} payto=${_qr_payto}"
|
||||
else
|
||||
if [ "${LOCAL_STACK:-1}" = "1" ]; then
|
||||
fail "qr form" "${_qr_form_ok}/${_qr_n} ok · ${_qr_form_bad} bad${_qr_bad_sample:+ · $_qr_bad_sample}"
|
||||
else
|
||||
warn "qr form" "${_qr_form_ok}/${_qr_n} ok · ${_qr_form_bad} bad"
|
||||
fi
|
||||
fi
|
||||
if [ "$_qr_tools" = "1" ]; then
|
||||
if [ "$_qr_rt_bad" -eq 0 ] && [ "$_qr_rt_ok" -gt 0 ]; then
|
||||
ok "qr encode/decode" "${_qr_rt_ok} roundtrips (ecc default ${QR_ECC})"
|
||||
elif [ "$_qr_rt_ok" -eq 0 ] && [ "$_qr_rt_bad" -eq 0 ]; then
|
||||
info "qr encode/decode" "no payloads to encode"
|
||||
else
|
||||
fail "qr encode/decode" "${_qr_rt_ok} ok · ${_qr_rt_bad} fail"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ "${QR_CHECK:-1}" != "1" ]; then
|
||||
info "qr checks" "skipped (QR_CHECK=0)"
|
||||
fi
|
||||
fi
|
||||
|
||||
summary
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue