diff --git a/.changeset/quick-cloths-repair.md b/.changeset/quick-cloths-repair.md new file mode 100644 index 00000000000..fbb3fb42d15 --- /dev/null +++ b/.changeset/quick-cloths-repair.md @@ -0,0 +1,6 @@ +--- +'@tanstack/start-server-core': patch +'@tanstack/router-core': patch +--- + +chore: bump to h3 v2-rc.20 diff --git a/e2e/react-start/basic-test-suite/src/special-characters.spec.ts b/e2e/react-start/basic-test-suite/src/special-characters.spec.ts index 8e05ec5e0a2..e88280e2e1f 100644 --- a/e2e/react-start/basic-test-suite/src/special-characters.spec.ts +++ b/e2e/react-start/basic-test-suite/src/special-characters.spec.ts @@ -187,6 +187,12 @@ test.describe('Unicode route rendering', () => { }) test.describe('malformed paths', () => { + const malformedPathnames = [ + '/specialChars/malformed/%E0%A4', + '/specialChars/malformed/%80', + '/specialChars/malformed/%FF', + ] + test.use({ whitelistErrors: [ 'Failed to load resource: the server responded with a status of 404', @@ -194,24 +200,22 @@ test.describe('Unicode route rendering', () => { ], }) - test('un-matched malformed paths should return not found on direct navigation', async ({ - page, - }) => { - const res = await page.goto('/specialChars/malformed/%E0%A4') - - await page.waitForLoadState(`load`) - - // in spa mode this is caught and handled at server level - if (!isSpaMode) { - expect(res!.status()).toBe(404) - - await expect( - page.getByTestId('default-not-found-component'), - ).toBeInViewport() - } else { - expect(res!.status()).toBe(400) - } - }) + for (const pathname of malformedPathnames) { + test(`un-matched malformed path "${pathname}" should return bad request on direct navigation`, async ({ + page, + }) => { + const res = await page.goto(pathname) + + await page.waitForLoadState(`load`) + + // in spa mode this is caught and handled at server level + if (!isSpaMode) { + expect(res!.status()).toBe(400) + } else { + expect(res!.status()).toBe(400) + } + }) + } test('malformed path params should return not found on router link', async ({ page, diff --git a/e2e/solid-start/basic-test-suite/src/special-characters.spec.ts b/e2e/solid-start/basic-test-suite/src/special-characters.spec.ts index 6bbb240a842..284097d72b1 100644 --- a/e2e/solid-start/basic-test-suite/src/special-characters.spec.ts +++ b/e2e/solid-start/basic-test-suite/src/special-characters.spec.ts @@ -175,6 +175,12 @@ test.describe('Unicode route rendering', () => { }) test.describe('malformed paths', () => { + const malformedPathnames = [ + '/specialChars/malformed/%E0%A4', + '/specialChars/malformed/%80', + '/specialChars/malformed/%FF', + ] + test.use({ whitelistErrors: [ 'Failed to load resource: the server responded with a status of 404', @@ -182,24 +188,22 @@ test.describe('Unicode route rendering', () => { ], }) - test('un-matched malformed paths should return not found on direct navigation', async ({ - page, - }) => { - const res = await page.goto('/specialChars/malformed/%E0%A4') - - await page.waitForLoadState(`load`) - - // in spa mode this is caught and handled at server level - if (!isSpaMode) { - expect(res!.status()).toBe(404) - - await expect( - page.getByTestId('default-not-found-component'), - ).toBeInViewport() - } else { - expect(res!.status()).toBe(400) - } - }) + for (const pathname of malformedPathnames) { + test(`un-matched malformed path "${pathname}" should return bad request on direct navigation`, async ({ + page, + }) => { + const res = await page.goto(pathname) + + await page.waitForLoadState(`load`) + + // in spa mode this is caught and handled at server level + if (!isSpaMode) { + expect(res!.status()).toBe(400) + } else { + expect(res!.status()).toBe(400) + } + }) + } test('malformed path params should return not found on router link', async ({ page, diff --git a/e2e/vue-start/basic-test-suite/src/special-characters.spec.ts b/e2e/vue-start/basic-test-suite/src/special-characters.spec.ts index 6bbb240a842..284097d72b1 100644 --- a/e2e/vue-start/basic-test-suite/src/special-characters.spec.ts +++ b/e2e/vue-start/basic-test-suite/src/special-characters.spec.ts @@ -175,6 +175,12 @@ test.describe('Unicode route rendering', () => { }) test.describe('malformed paths', () => { + const malformedPathnames = [ + '/specialChars/malformed/%E0%A4', + '/specialChars/malformed/%80', + '/specialChars/malformed/%FF', + ] + test.use({ whitelistErrors: [ 'Failed to load resource: the server responded with a status of 404', @@ -182,24 +188,22 @@ test.describe('Unicode route rendering', () => { ], }) - test('un-matched malformed paths should return not found on direct navigation', async ({ - page, - }) => { - const res = await page.goto('/specialChars/malformed/%E0%A4') - - await page.waitForLoadState(`load`) - - // in spa mode this is caught and handled at server level - if (!isSpaMode) { - expect(res!.status()).toBe(404) - - await expect( - page.getByTestId('default-not-found-component'), - ).toBeInViewport() - } else { - expect(res!.status()).toBe(400) - } - }) + for (const pathname of malformedPathnames) { + test(`un-matched malformed path "${pathname}" should return bad request on direct navigation`, async ({ + page, + }) => { + const res = await page.goto(pathname) + + await page.waitForLoadState(`load`) + + // in spa mode this is caught and handled at server level + if (!isSpaMode) { + expect(res!.status()).toBe(400) + } else { + expect(res!.status()).toBe(400) + } + }) + } test('malformed path params should return not found on router link', async ({ page, diff --git a/packages/router-core/package.json b/packages/router-core/package.json index 486d7b93400..d5e9fea3833 100644 --- a/packages/router-core/package.json +++ b/packages/router-core/package.json @@ -183,7 +183,7 @@ }, "dependencies": { "@tanstack/history": "workspace:*", - "cookie-es": "^2.0.0", + "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json index 95bc18448a9..6c22e25e9dd 100644 --- a/packages/start-server-core/package.json +++ b/packages/start-server-core/package.json @@ -82,13 +82,13 @@ "@tanstack/router-core": "workspace:*", "@tanstack/start-client-core": "workspace:*", "@tanstack/start-storage-context": "workspace:*", - "h3-v2": "npm:h3@2.0.1-rc.16", + "h3-v2": "npm:h3@2.0.1-rc.20", "seroval": "^1.5.0" }, "devDependencies": { "@standard-schema/spec": "^1.0.0", "@tanstack/intent": "^0.0.14", - "cookie-es": "^2.0.0", + "cookie-es": "^3.0.0", "fetchdts": "^0.1.6", "vite": "*", "@types/node": ">=20" diff --git a/packages/start-server-core/src/request-response.ts b/packages/start-server-core/src/request-response.ts index f8de03aa566..161c24dee65 100644 --- a/packages/start-server-core/src/request-response.ts +++ b/packages/start-server-core/src/request-response.ts @@ -122,7 +122,18 @@ export function requestHandler( handler: RequestHandler, ) { return (request: Request, requestOpts: any): Promise | Response => { - const h3Event = new H3Event(request) + let h3Event: H3Event + try { + h3Event = new H3Event(request) + } catch (error) { + if (error instanceof URIError) { + return new Response(null, { + status: 400, + statusText: 'Bad Request', + }) + } + throw error + } const response = eventStorage.run({ h3Event }, () => handler(request, requestOpts), @@ -284,7 +295,16 @@ export function setResponseStatus(code?: number, text?: string): void { */ export function getCookies(): Record { const event = getH3Event() - return h3_parseCookies(event) + const cookies = h3_parseCookies(event) + const definedCookies: Record = Object.create(null) + + for (const [name, value] of Object.entries(cookies)) { + if (value !== undefined) { + definedCookies[name] = value + } + } + + return definedCookies } /** @@ -296,7 +316,7 @@ export function getCookies(): Record { * ``` */ export function getCookie(name: string): string | undefined { - return getCookies()[name] || undefined + return getCookies()[name] } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb407916001..46f916fc72c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12356,8 +12356,8 @@ importers: specifier: workspace:* version: link:../history cookie-es: - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^3.0.0 + version: 3.1.1 seroval: specifier: ^1.5.0 version: 1.5.0 @@ -12950,8 +12950,8 @@ importers: specifier: workspace:* version: link:../start-storage-context h3-v2: - specifier: npm:h3@2.0.1-rc.16 - version: h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.15)) + specifier: npm:h3@2.0.1-rc.20 + version: h3@2.0.1-rc.20(crossws@0.4.4(srvx@0.11.15)) seroval: specifier: ^1.5.0 version: 1.5.0 @@ -12966,8 +12966,8 @@ importers: specifier: 25.0.9 version: 25.0.9 cookie-es: - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^3.0.0 + version: 3.1.1 fetchdts: specifier: ^0.1.6 version: 0.1.7 @@ -20447,6 +20447,9 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -34385,6 +34388,8 @@ snapshots: cookie-es@2.0.0: {} + cookie-es@3.1.1: {} + cookie-signature@1.0.6: {} cookie-signature@1.2.2: {} @@ -34911,9 +34916,9 @@ snapshots: env-runner@0.1.6(miniflare@4.20260317.0): dependencies: - crossws: 0.4.4(srvx@0.11.12) + crossws: 0.4.4(srvx@0.11.15) httpxy: 0.3.1 - srvx: 0.11.12 + srvx: 0.11.15 optionalDependencies: miniflare: 4.20260317.0 @@ -36070,24 +36075,17 @@ snapshots: h3@2.0.1-rc.14(crossws@0.4.3(srvx@0.10.1)): dependencies: rou3: 0.7.12 - srvx: 0.11.12 + srvx: 0.11.15 optionalDependencies: crossws: 0.4.3(srvx@0.10.1) h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.12)): dependencies: rou3: 0.8.1 - srvx: 0.11.12 + srvx: 0.11.15 optionalDependencies: crossws: 0.4.4(srvx@0.11.12) - h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.15)): - dependencies: - rou3: 0.8.1 - srvx: 0.11.12 - optionalDependencies: - crossws: 0.4.4(srvx@0.11.15) - h3@2.0.1-rc.20(crossws@0.4.4(srvx@0.11.15)): dependencies: rou3: 0.8.1