Skip to content

Commit 36b81f9

Browse files
authored
Fix: Added the content-disposition header (#27521)
In this PR I've added the `Content-Disposition` header to the response of the image `/_next/image` route. That header is used by the browser when showing the dialog for the `Save image as...` action. There are some differences between the browsers, ex: When requesting the image `test.jpg`, the response header `Content-Type: image/webp` - in FF the filename from the `Save image as...` dialog would be `test.webp` but in Chrome `test.jpg` even if the `Content-Disposition: inline; filename="test.webp"` is present in the headers. The same about png images, the rest types are fine. It looks like FF is checking the `Content-Type` for the extension but the Chrome does not and is doing another type of check. Fixes #26737 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [x] Make sure the linting passes
1 parent 1af7892 commit 36b81f9

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

packages/next/server/image-optimizer.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export async function imageOptimizer(
194194
const result = setResponseHeaders(
195195
req,
196196
res,
197+
url,
197198
etag,
198199
maxAge,
199200
contentType,
@@ -310,6 +311,7 @@ export async function imageOptimizer(
310311
sendResponse(
311312
req,
312313
res,
314+
url,
313315
maxAge,
314316
upstreamType,
315317
upstreamBuffer,
@@ -430,6 +432,7 @@ export async function imageOptimizer(
430432
sendResponse(
431433
req,
432434
res,
435+
url,
433436
maxAge,
434437
contentType,
435438
optimizedBuffer,
@@ -443,6 +446,7 @@ export async function imageOptimizer(
443446
sendResponse(
444447
req,
445448
res,
449+
url,
446450
maxAge,
447451
upstreamType,
448452
upstreamBuffer,
@@ -473,9 +477,25 @@ async function writeToCacheDir(
473477
await promises.writeFile(filename, buffer)
474478
}
475479

480+
function getFileNameWithExtension(
481+
url: string,
482+
contentType: string | null
483+
): string | void {
484+
const [urlWithoutQueryParams] = url.split('?')
485+
const fileNameWithExtension = urlWithoutQueryParams.split('/').pop()
486+
if (!contentType || !fileNameWithExtension) {
487+
return
488+
}
489+
490+
const [fileName] = fileNameWithExtension.split('.')
491+
const extension = getExtension(contentType)
492+
return `${fileName}.${extension}`
493+
}
494+
476495
function setResponseHeaders(
477496
req: IncomingMessage,
478497
res: ServerResponse,
498+
url: string,
479499
etag: string,
480500
maxAge: number,
481501
contentType: string | null,
@@ -496,12 +516,19 @@ function setResponseHeaders(
496516
if (contentType) {
497517
res.setHeader('Content-Type', contentType)
498518
}
519+
520+
const fileName = getFileNameWithExtension(url, contentType)
521+
if (fileName) {
522+
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`)
523+
}
524+
499525
return { finished: false }
500526
}
501527

502528
function sendResponse(
503529
req: IncomingMessage,
504530
res: ServerResponse,
531+
url: string,
505532
maxAge: number,
506533
contentType: string | null,
507534
buffer: Buffer,
@@ -512,6 +539,7 @@ function sendResponse(
512539
const result = setResponseHeaders(
513540
req,
514541
res,
542+
url,
515543
etag,
516544
maxAge,
517545
contentType,

0 commit comments

Comments
 (0)