From 7dd7d4c6495121d77ec51ab3cf713c9a7cce64ba Mon Sep 17 00:00:00 2001 From: hexin17 Date: Wed, 5 Jul 2023 19:00:37 +0800 Subject: [PATCH] add test for base fs-serve --- .../__tests__/base/fs-serve-base.spec.ts | 104 ++++++++++++++++++ playground/fs-serve/__tests__/base/serve.ts | 59 ++++++++++ .../fs-serve/__tests__/fs-serve.spec.ts | 11 +- playground/fs-serve/package.json | 5 +- playground/fs-serve/root/src/index.html | 34 +++--- playground/fs-serve/root/vite.config-base.js | 36 ++++++ 6 files changed, 228 insertions(+), 21 deletions(-) create mode 100644 playground/fs-serve/__tests__/base/fs-serve-base.spec.ts create mode 100644 playground/fs-serve/__tests__/base/serve.ts create mode 100644 playground/fs-serve/root/vite.config-base.js diff --git a/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts b/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts new file mode 100644 index 00000000000000..4660fafcc8031f --- /dev/null +++ b/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts @@ -0,0 +1,104 @@ +import fetch from 'node-fetch' +import { beforeAll, describe, expect, test } from 'vitest' +import testJSON from '../../safe.json' +import { isServe, page, viteTestUrl } from '~utils' + +const stringified = JSON.stringify(testJSON) + +describe.runIf(isServe)('main', () => { + beforeAll(async () => { + const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/' + await page.goto(viteTestUrl + srcPrefix + 'src/') + }) + + test('default import', async () => { + expect(await page.textContent('.full')).toBe(stringified) + }) + + test('named import', async () => { + expect(await page.textContent('.named')).toBe(testJSON.msg) + }) + + test('safe fetch', async () => { + expect(await page.textContent('.safe-fetch')).toMatch('KEY=safe') + expect(await page.textContent('.safe-fetch-status')).toBe('200') + }) + + test('safe fetch with query', async () => { + expect(await page.textContent('.safe-fetch-query')).toMatch('KEY=safe') + expect(await page.textContent('.safe-fetch-query-status')).toBe('200') + }) + + test('safe fetch with special characters', async () => { + expect( + await page.textContent('.safe-fetch-subdir-special-characters'), + ).toMatch('KEY=safe') + expect( + await page.textContent('.safe-fetch-subdir-special-characters-status'), + ).toBe('200') + }) + + test('unsafe fetch', async () => { + expect(await page.textContent('.unsafe-fetch')).toMatch('403 Restricted') + expect(await page.textContent('.unsafe-fetch-status')).toBe('403') + }) + + test('unsafe fetch with special characters (#8498)', async () => { + expect(await page.textContent('.unsafe-fetch-8498')).toBe('') + expect(await page.textContent('.unsafe-fetch-8498-status')).toBe('404') + }) + + test('unsafe fetch with special characters 2 (#8498)', async () => { + expect(await page.textContent('.unsafe-fetch-8498-2')).toBe('') + expect(await page.textContent('.unsafe-fetch-8498-2-status')).toBe('404') + }) + + test('safe fs fetch', async () => { + expect(await page.textContent('.safe-fs-fetch')).toBe(stringified) + expect(await page.textContent('.safe-fs-fetch-status')).toBe('200') + }) + + test('safe fs fetch', async () => { + expect(await page.textContent('.safe-fs-fetch-query')).toBe(stringified) + expect(await page.textContent('.safe-fs-fetch-query-status')).toBe('200') + }) + + test('safe fs fetch with special characters', async () => { + expect(await page.textContent('.safe-fs-fetch-special-characters')).toBe( + stringified, + ) + expect( + await page.textContent('.safe-fs-fetch-special-characters-status'), + ).toBe('200') + }) + + test('unsafe fs fetch', async () => { + expect(await page.textContent('.unsafe-fs-fetch')).toBe('') + expect(await page.textContent('.unsafe-fs-fetch-status')).toBe('403') + }) + + test('unsafe fs fetch with special characters (#8498)', async () => { + expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('') + expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404') + }) + + test('unsafe fs fetch with special characters 2 (#8498)', async () => { + expect(await page.textContent('.unsafe-fs-fetch-8498-2')).toBe('') + expect(await page.textContent('.unsafe-fs-fetch-8498-2-status')).toBe('404') + }) + + test('nested entry', async () => { + expect(await page.textContent('.nested-entry')).toBe('foobar') + }) + + test('denied', async () => { + expect(await page.textContent('.unsafe-dotenv')).toBe('404') + }) +}) + +describe('fetch', () => { + test('serve with configured headers', async () => { + const res = await fetch(viteTestUrl + '/src/') + expect(res.headers.get('x-served-by')).toBe('vite') + }) +}) diff --git a/playground/fs-serve/__tests__/base/serve.ts b/playground/fs-serve/__tests__/base/serve.ts new file mode 100644 index 00000000000000..0dda3c7894f9ee --- /dev/null +++ b/playground/fs-serve/__tests__/base/serve.ts @@ -0,0 +1,59 @@ +import path from 'node:path' +import { mergeConfig } from 'vite' +import config from '../../root/vite.config-base' +import { isBuild, page, rootDir, setViteUrl, viteTestUrl } from '~utils' + +export async function serve(): Promise<{ close(): Promise }> { + const { createServer } = await import('vite') + process.env.VITE_INLINE = 'inline-serve' + + const options = { + ...config, + root: rootDir, + logLevel: 'silent', + build: { + target: 'esnext', + }, + server: { + watch: { + usePolling: true, + interval: 100, + }, + host: true, + fs: { + strict: !isBuild, + }, + }, + } + + const rewriteTestRootOptions = { + build: { + rollupOptions: { + input: { + main: path.resolve(rootDir, 'src/index.html'), + }, + }, + }, + server: { + fs: { + strict: true, + allow: [path.resolve(rootDir, 'src')], + }, + }, + define: { + ROOT: JSON.stringify(path.dirname(rootDir).replace(/\\/g, '/')), + }, + } + + const viteServer = await ( + await createServer(mergeConfig(options, rewriteTestRootOptions)) + ).listen() + + // use resolved port/base from server + const devBase = viteServer.config.base === '/' ? '' : viteServer.config.base + + setViteUrl(`http://localhost:${viteServer.config.server.port}${devBase}`) + await page.goto(viteTestUrl) + + return viteServer +} diff --git a/playground/fs-serve/__tests__/fs-serve.spec.ts b/playground/fs-serve/__tests__/fs-serve.spec.ts index 8fab74d1bed7e3..86e030326ea420 100644 --- a/playground/fs-serve/__tests__/fs-serve.spec.ts +++ b/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -7,7 +7,8 @@ const stringified = JSON.stringify(testJSON) describe.runIf(isServe)('main', () => { beforeAll(async () => { - await page.goto(viteTestUrl + '/src/') + const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/' + await page.goto(viteTestUrl + srcPrefix + 'src/') }) test('default import', async () => { @@ -66,7 +67,9 @@ describe.runIf(isServe)('main', () => { expect(await page.textContent('.safe-fs-fetch-special-characters')).toBe( stringified, ) - expect(await page.textContent('.safe-fs-fetch-status')).toBe('200') + expect( + await page.textContent('.safe-fs-fetch-special-characters-status'), + ).toBe('200') }) test('unsafe fs fetch', async () => { @@ -88,10 +91,6 @@ describe.runIf(isServe)('main', () => { expect(await page.textContent('.nested-entry')).toBe('foobar') }) - test('nested entry', async () => { - expect(await page.textContent('.nested-entry')).toBe('foobar') - }) - test('denied', async () => { expect(await page.textContent('.unsafe-dotenv')).toBe('404') }) diff --git a/playground/fs-serve/package.json b/playground/fs-serve/package.json index ceb4552d1c244f..b66b79268d8601 100644 --- a/playground/fs-serve/package.json +++ b/playground/fs-serve/package.json @@ -7,6 +7,9 @@ "dev": "vite root", "build": "vite build root", "debug": "node --inspect-brk ../../packages/vite/bin/vite", - "preview": "vite preview root" + "preview": "vite preview root", + "dev:base": "vite root --config ./root/vite.config-base.js", + "build:base": "vite build root --config ./root/vite.config-base.js", + "preview:base": "vite preview root --config ./root/vite.config-base.js" } } diff --git a/playground/fs-serve/root/src/index.html b/playground/fs-serve/root/src/index.html index 9e9cfe501d10fe..5e8d0431c8da3b 100644 --- a/playground/fs-serve/root/src/index.html +++ b/playground/fs-serve/root/src/index.html @@ -53,8 +53,10 @@

Denied

text('.full', JSON.stringify(json)) text('.named', msg) + const base = typeof BASE !== 'undefined' ? BASE : '' + // inside allowed dir, safe fetch - fetch('/src/safe.txt') + fetch(base + '/src/safe.txt') .then((r) => { text('.safe-fetch-status', r.status) return r.text() @@ -64,7 +66,7 @@

Denied

}) // inside allowed dir with query, safe fetch - fetch('/src/safe.txt?query') + fetch(base + '/src/safe.txt?query') .then((r) => { text('.safe-fetch-query-status', r.status) return r.text() @@ -74,7 +76,7 @@

Denied

}) // inside allowed dir, safe fetch - fetch('/src/subdir/safe.txt') + fetch(base + '/src/subdir/safe.txt') .then((r) => { text('.safe-fetch-subdir-status', r.status) return r.text() @@ -84,7 +86,7 @@

Denied

}) // inside allowed dir, with special characters, safe fetch - fetch('/src/special%20characters%20%C3%A5%C3%A4%C3%B6/safe.txt') + fetch(base + '/src/special%20characters%20%C3%A5%C3%A4%C3%B6/safe.txt') .then((r) => { text('.safe-fetch-subdir-special-characters-status', r.status) return r.text() @@ -94,7 +96,7 @@

Denied

}) // outside of allowed dir, treated as unsafe - fetch('/unsafe.txt') + fetch(base + '/unsafe.txt') .then((r) => { text('.unsafe-fetch-status', r.status) return r.text() @@ -107,7 +109,7 @@

Denied

}) // outside of allowed dir with special characters #8498 - fetch('/src/%2e%2e%2funsafe%2etxt') + fetch(base + '/src/%2e%2e%2funsafe%2etxt') .then((r) => { text('.unsafe-fetch-8498-status', r.status) return r.text() @@ -120,7 +122,7 @@

Denied

}) // outside of allowed dir with special characters 2 #8498 - fetch('/src/%252e%252e%252funsafe%252etxt') + fetch(base + '/src/%252e%252e%252funsafe%252etxt') .then((r) => { text('.unsafe-fetch-8498-2-status', r.status) return r.text() @@ -133,7 +135,7 @@

Denied

}) // imported before, should be treated as safe - fetch('/@fs/' + ROOT + '/safe.json') + fetch(base + '/@fs/' + ROOT + '/safe.json') .then((r) => { text('.safe-fs-fetch-status', r.status) return r.json() @@ -143,7 +145,7 @@

Denied

}) // imported before with query, should be treated as safe - fetch('/@fs/' + ROOT + '/safe.json?query') + fetch(base + '/@fs/' + ROOT + '/safe.json?query') .then((r) => { text('.safe-fs-fetch-query-status', r.status) return r.json() @@ -153,7 +155,7 @@

Denied

}) // not imported before, outside of root, treated as unsafe - fetch('/@fs/' + ROOT + '/unsafe.json') + fetch(base + '/@fs/' + ROOT + '/unsafe.json') .then((r) => { text('.unsafe-fs-fetch-status', r.status) return r.json() @@ -166,7 +168,7 @@

Denied

}) // outside root with special characters #8498 - fetch('/@fs/' + ROOT + '/root/src/%2e%2e%2f%2e%2e%2funsafe%2ejson') + fetch(base + '/@fs/' + ROOT + '/root/src/%2e%2e%2f%2e%2e%2funsafe%2ejson') .then((r) => { text('.unsafe-fs-fetch-8498-status', r.status) return r.json() @@ -177,7 +179,10 @@

Denied

// outside root with special characters 2 #8498 fetch( - '/@fs/' + ROOT + '/root/src/%252e%252e%252f%252e%252e%252funsafe%252ejson', + base + + '/@fs/' + + ROOT + + '/root/src/%252e%252e%252f%252e%252e%252funsafe%252ejson', ) .then((r) => { text('.unsafe-fs-fetch-8498-2-status', r.status) @@ -189,7 +194,8 @@

Denied

// not imported before, inside root with special characters, treated as safe fetch( - '/@fs/' + + base + + '/@fs/' + ROOT + '/root/src/special%20characters%20%C3%A5%C3%A4%C3%B6/safe.json', ) @@ -202,7 +208,7 @@

Denied

}) // .env, denied by default - fetch('/@fs/' + ROOT + '/root/.env') + fetch(base + '/@fs/' + ROOT + '/root/.env') .then((r) => { text('.unsafe-dotenv', r.status) }) diff --git a/playground/fs-serve/root/vite.config-base.js b/playground/fs-serve/root/vite.config-base.js new file mode 100644 index 00000000000000..887e87882bf012 --- /dev/null +++ b/playground/fs-serve/root/vite.config-base.js @@ -0,0 +1,36 @@ +import path from 'node:path' +import { defineConfig } from 'vite' + +const BASE = '/base' + +export default defineConfig({ + base: BASE, + build: { + rollupOptions: { + input: { + main: path.resolve(__dirname, 'src/index.html'), + }, + }, + }, + server: { + fs: { + strict: true, + allow: [path.resolve(__dirname, 'src')], + }, + hmr: { + overlay: false, + }, + headers: { + 'x-served-by': 'vite', + }, + }, + preview: { + headers: { + 'x-served-by': 'vite', + }, + }, + define: { + ROOT: JSON.stringify(path.dirname(__dirname).replace(/\\/g, '/')), + BASE: JSON.stringify(BASE), + }, +})