Skip to content

Commit 07b9068

Browse files
authored
Merge pull request #46717 from nextcloud/backport/46689/stable28
[stable28] fix(files): validate input when creating file/directory
2 parents b305be4 + 7bf8dba commit 07b9068

8 files changed

Lines changed: 76 additions & 19 deletions

File tree

apps/files/src/components/FileEntry/FileEntryName.vue

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -223,25 +223,22 @@ export default Vue.extend({
223223
},
224224
isFileNameValid(name) {
225225
const trimmedName = name.trim()
226+
const char = trimmedName.indexOf('/') !== -1
227+
? '/'
228+
: forbiddenCharacters.find((char) => trimmedName.includes(char))
229+
226230
if (trimmedName === '.' || trimmedName === '..') {
227231
throw new Error(t('files', '"{name}" is an invalid file name.', { name }))
228232
} else if (trimmedName.length === 0) {
229233
throw new Error(t('files', 'File name cannot be empty.'))
230-
} else if (trimmedName.indexOf('/') !== -1) {
231-
throw new Error(t('files', '"/" is not allowed inside a file name.'))
234+
} else if (char) {
235+
throw new Error(t('files', '"{char}" is not allowed inside a file name.', { char }))
232236
} else if (trimmedName.match(OC.config.blacklist_files_regex)) {
233237
throw new Error(t('files', '"{name}" is not an allowed filetype.', { name }))
234238
} else if (this.checkIfNodeExists(name)) {
235239
throw new Error(t('files', '{newName} already exists.', { newName: name }))
236240
}
237241
238-
const toCheck = trimmedName.split('')
239-
toCheck.forEach(char => {
240-
if (forbiddenCharacters.indexOf(char) !== -1) {
241-
throw new Error(this.t('files', '"{char}" is not allowed inside a file name.', { char }))
242-
}
243-
})
244-
245242
return true
246243
},
247244
checkIfNodeExists(name) {

apps/files/src/components/NewNodeDialog.vue

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
</template>
3535
<form @submit.prevent="onCreate">
3636
<NcTextField ref="input"
37+
class="dialog__input"
3738
:error="!isUniqueName"
3839
:helper-text="errorMessage"
3940
:label="label"
40-
:value.sync="localDefaultName" />
41+
:value.sync="localDefaultName"
42+
@keyup="checkInputValidity" />
4143
</form>
4244
</NcDialog>
4345
</template>
@@ -48,15 +50,19 @@ import type { PropType } from 'vue'
4850
import { defineComponent } from 'vue'
4951
import { translate as t } from '@nextcloud/l10n'
5052
import { getUniqueName } from '@nextcloud/files'
53+
import { loadState } from '@nextcloud/initial-state'
5154
5255
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
5356
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
5457
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
58+
import logger from '../logger.js'
5559
5660
interface ICanFocus {
5761
focus: () => void
5862
}
5963
64+
const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', [])
65+
6066
export default defineComponent({
6167
name: 'NewNodeDialog',
6268
components: {
@@ -161,6 +167,60 @@ export default defineComponent({
161167
this.$emit('close', null)
162168
}
163169
},
170+
171+
/**
172+
* Check if the file name is valid and update the
173+
* input validity using browser's native validation.
174+
* @param event the keyup event
175+
*/
176+
checkInputValidity(event: KeyboardEvent) {
177+
const input = event.target as HTMLInputElement
178+
const newName = this.localDefaultName.trim?.() || ''
179+
logger.debug('Checking input validity', { newName })
180+
try {
181+
this.isFileNameValid(newName)
182+
input.setCustomValidity('')
183+
input.title = ''
184+
} catch (e) {
185+
if (e instanceof Error) {
186+
input.setCustomValidity(e.message)
187+
input.title = e.message
188+
} else {
189+
input.setCustomValidity(t('files', 'Invalid file name'))
190+
}
191+
} finally {
192+
input.reportValidity()
193+
}
194+
},
195+
196+
isFileNameValid(name: string) {
197+
const trimmedName = name.trim()
198+
const char = trimmedName.indexOf('/') !== -1
199+
? '/'
200+
: forbiddenCharacters.find((char) => trimmedName.includes(char))
201+
202+
if (trimmedName === '.' || trimmedName === '..') {
203+
throw new Error(t('files', '"{name}" is an invalid file name.', { name }))
204+
} else if (trimmedName.length === 0) {
205+
throw new Error(t('files', 'File name cannot be empty.'))
206+
} else if (char) {
207+
throw new Error(t('files', '"{char}" is not allowed inside a file name.', { char }))
208+
} else if (trimmedName.match(window.OC.config.blacklist_files_regex)) {
209+
throw new Error(t('files', '"{name}" is not an allowed filetype.', { name }))
210+
}
211+
212+
return true
213+
},
164214
},
165215
})
166216
</script>
217+
218+
<style lang="scss" scoped>
219+
.dialog__input {
220+
:deep(input:invalid) {
221+
// Show red border on invalid input
222+
border-color: var(--color-error);
223+
color: red;
224+
}
225+
}
226+
</style>

dist/core-common.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core-common.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-init.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-init.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)