mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
fix(nuxt): use URL to encode redirected URLs (#27822)
This commit is contained in:
parent
0d854d9a06
commit
8f1376093d
@ -135,7 +135,8 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const isExternal = options?.external || hasProtocol(toPath, { acceptRelative: true })
|
||||
const isExternalHost = hasProtocol(toPath, { acceptRelative: true })
|
||||
const isExternal = options?.external || isExternalHost
|
||||
if (isExternal) {
|
||||
if (!options?.external) {
|
||||
throw new Error('Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.')
|
||||
@ -166,10 +167,12 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
||||
// TODO: consider deprecating in favour of `app:rendered` and removing
|
||||
await nuxtApp.callHook('app:redirected')
|
||||
const encodedLoc = location.replace(/"/g, '%22')
|
||||
const encodedHeader = encodeURL(location, isExternalHost)
|
||||
|
||||
nuxtApp.ssrContext!._renderResponse = {
|
||||
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
|
||||
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
||||
headers: { location: encodeURI(location) },
|
||||
headers: { location: encodedHeader },
|
||||
}
|
||||
return response
|
||||
}
|
||||
@ -259,3 +262,17 @@ export const setPageLayout = (layout: unknown extends PageMeta['layout'] ? strin
|
||||
export function resolveRouteObject (to: Exclude<RouteLocationRaw, string>) {
|
||||
return withQuery(to.path || '', to.query || {}) + (to.hash || '')
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function encodeURL (location: string, isExternalHost = false) {
|
||||
const url = new URL(location, 'http://localhost')
|
||||
if (!isExternalHost) {
|
||||
return url.pathname + url.search + url.hash
|
||||
}
|
||||
if (location.startsWith('//')) {
|
||||
return url.toString().replace(url.protocol, '')
|
||||
}
|
||||
return url.toString()
|
||||
}
|
||||
|
@ -1011,9 +1011,10 @@ describe('navigate', () => {
|
||||
})
|
||||
|
||||
it('expect to redirect with encoding', async () => {
|
||||
const { status } = await fetch('/redirect-with-encode', { redirect: 'manual' })
|
||||
const { status, headers } = await fetch('/redirect-with-encode', { redirect: 'manual' })
|
||||
|
||||
expect(status).toEqual(302)
|
||||
expect(headers.get('location') || '').toEqual(encodeURI('/cœur') + '?redirected=' + encodeURIComponent('https://google.com'))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -72,7 +72,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output-inline/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"531k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"532k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"76.2k"`)
|
||||
|
@ -5,5 +5,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
await navigateTo('/cœur')
|
||||
await navigateTo('/cœur?redirected=' + encodeURIComponent('https://google.com'))
|
||||
</script>
|
||||
|
@ -6,6 +6,7 @@ import { defineEventHandler } from 'h3'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||
|
||||
import { hasProtocol } from 'ufo'
|
||||
import * as composables from '#app/composables'
|
||||
|
||||
import { clearNuxtData, refreshNuxtData, useAsyncData, useNuxtData } from '#app/composables/asyncData'
|
||||
@ -19,6 +20,7 @@ import { useId } from '#app/composables/id'
|
||||
import { callOnce } from '#app/composables/once'
|
||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||
import { encodeURL, resolveRouteObject } from '#app/composables/router'
|
||||
|
||||
import { asyncDataDefaults, nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||
|
||||
@ -599,6 +601,29 @@ describe('routing utilities: `navigateTo`', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('routing utilities: `resolveRouteObject`', () => {
|
||||
it('resolveRouteObject should correctly resolve a route object', () => {
|
||||
expect(resolveRouteObject({ path: '/test' })).toMatchInlineSnapshot(`"/test"`)
|
||||
expect(resolveRouteObject({ path: '/test', hash: '#thing', query: { foo: 'bar' } })).toMatchInlineSnapshot(`"/test?foo=bar#thing"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('routing utilities: `encodeURL`', () => {
|
||||
const encode = (url: string) => {
|
||||
const isExternal = hasProtocol(url, { acceptRelative: true })
|
||||
return encodeURL(url, isExternal)
|
||||
}
|
||||
it('encodeURL should correctly encode a URL', () => {
|
||||
expect(encode('https://test.com')).toMatchInlineSnapshot(`"https://test.com/"`)
|
||||
expect(encode('//test.com')).toMatchInlineSnapshot(`"//test.com/"`)
|
||||
expect(encode('mailto:daniel@cœur.com')).toMatchInlineSnapshot(`"mailto:daniel@c%C5%93ur.com"`)
|
||||
const encoded = encode('/cœur?redirected=' + encodeURIComponent('https://google.com'))
|
||||
expect(new URL('/cœur', 'http://localhost').pathname).toMatchInlineSnapshot(`"/c%C5%93ur"`)
|
||||
expect(encoded).toMatchInlineSnapshot(`"/c%C5%93ur?redirected=https%3A%2F%2Fgoogle.com"`)
|
||||
expect(useRouter().resolve(encoded).query.redirected).toMatchInlineSnapshot(`"https://google.com"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('routing utilities: `useRoute`', () => {
|
||||
it('should show provide a mock route', () => {
|
||||
expect(useRoute()).toMatchObject({
|
||||
|
Loading…
Reference in New Issue
Block a user