Skip to content

Commit 78a17b7

Browse files
Merge pull request #15824 from nextcloud/skip-auto-rename-if-wcf-disabled
skip auto rename and filename validator if wcf disabled
2 parents ea25524 + c136c2e commit 78a17b7

File tree

12 files changed

+1343
-10
lines changed

12 files changed

+1343
-10
lines changed

app/schemas/com.nextcloud.client.database.NextcloudDatabase/94.json

Lines changed: 1210 additions & 0 deletions
Large diffs are not rendered by default.

app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package com.nextcloud.utils
1010
import com.nextcloud.utils.autoRename.AutoRename
1111
import com.owncloud.android.AbstractOnServerIT
1212
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile
13+
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
1314
import com.owncloud.android.lib.resources.status.NextcloudVersion
1415
import com.owncloud.android.lib.resources.status.OCCapability
1516
import org.junit.Before
@@ -27,6 +28,7 @@ class AutoRenameTests : AbstractOnServerIT() {
2728
testOnlyOnServer(NextcloudVersion.nextcloud_30)
2829

2930
capability = capability.apply {
31+
isWCFEnabled = CapabilityBooleanType.TRUE
3032
forbiddenFilenameExtensionJson = listOf(
3133
"""[" ",".",".part",".part"]""",
3234
"""[".",".part",".part"," "]""",
@@ -238,4 +240,14 @@ class AutoRenameTests : AbstractOnServerIT() {
238240
val expectedFilename = "Foo.Bar.Baz"
239241
assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
240242
}
243+
244+
@Test
245+
fun skipAutoRenameWhenWCFDisabled() {
246+
capability = capability.apply {
247+
isWCFEnabled = CapabilityBooleanType.FALSE
248+
}
249+
val filename = " readme.txt "
250+
val result = AutoRename.rename(filename, capability, isFolderPath = true)
251+
assert(result == filename) { "Expected $filename but got $result" }
252+
}
241253
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <[email protected]>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
package com.nextcloud.utils
8+
9+
import com.nextcloud.utils.extensions.checkWCFRestrictions
10+
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
11+
import com.owncloud.android.lib.resources.status.NextcloudVersion
12+
import com.owncloud.android.lib.resources.status.OCCapability
13+
import org.junit.Assert.assertFalse
14+
import org.junit.Assert.assertTrue
15+
import org.junit.Test
16+
17+
@Suppress("MagicNumber")
18+
class CheckWCFRestrictionsTests {
19+
20+
private fun createCapability(
21+
version: NextcloudVersion,
22+
isWCFEnabled: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN
23+
): OCCapability = OCCapability().apply {
24+
this.versionMayor = version.majorVersionNumber
25+
this.isWCFEnabled = isWCFEnabled
26+
}
27+
28+
@Test
29+
fun testReturnsFalseForVersionsOlderThan30() {
30+
val capability = createCapability(NextcloudVersion.nextcloud_29)
31+
assertFalse(capability.checkWCFRestrictions())
32+
}
33+
34+
@Test
35+
fun testReturnsTrueForVersion30WhenWCFAlwaysEnabled() {
36+
val capability = createCapability(NextcloudVersion.nextcloud_30)
37+
assertTrue(capability.checkWCFRestrictions())
38+
}
39+
40+
@Test
41+
fun testReturnsTrueForVersion31WhenWCFAlwaysEnabled() {
42+
val capability = createCapability(NextcloudVersion.nextcloud_31)
43+
assertTrue(capability.checkWCFRestrictions())
44+
}
45+
46+
@Test
47+
fun testReturnsTrueForVersion32WhenWCFEnabled() {
48+
val capability = createCapability(NextcloudVersion.nextcloud_32, CapabilityBooleanType.TRUE)
49+
assertTrue(capability.checkWCFRestrictions())
50+
}
51+
52+
@Test
53+
fun testReturnsFalseForVersion32WhenWCFDisabled() {
54+
val capability = createCapability(NextcloudVersion.nextcloud_32, CapabilityBooleanType.FALSE)
55+
assertFalse(capability.checkWCFRestrictions())
56+
}
57+
58+
@Test
59+
fun testReturnsFalseForVersion32WhenWCFIsUnknown() {
60+
val capability = createCapability(NextcloudVersion.nextcloud_32)
61+
assertFalse(capability.checkWCFRestrictions())
62+
}
63+
}

app/src/androidTest/java/com/nextcloud/utils/FileNameValidatorTests.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package com.nextcloud.utils
1010
import com.nextcloud.utils.fileNameValidator.FileNameValidator
1111
import com.owncloud.android.AbstractOnServerIT
1212
import com.owncloud.android.R
13+
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
1314
import com.owncloud.android.lib.resources.status.NextcloudVersion
1415
import com.owncloud.android.lib.resources.status.OCCapability
1516
import org.junit.Assert.assertEquals
@@ -27,6 +28,7 @@ class FileNameValidatorTests : AbstractOnServerIT() {
2728
@Before
2829
fun setup() {
2930
capability = capability.apply {
31+
isWCFEnabled = CapabilityBooleanType.TRUE
3032
forbiddenFilenamesJson = """[".htaccess",".htaccess"]"""
3133
forbiddenFilenameBaseNamesJson = """
3234
["con", "prn", "aux", "nul", "com0", "com1", "com2", "com3", "com4",
@@ -228,4 +230,14 @@ class FileNameValidatorTests : AbstractOnServerIT() {
228230
val result = FileNameValidator.checkFolderAndFilePaths(folderPath, listOf(), capability, targetContext)
229231
assertFalse(result)
230232
}
233+
234+
@Test
235+
fun skipValidationWhenWCFDisabled() {
236+
capability = capability.apply {
237+
isWCFEnabled = CapabilityBooleanType.FALSE
238+
}
239+
val filename = "abc.txt"
240+
val result = FileNameValidator.checkFileName(filename, capability, targetContext)
241+
assertNull(result)
242+
}
231243
}

app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ import com.owncloud.android.db.ProviderMeta
8383
AutoMigration(from = 89, to = 90),
8484
AutoMigration(from = 90, to = 91),
8585
AutoMigration(from = 91, to = 92),
86-
AutoMigration(from = 92, to = 93, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
86+
AutoMigration(from = 92, to = 93, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
87+
AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
8788
],
8889
exportSchema = true
8990
)

app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,7 @@ data class CapabilityEntity(
142142
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_DEFAULT_PERMISSIONS)
143143
val defaultPermissions: Int?,
144144
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_BUSY)
145-
val userStatusSupportsBusy: Int?
145+
val userStatusSupportsBusy: Int?,
146+
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_WINDOWS_COMPATIBLE_FILENAMES)
147+
val isWCFEnabled: Int?
146148
)

app/src/main/java/com/nextcloud/utils/autoRename/AutoRename.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
package com.nextcloud.utils.autoRename
99

1010
import com.nextcloud.utils.extensions.StringConstants
11+
import com.nextcloud.utils.extensions.checkWCFRestrictions
1112
import com.nextcloud.utils.extensions.forbiddenFilenameCharacters
1213
import com.nextcloud.utils.extensions.forbiddenFilenameExtensions
1314
import com.nextcloud.utils.extensions.shouldRemoveNonPrintableUnicodeCharactersAndConvertToUTF8
1415
import com.owncloud.android.datamodel.OCFile
1516
import com.owncloud.android.lib.common.utils.Log_OC
16-
import com.owncloud.android.lib.resources.status.NextcloudVersion
1717
import com.owncloud.android.lib.resources.status.OCCapability
1818
import org.apache.commons.io.FilenameUtils
1919
import java.util.regex.Pattern
@@ -25,12 +25,12 @@ object AutoRename {
2525
@Suppress("NestedBlockDepth")
2626
@JvmOverloads
2727
fun rename(filename: String, capability: OCCapability, isFolderPath: Boolean? = null): String {
28-
Log_OC.d(TAG, "Before - $filename")
29-
30-
if (!capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30)) {
28+
if (!capability.checkWCFRestrictions()) {
3129
return filename
3230
}
3331

32+
Log_OC.d(TAG, "Before - $filename")
33+
3434
val isFolder = isFolderPath ?: filename.endsWith(OCFile.PATH_SEPARATOR)
3535
val pathSegments = filename.split(OCFile.PATH_SEPARATOR).toMutableList()
3636

app/src/main/java/com/nextcloud/utils/extensions/OCCapabilityExtensions.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,31 @@
88
package com.nextcloud.utils.extensions
99

1010
import com.google.gson.Gson
11+
import com.owncloud.android.lib.resources.status.NextcloudVersion
1112
import com.owncloud.android.lib.resources.status.OCCapability
1213
import org.json.JSONException
1314

1415
private val gson = Gson()
1516

17+
/**
18+
* Determines whether **Windows-compatible file (WCF)** restrictions should be applied
19+
* for the current server version and configuration.
20+
*
21+
* Behavior:
22+
* - For **Nextcloud 32 and newer**, WCF enforcement depends on the [`isWCFEnabled`] flag
23+
* provided by the server capabilities.
24+
* - For **Nextcloud 30 and 31**, WCF restrictions are always applied (feature considered enabled).
25+
* - For **versions older than 30**, WCF is not supported, and no restrictions are applied.
26+
*
27+
* @return `true` if WCF restrictions should be enforced based on the server version and configuration;
28+
* `false` otherwise.
29+
*/
30+
fun OCCapability.checkWCFRestrictions(): Boolean = if (version.isNewerOrEqual(NextcloudVersion.nextcloud_32)) {
31+
isWCFEnabled.isTrue
32+
} else {
33+
version.isNewerOrEqual(NextcloudVersion.nextcloud_30)
34+
}
35+
1636
fun OCCapability.forbiddenFilenames(): List<String> = jsonToList(forbiddenFilenamesJson)
1737

1838
fun OCCapability.forbiddenFilenameCharacters(): List<String> = jsonToList(forbiddenFilenameCharactersJson)
@@ -33,7 +53,7 @@ private fun jsonToList(json: String?): List<String> {
3353

3454
return try {
3555
return gson.fromJson(json, Array<String>::class.java).toList()
36-
} catch (e: JSONException) {
56+
} catch (_: JSONException) {
3757
emptyList()
3858
}
3959
}

app/src/main/java/com/nextcloud/utils/fileNameValidator/FileNameValidator.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ package com.nextcloud.utils.fileNameValidator
1010
import android.content.Context
1111
import android.text.TextUtils
1212
import com.nextcloud.utils.extensions.StringConstants
13+
import com.nextcloud.utils.extensions.checkWCFRestrictions
1314
import com.nextcloud.utils.extensions.forbiddenFilenameBaseNames
1415
import com.nextcloud.utils.extensions.forbiddenFilenameCharacters
1516
import com.nextcloud.utils.extensions.forbiddenFilenameExtensions
1617
import com.nextcloud.utils.extensions.forbiddenFilenames
1718
import com.nextcloud.utils.extensions.removeFileExtension
1819
import com.owncloud.android.R
1920
import com.owncloud.android.datamodel.OCFile
20-
import com.owncloud.android.lib.resources.status.NextcloudVersion
2121
import com.owncloud.android.lib.resources.status.OCCapability
2222

2323
object FileNameValidator {
@@ -49,10 +49,11 @@ object FileNameValidator {
4949
}
5050
}
5151

52-
if (!capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30)) {
52+
if (!capability.checkWCFRestrictions()) {
5353
return null
5454
}
5555

56+
// region WCF related checks
5657
checkInvalidCharacters(filename, capability, context)?.let { return it }
5758

5859
val filenameVariants = setOf(filename.lowercase(), filename.removeFileExtension().lowercase())
@@ -91,6 +92,7 @@ object FileNameValidator {
9192
}
9293
}
9394
}
95+
// endregion
9496

9597
return null
9698
}

app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,7 @@ private ContentValues createContentValues(String accountName, OCCapability capab
24112411
contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES, capability.getForbiddenFilenamesJson());
24122412
contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS, capability.getForbiddenFilenameExtensionJson());
24132413
contentValues.put(ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_BASE_NAMES, capability.getForbiddenFilenameBaseNamesJson());
2414+
contentValues.put(ProviderTableMeta.CAPABILITIES_WINDOWS_COMPATIBLE_FILENAMES, capability.isWCFEnabled().getValue());
24142415
contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT, capability.getFilesDownloadLimit().getValue());
24152416
contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT_DEFAULT, capability.getFilesDownloadLimitDefault());
24162417

@@ -2595,6 +2596,7 @@ private OCCapability createCapabilityInstance(Cursor cursor) {
25952596
capability.setForbiddenFilenamesJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES));
25962597
capability.setForbiddenFilenameExtensionJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS));
25972598
capability.setForbiddenFilenameBaseNamesJson(getString(cursor, ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_BASE_NAMES));
2599+
capability.setWCFEnabled(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_WINDOWS_COMPATIBLE_FILENAMES));
25982600
capability.setFilesDownloadLimit(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT));
25992601
capability.setFilesDownloadLimitDefault(getInt(cursor, ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT_DEFAULT));
26002602

0 commit comments

Comments
 (0)