From 3ef6c15e36a92291b506ea6fcaf0d340b2f53264 Mon Sep 17 00:00:00 2001 From: Aniruddh Singh Date: Sun, 11 Oct 2026 21:52:48 +0530 Subject: [PATCH] Fix uppercase image extension MIME handling --- playwright/e2e/actions/edit.spec.ts | 37 ++++++++++++++++++++++++++++- src/components/ImageEditor.vue | 21 ++++++++++++---- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/playwright/e2e/actions/edit.spec.ts b/playwright/e2e/actions/edit.spec.ts index b35176eee..e124120c4 100644 --- a/playwright/e2e/actions/edit.spec.ts +++ b/playwright/e2e/actions/edit.spec.ts @@ -13,7 +13,11 @@ test.describe.serial('Open the new saved as image', () => { let page: Page test.beforeAll(async ({ browser }) => { - ({ page } = await setupFilesPage(browser, [{ fixture: 'image1.jpg', mimeType: 'image/jpeg' }])) + ({ page } = await setupFilesPage(browser, [ + { fixture: 'image1.jpg', mimeType: 'image/jpeg' }, + { fixture: 'image1.jpg', mimeType: 'image/jpeg', target: '/ABC1234.JPG' }, + { fixture: 'image1.jpg', mimeType: 'image/jpeg', target: '/ABC1234.JPEG' }, + ])) }) test.afterAll(async () => { @@ -51,4 +55,35 @@ test.describe.serial('Open the new saved as image', () => { test('See the new saved image in the list', async () => { await expect(getRowForFile(page, 'imageSave.jpg')).toContainText('imageSave .jpg') }) + + for (const [sourceName, savedName] of [ + ['ABC1234.JPG', 'imageSaveUppercaseJPG.jpg'], + ['ABC1234.JPEG', 'imageSaveUppercaseJPEG.jpeg'], + ] as const) { + test(`Save ${sourceName} as a valid JPEG with a lowercase extension`, async () => { + await openFile(page, sourceName) + await getEditButton(page).click() + await page.locator('.FIE_topbar-save-button').click() + + const nameInput = page.locator('input[type="text"].SfxInput-Base') + await nameInput.clear() + await nameInput.fill(savedName.slice(0, savedName.lastIndexOf('.'))) + + const uploadRequestPromise = page.waitForRequest(request => + request.method() === 'PUT' + && decodeURIComponent(new URL(request.url()).pathname).endsWith(`/${savedName}`), + ) + await page.locator('.SfxModal-Container button[color="primary"].SfxButton-root') + .filter({ hasText: 'Save' }) + .click() + + const uploadRequest = await uploadRequestPromise + expect(uploadRequest.postDataBuffer()?.subarray(0, 2)).toEqual(Buffer.from([0xFF, 0xD8])) + + await page.locator('.FIE_topbar-close-button').click() + await expect(getViewerName(page)).toContainText(savedName) + await getCloseButton(page).click() + await expect(getRowForFile(page, savedName)).toContainText(savedName) + }) + } }) diff --git a/src/components/ImageEditor.vue b/src/components/ImageEditor.vue index 60839d4db..96fb8ab72 100644 --- a/src/components/ImageEditor.vue +++ b/src/components/ImageEditor.vue @@ -116,8 +116,10 @@ export default { defaultSavedImageName() { return basename(this.decodedSourceName, extname(this.decodedSourceName)) }, + defaultSavedImageType() { - return extname(this.decodedSourceName).slice(1) || 'jpeg' + const extension = extname(this.decodedSourceName).slice(1) + return extension ? extension.toLowerCase() : 'jpeg' }, hasHighContrastEnabled() { @@ -278,14 +280,25 @@ export default { } // toBlob is not very smart... - mimeType = mimeType.replace('jpg', 'jpeg') + mimeType = mimeType.toLowerCase().replace(/^image\/jpg$/, 'image/jpeg') // Sanity check, 0 < quality < 1 quality = Math.max(Math.min(quality, 1), 0) || 1 try { - const blob = await new Promise(resolve => imageCanvas.toBlob(resolve, mimeType, quality)) - const response = await axios.put(putUrl, new File([blob], fullName)) + const blob = await new Promise(resolve => { + imageCanvas.toBlob(resolve, mimeType, quality) + }) + + if (!blob || blob.type !== mimeType) { + showError(t('viewer', 'Error saving image')) + return + } + + const response = await axios.put( + putUrl, + new File([blob], fullName), + ) logger.info('Edited image saved!', { response }) showSuccess(t('viewer', 'Image saved')) if (putUrl !== this.src) {