mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-26 07:57:10 +00:00
fix: harden Dashboard hash routing against malformed and unknown tabs
Three routing defects, all reachable from a shareable URL: - A malformed percent-sequence (e.g. `#100%`) made parseDashboardRoute's decodeURIComponent throw at module load, blanking the entire GUI. Guard the decode and fall back to the raw hash. Verified: `#100%` now boots. - A hash whose tab segment contained a quote (`#foo"bar`) was interpolated into a jQuery selector that throws, leaving dashboard event handlers unbound. Resolve the route tab against the known tab set (falling back to Apps) before it ever reaches a selector. This also fixes an unknown hash activating the Apps section without its `.dashboard-content.apps` styling. - Re-clicking the already-active sidebar tab pushed duplicate history entries, so Back became a no-op until pressed repeatedly. Only pushState when the hash actually changes. Verified live for all three.
This commit is contained in:
@@ -349,13 +349,19 @@ async function UIDashboard (options) {
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve an untrusted route tab id (from the URL hash) to a known tab id,
|
||||
// falling back to Apps. This keeps a crafted hash (e.g. one containing a
|
||||
// quote) from being interpolated into a jQuery selector, which throws and
|
||||
// would otherwise leave the dashboard's event handlers unbound.
|
||||
const knownTabId = tab => (tabs.some(t => t !== '-' && t.id === tab) ? tab : 'apps');
|
||||
|
||||
// Apply initial route from URL - activate the correct tab
|
||||
if ( window.dashboard_initial_route ) {
|
||||
const route = window.dashboard_initial_route;
|
||||
|
||||
// Activate the correct tab if not home
|
||||
if ( route.tab && route.tab !== 'home' ) {
|
||||
const tabId = route.tab;
|
||||
const tabId = knownTabId(route.tab);
|
||||
const $targetTab = $el_window.find(`.dashboard-sidebar-item[data-section="${tabId}"]`);
|
||||
|
||||
// Only switch if the tab exists
|
||||
@@ -386,7 +392,7 @@ async function UIDashboard (options) {
|
||||
if ( window.location.href === lastHandledHref ) return;
|
||||
lastHandledHref = window.location.href;
|
||||
const route = window.parseDashboardRoute();
|
||||
const tab = route.tab;
|
||||
const tab = knownTabId(route.tab);
|
||||
const filePath = route.path;
|
||||
|
||||
// Switch to correct tab
|
||||
@@ -456,9 +462,13 @@ async function UIDashboard (options) {
|
||||
document.querySelector('.dashboard-content').classList.add(section);
|
||||
|
||||
// Reflect the current tab in the hash. Root (no hash) defaults to Apps,
|
||||
// but selecting any tab — including Apps — shows its #tab.
|
||||
history.pushState(null, '', `#${section}`);
|
||||
lastHandledHref = window.location.href;
|
||||
// but selecting any tab — including Apps — shows its #tab. Only push a
|
||||
// new history entry when the hash actually changes, so re-clicking the
|
||||
// current tab doesn't stack duplicate entries that make Back a no-op.
|
||||
if ( window.location.hash !== `#${section}` ) {
|
||||
history.pushState(null, '', `#${section}`);
|
||||
lastHandledHref = window.location.href;
|
||||
}
|
||||
|
||||
// Scroll content area to top
|
||||
$el_window.find('.dashboard-content').scrollTop(0);
|
||||
|
||||
+10
-1
@@ -536,7 +536,16 @@ if (jQuery) {
|
||||
* @returns {{ tab: string, path: string|null }} Route object with tab name and optional file path
|
||||
*/
|
||||
function parseDashboardRoute() {
|
||||
const hash = decodeURIComponent(window.location.hash.slice(1)); // Remove '#' and decode URL encoding
|
||||
// decodeURIComponent throws URIError on a malformed percent-sequence (e.g.
|
||||
// `#100%`). This runs at module load, so an unguarded throw blanks the whole
|
||||
// GUI — fall back to the raw hash instead.
|
||||
const rawHash = window.location.hash.slice(1); // Remove '#'
|
||||
let hash;
|
||||
try {
|
||||
hash = decodeURIComponent(rawHash);
|
||||
} catch {
|
||||
hash = rawHash;
|
||||
}
|
||||
if (!hash) return { tab: 'apps', path: null };
|
||||
|
||||
const parts = hash.split('/').filter(Boolean); // ['files', 'username', 'Documents']
|
||||
|
||||
Reference in New Issue
Block a user