diff --git a/app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt b/app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt index 4058e8688b..c39655edc2 100644 --- a/app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt +++ b/app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt @@ -10,7 +10,6 @@ package com.nextcloud.utils import com.nextcloud.utils.fileNameValidator.FileNameValidator import com.owncloud.android.AbstractIT import com.owncloud.android.R -import com.owncloud.android.lib.resources.status.CapabilityBooleanType import com.owncloud.android.lib.resources.status.OCCapability import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -26,9 +25,13 @@ class FileNameValidatorTests : AbstractIT() { @Before fun setup() { capability = capability.apply { - forbiddenFilenames = CapabilityBooleanType.TRUE - forbiddenFilenameExtension = CapabilityBooleanType.TRUE - forbiddenFilenameCharacters = CapabilityBooleanType.TRUE + forbiddenFilenamesJson = + """["CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³", + "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", + "LPT8", "LPT9", "LPT¹", "LPT²", "LPT³"]""" + forbiddenFilenameExtensionJson = """"[".filepart",".part"]""" + forbiddenFilenameCharactersJson = """["\\[<>:\"/\\\\|?*\\]","\\[/<>|:&\\]"]""" } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCCapabilityExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCCapabilityExtensions.kt new file mode 100644 index 0000000000..1f012517d3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCCapabilityExtensions.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2024 Your Name + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.google.gson.Gson +import com.owncloud.android.lib.resources.status.OCCapability +import org.json.JSONException + +private val gson = Gson() + +fun OCCapability.forbiddenFilenames(): List = jsonToList(forbiddenFilenamesJson) + +fun OCCapability.forbiddenFilenameCharacters(): List = jsonToList(forbiddenFilenameCharactersJson) + +fun OCCapability.forbiddenFilenameExtension(): List = jsonToList(forbiddenFilenameExtensionJson) + +private fun jsonToList(json: String?): List { + if (json == null) return emptyList() + + return try { + return gson.fromJson(json, Array::class.java).toList() + } catch (e: JSONException) { + emptyList() + } +} diff --git a/app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt b/app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt index 4d95bd36b0..3e9b82efe2 100644 --- a/app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt +++ b/app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt @@ -10,22 +10,15 @@ package com.nextcloud.utils.fileNameValidator import android.content.Context import android.text.TextUtils import com.nextcloud.utils.extensions.dot +import com.nextcloud.utils.extensions.forbiddenFilenameCharacters +import com.nextcloud.utils.extensions.forbiddenFilenameExtension +import com.nextcloud.utils.extensions.forbiddenFilenames import com.nextcloud.utils.extensions.removeFileExtension import com.nextcloud.utils.extensions.space import com.owncloud.android.R import com.owncloud.android.lib.resources.status.OCCapability object FileNameValidator { - private val reservedWindowsChars = "[<>:\"/\\\\|?*]".toRegex() - private val reservedUnixChars = "[/<>|:&]".toRegex() - private val reservedWindowsNames = listOf( - "CON", "PRN", "AUX", "NUL", - "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "COM¹", "COM²", "COM³", - "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", - "LPT¹", "LPT²", "LPT³" - ) - private val forbiddenFileExtensions = listOf(".filepart", ".part") /** * Checks the validity of a file name. @@ -61,25 +54,28 @@ object FileNameValidator { return it } - if (capability.forbiddenFilenames.isTrue && - ( - reservedWindowsNames.contains(filename.uppercase()) || - reservedWindowsNames.contains(filename.removeFileExtension().uppercase()) + capability.forbiddenFilenamesJson?.let { + val forbiddenFilenames = capability.forbiddenFilenames() + if (forbiddenFilenames.contains(filename.uppercase()) || forbiddenFilenames.contains( + filename.removeFileExtension().uppercase() ) - ) { - return context.getString(R.string.file_name_validator_error_reserved_names, filename.substringBefore(dot())) + ) { + return context.getString( + R.string.file_name_validator_error_reserved_names, + filename.substringBefore(dot()) + ) + } } - if (capability.forbiddenFilenameExtension.isTrue && forbiddenFileExtensions.any { - filename.endsWith( - it, - ignoreCase = true + capability.forbiddenFilenameExtensionJson?.let { + val forbiddenFilenameExtension = capability.forbiddenFilenameExtension() + + if (forbiddenFilenameExtension.any { filename.endsWith(it, ignoreCase = true) }) { + return context.getString( + R.string.file_name_validator_error_forbidden_file_extensions, + filename.substringAfter(dot()) ) - }) { - return context.getString( - R.string.file_name_validator_error_forbidden_file_extensions, - filename.substringAfter(dot()) - ) + } } return null @@ -114,16 +110,19 @@ object FileNameValidator { @Suppress("ReturnCount") private fun checkInvalidCharacters(name: String, capability: OCCapability, context: Context): String? { - if (capability.forbiddenFilenameCharacters.isFalse) return null + capability.forbiddenFilenameCharactersJson?.let { + val forbiddenFilenameCharacters = capability.forbiddenFilenameCharacters() - val invalidCharacter = name.find { - val input = it.toString() - input.matches(reservedWindowsChars) || input.matches(reservedUnixChars) + val invalidCharacter = forbiddenFilenameCharacters.find { forbiddenSuffix -> + name.endsWith(forbiddenSuffix, ignoreCase = true) + } + + if (invalidCharacter == null) return null + + return context.getString(R.string.file_name_validator_error_invalid_character, invalidCharacter) } - if (invalidCharacter == null) return null - - return context.getString(R.string.file_name_validator_error_invalid_character, invalidCharacter) + return null } fun isFileHidden(name: String): Boolean = !TextUtils.isEmpty(name) && name[0] == '.' diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 54ff115fc3..fbfe32b526 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -2053,9 +2053,9 @@ public class FileDataStorageManager { contentValues.put(ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT, capability.getDropAccount().getValue()); contentValues.put(ProviderTableMeta.CAPABILITIES_SECURITY_GUARD, capability.getSecurityGuard().getValue()); - contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS, capability.getForbiddenFilenameCharacters().getValue()); - contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES, capability.getForbiddenFilenames().getValue()); - contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS, capability.getForbiddenFilenameExtension().getValue()); + contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS, capability.getForbiddenFilenameCharactersJson()); + contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES, capability.getForbiddenFilenamesJson()); + contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS, capability.getForbiddenFilenameExtensionJson()); return contentValues; } @@ -2226,9 +2226,9 @@ public class FileDataStorageManager { capability.setDropAccount(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT)); capability.setSecurityGuard(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SECURITY_GUARD)); - capability.setForbiddenFilenameCharacters(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS)); - capability.setForbiddenFilenames(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES)); - capability.setForbiddenFilenameExtension(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS)); + capability.setForbiddenFilenameCharactersJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS)); + capability.setForbiddenFilenamesJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES)); + capability.setForbiddenFilenameExtensionJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS)); } return capability;