Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import escapeHtml from 'escape-html'
import type { MinimalPluginContextWithoutEnvironment, Plugin } from '../plugin'
import type { ViteDevServer } from '../server'
import {
decodeURIIfPossible,
encodeURIPath,
generateCodeFrame,
getHash,
Expand Down Expand Up @@ -1568,12 +1569,3 @@ function serializeAttrs(attrs: HtmlTagDescriptor['attrs']): string {
function incrementIndent(indent: string = '') {
return `${indent}${indent[0] === '\t' ? '\t' : ' '}`
}

function decodeURIIfPossible(input: string): string | undefined {
try {
return decodeURI(input)
} catch {
// url is malformed, probably a interpolate syntax of template engines
return
}
}
8 changes: 7 additions & 1 deletion packages/vite/src/node/server/middlewares/htmlFallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ export function htmlFallbackMiddleware(
}

const url = cleanUrl(req.url!)
const pathname = decodeURIComponent(url)
let pathname
try {
pathname = decodeURIComponent(url)
} catch {
// ignore malformed URI
return next()
}

// .html files are not handled by serveStaticMiddleware
// so we need to check if the file exists
Expand Down
12 changes: 10 additions & 2 deletions packages/vite/src/node/server/middlewares/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ViteDevServer } from '../../server'
import type { ResolvedConfig } from '../../config'
import { FS_PREFIX } from '../../constants'
import {
decodeURIIfPossible,
fsPathFromUrl,
isFileReadable,
isImportRequest,
Expand Down Expand Up @@ -151,7 +152,10 @@ export function serveStaticMiddleware(
}

const url = new URL(req.url!, 'http://example.com')
const pathname = decodeURI(url.pathname)
const pathname = decodeURIIfPossible(url.pathname)
if (pathname === undefined) {
return next()
}

// apply aliases to static requests as well
let redirectedPathname: string | undefined
Expand Down Expand Up @@ -213,7 +217,11 @@ export function serveRawFsMiddleware(
// searching based from fs root.
if (req.url!.startsWith(FS_PREFIX)) {
const url = new URL(req.url!, 'http://example.com')
const pathname = decodeURI(url.pathname)
const pathname = decodeURIIfPossible(url.pathname)
if (pathname === undefined) {
return next()
}

let newPathname = pathname.slice(FS_PREFIX.length)
if (isWindows) newPathname = newPathname.replace(/^[A-Z]:/i, '')
url.pathname = encodeURI(newPathname)
Expand Down
4 changes: 3 additions & 1 deletion packages/vite/src/node/server/middlewares/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ export function transformMiddleware(
} catch (e) {
if (e instanceof URIError) {
server.config.logger.warn(
colors.yellow('Malformed URI sequence in request URL'),
colors.yellow(
`Malformed URI sequence in request URL: ${removeTimestampQuery(req.url!)}`,
),
)
return next()
}
Expand Down
9 changes: 9 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,15 @@ export function partialEncodeURIPath(uri: string): string {
return filePath.replaceAll('%', '%25') + postfix
}

export function decodeURIIfPossible(input: string): string | undefined {
try {
return decodeURI(input)
} catch {
// url is malformed, probably a interpolate syntax of template engines
return
}
}

type SigtermCallback = (signal?: 'SIGTERM', exitCode?: number) => Promise<void>

// Use a shared callback when attaching sigterm listeners to avoid `MaxListenersExceededWarning`
Expand Down
16 changes: 16 additions & 0 deletions playground/html/__tests__/html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,19 @@ test('invalidate inline proxy module on reload', async () => {
await page.reload()
expect(await page.textContent('.test')).toContain('ok')
})

test.runIf(isServe)(
'malformed URLs in src attributes should show errors',
async () => {
serverLogs.length = 0
await page.goto(`${viteTestUrl}/malformed-url.html`)
expect(await page.textContent('.status')).toContain(
'Page loaded successfully',
)
expect(serverLogs).not.toEqual(
expect.arrayContaining([
expect.stringMatching('Internal server error: URI malformed'),
]),
)
},
)
16 changes: 16 additions & 0 deletions playground/html/malformed-url.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Malformed URL Test</title>
</head>
<body>
<h1>Malformed URL Test</h1>
<img
src="{% if product[0].image_urls[0] %}{{product[0].image_urls[0]}}{% endif %}"
class="template-expression"
/>
<p class="status">Page loaded successfully</p>
</body>
</html>
1 change: 1 addition & 0 deletions playground/html/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default defineConfig({
process.cwd(),
resolve(__dirname, 'relative-input.html'),
),
malformedUrl: resolve(__dirname, 'malformed-url.html'),
},
external: ['/external-path-by-rollup-options.js'],
},
Expand Down
Loading