Skip to content

Commit 595aa41

Browse files
Merge pull request #19236 from nextcloud/backport/19228/stable33
[stable33] fix(FilePreview): sanitize file name
2 parents 52b84ee + 6b75dfc commit 595aa41

4 files changed

Lines changed: 46 additions & 8 deletions

File tree

src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
@click.exact="handleClick"
2020
@keydown.enter="handleClick">
2121
<span
22-
:title="file.name"
22+
:title="sanitizedFileName"
2323
class="image-container"
2424
:class="{ playable: isPlayable }"
2525
:style="imageContainerStyle">
2626
<img
2727
class="file-preview__image"
2828
:class="previewImageClass"
29-
:alt="file.name"
29+
:alt="sanitizedFileName"
3030
:src="failed ? defaultIconUrl : previewUrl"
3131
@load="onLoad"
3232
@error="onError">
@@ -65,7 +65,8 @@
6565
</template>
6666
</NcButton>
6767
<div v-if="shouldShowFileDetail" class="name-container">
68-
{{ fileDetail }}
68+
<span class="name-container__basename">{{ fileNameWithoutExtension }}</span>
69+
<span v-if="fileExtension" class="name-container__extension">{{ fileExtension }}</span>
6970
</div>
7071
</component>
7172
</template>
@@ -88,6 +89,7 @@ import { SHARED_ITEM } from '../../../../../constants.ts'
8889
import { getTalkConfig } from '../../../../../services/CapabilitiesManager.ts'
8990
import { useActorStore } from '../../../../../stores/actor.ts'
9091
import { useSharedItemsStore } from '../../../../../stores/sharedItems.ts'
92+
import { getFileExtension, sanitizeFileName } from '../../../../../utils/fileUpload.js'
9193
9294
const PREVIEW_TYPE = {
9395
TEMPORARY: 0,
@@ -215,8 +217,18 @@ export default {
215217
)
216218
},
217219
218-
fileDetail() {
219-
return this.file.name
220+
// file.name with bidi control chars replaced by '_', for title/alt/aria-label text
221+
sanitizedFileName() {
222+
return sanitizeFileName(this.file.name)
223+
},
224+
225+
fileNameWithoutExtension() {
226+
return this.file.name.slice(0, this.file.name.length - getFileExtension(this.file.name).length)
227+
},
228+
229+
// Dot included, original case
230+
fileExtension() {
231+
return getFileExtension(this.file.name)
220232
},
221233
222234
fallbackLocalUrl() {
@@ -452,7 +464,7 @@ export default {
452464
},
453465
454466
removeAriaLabel() {
455-
return t('spreed', 'Remove {fileName}', { fileName: this.file.name })
467+
return t('spreed', 'Remove {fileName}', { fileName: this.sanitizedFileName })
456468
},
457469
},
458470
@@ -648,7 +660,19 @@ export default {
648660
width: 100%;
649661
overflow: hidden;
650662
white-space: nowrap;
651-
text-overflow: ellipsis;
663+
display: inline-flex;
664+
665+
&__basename {
666+
unicode-bidi: isolate;
667+
overflow: hidden;
668+
white-space: nowrap;
669+
text-overflow: ellipsis;
670+
}
671+
672+
&__extension {
673+
color: var(--color-text-maxcontrast);
674+
overflow: visible;
675+
}
652676
}
653677
654678
&:not(.file-preview--viewer-available) {

src/test-setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ vi.mock('@nextcloud/dialogs', () => ({
2828

2929
vi.mock('@nextcloud/files', () => ({
3030
validateFileName: vi.fn(),
31+
formatFileSize: vi.fn((size) => `${size} B`),
3132
}))
3233

3334
vi.mock('@nextcloud/files/dav', () => ({

src/utils/fileUpload.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
const extensionRegex = /\.[0-9a-z]+$/i
77
const suffixRegex = / \(\d+\)$/
8+
const bidiControlRegex = /[\u202A-\u202E\u2066-\u2069]/g
89

910
/**
1011
* Returns the file extension for the given path
@@ -16,6 +17,16 @@ function getFileExtension(path) {
1617
return path.match(extensionRegex)?.[0] ?? ''
1718
}
1819

20+
/**
21+
* Returns name with bidi control characters replaced by '_'
22+
*
23+
* @param {string} name file name
24+
* @return {string} sanitized file name
25+
*/
26+
export function sanitizeFileName(name) {
27+
return name.replace(bidiControlRegex, '_')
28+
}
29+
1930
/**
2031
* Returns the file suffix for the given path
2132
*

src/utils/textParse.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { ChatMessage, Mention } from '../types/index.ts'
88
import { getBaseUrl } from '@nextcloud/router'
99
import { decodeHTML } from 'entities'
1010
import { MENTION } from '../constants.ts'
11+
import { sanitizeFileName } from './fileUpload.js'
1112

1213
/**
1314
* Parse message text to return proper formatting for mentions
@@ -63,7 +64,8 @@ function parseToSimpleMessage(text: string, parameters: ChatMessage['messagePara
6364
}
6465

6566
Object.entries(parameters).forEach(([key, value]) => {
66-
text = text.replaceAll('{' + key + '}', value.name)
67+
const name = key === 'file' ? sanitizeFileName(value.name) : value.name
68+
text = text.replaceAll('{' + key + '}', name)
6769
})
6870
return text.trim()
6971
}

0 commit comments

Comments
 (0)