Skip to content

Commit

Permalink
Merge pull request #13220 from nextcloud/feature/compatible-file-names
Browse files Browse the repository at this point in the history
Feature Compatible File Names
  • Loading branch information
tobiasKaminsky authored Aug 23, 2024
2 parents 5002593 + 425288e commit 6d32493
Show file tree
Hide file tree
Showing 37 changed files with 2,532 additions and 620 deletions.
1,233 changes: 1,233 additions & 0 deletions app/schemas/com.nextcloud.client.database.NextcloudDatabase/82.json

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
193 changes: 193 additions & 0 deletions app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

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.OCCapability
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

@Suppress("TooManyFunctions")
class FileNameValidatorTests : AbstractIT() {

private var capability: OCCapability = fileDataStorageManager.getCapability(account.name)

@Before
fun setup() {
capability = capability.apply {
forbiddenFilenamesJson = """[".htaccess",".htaccess"]"""
forbiddenFilenameBaseNamesJson = """
["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 = """["<", ">", ":", "\\\\", "/", "|", "?", "*", "&"]"""
}
}

@Test
fun testInvalidCharacter() {
val result = FileNameValidator.checkFileName("file<name", capability, targetContext)
assertEquals(
String.format(targetContext.getString(R.string.file_name_validator_error_invalid_character), "<"),
result
)
}

@Test
fun testReservedName() {
val result = FileNameValidator.checkFileName("CON", capability, targetContext)
assertEquals(targetContext.getString(R.string.file_name_validator_error_reserved_names, "CON"), result)
}

@Test
fun testForbiddenFilenameExtension() {
val result = FileNameValidator.checkFileName("my_fav_file.filepart", capability, targetContext)
assertEquals(
targetContext.getString(R.string.file_name_validator_error_forbidden_file_extensions, "filepart"),
result
)
}

@Test
fun testEndsWithSpaceOrPeriod() {
val result = FileNameValidator.checkFileName("filename ", capability, targetContext)
assertEquals(targetContext.getString(R.string.file_name_validator_error_ends_with_space_period), result)

val result2 = FileNameValidator.checkFileName("filename.", capability, targetContext)
assertEquals(targetContext.getString(R.string.file_name_validator_error_ends_with_space_period), result2)
}

@Test
fun testEmptyFileName() {
val result = FileNameValidator.checkFileName("", capability, targetContext)
assertEquals(targetContext.getString(R.string.filename_empty), result)
}

@Test
fun testFileAlreadyExists() {
val existingFiles = mutableSetOf("existingFile")
val result = FileNameValidator.checkFileName("existingFile", capability, targetContext, existingFiles)
assertEquals(targetContext.getString(R.string.file_already_exists), result)
}

@Test
fun testValidFileName() {
val result = FileNameValidator.checkFileName("validFileName", capability, targetContext)
assertNull(result)
}

@Test
fun testIsFileHidden() {
assertTrue(FileNameValidator.isFileHidden(".hiddenFile"))
assertFalse(FileNameValidator.isFileHidden("visibleFile"))
}

@Test
fun testIsFileNameAlreadyExist() {
val existingFiles = mutableSetOf("existingFile")
assertTrue(FileNameValidator.isFileNameAlreadyExist("existingFile", existingFiles))
assertFalse(FileNameValidator.isFileNameAlreadyExist("newFile", existingFiles))
}

@Test
fun testValidFolderAndFilePaths() {
val folderPath = "validFolder"
val filePaths = listOf("file1.txt", "file2.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertTrue(result)
}

@Test
fun testFolderPathWithReservedName() {
val folderPath = "CON"
val filePaths = listOf("file1.txt", "file2.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFilePathWithReservedName() {
val folderPath = "validFolder"
val filePaths = listOf("file1.txt", "PRN.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFolderPathWithInvalidCharacter() {
val folderPath = "invalid<Folder"
val filePaths = listOf("file1.txt", "file2.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFilePathWithInvalidCharacter() {
val folderPath = "validFolder"
val filePaths = listOf("file1.txt", "file|2.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFolderPathEndingWithSpace() {
val folderPath = "folderWithSpace "
val filePaths = listOf("file1.txt", "file2.doc", "file3.jpg")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFilePathEndingWithPeriod() {
val folderPath = "validFolder"
val filePaths = listOf("file1.txt", "file2.doc", "file3.")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testFilePathWithNestedFolder() {
val folderPath = "validFolder\\secondValidFolder\\CON"
val filePaths = listOf("file1.txt", "file2.doc", "file3.")

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, filePaths, capability, targetContext)
assertFalse(result)
}

@Test
fun testOnlyFolderPath() {
val folderPath = "/A1/Aaaww/W/C2/"

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, listOf(), capability, targetContext)
assertTrue(result)
}

@Test
fun testOnlyFolderPathWithOneReservedName() {
val folderPath = "/A1/Aaaww/CON/W/C2/"

val result = FileNameValidator.checkFolderAndFilePaths(folderPath, listOf(), capability, targetContext)
assertFalse(result)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AssistantMockRepository(private val giveEmptyTasks: Boolean = false) : Ass
return RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
}

@Suppress("LongMethod")
override fun getTaskList(appId: String): RemoteOperationResult<TaskList> {
val taskList = if (giveEmptyTasks) {
TaskList(listOf())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ import com.owncloud.android.db.ProviderMeta
AutoMigration(from = 77, to = 78),
AutoMigration(from = 78, to = 79, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
AutoMigration(from = 79, to = 80),
AutoMigration(from = 80, to = 81)
AutoMigration(from = 80, to = 81),
AutoMigration(from = 81, to = 82)
],
exportSchema = true
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,13 @@ data class CapabilityEntity(
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT)
val dropAccount: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SECURITY_GUARD)
val securityGuard: Int?
val securityGuard: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS)
val forbiddenFileNameCharacters: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES)
val forbiddenFileNames: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS)
val forbiddenFileNameExtensions: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_BASE_NAMES)
val forbiddenFilenameBaseNames: Int?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* 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<String> = jsonToList(forbiddenFilenamesJson)

fun OCCapability.forbiddenFilenameCharacters(): List<String> = jsonToList(forbiddenFilenameCharactersJson)

fun OCCapability.forbiddenFilenameExtension(): List<String> = jsonToList(forbiddenFilenameExtensionJson)

fun OCCapability.forbiddenFilenameBaseNames(): List<String> = jsonToList(forbiddenFilenameBaseNamesJson)

@Suppress("ReturnCount")
private fun jsonToList(json: String?): List<String> {
if (json == null) return emptyList()

return try {
return gson.fromJson(json, Array<String>::class.java).toList()
} catch (e: JSONException) {
emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,18 @@ fun String.getRandomString(length: Int): String {

return this + result
}

fun String.removeFileExtension(): String {
val dotIndex = lastIndexOf('.')
return if (dotIndex != -1) {
substring(0, dotIndex)
} else {
this
}
}

object StringConstants {
const val SLASH = "/"
const val DOT = "."
const val SPACE = " "
}
Loading

0 comments on commit 6d32493

Please sign in to comment.