mirror of
https://github.com/nextcloud/server.git
synced 2026-06-07 07:43:18 -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
b305be43ca
commit
1c0a1cbbe0
2 changed files with 67 additions and 10 deletions
|
|
@ -223,25 +223,22 @@ export default Vue.extend({
|
|||
},
|
||||
isFileNameValid(name) {
|
||||
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(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 toCheck = trimmedName.split('')
|
||||
toCheck.forEach(char => {
|
||||
if (forbiddenCharacters.indexOf(char) !== -1) {
|
||||
throw new Error(this.t('files', '"{char}" is not allowed inside a file name.', { char }))
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
checkIfNodeExists(name) {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,12 @@
|
|||
</template>
|
||||
<form @submit.prevent="onCreate">
|
||||
<NcTextField ref="input"
|
||||
class="dialog__input"
|
||||
:error="!isUniqueName"
|
||||
:helper-text="errorMessage"
|
||||
:label="label"
|
||||
:value.sync="localDefaultName" />
|
||||
:value.sync="localDefaultName"
|
||||
@keyup="checkInputValidity" />
|
||||
</form>
|
||||
</NcDialog>
|
||||
</template>
|
||||
|
|
@ -48,15 +50,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: {
|
||||
|
|
@ -161,6 +167,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