mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-20 02:18:30 +00:00
fix(regions): reject a slug that only uppercases into a region code
toUpperCase() is full Unicode case mapping, so the accepted set was every string whose uppercase equals a code, not just a re-casing. `hn-badinh` with a dotless i (U+0131) and `hn-sontay` with a long s (U+017F) both resolved and played a real round. Each such spelling is a distinct URL: its own year-long ISR entry and its own analytics row. Splitting one region across an unbounded set of URLs is exactly what putting the region in the path was meant to prevent. A round trip through regionSlug accepts a re-casing and nothing else. /game/TPHCM still plays; there is still no canonical-casing redirect.
This commit is contained in:
1 parent
6d7cb30e5e
commit
45cb389d9d
2 files changed
+23
-2
No files matched your search
+17
-2
@@ -62,12 +62,27 @@ export function regionSlug(code) {
|
||||
* is deliberately no redirect to the canonical casing -- a redirect on a
|
||||
* prerendered route gets cached as that route's response, and an uppercase URL
|
||||
* is rare enough that one stray analytics row costs less than that mechanism.
|
||||
*
|
||||
* Re-casing is the only difference THIS FUNCTION accepts, hence the round-trip
|
||||
* check. It cannot speak for the URL string above it: the router decodes the
|
||||
* path segment before handing it over, so `/game/%74phcm` arrives as `tphcm`
|
||||
* and plays, at a URL that is its own cache entry and analytics row. Seeing
|
||||
* the raw encoding would mean reading headers(), which opts the route out of
|
||||
* static rendering -- 85 prerendered pages traded for a hand-crafted URL
|
||||
* nobody links to.
|
||||
* toUpperCase() is full Unicode case mapping, so on its own it would accept
|
||||
* every string that merely uppercases into a code: `hn-bad<U+0131>nh` (dotless
|
||||
* i) and `hn-<U+017F>ontay` (long s) among an unbounded set. Each such spelling
|
||||
* is a distinct URL -- its own year-long ISR entry and its own analytics row --
|
||||
* which is precisely the split the region-in-the-path change exists to prevent.
|
||||
* @param {string} slug Path segment from the URL.
|
||||
* @returns {string|null} The canonical region code, or null when unknown.
|
||||
*/
|
||||
export function regionFromSlug(slug) {
|
||||
const code = String(slug).toUpperCase();
|
||||
return isRegion(code) ? code : null;
|
||||
const raw = String(slug);
|
||||
const code = raw.toUpperCase();
|
||||
if (!isRegion(code)) return null;
|
||||
return regionSlug(code) === raw.toLowerCase() ? code : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,12 @@ describe('region tree shape', () => {
|
||||
expect(regionFromSlug('hn-badinh')).toBe('HN-BADINH');
|
||||
expect(regionFromSlug('notaregion')).toBeNull();
|
||||
expect(regionFromSlug('')).toBeNull();
|
||||
|
||||
// Re-casing is the only accepted difference. toUpperCase() is full Unicode
|
||||
// case mapping, so these uppercase into real codes without being them --
|
||||
// and each spelling is a separate URL, ISR entry and analytics row.
|
||||
expect(regionFromSlug('hn-badınh')).toBeNull(); // dotless i
|
||||
expect(regionFromSlug('hn-ſontay')).toBeNull(); // long s
|
||||
});
|
||||
|
||||
it('every parent reference resolves', () => {
|
||||
|
||||
Reference in new issue
Block a user