docs: stop double-encoding redirectTo in the Task 7 plan snippet
The previous fix used encodeURI to keep a non-ASCII redirect target from throwing inside new Response. But encodeURI also escapes "%", so an already-percent-encoded target is corrupted: /login?next=%2Fdash becomes /login?next=%252Fdash, which single-decodes to the literal "%2Fdash" rather than the intended path. That is the most common real use of redirectTo - "send them to login, then bounce back". Replaced with headerSafePath, a codepoint loop that encodes only what cannot be sent in a Latin-1 header and leaves existing escapes and reserved ASCII untouched. My prescription, my defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2155,6 +2155,20 @@ function wantsJson(ctx: Context): boolean {
|
||||
return accept.includes("application/json") && !accept.includes("text/html");
|
||||
}
|
||||
|
||||
/**
|
||||
* Header values must be Latin-1, so a localized path would otherwise throw
|
||||
* inside `new Response` and 500 on a denial path. Encode ONLY the codepoints
|
||||
* that cannot be sent: encodeURI would also escape "%", corrupting a target
|
||||
* that already carries a percent-encoded return path.
|
||||
*/
|
||||
function headerSafePath(value: string): string {
|
||||
let out = "";
|
||||
for (const character of value) {
|
||||
out += character.codePointAt(0)! <= 0x7f ? character : encodeURIComponent(character);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Reject anything that could navigate off-site or inject a header. */
|
||||
function isLocalPath(value: string): boolean {
|
||||
if (!value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return false;
|
||||
@@ -2196,9 +2210,7 @@ export function guardPermission(permission: string, options: GuardOptions = {}):
|
||||
return new Response(null, {
|
||||
status: 303,
|
||||
headers: {
|
||||
// Header values must be Latin-1; a localized path like /accounts
|
||||
// in non-ASCII would otherwise throw and 500 on a denial path.
|
||||
location: encodeURI(options.redirectTo),
|
||||
location: headerSafePath(options.redirectTo),
|
||||
"cache-control": "private, no-store",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user