Skip to content

Prevent panic in HashHistory::location() on malformed hash URLs#549

Open
ranile wants to merge 2 commits into
masterfrom
hash-history-malformed-hash-panic
Open

Prevent panic in HashHistory::location() on malformed hash URLs#549
ranile wants to merge 2 commits into
masterfrom
hash-history-malformed-hash-panic

Conversation

@ranile
Copy link
Copy Markdown
Owner

@ranile ranile commented Apr 2, 2026

HashHistory::location() called assert_absolute_path on the URL hash, which panicked if the hash didn't start with #/. Users can edit the URL bar at any time to produce hashes like #aaa or just #, crashing the application on the next location() call.

Replace the assert_absolute_path call in location() with graceful normalization:

  • If the hash is empty or doesn't start with /, prepend / (or default to / if empty).
  • Log a warning to the browser console so developers can diagnose unexpected routing behavior.
  • Silently correct the URL in the address bar via replaceState so it stays in canonical #/... form.

The push/replace methods continue to assert, since those are developer-controlled inputs where a relative path is always a bug.

This approach mirrors how every mainstream JS router library handles the same problem. None of them throw on a malformed hash:

  • history v4 / React Router 5 (source): addLeadingSlash normalizes on read; replaceHashPath auto-corrects the URL on every hashchange.
  • history v5 / React Router 6 (source): parsePath with destructuring defaults (pathname = "/") silently falls back to root.
  • React Router v7 (source): Explicit pathname = "/" + pathname prepend with a code comment explaining why.
  • Vue Router 3 (source): ensureSlash() runs on every hashchange, prepends / if missing, replaces the URL, and aborts the transition.
  • Vue Router 4 (source): createWebHashHistory delegates to createWebHistory with a #-based base, unifying URL parsing across modes.

Fixes #470


This prior art was researched by Claude and used to implement the fix in this PR

…ash URLs

HashHistory::location() called assert_absolute_path on the URL hash,
which panicked if the hash didn't start with #/. Users can edit the
URL bar at any time to produce hashes like #aaa or just #, crashing
the application on the next location() call.

Replace the assertion with graceful normalization: prepend / if missing,
log a console warning, and auto-correct the URL via replaceState. This
matches how all major JS router libraries (React Router, Vue Router)
handle the same problem — none of them throw on a malformed hash.

The push/replace methods continue to assert since those are
developer-controlled inputs where a relative path is always a bug.

Fixes #470

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ranile ranile requested a review from Madoshakalaka April 2, 2026 08:16
The browser history state is shared between tests in wasm-pack so separate tests can cause issues
Comment on lines +260 to +265
&format!(
"[gloo_history] HashHistory: URL hash '#{}' does not start with '/'. \
The hash was normalized to '#/{}'. \
Ensure hash-based routes always begin with '#/'.",
raw, raw
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems good. except can you inline these two raws? clippy doesn't like it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[gloo_history::HashHistory] Assertion failed when calling HashHistory's location()

2 participants