Skip to content

Commit f169b08

Browse files
authored
fix(next/image)!: error when src has leading or trailing space (#65637)
BREAKING CHANGE: Using the built-in image optimization API, the URL is parsed with `new URL()` constructor which automatically trims spaces. However, the developer may choose a 3rd party image optimization API via `loader` or `loaderFile` (or perhaps a deployment platform that has its own built in loader), so we shouldn't assume the API will parse the URL in the same way as [WHATWG](https://url.spec.whatwg.org/#:~:text=If%20input%20contains%20any%20leading%20or%20trailing%20C0%20control%20or%20space%2C%20invalid%2DURL%2Dunit%20validation%20error.). While we could trim on the client, its probably best to fail fast and let the developer make a conscience decision if a trailing space should be removed or remain (by explicitly using `%20`).
1 parent 46f89b0 commit f169b08

File tree

7 files changed

+88
-0
lines changed

7 files changed

+88
-0
lines changed

packages/next/src/shared/lib/get-img-props.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,18 @@ export function getImgProps(
465465
`Image with src "${src}" has invalid "height" property. Expected a numeric value in pixels but received "${height}".`
466466
)
467467
}
468+
// eslint-disable-next-line no-control-regex
469+
if (/^[\x00-\x20]/.test(src)) {
470+
throw new Error(
471+
`Image with src "${src}" cannot start with a space or control character. Use src.trimStart() to remove it or encodeURIComponent(src) to keep it.`
472+
)
473+
}
474+
// eslint-disable-next-line no-control-regex
475+
if (/[\x00-\x20]$/.test(src)) {
476+
throw new Error(
477+
`Image with src "${src}" cannot end with a space or control character. Use src.trimEnd() to remove it or encodeURIComponent(src) to keep it.`
478+
)
479+
}
468480
}
469481
}
470482
if (!VALID_LOADING_VALUES.includes(loading)) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
export default function Page() {
5+
return (
6+
<div>
7+
<h2>Invalid src with leading space</h2>
8+
<Image src=" /test.jpg" width={200} height={200} />
9+
</div>
10+
)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
export default function Page() {
5+
return (
6+
<div>
7+
<h2>Invalid src with trailing space</h2>
8+
<Image src="/test.png " width={200} height={200} />
9+
</div>
10+
)
11+
}

test/integration/next-image-new/app-dir/test/index.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,22 @@ function runTests(mode) {
915915
)
916916
})
917917

918+
it('should show invalid src with leading space', async () => {
919+
const browser = await webdriver(appPort, '/invalid-src-leading-space')
920+
expect(await hasRedbox(browser)).toBe(true)
921+
expect(await getRedboxHeader(browser)).toContain(
922+
'Image with src " /test.jpg" cannot start with a space or control character.'
923+
)
924+
})
925+
926+
it('should show invalid src with trailing space', async () => {
927+
const browser = await webdriver(appPort, '/invalid-src-trailing-space')
928+
expect(await hasRedbox(browser)).toBe(true)
929+
expect(await getRedboxHeader(browser)).toContain(
930+
'Image with src "/test.png " cannot end with a space or control character.'
931+
)
932+
})
933+
918934
it('should show error when string src and placeholder=blur and blurDataURL is missing', async () => {
919935
const browser = await webdriver(appPort, '/invalid-placeholder-blur')
920936

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
export default function Page() {
5+
return (
6+
<div>
7+
<h2>Invalid src with leading space</h2>
8+
<Image src=" /test.jpg" width={200} height={200} />
9+
</div>
10+
)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
export default function Page() {
5+
return (
6+
<div>
7+
<h2>Invalid src with trailing space</h2>
8+
<Image src="/test.png " width={200} height={200} />
9+
</div>
10+
)
11+
}

test/integration/next-image-new/default/test/index.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,22 @@ function runTests(mode) {
916916
)
917917
})
918918

919+
it('should show invalid src with leading space', async () => {
920+
const browser = await webdriver(appPort, '/invalid-src-leading-space')
921+
expect(await hasRedbox(browser)).toBe(true)
922+
expect(await getRedboxHeader(browser)).toContain(
923+
'Image with src " /test.jpg" cannot start with a space or control character.'
924+
)
925+
})
926+
927+
it('should show invalid src with trailing space', async () => {
928+
const browser = await webdriver(appPort, '/invalid-src-trailing-space')
929+
expect(await hasRedbox(browser)).toBe(true)
930+
expect(await getRedboxHeader(browser)).toContain(
931+
'Image with src "/test.png " cannot end with a space or control character.'
932+
)
933+
})
934+
919935
it('should show error when string src and placeholder=blur and blurDataURL is missing', async () => {
920936
const browser = await webdriver(appPort, '/invalid-placeholder-blur')
921937

0 commit comments

Comments
 (0)