diff --git a/cypress/e2e/attachments.spec.js b/cypress/e2e/attachments.spec.js index 5ae6840a497..f843ae30749 100644 --- a/cypress/e2e/attachments.spec.js +++ b/cypress/e2e/attachments.spec.js @@ -298,6 +298,48 @@ describe('Test all attachment insertion methods', () => { cy.closeFile() }) + it('Upload a local image file with RTLO character in name (RTLO is stripped)', () => { + const filename = randHash() + '.md' + cy.uploadFile('empty.md', 'text/markdown', filename) + cy.visit('/apps/files') + cy.openFile(filename) + + const requestAlias = 'uploadRTLORequest' + cy.intercept({ method: 'POST', url: '**/text/attachment/upload?**' }).as( + requestAlias, + ) + + clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE).then(() => { + cy.getEditor() + .find('input[type="file"][data-text-el="attachment-file-input"]') + .selectFile( + [ + { + contents: 'cypress/fixtures/github.png', + fileName: 'git\u202e.png', + }, + ], + { force: true }, + ) + + return cy.wait('@' + requestAlias).then((req) => { + const fileName = req.response.body.name // server echoes back name with RTLO + const documentId = req.response.body.documentId + + // insertAttachment strips RTLO from the name before building the src URL and the + // alt text. The src URL no longer matches the on-disk filename (which still has + // the RTLO), so the image 404s. The alt text shows the clean name, exposing the + // real file extension instead of the visually spoofed one. + const strippedName = fileName.replaceAll('\u202e', '') + const encodedName = fixedEncodeURIComponent(strippedName) + cy.get( + `.text-editor__main [data-component="image-view"][data-src=".attachments.${documentId}/${encodedName}"]`, + ).should('exist') + }) + }) + cy.closeFile() + }) + it('Create a new whiteboard file as an attachment', () => { const check = (documentId, fileName) => { cy.log( diff --git a/src/components/Editor/MediaHandler.vue b/src/components/Editor/MediaHandler.vue index 1eee020f0d7..2ba16828d16 100644 --- a/src/components/Editor/MediaHandler.vue +++ b/src/components/Editor/MediaHandler.vue @@ -228,17 +228,18 @@ export default { this.editor.chain().focus().insertPreview(href).run() }, insertAttachment(name, fileId, mimeType, position = null, dirname = '') { + const sanitizedName = name.replaceAll('\u202E', '') // inspired by the fixedEncodeURIComponent function suggested in // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent const src = dirname + '/' - + encodeURIComponent(name).replace(/[!'()*]/g, (c) => { + + encodeURIComponent(sanitizedName).replace(/[!'()*]/g, (c) => { return '%' + c.charCodeAt(0).toString(16).toUpperCase() }) // simply get rid of brackets to make sure link text is valid // as it does not need to be unique and matching the real file name - const alt = name.replaceAll(/[[\]]/g, '') + const alt = sanitizedName.replaceAll(/[[\]]/g, '') const chain = position ? this.editor.chain().focus(position)