Skip to content

Commit ef352ea

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/nitro-v3
# Conflicts: # packages/nitro-server/src/runtime/handlers/island.ts # test/bundle.test.ts
2 parents d23f710 + 10ac237 commit ef352ea

2 files changed

Lines changed: 18 additions & 10 deletions

File tree

packages/nitro-server/src/runtime/handlers/island.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { RenderResponse } from 'nitro/types'
33
import type { Link, SerializableHead } from '@unhead/vue/types'
44
import { destr } from 'destr'
55
import type { H3Event } from 'nitro/h3'
6-
import { defineEventHandler, getQuery, readBody } from 'nitro/h3'
6+
import { defineEventHandler, getQuery, HTTPError, readBody } from 'nitro/h3'
77
import { resolveUnrefHeadInput } from '@unhead/vue'
88
import { getRequestDependencies } from 'vue-bundle-renderer/runtime'
99
import { getQuery as getURLQuery } from 'ufo'
@@ -107,29 +107,37 @@ const handler: ReturnType<typeof defineEventHandler> = defineEventHandler(async
107107

108108
export default handler
109109

110+
const ISLAND_PATH_PREFIX = '/__nuxt_island/'
111+
const VALID_COMPONENT_NAME_RE = /^[a-z][\w.-]*$/i
112+
110113
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
111-
// TODO: Strict validation for url
112114
let url = event.url.pathname + event.url.search + event.url.hash
113115
if (import.meta.prerender && url && await islandPropCache!.hasItem(url)) {
114116
// rehydrate props from cache so we can rerender island if cache does not have it any more
115117
url = await islandPropCache!.getItem(url) as string
116118
}
117-
const componentParts = url.substring('/__nuxt_island'.length + 1).replace(ISLAND_SUFFIX_RE, '').split('_')
119+
120+
if (!url.startsWith(ISLAND_PATH_PREFIX)) {
121+
throw new HTTPError({ status: 400, statusText: 'Invalid island request path' })
122+
}
123+
124+
const componentParts = url.substring(ISLAND_PATH_PREFIX.length).replace(ISLAND_SUFFIX_RE, '').split('_')
118125
const hashId = componentParts.length > 1 ? componentParts.pop() : undefined
119126
const componentName = componentParts.join('_')
120127

121-
// TODO: Validate context
128+
if (!componentName || !VALID_COMPONENT_NAME_RE.test(componentName)) {
129+
throw new HTTPError({ status: 400, statusText: 'Invalid island component name' })
130+
}
131+
122132
const context = event.req.method === 'GET' ? getQuery<NuxtIslandContext>(event) : await readBody<NuxtIslandContext>(event)
123133

124-
const ctx: NuxtIslandContext = {
125-
url: '/',
126-
...context,
134+
// Only extract known context fields to prevent arbitrary data injection
135+
return {
136+
url: typeof context?.url === 'string' ? context.url : '/',
127137
id: hashId,
128138
name: componentName,
129139
props: destr(context?.props) || {},
130140
slots: {},
131141
components: {},
132142
}
133-
134-
return ctx
135143
}

packages/nuxt/src/app/composables/preload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export async function preloadRouteComponents (to: RouteLocationRaw, router: Rout
6868
}
6969
const promise = Promise.resolve((component as () => unknown)())
7070
.catch(() => {})
71-
.finally(() => promises.splice(promises.indexOf(promise)))
71+
.finally(() => promises.splice(promises.indexOf(promise), 1))
7272
promises.push(promise)
7373
}
7474

0 commit comments

Comments
 (0)