Skip to content

Commit d65a983

Browse files
Copilotsapphi-redbluwy
authored
fix(server): improve malformed URL handling in middlewares (#20830)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: sapphi-red <[email protected]> Co-authored-by: bluwy <[email protected]>
1 parent fdb758a commit d65a983

File tree

8 files changed

+63
-13
lines changed

8 files changed

+63
-13
lines changed

packages/vite/src/node/plugins/html.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import escapeHtml from 'escape-html'
2020
import type { MinimalPluginContextWithoutEnvironment, Plugin } from '../plugin'
2121
import type { ViteDevServer } from '../server'
2222
import {
23+
decodeURIIfPossible,
2324
encodeURIPath,
2425
generateCodeFrame,
2526
getHash,
@@ -1568,12 +1569,3 @@ function serializeAttrs(attrs: HtmlTagDescriptor['attrs']): string {
15681569
function incrementIndent(indent: string = '') {
15691570
return `${indent}${indent[0] === '\t' ? '\t' : ' '}`
15701571
}
1571-
1572-
function decodeURIIfPossible(input: string): string | undefined {
1573-
try {
1574-
return decodeURI(input)
1575-
} catch {
1576-
// url is malformed, probably a interpolate syntax of template engines
1577-
return
1578-
}
1579-
}

packages/vite/src/node/server/middlewares/htmlFallback.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ export function htmlFallbackMiddleware(
2929
}
3030

3131
const url = cleanUrl(req.url!)
32-
const pathname = decodeURIComponent(url)
32+
let pathname
33+
try {
34+
pathname = decodeURIComponent(url)
35+
} catch {
36+
// ignore malformed URI
37+
return next()
38+
}
3339

3440
// .html files are not handled by serveStaticMiddleware
3541
// so we need to check if the file exists

packages/vite/src/node/server/middlewares/static.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { ViteDevServer } from '../../server'
88
import type { ResolvedConfig } from '../../config'
99
import { FS_PREFIX } from '../../constants'
1010
import {
11+
decodeURIIfPossible,
1112
fsPathFromUrl,
1213
isFileReadable,
1314
isImportRequest,
@@ -151,7 +152,10 @@ export function serveStaticMiddleware(
151152
}
152153

153154
const url = new URL(req.url!, 'http://example.com')
154-
const pathname = decodeURI(url.pathname)
155+
const pathname = decodeURIIfPossible(url.pathname)
156+
if (pathname === undefined) {
157+
return next()
158+
}
155159

156160
// apply aliases to static requests as well
157161
let redirectedPathname: string | undefined
@@ -213,7 +217,11 @@ export function serveRawFsMiddleware(
213217
// searching based from fs root.
214218
if (req.url!.startsWith(FS_PREFIX)) {
215219
const url = new URL(req.url!, 'http://example.com')
216-
const pathname = decodeURI(url.pathname)
220+
const pathname = decodeURIIfPossible(url.pathname)
221+
if (pathname === undefined) {
222+
return next()
223+
}
224+
217225
let newPathname = pathname.slice(FS_PREFIX.length)
218226
if (isWindows) newPathname = newPathname.replace(/^[A-Z]:/i, '')
219227
url.pathname = encodeURI(newPathname)

packages/vite/src/node/server/middlewares/transform.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ export function transformMiddleware(
116116
} catch (e) {
117117
if (e instanceof URIError) {
118118
server.config.logger.warn(
119-
colors.yellow('Malformed URI sequence in request URL'),
119+
colors.yellow(
120+
`Malformed URI sequence in request URL: ${removeTimestampQuery(req.url!)}`,
121+
),
120122
)
121123
return next()
122124
}

packages/vite/src/node/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,15 @@ export function partialEncodeURIPath(uri: string): string {
16261626
return filePath.replaceAll('%', '%25') + postfix
16271627
}
16281628

1629+
export function decodeURIIfPossible(input: string): string | undefined {
1630+
try {
1631+
return decodeURI(input)
1632+
} catch {
1633+
// url is malformed, probably a interpolate syntax of template engines
1634+
return
1635+
}
1636+
}
1637+
16291638
type SigtermCallback = (signal?: 'SIGTERM', exitCode?: number) => Promise<void>
16301639

16311640
// Use a shared callback when attaching sigterm listeners to avoid `MaxListenersExceededWarning`

playground/html/__tests__/html.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,19 @@ test('invalidate inline proxy module on reload', async () => {
517517
await page.reload()
518518
expect(await page.textContent('.test')).toContain('ok')
519519
})
520+
521+
test.runIf(isServe)(
522+
'malformed URLs in src attributes should show errors',
523+
async () => {
524+
serverLogs.length = 0
525+
await page.goto(`${viteTestUrl}/malformed-url.html`)
526+
expect(await page.textContent('.status')).toContain(
527+
'Page loaded successfully',
528+
)
529+
expect(serverLogs).not.toEqual(
530+
expect.arrayContaining([
531+
expect.stringMatching('Internal server error: URI malformed'),
532+
]),
533+
)
534+
},
535+
)

playground/html/malformed-url.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Malformed URL Test</title>
7+
</head>
8+
<body>
9+
<h1>Malformed URL Test</h1>
10+
<img
11+
src="{% if product[0].image_urls[0] %}{{product[0].image_urls[0]}}{% endif %}"
12+
class="template-expression"
13+
/>
14+
<p class="status">Page loaded successfully</p>
15+
</body>
16+
</html>

playground/html/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default defineConfig({
4040
process.cwd(),
4141
resolve(__dirname, 'relative-input.html'),
4242
),
43+
malformedUrl: resolve(__dirname, 'malformed-url.html'),
4344
},
4445
external: ['/external-path-by-rollup-options.js'],
4546
},

0 commit comments

Comments
 (0)