mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
fix(nuxt): use URL to encode redirected URLs (#27822)
This commit is contained in:
parent
07e1818f30
commit
c628975efc
@ -135,7 +135,8 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
|||||||
return Promise.resolve()
|
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 (isExternal) {
|
||||||
if (!options?.external) {
|
if (!options?.external) {
|
||||||
throw new Error('Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.')
|
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
|
// TODO: consider deprecating in favour of `app:rendered` and removing
|
||||||
await nuxtApp.callHook('app:redirected')
|
await nuxtApp.callHook('app:redirected')
|
||||||
const encodedLoc = location.replace(/"/g, '%22')
|
const encodedLoc = location.replace(/"/g, '%22')
|
||||||
|
const encodedHeader = encodeURL(location, isExternalHost)
|
||||||
|
|
||||||
nuxtApp.ssrContext!._renderResponse = {
|
nuxtApp.ssrContext!._renderResponse = {
|
||||||
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
|
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
|
||||||
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
||||||
headers: { location: encodeURI(location) },
|
headers: { location: encodedHeader },
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -259,3 +262,17 @@ export const setPageLayout = (layout: unknown extends PageMeta['layout'] ? strin
|
|||||||
export function resolveRouteObject (to: Exclude<RouteLocationRaw, string>) {
|
export function resolveRouteObject (to: Exclude<RouteLocationRaw, string>) {
|
||||||
return withQuery(to.path || '', to.query || {}) + (to.hash || '')
|
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()
|
||||||
|
}
|
||||||
|
@ -1005,9 +1005,10 @@ describe('navigate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('expect to redirect with encoding', async () => {
|
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(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 serverDir = join(rootDir, '.output-inline/server')
|
||||||
|
|
||||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
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)
|
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"76.2k"`)
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"76.2k"`)
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
await navigateTo('/cœur')
|
await navigateTo('/cœur?redirected=' + encodeURIComponent('https://google.com'))
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,6 +6,7 @@ import { defineEventHandler } from 'h3'
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
|
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
|
||||||
|
|
||||||
|
import { hasProtocol } from 'ufo'
|
||||||
import * as composables from '#app/composables'
|
import * as composables from '#app/composables'
|
||||||
|
|
||||||
import { clearNuxtData, refreshNuxtData, useAsyncData, useNuxtData } from '#app/composables/asyncData'
|
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 { callOnce } from '#app/composables/once'
|
||||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||||
|
import { encodeURL, resolveRouteObject } from '#app/composables/router'
|
||||||
|
|
||||||
registerEndpoint('/api/test', defineEventHandler(event => ({
|
registerEndpoint('/api/test', defineEventHandler(event => ({
|
||||||
method: event.method,
|
method: event.method,
|
||||||
@ -596,6 +598,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`', () => {
|
describe('routing utilities: `useRoute`', () => {
|
||||||
it('should show provide a mock route', () => {
|
it('should show provide a mock route', () => {
|
||||||
expect(useRoute()).toMatchObject({
|
expect(useRoute()).toMatchObject({
|
||||||
|
Loading…
Reference in New Issue
Block a user