diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index 68a5e57645cf7..0d350f422cdb3 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -138,6 +138,9 @@ export function detectContentType(buffer: Buffer) { if ([0x3c, 0x3f, 0x78, 0x6d, 0x6c].every((b, i) => buffer[i] === b)) { return SVG } + if ([0x3c, 0x73, 0x76, 0x67].every((b, i) => buffer[i] === b)) { + return SVG + } if ( [0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every( (b, i) => !b || buffer[i] === b diff --git a/test/integration/image-optimizer/app/pages/api/application.svg.js b/test/integration/image-optimizer/app/pages/api/application.svg.js index 1531b13aa9f6e..59cda0da6288f 100644 --- a/test/integration/image-optimizer/app/pages/api/application.svg.js +++ b/test/integration/image-optimizer/app/pages/api/application.svg.js @@ -1,6 +1,4 @@ export default function handler(_req, res) { res.setHeader('Content-Type', 'application/svg+xml') - res.end( - `hi` - ) + res.end('body is not svg to force fallback to Content-Type header') } diff --git a/test/integration/image-optimizer/app/pages/api/comma.svg.js b/test/integration/image-optimizer/app/pages/api/comma.svg.js index 5fa8a430a56d5..5eea24e39bba4 100644 --- a/test/integration/image-optimizer/app/pages/api/comma.svg.js +++ b/test/integration/image-optimizer/app/pages/api/comma.svg.js @@ -1,6 +1,4 @@ export default function handler(_req, res) { res.setHeader('Content-Type', 'image/foo, text/html') - res.end( - `hi` - ) + res.end('body is not svg to force fallback to Content-Type header') } diff --git a/test/integration/image-optimizer/app/pages/api/stateful/test.png.js b/test/integration/image-optimizer/app/pages/api/stateful/test.png.js new file mode 100644 index 0000000000000..35fa0040bccbb --- /dev/null +++ b/test/integration/image-optimizer/app/pages/api/stateful/test.png.js @@ -0,0 +1,19 @@ +const images = [ + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5/hPwAIAgL/4d1j8wAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P//PwAGBAL/VJiKjgAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=', +] + +let state = 0 + +export default function handler(_req, res) { + state++ + const index = state % images.length + const buffer = Buffer.from(images[index], 'base64') + res.setHeader('Content-Type', 'image/png') + return res.end(buffer) +} diff --git a/test/integration/image-optimizer/app/pages/api/uppercase.svg.js b/test/integration/image-optimizer/app/pages/api/uppercase.svg.js index 6958255491f07..aa62434d59e11 100644 --- a/test/integration/image-optimizer/app/pages/api/uppercase.svg.js +++ b/test/integration/image-optimizer/app/pages/api/uppercase.svg.js @@ -1,6 +1,4 @@ export default function handler(_req, res) { res.setHeader('Content-Type', 'image/SVG+XML') - res.end( - `hi` - ) + res.end('body is not svg to force fallback to Content-Type header') } diff --git a/test/integration/image-optimizer/app/pages/api/wrong-header.svg.js b/test/integration/image-optimizer/app/pages/api/wrong-header.svg.js new file mode 100644 index 0000000000000..d7203ef8d632c --- /dev/null +++ b/test/integration/image-optimizer/app/pages/api/wrong-header.svg.js @@ -0,0 +1,6 @@ +export default function handler(_req, res) { + res.setHeader('Content-Type', 'image/png') + res.end( + `hi` + ) +} diff --git a/test/integration/image-optimizer/test/dangerously-allow-svg.test.ts b/test/integration/image-optimizer/test/dangerously-allow-svg.test.ts new file mode 100644 index 0000000000000..986dab2ec8e74 --- /dev/null +++ b/test/integration/image-optimizer/test/dangerously-allow-svg.test.ts @@ -0,0 +1,13 @@ +import { join } from 'path' +import { setupTests } from './util' + +const appDir = join(__dirname, '../app') +const imagesDir = join(appDir, '.next', 'cache', 'images') + +describe('with dangerouslyAllowSVG config', () => { + setupTests({ + nextConfigImages: { dangerouslyAllowSVG: true }, + appDir, + imagesDir, + }) +}) diff --git a/test/integration/image-optimizer/test/util.ts b/test/integration/image-optimizer/test/util.ts index 4a69b6f6586d9..362e015d92327 100644 --- a/test/integration/image-optimizer/test/util.ts +++ b/test/integration/image-optimizer/test/util.ts @@ -17,6 +17,21 @@ import { import isAnimated from 'next/dist/compiled/is-animated' import type { RequestInit } from 'node-fetch' +type SetupTestsCtx = { + appDir: string + imagesDir: string + nextConfigImages?: Partial + isDev?: boolean +} + +type RunTestsCtx = SetupTestsCtx & { + w: number + app?: import('child_process').ChildProcess + appDir?: string + appPort?: number + nextOutput?: string +} + const largeSize = 1080 // defaults defined in server/config.ts const animatedWarnText = 'is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the .' @@ -53,7 +68,7 @@ export async function serveSlowImage() { } } -export async function fsToJson(dir, output = {}) { +export async function fsToJson(dir: string, output = {}) { const files = await fs.readdir(dir) for (let file of files) { const fsPath = join(dir, file) @@ -77,12 +92,16 @@ export async function expectWidth(res, w, { expectAnimated = false } = {}) { expect(isAnimated(buffer)).toBe(expectAnimated) } -export const cleanImagesDir = async (ctx) => { +export const cleanImagesDir = async (ctx: { imagesDir: string }) => { console.warn('Cleaning', ctx.imagesDir) await fs.remove(ctx.imagesDir) } -async function expectAvifSmallerThanWebp(w, q, appPort) { +async function expectAvifSmallerThanWebp( + w: number, + q: number, + appPort: number +) { const query = { url: '/mountains.jpg', w, q } const res1 = await fetchViaHTTP(appPort, '/_next/image', query, { headers: { @@ -130,7 +149,7 @@ async function fetchWithDuration( return { duration, buffer, res } } -export function runTests(ctx) { +export function runTests(ctx: RunTestsCtx) { const { isDev, nextConfigImages } = ctx const { contentDispositionType = 'inline', @@ -279,7 +298,7 @@ export function runTests(ctx) { expect(ctx.nextOutput).toContain(animatedWarnText) }) - if (ctx.dangerouslyAllowSVG) { + if (ctx.nextConfigImages?.dangerouslyAllowSVG) { it('should maintain vector svg', async () => { const query = { w: ctx.w, q: 90, url: '/test.svg' } const opts = { headers: { accept: 'image/webp' } } @@ -303,7 +322,12 @@ export function runTests(ctx) { 'utf8' ) expect(actual).toMatch(expected) - expect(ctx.nextOutput).not.toContain('The requested resource') + expect(ctx.nextOutput).not.toContain( + `The requested resource isn't a valid image` + ) + expect(ctx.nextOutput).not.toContain( + `valid but image type is not allowed` + ) }) } else { it('should not allow vector svg', async () => { @@ -343,6 +367,16 @@ export function runTests(ctx) { '"url" parameter is valid but image type is not allowed' ) }) + + it('should not allow svg with wrong header', async () => { + const query = { w: ctx.w, q: 65, url: '/api/wrong-header.svg' } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) + expect(res.status).toBe(400) + expect(await res.text()).toContain( + '"url" parameter is valid but image type is not allowed' + ) + }) } it('should maintain ico format', async () => { @@ -820,8 +854,14 @@ export function runTests(ctx) { await cleanImagesDir(ctx) const delay = 500 + if (globalThis.isrImgQuality) { + globalThis.isrImgQuality++ + } else { + globalThis.isrImgQuality = 40 + } + const url = `http://localhost:${slowImageServer.port}/slow.png?delay=${delay}` - const query = { url, w: ctx.w, q: 39 } + const query = { url, w: ctx.w, q: globalThis.isrImgQuality } const opts = { headers: { accept: 'image/webp' } } const one = await fetchWithDuration( @@ -864,9 +904,9 @@ export function runTests(ctx) { const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) - if (ctx.minimumCacheTTL) { + if (ctx.nextConfigImages?.minimumCacheTTL) { // Wait until expired so we can confirm image is regenerated - await waitFor(ctx.minimumCacheTTL * 1000) + await waitFor(ctx.nextConfigImages.minimumCacheTTL * 1000) const [three, four] = await Promise.all([ fetchWithDuration(ctx.appPort, '/_next/image', query, opts), @@ -968,7 +1008,17 @@ export function runTests(ctx) { it('should use cache and stale-while-revalidate when query is the same for internal image', async () => { await cleanImagesDir(ctx) - const query = { url: '/test.png', w: ctx.w, q: 80 } + if (globalThis.isrImgQuality) { + globalThis.isrImgQuality++ + } else { + globalThis.isrImgQuality = 80 + } + + const query = { + url: '/api/stateful/test.png', + w: ctx.w, + q: globalThis.isrImgQuality, + } const opts = { headers: { accept: 'image/webp' } } const one = await fetchWithDuration( @@ -1010,16 +1060,15 @@ export function runTests(ctx) { const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) - if (ctx.minimumCacheTTL) { + if (ctx.nextConfigImages?.minimumCacheTTL) { // Wait until expired so we can confirm image is regenerated - await waitFor(ctx.minimumCacheTTL * 1000) + await waitFor(ctx.nextConfigImages.minimumCacheTTL * 1000) const [three, four] = await Promise.all([ fetchWithDuration(ctx.appPort, '/_next/image', query, opts), fetchWithDuration(ctx.appPort, '/_next/image', query, opts), ]) - expect(three.duration).toBeLessThan(one.duration) expect(three.res.status).toBe(200) expect(three.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(three.res.headers.get('Content-Type')).toBe('image/webp') @@ -1027,7 +1076,6 @@ export function runTests(ctx) { `${contentDispositionType}; filename="test.webp"` ) - expect(four.duration).toBeLessThan(one.duration) expect(four.res.status).toBe(200) expect(four.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(four.res.headers.get('Content-Type')).toBe('image/webp') @@ -1069,7 +1117,7 @@ export function runTests(ctx) { } }) - if (ctx.dangerouslyAllowSVG) { + if (ctx.nextConfigImages?.dangerouslyAllowSVG) { it('should use cached image file when parameters are the same for svg', async () => { await cleanImagesDir(ctx) @@ -1340,7 +1388,7 @@ export function runTests(ctx) { } } -export const setupTests = (ctx) => { +export const setupTests = (ctx: SetupTestsCtx) => { const nextConfig = new File(join(ctx.appDir, 'next.config.js')) describe('dev support w/o next.config.js', () => { @@ -1349,7 +1397,7 @@ export const setupTests = (ctx) => { return } const size = 384 // defaults defined in server/config.ts - const curCtx = { + const curCtx: RunTestsCtx = { ...ctx, w: size, isDev: true, @@ -1382,7 +1430,7 @@ export const setupTests = (ctx) => { describe('dev support with next.config.js', () => { const size = 400 - const curCtx = { + const curCtx: RunTestsCtx = { ...ctx, w: size, isDev: true, @@ -1394,7 +1442,7 @@ export const setupTests = (ctx) => { 'assets.vercel.com', 'image-optimization-test.vercel.app', ], - formats: ['image/avif', 'image/webp'], + formats: ['image/avif', 'image/webp'] as any, deviceSizes: [largeSize], imageSizes: [size], ...ctx.nextConfigImages, @@ -1425,91 +1473,89 @@ export const setupTests = (ctx) => { runTests(curCtx) }) - ;(process.env.TURBOPACK_DEV ? describe.skip : describe)( - 'Production Mode Server support w/o next.config.js', - () => { - if (ctx.nextConfigImages) { - // skip this test because it requires next.config.js - return - } - const size = 384 // defaults defined in server/config.ts - const curCtx = { - ...ctx, - w: size, - isDev: false, - } - beforeAll(async () => { - const json = JSON.stringify({ - experimental: { - outputFileTracingRoot: join(__dirname, '../../../..'), - }, - }) - nextConfig.replace('{ /* replaceme */ }', json) - curCtx.nextOutput = '' - await nextBuild(curCtx.appDir) - await cleanImagesDir(ctx) - curCtx.appPort = await findPort() - curCtx.app = await nextStart(curCtx.appDir, curCtx.appPort, { - onStderr(msg) { - curCtx.nextOutput += msg - }, - cwd: curCtx.appDir, - }) + ;(process.env.TURBOPACK_DEV || process.env.TURBOPACK_BUILD + ? describe.skip + : describe)('Production Mode Server support w/o next.config.js', () => { + if (ctx.nextConfigImages) { + // skip this test because it requires next.config.js + return + } + const size = 384 // defaults defined in server/config.ts + const curCtx: RunTestsCtx = { + ...ctx, + w: size, + isDev: false, + } + beforeAll(async () => { + const json = JSON.stringify({ + experimental: { + outputFileTracingRoot: join(__dirname, '../../../..'), + }, }) - afterAll(async () => { - nextConfig.restore() - if (curCtx.app) await killApp(curCtx.app) + nextConfig.replace('{ /* replaceme */ }', json) + curCtx.nextOutput = '' + await nextBuild(curCtx.appDir) + await cleanImagesDir(ctx) + curCtx.appPort = await findPort() + curCtx.app = await nextStart(curCtx.appDir, curCtx.appPort, { + onStderr(msg) { + curCtx.nextOutput += msg + }, + cwd: curCtx.appDir, }) + }) + afterAll(async () => { + nextConfig.restore() + if (curCtx.app) await killApp(curCtx.app) + }) - runTests(curCtx) + runTests(curCtx) + }) + ;(process.env.TURBOPACK_DEV || process.env.TURBOPACK_BUILD + ? describe.skip + : describe)('Production Mode Server support with next.config.js', () => { + const size = 399 + const curCtx: RunTestsCtx = { + ...ctx, + w: size, + isDev: false, + nextConfigImages: { + domains: [ + 'localhost', + '127.0.0.1', + 'example.com', + 'assets.vercel.com', + 'image-optimization-test.vercel.app', + ], + formats: ['image/avif', 'image/webp'] as any, + deviceSizes: [size, largeSize], + ...ctx.nextConfigImages, + }, } - ) - ;(process.env.TURBOPACK_DEV ? describe.skip : describe)( - 'Production Mode Server support with next.config.js', - () => { - const size = 399 - const curCtx = { - ...ctx, - w: size, - isDev: false, - nextConfigImages: { - domains: [ - 'localhost', - '127.0.0.1', - 'example.com', - 'assets.vercel.com', - 'image-optimization-test.vercel.app', - ], - formats: ['image/avif', 'image/webp'], - deviceSizes: [size, largeSize], - ...ctx.nextConfigImages, + beforeAll(async () => { + const json = JSON.stringify({ + images: curCtx.nextConfigImages, + experimental: { + outputFileTracingRoot: join(__dirname, '../../../..'), }, - } - beforeAll(async () => { - const json = JSON.stringify({ - images: curCtx.nextConfigImages, - experimental: { - outputFileTracingRoot: join(__dirname, '../../../..'), - }, - }) - curCtx.nextOutput = '' - nextConfig.replace('{ /* replaceme */ }', json) - await nextBuild(curCtx.appDir) - await cleanImagesDir(ctx) - curCtx.appPort = await findPort() - curCtx.app = await nextStart(curCtx.appDir, curCtx.appPort, { - onStderr(msg) { - curCtx.nextOutput += msg - }, - cwd: curCtx.appDir, - }) }) - afterAll(async () => { - nextConfig.restore() - if (curCtx.app) await killApp(curCtx.app) + curCtx.nextOutput = '' + nextConfig.replace('{ /* replaceme */ }', json) + await nextBuild(curCtx.appDir) + await cleanImagesDir(ctx) + curCtx.appPort = await findPort() + curCtx.app = await nextStart(curCtx.appDir, curCtx.appPort, { + onStderr(msg) { + curCtx.nextOutput += msg + }, + cwd: curCtx.appDir, }) + }) + afterAll(async () => { + nextConfig.restore() + if (curCtx.app) await killApp(curCtx.app) + }) - runTests(curCtx) - } - ) + runTests(curCtx) + }) } diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index 38e08bbbba840..861b17c3df266 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -10842,12 +10842,133 @@ "with contentDispositionType attachment dev support with next.config.js should use cached image file when parameters are the same for animated gif" ], "failed": [ + "with contentDispositionType attachment Production Mode Server support w/o next.config.js should set cache-control to immutable for static images", "with contentDispositionType attachment Production Mode Server support with next.config.js should set cache-control to immutable for static images" ], "pending": [], "flakey": [], "runtimeError": false }, + "test/integration/image-optimizer/test/dangerously-allow-svg.test.ts": { + "passed": [ + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should automatically detect image type when content-type is octet-stream", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should compress avif smaller than webp at q=100", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should compress avif smaller than webp at q=50", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should compress avif smaller than webp at q=75", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should downlevel avif format to jpeg for old Safari", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should downlevel webp format to jpeg for old Safari", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should emit blur svg when width is 8 in dev but not prod", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should emit blur svg when width is less than 8 in dev but not prod", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should error if the image file does not exist", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should error if the resource isn't a valid image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when domain is not defined in next.config.js", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when internal url is not an image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when q is greater than 100", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when q is less than 1", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when q is missing", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when q is not a number", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when url fails to load an image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when url has file protocol", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when url has ftp protocol", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when url is missing", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when w is 0", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when w is less than 0", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when w is missing", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when w is not a number", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should fail when width is not in next.config.js", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should handle concurrent requests", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should handle non-ascii characters in image url", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain animated gif", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain animated png", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain animated png 2", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain animated webp", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain bmp", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain ico format", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain jpg format for old Safari", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should maintain png format for old Safari", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should normalize invalid status codes", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should not allow svg with application header", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should not allow svg with comma header", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should not allow svg with uppercase header", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should not allow vector svg", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should not resize if requested width is larger than original source image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize absolute url from localhost", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url and new Chrome accept header as avif", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url and old Chrome accept header as webp", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url and png accept header", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url and webp Firefox accept header", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url with invalid accept header as gif", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url with invalid accept header as png", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should resize relative url with invalid accept header as tiff", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should return home page", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should set 304 status without body when etag matches if-none-match", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should use cache and stale-while-revalidate when query is the same for external image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should use cache and stale-while-revalidate when query is the same for internal image", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should use cached image file when parameters are the same for animated gif", + "with dangerouslyAllowSVG config dev support with next.config.js should automatically detect image type when content-type is octet-stream", + "with dangerouslyAllowSVG config dev support with next.config.js should compress avif smaller than webp at q=100", + "with dangerouslyAllowSVG config dev support with next.config.js should compress avif smaller than webp at q=50", + "with dangerouslyAllowSVG config dev support with next.config.js should compress avif smaller than webp at q=75", + "with dangerouslyAllowSVG config dev support with next.config.js should downlevel avif format to jpeg for old Safari", + "with dangerouslyAllowSVG config dev support with next.config.js should downlevel webp format to jpeg for old Safari", + "with dangerouslyAllowSVG config dev support with next.config.js should emit blur svg when width is 8 in dev but not prod", + "with dangerouslyAllowSVG config dev support with next.config.js should emit blur svg when width is less than 8 in dev but not prod", + "with dangerouslyAllowSVG config dev support with next.config.js should error if the image file does not exist", + "with dangerouslyAllowSVG config dev support with next.config.js should error if the resource isn't a valid image", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when domain is not defined in next.config.js", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when internal url is not an image", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when q is greater than 100", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when q is less than 1", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when q is missing", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when q is not a number", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when url fails to load an image", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when url has file protocol", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when url has ftp protocol", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when url is missing", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when w is 0", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when w is less than 0", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when w is missing", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when w is not a number", + "with dangerouslyAllowSVG config dev support with next.config.js should fail when width is not in next.config.js", + "with dangerouslyAllowSVG config dev support with next.config.js should handle concurrent requests", + "with dangerouslyAllowSVG config dev support with next.config.js should handle non-ascii characters in image url", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain animated gif", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain animated png", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain animated png 2", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain animated webp", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain bmp", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain ico format", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain jpg format for old Safari", + "with dangerouslyAllowSVG config dev support with next.config.js should maintain png format for old Safari", + "with dangerouslyAllowSVG config dev support with next.config.js should normalize invalid status codes", + "with dangerouslyAllowSVG config dev support with next.config.js should not allow svg with application header", + "with dangerouslyAllowSVG config dev support with next.config.js should not allow svg with comma header", + "with dangerouslyAllowSVG config dev support with next.config.js should not allow svg with uppercase header", + "with dangerouslyAllowSVG config dev support with next.config.js should not allow vector svg", + "with dangerouslyAllowSVG config dev support with next.config.js should not resize if requested width is larger than original source image", + "with dangerouslyAllowSVG config dev support with next.config.js should resize absolute url from localhost", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url and new Chrome accept header as avif", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url and old Chrome accept header as webp", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url and png accept header", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url and webp Firefox accept header", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url with invalid accept header as gif", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url with invalid accept header as png", + "with dangerouslyAllowSVG config dev support with next.config.js should resize relative url with invalid accept header as tiff", + "with dangerouslyAllowSVG config dev support with next.config.js should return home page", + "with dangerouslyAllowSVG config dev support with next.config.js should set 304 status without body when etag matches if-none-match", + "with dangerouslyAllowSVG config dev support with next.config.js should set cache-control to immutable for static images", + "with dangerouslyAllowSVG config dev support with next.config.js should use cache and stale-while-revalidate when query is the same for external image", + "with dangerouslyAllowSVG config dev support with next.config.js should use cache and stale-while-revalidate when query is the same for internal image", + "with dangerouslyAllowSVG config dev support with next.config.js should use cached image file when parameters are the same for animated gif" + ], + "failed": [ + "with dangerouslyAllowSVG config Production Mode Server support w/o next.config.js should set cache-control to immutable for static images", + "with dangerouslyAllowSVG config Production Mode Server support with next.config.js should set cache-control to immutable for static images" + ], + "pending": [], + "flakey": [], + "runtimeError": false + }, "test/integration/image-optimizer/test/index.test.ts": { "passed": [ "Image Optimizer External rewrite support with for serving static content in images production mode should return response when image is served from an external rewrite", @@ -10995,6 +11116,7 @@ "with minimumCacheTTL of 5 sec dev support with next.config.js should use cached image file when parameters are the same for animated gif" ], "failed": [ + "with minimumCacheTTL of 5 sec Production Mode Server support w/o next.config.js should set cache-control to immutable for static images", "with minimumCacheTTL of 5 sec Production Mode Server support with next.config.js should set cache-control to immutable for static images" ], "pending": [], diff --git a/test/unit/image-optimizer/detect-content-type.test.ts b/test/unit/image-optimizer/detect-content-type.test.ts index 24df3cd9a48d3..d8731cca0096a 100644 --- a/test/unit/image-optimizer/detect-content-type.test.ts +++ b/test/unit/image-optimizer/detect-content-type.test.ts @@ -22,6 +22,10 @@ describe('detectContentType', () => { const buffer = await getImage('./images/test.svg') expect(detectContentType(buffer)).toBe('image/svg+xml') }) + it('should return svg for inline svg', async () => { + const buffer = await getImage('./images/test-inline.svg') + expect(detectContentType(buffer)).toBe('image/svg+xml') + }) it('should return avif', async () => { const buffer = await getImage('./images/test.avif') expect(detectContentType(buffer)).toBe('image/avif') diff --git a/test/unit/image-optimizer/images/test-inline.svg b/test/unit/image-optimizer/images/test-inline.svg new file mode 100644 index 0000000000000..811eeaf6b154e --- /dev/null +++ b/test/unit/image-optimizer/images/test-inline.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file