2c6062a53e
The /web/ prefix predated the /api/ split; today it just clutters every URL
without disambiguating anything. All 16 browser routes move to the apex
(/cases, /login, /magic, /events, /audio/...). The 6 /api/* routes are
unchanged. A new /->>/cases redirect closes the apex 404.
The open-redirect guard in magic.rs and case_actions.rs flips from a
positive whitelist (starts_with("/web/")) to a deny-list: same-origin path,
not protocol-relative, not under /api/, no \. The /api/ exclusion is now
load-bearing and covered by tests.
Pre-production: no transition redirects.
48 lines
1.7 KiB
Rust
48 lines
1.7 KiB
Rust
//! Regression: SSE connection-pool leak via missing `EventSource.close()`.
|
|
//!
|
|
//! Every HTML template that opens `new EventSource('/events')` must
|
|
//! also close it on `pagehide`. Without the close, Chrome keeps the
|
|
//! socket in a "draining" state after navigation; after ~6 rapid
|
|
//! back-and-forth navigations all 6 per-origin pool slots are draining
|
|
//! and new requests stall with `blocked` timings of 15-30 seconds
|
|
//! (confirmed by HAR capture on 2026-04-20).
|
|
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn every_event_source_template_closes_it_on_pagehide() {
|
|
let template_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("templates");
|
|
let entries = fs::read_dir(&template_dir).expect("templates directory readable");
|
|
|
|
let mut checked = 0;
|
|
let mut missing: Vec<String> = Vec::new();
|
|
|
|
for entry in entries {
|
|
let path = entry.unwrap().path();
|
|
if path.extension().and_then(|s| s.to_str()) != Some("html") {
|
|
continue;
|
|
}
|
|
let content = fs::read_to_string(&path).unwrap();
|
|
if !content.contains("new EventSource('/events')") {
|
|
continue;
|
|
}
|
|
checked += 1;
|
|
let has_pagehide = content.contains("pagehide");
|
|
let has_close = content.contains(".close()");
|
|
if !(has_pagehide && has_close) {
|
|
missing.push(path.file_name().unwrap().to_string_lossy().into_owned());
|
|
}
|
|
}
|
|
|
|
assert!(
|
|
checked >= 1,
|
|
"test itself is stale: no template contains EventSource('/events')",
|
|
);
|
|
assert!(
|
|
missing.is_empty(),
|
|
"templates open EventSource but lack `pagehide` + `.close()` cleanup: {missing:?} \
|
|
— this leaks Chrome socket-pool slots and stalls navigation after ~6 cycles",
|
|
);
|
|
}
|