Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion edge-runtime/lib/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export const buildResponse = async ({
logger.withFields({ redirect_url: redirect }).debug('Redirect url is same as original url')
return
}
edgeResponse.headers.set('location', redirect)
edgeResponse.headers.set('location', relativizeURL(redirect, request.url))
Copy link
Contributor Author

@pieh pieh Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: if redirect target is external, the location will remain absolute - relativeURL only make it relative if origin/host of redirect and request.url matches

export function relativizeURL(url: string | string, base: string | URL) {
const baseURL = typeof base === 'string' ? new URL(base) : base
const relative = new URL(url, base)
const origin = `${baseURL.protocol}//${baseURL.host}`
return `${relative.protocol}//${relative.host}` === origin
? relative.toString().replace(origin, '')
: relative.toString()
}

Added some unit tests for this in dbde615

}

// Data requests shouldn't automatically redirect in the browser (they might be HTML pages): they're handled by the router
Expand Down
25 changes: 24 additions & 1 deletion edge-runtime/lib/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertEquals } from 'https://deno.land/std@0.175.0/testing/asserts.ts'
import { rewriteDataPath } from './util.ts'
import { relativizeURL, rewriteDataPath } from './util.ts'

Deno.test('rewriteDataPath', async (t) => {
await t.step('should rewrite a data url', async () => {
Expand Down Expand Up @@ -37,3 +37,26 @@ Deno.test('rewriteDataPath', async (t) => {
assertEquals(result, '/_next/data/build-id/target.json')
})
})

Deno.test('relativizeURL', async (t) => {
await t.step('should relativize a URL when origin matches', async () => {
const url = 'https://example.com/pathname'
const base = 'https://example.com/'
const result = relativizeURL(url, base)
assertEquals(result, '/pathname')
})

await t.step('should NOT relativize a URL when origin does not match', async () => {
const url = 'https://example.com/pathname'
const base = 'https://not-example.com/'
const result = relativizeURL(url, base)
assertEquals(result, 'https://example.com/pathname')
})

await t.step('accepts relative URL strings and produce relative URL as output', async () => {
const url = '/pathname'
const base = 'https://example.com/'
const result = relativizeURL(url, base)
assertEquals(result, '/pathname')
})
})
8 changes: 4 additions & 4 deletions tests/integration/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ for (const {
expect(response.status).toBe(307)
expect(response.headers.get('location'), 'added a location header').toBeTypeOf('string')
expect(
new URL(response.headers.get('location') as string).pathname,
new URL(response.headers.get('location') as string, 'http://n').pathname,
Copy link
Contributor Author

@pieh pieh Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added base here (and few other tests) because relative location without it just fails to construct URL object and throws Invalid URL. We do assert on pathname URL portion anyway so base will be discarded on actual assertion

'redirected to the correct path',
).toEqual('/other')
expect(response.headers.get('x-runtime')).toEqual(expectedRuntime)
Expand All @@ -154,7 +154,7 @@ for (const {
expect(response.status).toBe(307)
expect(response.headers.get('location'), 'added a location header').toBeTypeOf('string')
expect(
new URL(response.headers.get('location') as string).pathname,
new URL(response.headers.get('location') as string, 'http://n').pathname,
'redirected to the correct path',
).toEqual('/other')
expect(response.headers.get('x-header-from-redirect'), 'hello').toBe('hello')
Expand Down Expand Up @@ -357,7 +357,7 @@ for (const {
expect(response.status).toBe(307)
expect(response.headers.get('location'), 'added a location header').toBeTypeOf('string')
expect(
new URL(response.headers.get('location') as string).pathname,
new URL(response.headers.get('location') as string, 'http://n').pathname,
'redirected to the correct path',
).toEqual('/other')
expect(response.headers.get('x-header-from-redirect'), 'hello').toBe('hello')
Expand All @@ -382,7 +382,7 @@ for (const {
expect(response.status).toBe(307)
expect(response.headers.get('location'), 'added a location header').toBeTypeOf('string')
expect(
new URL(response.headers.get('location') as string).pathname,
new URL(response.headers.get('location') as string, 'http://n').pathname,
'redirected to the correct path',
).toEqual('/other')
expect(response.headers.get('x-header-from-redirect'), 'hello').toBe('hello')
Expand Down
Loading