Skip to content

Commit

Permalink
chore(next/image): improve imgopt api bypass detection for unsupporte…
Browse files Browse the repository at this point in the history
…d images (#74569)

Backport #73909 to 15.1.x
  • Loading branch information
styfle authored Jan 7, 2025
1 parent 4cbaaa1 commit 5a501d3
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 12 deletions.
18 changes: 11 additions & 7 deletions packages/next/src/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ const JPEG = 'image/jpeg'
const GIF = 'image/gif'
const SVG = 'image/svg+xml'
const ICO = 'image/x-icon'
const ICNS = 'image/x-icns'
const TIFF = 'image/tiff'
const BMP = 'image/bmp'
const CACHE_VERSION = 4
const ANIMATABLE_TYPES = [WEBP, PNG, GIF]
const VECTOR_TYPES = [SVG]
const BYPASS_TYPES = [SVG, ICO, ICNS, BMP]
const BLUR_IMG_SIZE = 8 // should match `next-image-loader`
const BLUR_QUALITY = 70 // should match `next-image-loader`

Expand Down Expand Up @@ -186,6 +187,9 @@ export function detectContentType(buffer: Buffer) {
if ([0x00, 0x00, 0x01, 0x00].every((b, i) => buffer[i] === b)) {
return ICO
}
if ([0x69, 0x63, 0x6e, 0x73].every((b, i) => buffer[i] === b)) {
return ICNS
}
if ([0x49, 0x49, 0x2a, 0x00].every((b, i) => buffer[i] === b)) {
return TIFF
}
Expand Down Expand Up @@ -673,7 +677,10 @@ export async function imageOptimizer(
}> {
const { href, quality, width, mimeType } = paramsResult
const { buffer: upstreamBuffer, etag: upstreamEtag } = imageUpstream
const maxAge = getMaxAge(imageUpstream.cacheControl)
const maxAge = Math.max(
nextConfig.images.minimumCacheTTL,
getMaxAge(imageUpstream.cacheControl)
)

const upstreamType =
detectContentType(upstreamBuffer) ||
Expand Down Expand Up @@ -708,10 +715,7 @@ export async function imageOptimizer(
upstreamEtag,
}
}
if (VECTOR_TYPES.includes(upstreamType)) {
// We don't warn here because we already know that "dangerouslyAllowSVG"
// was enabled above, therefore the user explicitly opted in.
// If we add more VECTOR_TYPES besides SVG, perhaps we could warn for those.
if (BYPASS_TYPES.includes(upstreamType)) {
return {
buffer: upstreamBuffer,
contentType: upstreamType,
Expand Down Expand Up @@ -790,7 +794,7 @@ export async function imageOptimizer(
return {
buffer: optimizedBuffer,
contentType,
maxAge: Math.max(maxAge, nextConfig.images.minimumCacheTTL),
maxAge,
etag: getImageEtag(optimizedBuffer),
upstreamEtag,
}
Expand Down
10 changes: 5 additions & 5 deletions test/integration/image-optimizer/test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export function runTests(ctx: RunTestsCtx) {
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('image/gif')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=0, must-revalidate`
`public, max-age=${isDev ? 0 : minimumCacheTTL}, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('etag')).toBeTruthy()
Expand All @@ -258,7 +258,7 @@ export function runTests(ctx: RunTestsCtx) {
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('image/png')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=0, must-revalidate`
`public, max-age=${isDev ? 0 : minimumCacheTTL}, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('etag')).toBeTruthy()
Expand All @@ -275,7 +275,7 @@ export function runTests(ctx: RunTestsCtx) {
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('image/png')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=0, must-revalidate`
`public, max-age=${isDev ? 0 : minimumCacheTTL}, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('etag')).toBeTruthy()
Expand All @@ -292,7 +292,7 @@ export function runTests(ctx: RunTestsCtx) {
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('image/webp')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=0, must-revalidate`
`public, max-age=${isDev ? 0 : minimumCacheTTL}, must-revalidate`
)
expect(res.headers.get('Vary')).toBe('Accept')
expect(res.headers.get('etag')).toBeTruthy()
Expand All @@ -312,7 +312,7 @@ export function runTests(ctx: RunTestsCtx) {
expect(res.headers.get('Content-Length')).toBe('603')
expect(res.headers.get('Content-Type')).toContain('image/svg+xml')
expect(res.headers.get('Cache-Control')).toBe(
`public, max-age=0, must-revalidate`
`public, max-age=${isDev ? 0 : minimumCacheTTL}, must-revalidate`
)
// SVG is compressible so will have accept-encoding set from
// compression
Expand Down
4 changes: 4 additions & 0 deletions test/unit/image-optimizer/detect-content-type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ describe('detectContentType', () => {
const buffer = await getImage('./images/test.ico')
expect(detectContentType(buffer)).toBe('image/x-icon')
})
it('should return icns', async () => {
const buffer = await getImage('./images/test.icns')
expect(detectContentType(buffer)).toBe('image/x-icns')
})
it('should return tiff', async () => {
const buffer = await getImage('./images/test.tiff')
expect(detectContentType(buffer)).toBe('image/tiff')
Expand Down
Binary file added test/unit/image-optimizer/images/test.icns
Binary file not shown.

0 comments on commit 5a501d3

Please sign in to comment.