Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server): ensure consistency for url to file mapping in importAnalysis and static middleware #6518

Merged
merged 7 commits into from Mar 3, 2022
13 changes: 13 additions & 0 deletions packages/playground/fs-serve/root/src/index.html
Expand Up @@ -8,6 +8,10 @@ <h2>Safe Fetch</h2>
<pre class="safe-fetch-status"></pre>
<pre class="safe-fetch"></pre>

<h2>Safe Fetch Subdirectory</h2>
<pre class="safe-fetch-subdir-status"></pre>
<pre class="safe-fetch-subdir"></pre>

<h2>Unsafe Fetch</h2>
<pre class="unsafe-fetch-status"></pre>
<pre class="unsafe-fetch"></pre>
Expand Down Expand Up @@ -42,6 +46,15 @@ <h2>Denied</h2>
.then((data) => {
text('.safe-fetch', JSON.stringify(data))
})
// inside allowed dir, safe fetch
fetch('/src/subdir/safe.txt')
.then((r) => {
text('.safe-fetch-subdir-status', r.status)
return r.text()
})
.then((data) => {
text('.safe-fetch-subdir', JSON.stringify(data))
})

// outside of allowed dir, treated as unsafe
fetch('/unsafe.txt')
Expand Down
1 change: 1 addition & 0 deletions packages/playground/fs-serve/root/src/subdir/safe.txt
@@ -0,0 +1 @@
KEY=safe
7 changes: 3 additions & 4 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -21,7 +21,8 @@ import {
normalizePath,
removeImportQuery,
unwrapId,
moduleListContains
moduleListContains,
fsPathFromUrl
} from '../utils'
import {
debugHmr,
Expand Down Expand Up @@ -399,9 +400,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
let url = normalizedUrl

// record as safe modules
server?.moduleGraph.safeModulesPath.add(
cleanUrl(url).slice(4 /* '/@fs'.length */)
)
server?.moduleGraph.safeModulesPath.add(fsPathFromUrl(url))

// rewrite
if (url !== specifier) {
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/server/index.ts
Expand Up @@ -33,7 +33,7 @@ import { timeMiddleware } from './middlewares/time'
import type { ModuleNode } from './moduleGraph'
import { ModuleGraph } from './moduleGraph'
import type { Connect } from 'types/connect'
import { ensureLeadingSlash, normalizePath } from '../utils'
import { isParentDirectory, normalizePath } from '../utils'
import { errorMiddleware, prepareError } from './middlewares/error'
import type { HmrOptions } from './hmr'
import { handleHMRUpdate, handleFileAddUnlink } from './hmr'
Expand Down Expand Up @@ -693,7 +693,7 @@ function createServerCloseFn(server: http.Server | null) {
}

function resolvedAllowDir(root: string, dir: string): string {
return ensureLeadingSlash(normalizePath(path.resolve(root, dir)))
return normalizePath(path.resolve(root, dir))
}

export function resolveServerOptions(
Expand All @@ -715,7 +715,7 @@ export function resolveServerOptions(

// only push client dir when vite itself is outside-of-root
const resolvedClientDir = resolvedAllowDir(root, CLIENT_DIR)
if (!allowDirs.some((i) => resolvedClientDir.startsWith(i))) {
if (!allowDirs.some((dir) => isParentDirectory(dir, resolvedClientDir))) {
allowDirs.push(resolvedClientDir)
}

Expand Down
11 changes: 5 additions & 6 deletions packages/vite/src/node/server/middlewares/static.ts
Expand Up @@ -4,17 +4,17 @@ import type { Options } from 'sirv'
import sirv from 'sirv'
import type { Connect } from 'types/connect'
import type { ViteDevServer } from '../..'
import { normalizePath } from '../..'
import { FS_PREFIX } from '../../constants'
import {
cleanUrl,
ensureLeadingSlash,
fsPathFromId,
fsPathFromUrl,
isImportRequest,
isInternalRequest,
isWindows,
slash,
isFileReadable
isFileReadable,
isParentDirectory
} from '../../utils'
import { isMatch } from 'micromatch'

Expand Down Expand Up @@ -148,15 +148,14 @@ export function isFileServingAllowed(
): boolean {
if (!server.config.server.fs.strict) return true

const cleanedUrl = cleanUrl(url)
const file = ensureLeadingSlash(normalizePath(cleanedUrl))
const file = fsPathFromUrl(url)

if (server.config.server.fs.deny.some((i) => isMatch(file, i, _matchOptions)))
return false

if (server.moduleGraph.safeModulesPath.has(file)) return true

if (server.config.server.fs.allow.some((i) => file.startsWith(i + '/')))
if (server.config.server.fs.allow.some((dir) => isParentDirectory(dir, file)))
return true

return false
Expand Down
52 changes: 46 additions & 6 deletions packages/vite/src/node/utils.ts
Expand Up @@ -9,7 +9,8 @@ import {
DEFAULT_EXTENSIONS,
VALID_ID_PREFIX,
CLIENT_PUBLIC_PATH,
ENV_PUBLIC_PATH
ENV_PUBLIC_PATH,
CLIENT_ENTRY
} from './constants'
import resolve from 'resolve'
import { builtinModules } from 'module'
Expand Down Expand Up @@ -139,20 +140,63 @@ export function createDebugger(
}
}

function testCaseInsensitiveFS() {
if (!CLIENT_ENTRY.endsWith('client.mjs')) {
throw new Error(
`cannot test case insensitive FS, CLIENT_ENTRY const doesn't contain client.mjs`
)
}
if (!fs.existsSync(CLIENT_ENTRY)) {
throw new Error(
'cannot test case insensitive FS, CLIENT_ENTRY does not point to an existing file: ' +
CLIENT_ENTRY
)
}
return fs.existsSync(CLIENT_ENTRY.replace('client.mjs', 'cLiEnT.mjs'))
}

export const isCaseInsensitiveFS = testCaseInsensitiveFS()

export const isWindows = os.platform() === 'win32'

const VOLUME_RE = /^[A-Z]:/i

export function normalizePath(id: string): string {
return path.posix.normalize(isWindows ? slash(id) : id)
}

export function fsPathFromId(id: string): string {
const fsPath = normalizePath(id.slice(FS_PREFIX.length))
const fsPath = normalizePath(
id.startsWith(FS_PREFIX) ? id.slice(FS_PREFIX.length) : id
)
return fsPath.startsWith('/') || fsPath.match(VOLUME_RE)
? fsPath
: `/${fsPath}`
}

export function fsPathFromUrl(url: string): string {
return fsPathFromId(cleanUrl(url))
}

/**
* Check if dir is a parent of file
*
* Warning: parameters are not validated, only works with normalized absolute paths
*
* @param dir - normalized absolute path
* @param file - normalized absolute path
* @returns true if dir is a parent of file
*/
export function isParentDirectory(dir: string, file: string): boolean {
if (!dir.endsWith('/')) {
dir = `${dir}/`
}
return (
file.startsWith(dir) ||
(isCaseInsensitiveFS && file.toLowerCase().startsWith(dir.toLowerCase()))
)
}

export function ensureVolumeInPath(file: string): string {
return isWindows ? path.resolve(file) : file
}
Expand Down Expand Up @@ -466,10 +510,6 @@ export function copyDir(srcDir: string, destDir: string): void {
}
}

export function ensureLeadingSlash(path: string): string {
return !path.startsWith('/') ? '/' + path : path
}

export function ensureWatchedFile(
watcher: FSWatcher,
file: string | null,
Expand Down