mirror of
https://github.com/nextcloud/server.git
synced 2026-06-06 23:34:22 -04:00
fix(files): validate input when creating file/directory
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
This commit is contained in:
parent
fd9de4024b
commit
5709e54b8d
2 changed files with 67 additions and 8 deletions
|
|
@ -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
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue