From e858e695d35647eb19108b2ee089dd248e63efe0 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 10 Nov 2026 00:53:35 +0100 Subject: [PATCH] fix: keep the 404 GitHub link in the repository The hint decoded the address before building the link, so an encoded /blob/..%2F..%2Fother/x resolved to another account's page. The path is now split before it is decoded, each segment is checked and encoded, and the link is kept only under /Abdess/retrobios/. --- tests/test_site_components.py | 34 ++++++++++++++++++++++++++++++++++ website/src/scripts/site.ts | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/tests/test_site_components.py b/tests/test_site_components.py index cef9c0b13..a6f64c7bb 100644 --- a/tests/test_site_components.py +++ b/tests/test_site_components.py @@ -700,6 +700,40 @@ def _short_unmarked(html: str) -> list[str]: return found +class AGithubPathStaysInTheRepository(unittest.TestCase): + """The 404 hint linked a GitHub path typed under this site: an encoded + /blob/..%2F..%2Fother/x resolved to another account's page.""" + + def _hrefs(self, paths: list[str]) -> list: + source = _read(SITE_TS) + rule = source[source.index("function githubPathHref"):source.index("// End of the GitHub path rule.")] + out = _node_typescript(rule + f"console.log(JSON.stringify({json.dumps(paths)}.map(githubPathHref)));") + return json.loads(out) + + def test_a_path_of_the_repository_is_linked(self): + self.assertEqual(self._hrefs([ + "blob/main/bios/Sony/PlayStation/scph5501.bin", + "tree/main/bios/", + "raw/main/bios/3D%20Realms/x.zip", + ]), [ + "https://github.com/Abdess/retrobios/blob/main/bios/Sony/PlayStation/scph5501.bin", + "https://github.com/Abdess/retrobios/tree/main/bios", + "https://github.com/Abdess/retrobios/blob/main/bios/3D%20Realms/x.zip", + ]) + + def test_a_path_leaving_the_repository_gets_no_link(self): + self.assertEqual(self._hrefs([ + "blob/..%2F..%2F..%2Fother/x", + "blob/../../../other/x", + "blob/main/%2E%2E/%2E%2E/%2E%2E/other", + "blob/main/a%2Fb", + "blob/main/a%5Cb", + "blob/main//x", + "blob/main/%E0%A4%A", + "issues/1", + ]), [None] * 8) + + class OneVerdictWhateverHash(unittest.TestCase): """2.1: the SHA1 of scph5501.bin found 4 setups, its MD5 12, its SHA256 none (V1, J4). A setup's list writes one hash of a file.""" diff --git a/website/src/scripts/site.ts b/website/src/scripts/site.ts index ffb76523a..7b55d77e9 100644 --- a/website/src/scripts/site.ts +++ b/website/src/scripts/site.ts @@ -1306,6 +1306,31 @@ const startDownloads = () => { } }; +// The GitHub page of a repository path typed under this site, or null. +// The address is split before it is decoded, so an encoded slash stays in +// its segment, and a segment that decodes to nothing, '.', '..' or a slash +// gives no link: /blob/..%2F..%2Fother/x led to another account's page. +function githubPathHref(raw: string): string | null { + const match = raw.replace(/\/$/, '').match(/^(blob|tree|raw)\/(.+)$/); + if (!match) return null; + const kind = match[1] === 'raw' ? 'blob' : match[1]; + const segments: string[] = []; + for (const piece of match[2].split('/')) { + let segment: string; + try { + segment = decodeURIComponent(piece); + } catch { + return null; + } + if (!segment || segment === '.' || segment === '..' || /[\\/]/.test(segment)) return null; + segments.push(encodeURIComponent(segment)); + } + const prefix = `/Abdess/retrobios/${kind}/`; + const url = new URL(`https://github.com${prefix}${segments.join('/')}`); + return url.origin === 'https://github.com' && url.pathname.startsWith(prefix) ? url.href : null; +} +// End of the GitHub path rule. + // The 404 page: a search button, and what the address it was asked for // looks like. The addresses readers reached (counted, wiki/privacy.md): a // command pasted as a link (/i.ps1%20%7C%20iex), a file name under files/, @@ -1343,7 +1368,8 @@ const notFound = () => { const hint = document.getElementById('rb-notfound-hint'); if (!hint) return; const base = siteRoot().pathname; - let rest = location.pathname.startsWith(base) ? location.pathname.slice(base.length) : location.pathname; + const raw = location.pathname.startsWith(base) ? location.pathname.slice(base.length) : location.pathname; + let rest = raw; try { rest = decodeURIComponent(rest); } catch { @@ -1368,10 +1394,10 @@ const notFound = () => { return; } // A GitHub path: the files are browsed on the repository. - const github = rest.match(/^(blob|tree|raw)\/(.+)$/); - if (github) { + const href = githubPathHref(raw); + if (href) { const link = document.createElement('a'); - link.href = `https://github.com/Abdess/retrobios/${github[1] === 'raw' ? 'blob' : github[1]}/${github[2]}`; + link.href = href; link.textContent = 'the same path on GitHub'; show(hintLine('This is a path of the GitHub repository. Open ', link, '.')); return;