From 97836d4b7c59e829214c1c0077cf2fd3e8323895 Mon Sep 17 00:00:00 2001 From: silver Date: Tue, 17 Mar 2026 14:40:08 +0100 Subject: [PATCH 1/3] fix: sanitize name of attached file Signed-off-by: silver [skip ci] --- src/components/Editor/MediaHandler.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Editor/MediaHandler.vue b/src/components/Editor/MediaHandler.vue index 9b42cb66194..28048da485b 100644 --- a/src/components/Editor/MediaHandler.vue +++ b/src/components/Editor/MediaHandler.vue @@ -167,6 +167,7 @@ export default { }) }, 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 + '/' @@ -175,7 +176,7 @@ export default { }) // 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) From 6ad0647b957bad8f9bc13ecee250861eca3cd6ff Mon Sep 17 00:00:00 2001 From: silver Date: Tue, 17 Mar 2026 15:50:49 +0100 Subject: [PATCH 2/3] fix: sanitize name of file in src Signed-off-by: silver --- src/components/Editor/MediaHandler.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Editor/MediaHandler.vue b/src/components/Editor/MediaHandler.vue index 28048da485b..c1382188906 100644 --- a/src/components/Editor/MediaHandler.vue +++ b/src/components/Editor/MediaHandler.vue @@ -171,7 +171,7 @@ export default { // 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 From ce4b89db2c034fb6dd11b0aa8ca2f3f02582da20 Mon Sep 17 00:00:00 2001 From: silver Date: Wed, 18 Mar 2026 11:47:32 +0100 Subject: [PATCH 3/3] test: is rtlo being stripped from attached filenames? Signed-off-by: silver --- cypress/e2e/attachments.spec.js | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/cypress/e2e/attachments.spec.js b/cypress/e2e/attachments.spec.js index e19c5bc7b92..fdb794a0ac9 100644 --- a/cypress/e2e/attachments.spec.js +++ b/cypress/e2e/attachments.spec.js @@ -279,6 +279,49 @@ 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.getEditor() + .find('[data-component="image-view"]') + .should('have.length', 1) + .should('have.attr', 'data-src', `.attachments.${documentId}/${encodedName}`) + }) + }) + cy.closeFile() + }) + it('test if attachment files are in the attachment folder', () => { cy.visit('/apps/files')