Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,32 @@ Object.defineProperty(Response.prototype, Symbol.for('nodejs.util.inspect.custom
Object.setPrototypeOf(Response, GlobalResponse)
Object.setPrototypeOf(Response.prototype, GlobalResponse.prototype)

// Override Response.json() to return a LightweightResponse so the listener
// fast-path (cacheKey check) is hit instead of falling through to ReadableStream reading.
// Fast path regex: matches http:// or https:// followed by RFC 3986 allowed chars.
// Character class covers unreserved + reserved chars plus `%` for percent-encoding.
// ! #-; = ?-[ ] _ a-z ~ A-Z (A-Z is within ?-[ range but listed for clarity)
const validRedirectUrl = /^https?:\/\/[!#-;=?-[\]_a-z~A-Z]+$/
const parseRedirectUrl = (url: string | URL): string => {
if (url instanceof URL) {
return url.href
}
if (validRedirectUrl.test(url)) {
return url
}
return new URL(url).href
}

const validRedirectStatuses = new Set([301, 302, 303, 307, 308])

// Override Response.json() and Response.redirect() to return a LightweightResponse
// so the listener fast-path (cacheKey check) is hit instead of falling through to ReadableStream reading.
Object.defineProperty(Response, 'redirect', {
value: function redirect(url: string | URL, status = 302): Response {
if (![301, 302, 303, 307, 308].includes(status)) {
if (!validRedirectStatuses.has(status)) {
throw new RangeError('Invalid status code')
}
return new Response(null, {
status,
headers: { location: typeof url === 'string' ? url : url.href },
headers: { location: parseRedirectUrl(url) },
})
},
writable: true,
Expand All @@ -132,12 +148,15 @@ Object.defineProperty(Response, 'redirect', {
Object.defineProperty(Response, 'json', {
value: function json(data?: unknown, init?: ResponseInit): Response {
const body = JSON.stringify(data)
if (body === undefined) {
throw new TypeError('The data is not JSON serializable')
}
const initHeaders = init?.headers
let headers: Record<string, string> | Headers
if (initHeaders) {
headers = new Headers(initHeaders)
if (!(headers as Headers).has('content-type')) {
;(headers as Headers).set('content-type', 'application/json')
if (!headers.has('content-type')) {
headers.set('content-type', 'application/json')
}
} else {
headers = { 'content-type': 'application/json' }
Expand Down
Loading