Skip to content

Commit 474b209

Browse files
authored
fix: Download of cyrilic named files doesn't work on E2EE rooms (RocketChat#39612)
1 parent b93ddd6 commit 474b209

6 files changed

Lines changed: 65 additions & 26 deletions

File tree

.changeset/small-pants-reflect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
---
4+
5+
Fixes the download of attachments with non-unicode names on E2EE rooms

apps/meteor/client/components/message/hooks/useNormalizedMessage.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ const normalizeAttachments = (attachments: MessageAttachment[], name?: string, t
4040

4141
if (isFileAttachment(attachment)) {
4242
if (attachment.title_link && !attachment.title_link.startsWith('/file-decrypt/')) {
43-
attachment.title_link = `/file-decrypt${attachment.title_link}?key=${key}`;
43+
attachment.title_link = `/file-decrypt${attachment.title_link}?key=${encodeURIComponent(key)}`;
4444
}
4545
if (isFileImageAttachment(attachment) && !attachment.image_url.startsWith('/file-decrypt/')) {
46-
attachment.image_url = `/file-decrypt${attachment.image_url}?key=${key}`;
46+
attachment.image_url = `/file-decrypt${attachment.image_url}?key=${encodeURIComponent(key)}`;
4747
}
4848
if (isFileAudioAttachment(attachment) && !attachment.audio_url.startsWith('/file-decrypt/')) {
49-
attachment.audio_url = `/file-decrypt${attachment.audio_url}?key=${key}`;
49+
attachment.audio_url = `/file-decrypt${attachment.audio_url}?key=${encodeURIComponent(key)}`;
5050
}
5151
if (isFileVideoAttachment(attachment) && !attachment.video_url.startsWith('/file-decrypt/')) {
52-
attachment.video_url = `/file-decrypt${attachment.video_url}?key=${key}`;
52+
attachment.video_url = `/file-decrypt${attachment.video_url}?key=${encodeURIComponent(key)}`;
5353
}
5454
}
5555

apps/meteor/client/views/room/ImageGallery/hooks/useImagesList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const useImagesList = ({ roomId, startingFromId }: { roomId: IRoom['_id']
4040
type: decrypted.type,
4141
}),
4242
);
43-
decrypted.path = `/file-decrypt${decrypted.path}?key=${key}`;
43+
decrypted.path = `/file-decrypt${decrypted.path}?key=${encodeURIComponent(key)}`;
4444
Object.assign(file, decrypted);
4545
}
4646
}

apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const useFilesList = ({ rid, type, text }: { rid: Required<IUpload>['rid'
5656
type: decrypted.type,
5757
}),
5858
);
59-
decrypted.path = `/file-decrypt${decrypted.path}?key=${key}`;
59+
decrypted.path = `/file-decrypt${decrypted.path}?key=${encodeURIComponent(key)}`;
6060
Object.assign(file, decrypted);
6161
}
6262
}

apps/meteor/public/enc.js

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
self.addEventListener('install', function(event) {
2-
event.waitUntil(self.skipWaiting()); // Activate worker immediately
1+
self.addEventListener('install', function (event) {
2+
event.waitUntil(self.skipWaiting()); // Activate worker immediately
33
});
44

5-
self.addEventListener('activate', function(event) {
6-
event.waitUntil(self.clients.claim()); // Become available to all pages
5+
self.addEventListener('activate', function (event) {
6+
event.waitUntil(self.clients.claim()); // Become available to all pages
77
});
88

99
function base64Decode(string) {
@@ -32,7 +32,13 @@ const decrypt = async (key, iv, file) => {
3232
const getUrlParams = (url) => {
3333
const urlObj = new URL(url, location.origin);
3434

35-
const k = base64DecodeString(urlObj.searchParams.get('key'));
35+
const rawKey = urlObj.searchParams.get('key');
36+
if (!rawKey) {
37+
throw new Error('Missing "key" query param');
38+
}
39+
40+
41+
const k = base64DecodeString(decodeURIComponent(rawKey));
3642

3743
urlObj.searchParams.delete('key');
3844

@@ -74,7 +80,7 @@ self.addEventListener('fetch', (event) => {
7480
const result = await decrypt(key, iv, file);
7581

7682
const newHeaders = new Headers(res.headers);
77-
newHeaders.set('Content-Disposition', 'inline; filename="'+name+'"');
83+
newHeaders.set('Content-Disposition', 'inline; filename="' + name + '"');
7884
newHeaders.set('Content-Type', type);
7985

8086
const response = new Response(result, {
@@ -114,18 +120,17 @@ self.addEventListener('message', async (event) => {
114120

115121
const file = await res.arrayBuffer();
116122
const result = await decrypt(key, iv, file);
117-
event.source
118-
.postMessage({
119-
id: event.data.id,
120-
type: 'attachment-download-result',
121-
result,
122-
});
123-
// .catch((error) => {
124-
// console.error('Posting message failed:', error);
125-
// event.source.postMessage({
126-
// id: event.data.id,
127-
// type: 'attachment-download-result',
128-
// error,
129-
// });
130-
// });
123+
event.source.postMessage({
124+
id: event.data.id,
125+
type: 'attachment-download-result',
126+
result,
127+
});
128+
// .catch((error) => {
129+
// console.error('Posting message failed:', error);
130+
// event.source.postMessage({
131+
// id: event.data.id,
132+
// type: 'attachment-download-result',
133+
// error,
134+
// });
135+
// });
131136
});

apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,35 @@ test.describe('E2EE File Encryption', () => {
9696
});
9797
});
9898

99+
test('File with Unicode filename uploads and downloads correctly', async ({ page }) => {
100+
const UNICODE_FILE_NAME = 'Новый текстовый документ.txt';
101+
102+
await test.step('upload file with Unicode filename', async () => {
103+
await poHomeChannel.content.sendFileMessage(TEST_FILE_TXT);
104+
await poHomeChannel.composer.getFileByName(TEST_FILE_TXT).click();
105+
await poHomeChannel.content.inputFileUploadName.fill(UNICODE_FILE_NAME);
106+
await poHomeChannel.content.btnUpdateFileUpload.click();
107+
await poHomeChannel.composer.btnSend.click();
108+
109+
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
110+
await expect(poHomeChannel.content.getLastMessageByFileName(UNICODE_FILE_NAME)).toBeVisible();
111+
});
112+
113+
await test.step('download the file and verify the Unicode filename is preserved', async () => {
114+
await poHomeChannel.roomToolbar.openMoreOptions();
115+
await poHomeChannel.roomToolbar.menuItemFiles.click();
116+
117+
await expect(poHomeChannel.tabs.files.getFileByName(UNICODE_FILE_NAME)).toBeVisible();
118+
119+
const [download] = await Promise.all([
120+
page.waitForEvent('download'),
121+
poHomeChannel.tabs.files.getFileByName(UNICODE_FILE_NAME).click(),
122+
]);
123+
124+
expect(download.suggestedFilename()).toBe(UNICODE_FILE_NAME);
125+
});
126+
});
127+
99128
test('File encryption with whitelisted and blacklisted media types', async ({ api }) => {
100129
await test.step('send a text file in channel', async () => {
101130
const updatedFileName = `edited_${TEST_FILE_TXT}`;

0 commit comments

Comments
 (0)