diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index 4ddb4478f6..eabb4ee55a 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -69,22 +69,20 @@ export async function imageOptimizer( } const { headers } = req - const { url: decodedUrl, w, q } = parsedUrl.query + const { url, w, q } = parsedUrl.query const mimeType = getSupportedMimeType(MODERN_TYPES, headers.accept) let href: string - if (!decodedUrl) { + if (!url) { res.statusCode = 400 res.end('"url" parameter is required') return { finished: true } - } else if (Array.isArray(decodedUrl)) { + } else if (Array.isArray(url)) { res.statusCode = 400 res.end('"url" parameter cannot be an array') return { finished: true } } - const url = encodeURI(decodedUrl) - let isAbsolute: boolean if (url.startsWith('/')) { diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index d62b6ded0d..f3d90464fb 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -1158,7 +1158,14 @@ export default class Server { pathParts.splice(0, basePathParts.length) } - const path = `/${pathParts.join('/')}` + let path = `/${pathParts.join('/')}` + + if (!publicFiles.has(path)) { + // In `next-dev-server.ts`, we ensure encoded paths match + // decoded paths on the filesystem. So we need do the + // opposite here: make sure decoded paths match encoded. + path = encodeURI(path) + } if (publicFiles.has(path)) { await this.serveStatic( diff --git a/test/integration/image-component/unicode/next.config.js b/test/integration/image-component/unicode/next.config.js new file mode 100644 index 0000000000..eb554b46c1 --- /dev/null +++ b/test/integration/image-component/unicode/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + images: { + domains: ['image-optimization-test.vercel.app'], + }, +} diff --git a/test/integration/image-component/unicode/pages/index.js b/test/integration/image-component/unicode/pages/index.js new file mode 100644 index 0000000000..bdc4ea840f --- /dev/null +++ b/test/integration/image-component/unicode/pages/index.js @@ -0,0 +1,33 @@ +import React from 'react' +import Image from 'next/image' +import img from '../public/äöü.png' + +const Page = () => { + return ( +
+

Unicode Image URL

+ + + + + +
+ ) +} + +export default Page diff --git a/test/integration/image-component/unicode/public/hello world.jpg b/test/integration/image-component/unicode/public/hello world.jpg new file mode 100644 index 0000000000..e14fafc5cf Binary files /dev/null and b/test/integration/image-component/unicode/public/hello world.jpg differ diff --git a/test/integration/image-component/unicode/public/äöü.png b/test/integration/image-component/unicode/public/äöü.png new file mode 100644 index 0000000000..e14fafc5cf Binary files /dev/null and b/test/integration/image-component/unicode/public/äöü.png differ diff --git a/test/integration/image-component/unicode/test/index.test.js b/test/integration/image-component/unicode/test/index.test.js new file mode 100644 index 0000000000..53e224848d --- /dev/null +++ b/test/integration/image-component/unicode/test/index.test.js @@ -0,0 +1,96 @@ +/* eslint-env jest */ + +import { + findPort, + killApp, + launchApp, + nextBuild, + nextStart, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +//import fetch from 'node-fetch' +import { join } from 'path' + +jest.setTimeout(1000 * 60) + +const appDir = join(__dirname, '../') + +let appPort +let app +let browser + +function runTests() { + it('should load static unicode image', async () => { + const src = await browser.elementById('static').getAttribute('src') + expect(src).toMatch( + /_next%2Fstatic%2Fimage%2Fpublic%2F%C3%A4%C3%B6%C3%BC(.+)png/ + ) + const res = await fetch(src) + expect(res.status).toBe(200) + }) + + it('should load internal unicode image', async () => { + const src = await browser.elementById('internal').getAttribute('src') + expect(src).toMatch('/_next/image?url=%2F%C3%A4%C3%B6%C3%BC.png') + const res = await fetch(src) + expect(res.status).toBe(200) + }) + + it('should load external unicode image', async () => { + const src = await browser.elementById('external').getAttribute('src') + expect(src).toMatch( + '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2F%C3%A4%C3%B6%C3%BC.png' + ) + const res = await fetch(src) + expect(res.status).toBe(200) + }) + + it('should load internal image with space', async () => { + const src = await browser.elementById('internal-space').getAttribute('src') + expect(src).toMatch('/_next/image?url=%2Fhello%2520world.jpg') + const res = await fetch(src) + expect(res.status).toBe(200) + }) + + it('should load external image with space', async () => { + const src = await browser.elementById('external-space').getAttribute('src') + expect(src).toMatch( + '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2Fhello%2520world.jpg' + ) + const res = await fetch(src) + expect(res.status).toBe(200) + }) +} + +describe('Image Component Unicode Image URL', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + browser = await webdriver(appPort, '/') + }) + afterAll(() => { + killApp(app) + if (browser) { + browser.close() + } + }) + runTests() + }) + + describe('server mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + browser = await webdriver(appPort, '/') + }) + afterAll(() => { + killApp(app) + if (browser) { + browser.close() + } + }) + runTests() + }) +})