aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src
diff options
context:
space:
mode:
authorMaksim Sukharev <antreesy.web@gmail.com>2024-07-22 18:02:41 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2024-07-24 13:14:56 +0200
commit5709e54b8d5e6467bbc834363b23e5cb4c1e8c98 (patch)
treee31c3eeee5dbd9c03e56996d0a990c538d3bad27 /apps/files/src
parentfd9de4024b3d9cdcde62f22f65a6428c62f367ac (diff)
downloadnextcloud-server-5709e54b8d5e6467bbc834363b23e5cb4c1e8c98.tar.gz
nextcloud-server-5709e54b8d5e6467bbc834363b23e5cb4c1e8c98.zip
fix(files): validate input when creating file/directory
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
Diffstat (limited to 'apps/files/src')
-rw-r--r--apps/files/src/components/FileEntry/FileEntryName.vue13
-rw-r--r--apps/files/src/components/NewNodeDialog.vue62
2 files changed, 67 insertions, 8 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue
index 4e5a3571e74..4fb907dd005 100644
--- a/apps/files/src/components/FileEntry/FileEntryName.vue
+++ b/apps/files/src/components/FileEntry/FileEntryName.vue
@@ -211,23 +211,22 @@ export default defineComponent({
isFileNameValid(name: string) {
const trimmedName = name.trim()
+ const char = trimmedName.indexOf('/') !== -1
+ ? '/'
+ : forbiddenCharacters.find((char) => trimmedName.includes(char))
+
if (trimmedName === '.' || trimmedName === '..') {
throw new Error(t('files', '"{name}" is an invalid file name.', { name }))
} else if (trimmedName.length === 0) {
throw new Error(t('files', 'File name cannot be empty.'))
- } else if (trimmedName.indexOf('/') !== -1) {
- throw new Error(t('files', '"/" is not allowed inside a file name.'))
+ } else if (char) {
+ throw new Error(t('files', '"{char}" is not allowed inside a file name.', { char }))
} else if (trimmedName.match(window.OC.config.blacklist_files_regex)) {
throw new Error(t('files', '"{name}" is not an allowed filetype.', { name }))
} else if (this.checkIfNodeExists(name)) {
throw new Error(t('files', '{newName} already exists.', { newName: name }))
}
- const char = forbiddenCharacters.find((char) => trimmedName.includes(char))
- if (char) {
- throw new Error(t('files', '"{char}" is not allowed inside a file name.', { char }))
- }
-
return true
},
diff --git a/apps/files/src/components/NewNodeDialog.vue b/apps/files/src/components/NewNodeDialog.vue
index d73e363e39f..7a99c3062d5 100644
--- a/apps/files/src/components/NewNodeDialog.vue
+++ b/apps/files/src/components/NewNodeDialog.vue
@@ -20,10 +20,12 @@
<form @submit.prevent="onCreate">
<NcTextField ref="input"
data-cy-files-new-node-dialog-input
+ class="dialog__input"
:error="!isUniqueName"
:helper-text="errorMessage"
:label="label"
- :value.sync="localDefaultName" />
+ :value.sync="localDefaultName"
+ @keyup="checkInputValidity" />
</form>
</NcDialog>
</template>
@@ -34,15 +36,19 @@ import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { translate as t } from '@nextcloud/l10n'
import { getUniqueName } from '@nextcloud/files'
+import { loadState } from '@nextcloud/initial-state'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import logger from '../logger.js'
interface ICanFocus {
focus: () => void
}
+const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', [])
+
export default defineComponent({
name: 'NewNodeDialog',
components: {
@@ -147,6 +153,60 @@ export default defineComponent({
this.$emit('close', null)
}
},
+
+ /**
+ * Check if the file name is valid and update the
+ * input validity using browser's native validation.
+ * @param event the keyup event
+ */
+ checkInputValidity(event: KeyboardEvent) {
+ const input = event.target as HTMLInputElement
+ const newName = this.localDefaultName.trim?.() || ''
+ logger.debug('Checking input validity', { newName })
+ try {
+ this.isFileNameValid(newName)
+ input.setCustomValidity('')
+ input.title = ''
+ } catch (e) {
+ if (e instanceof Error) {
+ input.setCustomValidity(e.message)
+ input.title = e.message
+ } else {
+ input.setCustomValidity(t('files', 'Invalid file name'))
+ }
+ } finally {
+ input.reportValidity()
+ }
+ },
+
+ isFileNameValid(name: string) {
+ const trimmedName = name.trim()
+ const char = trimmedName.indexOf('/') !== -1
+ ? '/'
+ : forbiddenCharacters.find((char) => trimmedName.includes(char))
+
+ if (trimmedName === '.' || trimmedName === '..') {
+ throw new Error(t('files', '"{name}" is an invalid file name.', { name }))
+ } else if (trimmedName.length === 0) {
+ throw new Error(t('files', 'File name cannot be empty.'))
+ } else if (char) {
+ throw new Error(t('files', '"{char}" is not allowed inside a file name.', { char }))
+ } else if (trimmedName.match(window.OC.config.blacklist_files_regex)) {
+ throw new Error(t('files', '"{name}" is not an allowed filetype.', { name }))
+ }
+
+ return true
+ },
},
})
</script>
+
+<style lang="scss" scoped>
+.dialog__input {
+ :deep(input:invalid) {
+ // Show red border on invalid input
+ border-color: var(--color-error);
+ color: red;
+ }
+}
+</style>