Skip to content

Commit

Permalink
fix(server): ensure consistency for url to file mapping in importAnal…
Browse files Browse the repository at this point in the history
…ysis and static middleware (#6518)
  • Loading branch information
dominikg committed Mar 3, 2022
1 parent a516e85 commit b214115
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 19 deletions.
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 @@ -702,7 +702,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 @@ -724,7 +724,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 @@ -480,10 +524,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

0 comments on commit b214115

Please sign in to comment.