Skip to content

Commit 675bf07

Browse files
authoredOct 27, 2022
chore: join URL segments more safely (#10590)
1 parent f787a60 commit 675bf07

File tree

8 files changed

+46
-26
lines changed

8 files changed

+46
-26
lines changed
 

‎packages/vite/src/node/build.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import { isDepsOptimizerEnabled, resolveConfig } from './config'
2727
import { buildReporterPlugin } from './plugins/reporter'
2828
import { buildEsbuildPlugin } from './plugins/esbuild'
2929
import { terserPlugin } from './plugins/terser'
30-
import { copyDir, emptyDir, lookupFile, normalizePath } from './utils'
30+
import {
31+
copyDir,
32+
emptyDir,
33+
joinUrlSegments,
34+
lookupFile,
35+
normalizePath
36+
} from './utils'
3137
import { manifestPlugin } from './plugins/manifest'
3238
import type { Logger } from './logger'
3339
import { dataURIPlugin } from './plugins/dataUri'
@@ -1071,7 +1077,7 @@ export function toOutputFilePathInJS(
10711077
if (relative && !config.build.ssr) {
10721078
return toRelative(filename, hostId)
10731079
}
1074-
return config.base + filename
1080+
return joinUrlSegments(config.base, filename)
10751081
}
10761082

10771083
export function createToImportMetaURLBasedRelativeRuntime(

‎packages/vite/src/node/plugins/asset.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
} from '../build'
2020
import type { Plugin } from '../plugin'
2121
import type { ResolvedConfig } from '../config'
22-
import { cleanUrl, getHash, normalizePath } from '../utils'
22+
import { cleanUrl, getHash, joinUrlSegments, normalizePath } from '../utils'
2323
import { FS_PREFIX } from '../constants'
2424

2525
export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g
@@ -249,9 +249,8 @@ function fileToDevUrl(id: string, config: ResolvedConfig) {
249249
// (this is special handled by the serve static middleware
250250
rtn = path.posix.join(FS_PREFIX + id)
251251
}
252-
const origin = config.server?.origin ?? ''
253-
const devBase = config.base
254-
return origin + devBase + rtn.replace(/^\//, '')
252+
const base = joinUrlSegments(config.server?.origin ?? '', config.base)
253+
return joinUrlSegments(base, rtn.replace(/^\//, ''))
255254
}
256255

257256
export function getAssetFilename(
@@ -396,7 +395,7 @@ export function publicFileToBuiltUrl(
396395
): string {
397396
if (config.command !== 'build') {
398397
// We don't need relative base or renderBuiltUrl support during dev
399-
return config.base + url.slice(1)
398+
return joinUrlSegments(config.base, url)
400399
}
401400
const hash = getHash(url)
402401
let cache = publicAssetUrlCache.get(config)

‎packages/vite/src/node/plugins/css.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
isDataUrl,
4040
isExternalUrl,
4141
isObject,
42+
joinUrlSegments,
4243
normalizePath,
4344
parseRequest,
4445
processSrcSet,
@@ -211,7 +212,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
211212
if (encodePublicUrlsInCSS(config)) {
212213
return publicFileToBuiltUrl(url, config)
213214
} else {
214-
return config.base + url.slice(1)
215+
return joinUrlSegments(config.base, url)
215216
}
216217
}
217218
const resolved = await resolveUrl(url, importer)
@@ -249,7 +250,6 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
249250
// server only logic for handling CSS @import dependency hmr
250251
const { moduleGraph } = server
251252
const thisModule = moduleGraph.getModuleById(id)
252-
const devBase = config.base
253253
if (thisModule) {
254254
// CSS modules cannot self-accept since it exports values
255255
const isSelfAccepting =
@@ -258,6 +258,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
258258
// record deps in the module graph so edits to @import css can trigger
259259
// main import to hot update
260260
const depModules = new Set<string | ModuleNode>()
261+
const devBase = config.base
261262
for (const file of deps) {
262263
depModules.add(
263264
isCSSRequest(file)
@@ -387,10 +388,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
387388
}
388389

389390
const cssContent = await getContentWithSourcemap(css)
390-
const devBase = config.base
391391
const code = [
392392
`import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify(
393-
path.posix.join(devBase, CLIENT_PUBLIC_PATH)
393+
path.posix.join(config.base, CLIENT_PUBLIC_PATH)
394394
)}`,
395395
`const __vite__id = ${JSON.stringify(id)}`,
396396
`const __vite__css = ${JSON.stringify(cssContent)}`,

‎packages/vite/src/node/server/index.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,7 @@ export async function createServer(
547547
}
548548

549549
// base
550-
const devBase = config.base
551-
if (devBase !== '/') {
550+
if (config.base !== '/') {
552551
middlewares.use(baseMiddleware(server))
553552
}
554553

@@ -652,7 +651,6 @@ async function startServer(
652651

653652
const protocol = options.https ? 'https' : 'http'
654653
const info = server.config.logger.info
655-
const devBase = server.config.base
656654

657655
const serverPort = await httpServerStart(httpServer, {
658656
port,
@@ -681,7 +679,8 @@ async function startServer(
681679
}
682680

683681
if (options.open && !isRestart) {
684-
const path = typeof options.open === 'string' ? options.open : devBase
682+
const path =
683+
typeof options.open === 'string' ? options.open : server.config.base
685684
openBrowser(
686685
path.startsWith('http')
687686
? path

‎packages/vite/src/node/server/middlewares/base.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { Connect } from 'dep-types/connect'
22
import type { ViteDevServer } from '..'
3+
import { joinUrlSegments } from '../../utils'
34

45
// this middleware is only active when (config.base !== '/')
56

67
export function baseMiddleware({
78
config
89
}: ViteDevServer): Connect.NextHandleFunction {
9-
const devBase = config.base
10+
const devBase = config.base.endsWith('/') ? config.base : config.base + '/'
1011

1112
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
1213
return function viteBaseMiddleware(req, res, next) {
@@ -29,18 +30,18 @@ export function baseMiddleware({
2930
if (path === '/' || path === '/index.html') {
3031
// redirect root visit to based url with search and hash
3132
res.writeHead(302, {
32-
Location: devBase + (parsed.search || '') + (parsed.hash || '')
33+
Location: config.base + (parsed.search || '') + (parsed.hash || '')
3334
})
3435
res.end()
3536
return
3637
} else if (req.headers.accept?.includes('text/html')) {
3738
// non-based page visit
38-
const redirectPath = devBase + url.slice(1)
39+
const redirectPath = joinUrlSegments(config.base, url)
3940
res.writeHead(404, {
4041
'Content-Type': 'text/html'
4142
})
4243
res.end(
43-
`The server is configured with a public base URL of ${devBase} - ` +
44+
`The server is configured with a public base URL of ${config.base} - ` +
4445
`did you mean to visit <a href="${redirectPath}">${redirectPath}</a> instead?`
4546
)
4647
return

‎packages/vite/src/node/server/middlewares/indexHtml.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ensureWatchedFile,
2727
fsPathFromId,
2828
injectQuery,
29+
joinUrlSegments,
2930
normalizePath,
3031
processSrcSetSync,
3132
wrapId
@@ -93,7 +94,8 @@ const processNodeUrl = (
9394
const devBase = config.base
9495
if (startsWithSingleSlashRE.test(url)) {
9596
// prefix with base (dev only, base is never relative)
96-
overwriteAttrValue(s, sourceCodeLocation, devBase + url.slice(1))
97+
const fullUrl = joinUrlSegments(devBase, url)
98+
overwriteAttrValue(s, sourceCodeLocation, fullUrl)
9799
} else if (
98100
url.startsWith('.') &&
99101
originalUrl &&
@@ -132,7 +134,7 @@ const devHtmlHook: IndexHtmlTransformHook = async (
132134
const trailingSlash = htmlPath.endsWith('/')
133135
if (!trailingSlash && fs.existsSync(filename)) {
134136
proxyModulePath = htmlPath
135-
proxyModuleUrl = base + htmlPath.slice(1)
137+
proxyModuleUrl = joinUrlSegments(base, htmlPath)
136138
} else {
137139
// There are users of vite.transformIndexHtml calling it with url '/'
138140
// for SSR integrations #7993, filename is root for this case

‎packages/vite/src/node/ssr/ssrManifestPlugin.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { OutputChunk } from 'rollup'
55
import type { ResolvedConfig } from '..'
66
import type { Plugin } from '../plugin'
77
import { preloadMethod } from '../plugins/importAnalysisBuild'
8-
import { normalizePath } from '../utils'
8+
import { joinUrlSegments, normalizePath } from '../utils'
99

1010
export function ssrManifestPlugin(config: ResolvedConfig): Plugin {
1111
// module id => preload assets mapping
@@ -23,15 +23,15 @@ export function ssrManifestPlugin(config: ResolvedConfig): Plugin {
2323
const mappedChunks =
2424
ssrManifest[normalizedId] ?? (ssrManifest[normalizedId] = [])
2525
if (!chunk.isEntry) {
26-
mappedChunks.push(base + chunk.fileName)
26+
mappedChunks.push(joinUrlSegments(base, chunk.fileName))
2727
// <link> tags for entry chunks are already generated in static HTML,
2828
// so we only need to record info for non-entry chunks.
2929
chunk.viteMetadata.importedCss.forEach((file) => {
30-
mappedChunks.push(base + file)
30+
mappedChunks.push(joinUrlSegments(base, file))
3131
})
3232
}
3333
chunk.viteMetadata.importedAssets.forEach((file) => {
34-
mappedChunks.push(base + file)
34+
mappedChunks.push(joinUrlSegments(base, file))
3535
})
3636
}
3737
if (chunk.code.includes(preloadMethod)) {
@@ -59,7 +59,7 @@ export function ssrManifestPlugin(config: ResolvedConfig): Plugin {
5959
const chunk = bundle[filename] as OutputChunk | undefined
6060
if (chunk) {
6161
chunk.viteMetadata.importedCss.forEach((file) => {
62-
deps.push(join(base, file)) // TODO:base
62+
deps.push(joinUrlSegments(base, file)) // TODO:base
6363
})
6464
chunk.imports.forEach(addDeps)
6565
}

‎packages/vite/src/node/utils.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1191,3 +1191,16 @@ export const isNonDriveRelativeAbsolutePath = (p: string): boolean => {
11911191
if (!isWindows) return p.startsWith('/')
11921192
return windowsDrivePathPrefixRE.test(p)
11931193
}
1194+
1195+
export function joinUrlSegments(a: string, b: string): string {
1196+
if (!a || !b) {
1197+
return a || b || ''
1198+
}
1199+
if (a.endsWith('/')) {
1200+
a = a.substring(0, a.length - 1)
1201+
}
1202+
if (!b.startsWith('/')) {
1203+
b = '/' + b
1204+
}
1205+
return a + b
1206+
}

0 commit comments

Comments
 (0)
Please sign in to comment.