diff --git a/.changeset/honest-cobras-jog.md b/.changeset/honest-cobras-jog.md new file mode 100644 index 000000000000..ded1468ba233 --- /dev/null +++ b/.changeset/honest-cobras-jog.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: `fetch` not working when URL is same host but different than `paths.base` diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 3f8551ca2e12..ee35cd5d36a9 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -55,7 +55,12 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade request.headers.delete('origin'); } - if (url.origin !== event.url.origin) { + const decoded = decodeURIComponent(url.pathname); + + if ( + url.origin !== event.url.origin || + (paths.base && decoded !== paths.base && !decoded.startsWith(`${paths.base}/`)) + ) { // Allow cookie passthrough for "credentials: same-origin" and "credentials: include" // if SvelteKit is serving my.domain.com: // - domain.com WILL NOT receive cookies @@ -77,7 +82,6 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade // handle fetch requests for static assets. e.g. prebaked data, etc. // we need to support everything the browser's fetch supports const prefix = paths.assets || paths.base; - const decoded = decodeURIComponent(url.pathname); const filename = ( decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded ).slice(1); diff --git a/packages/kit/test/apps/dev-only/src/routes/request-abort/+page.svelte b/packages/kit/test/apps/dev-only/src/routes/request-abort/+page.svelte index ff81c82bb0bc..0db67a4d9a97 100644 --- a/packages/kit/test/apps/dev-only/src/routes/request-abort/+page.svelte +++ b/packages/kit/test/apps/dev-only/src/routes/request-abort/+page.svelte @@ -11,7 +11,7 @@ fetch('/request-abort', { headers: { accept: 'application/json' } }).then( async (r) => (result = await r.json()) ); - }, 100); + }, 50); } onMount(test_abort); diff --git a/packages/kit/test/apps/options/source/pages/+server.js b/packages/kit/test/apps/options/source/pages/+server.js new file mode 100644 index 000000000000..1b5032b55dc9 --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/+server.js @@ -0,0 +1,3 @@ +export function GET() { + return new Response('root'); +} diff --git a/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.server.js b/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.server.js new file mode 100644 index 000000000000..185b5a22270f --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.server.js @@ -0,0 +1,7 @@ +export async function load({ fetch }) { + const response = await fetch('/not-base-path/'); + return { + fetchUrl: response.url, + fetchResponse: await response.text() + }; +} diff --git a/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.svelte b/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.svelte new file mode 100644 index 000000000000..bf863e1be44d --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/fetch/link-outside-base/+page.svelte @@ -0,0 +1,7 @@ + + +

{data.fetchUrl}

+

{data.fetchResponse}

diff --git a/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.server.js b/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.server.js new file mode 100644 index 000000000000..1f9618585042 --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.server.js @@ -0,0 +1,20 @@ +export async function load({ fetch }) { + // fetch to root with trailing slash + const response1 = await fetch('/', { redirect: 'manual' }); + // fetch to root without trailing slash + const response2 = await fetch('', { redirect: 'manual' }); + return { + fetches: [ + { + url: response1.url, + response: await response1.text(), + redirect: response1.headers.get('location') + }, + { + url: response2.url, + response: await response2.text(), + redirect: response2.headers.get('location') + } + ] + }; +} diff --git a/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.svelte b/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.svelte new file mode 100644 index 000000000000..a2b6ba281cfc --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/fetch/link-root/+page.svelte @@ -0,0 +1,29 @@ + + +

Fetch URLs

+ +
+ {#each data.fetches as item, index} +
fetch{index + 1}-url
+
{item.url}
+ {/each} +
+ +

Fetch Responses

+
+ {#each data.fetches as item, index} +
fetch{index + 1}-response
+
{item.response}
+ {/each} +
+ +

Fetch Redirects

+
+ {#each data.fetches as item, index} +
fetch{index + 1}-redirect
+
{item.redirect}
+ {/each} +
diff --git a/packages/kit/test/apps/options/source/pages/fetch/link-root/+server.js b/packages/kit/test/apps/options/source/pages/fetch/link-root/+server.js new file mode 100644 index 000000000000..101d92e74b5a --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/fetch/link-root/+server.js @@ -0,0 +1,3 @@ +export function GET() { + return new Response('relative'); +} diff --git a/packages/kit/test/apps/options/test/paths-assets.test.js b/packages/kit/test/apps/options/test/paths-assets.test.js index dafc6ca9a39f..8292404084be 100644 --- a/packages/kit/test/apps/options/test/paths-assets.test.js +++ b/packages/kit/test/apps/options/test/paths-assets.test.js @@ -77,6 +77,34 @@ test.describe('base path', () => { expect(page.url()).toBe(`${baseURL}/path-base/resolve-route/resolved/`); expect(await page.textContent('h2')).toBe('resolved'); }); + + test('server load fetch without base path does not invoke the server', async ({ + page, + baseURL + }) => { + await page.goto('/path-base/fetch/link-outside-base/'); + await expect(page.locator('[data-testid="fetch-url"]')).toHaveText(`${baseURL}/not-base-path/`); + await expect(page.locator('[data-testid="fetch-response"]')).toContainText( + 'did you mean to visit' + ); + }); + + test('server load fetch to root does not invoke the server', async ({ page, baseURL }) => { + await page.goto('/path-base/fetch/link-root/'); + // fetch to root with trailing slash + await expect(page.locator('[data-testid="fetch1-url"]')).toHaveText(`${baseURL}/`); + if (process.env.DEV) { + await expect(page.locator('[data-testid="fetch1-redirect"]')).toHaveText('/path-base'); + } else { + await expect(page.locator('[data-testid="fetch1-response"]')).toContainText( + 'did you mean to visit' + ); + } + + // fetch to root without trailing slash should be relative + await expect(page.locator('[data-testid="fetch2-url"]')).toBeEmpty(); + await expect(page.locator('[data-testid="fetch2-response"]')).toHaveText('relative'); + }); }); test.describe('assets path', () => {