浏览代码

Merge pull request #10168 from pelzvieh/patch-1

Make SAF root IDs stable through server version updates
Álvaro Brey 3 年之前
父节点
当前提交
454f7c6b96

+ 1 - 1
app/src/androidTest/java/com/owncloud/android/providers/DocumentsStorageProviderIT.kt

@@ -38,7 +38,7 @@ class DocumentsStorageProviderIT : AbstractOnServerIT() {
     private val authority = context.getString(R.string.document_provider_authority)
 
     private val rootFileId = storageManager.getFileByEncryptedRemotePath(ROOT_PATH).fileId
-    private val documentId = "${user.hashCode()}${DOCUMENTID_SEPARATOR}$rootFileId"
+    private val documentId = "${DocumentsStorageProvider.rootIdForUser(user)}${DOCUMENTID_SEPARATOR}$rootFileId"
     private val uri = DocumentsContract.buildTreeDocumentUri(authority, documentId)
     private val rootDir get() = DocumentFile.fromTreeUri(context, uri)!!
 

+ 42 - 0
app/src/main/java/com/nextcloud/client/utils/HashUtil.kt

@@ -0,0 +1,42 @@
+/*
+ *  Nextcloud Android Library is available under MIT license
+ *
+ *  @author Álvaro Brey Vilas
+ *  Copyright (C) 2022 Álvaro Brey Vilas
+ *  Copyright (C) 2022 Nextcloud GmbH
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ *  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ *  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ */
+
+package com.nextcloud.client.utils
+
+import org.apache.commons.codec.binary.Hex
+import java.security.MessageDigest
+
+object HashUtil {
+    private const val ALGORITHM_MD5 = "MD5"
+
+    @JvmStatic
+    fun md5Hash(input: String): String {
+        val digest = MessageDigest.getInstance(ALGORITHM_MD5)
+            .digest(input.toByteArray())
+        return String(Hex.encodeHex(digest))
+    }
+}

+ 16 - 16
app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -40,7 +40,6 @@ import android.os.ParcelFileDescriptor;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.util.Log;
-import android.util.SparseArray;
 import android.widget.Toast;
 
 import com.nextcloud.client.account.User;
@@ -48,6 +47,7 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.files.downloader.DownloadTask;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferencesImpl;
+import com.nextcloud.client.utils.HashUtil;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -82,7 +82,9 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -113,7 +115,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @VisibleForTesting
     static final String DOCUMENTID_SEPARATOR = "/";
     private static final int DOCUMENTID_PARTS = 2;
-    private final SparseArray<FileDataStorageManager> rootIdToStorageManager = new SparseArray<>();
+    private final Map<String, FileDataStorageManager> rootIdToStorageManager = new HashMap<>();
 
     private final Executor executor = Executors.newCachedThreadPool();
 
@@ -132,8 +134,8 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         }
 
         final RootCursor result = new RootCursor(projection);
-        for(int i = 0; i < rootIdToStorageManager.size(); i++) {
-            result.addRoot(new Document(rootIdToStorageManager.valueAt(i), ROOT_PATH), getContext());
+        for(FileDataStorageManager manager: rootIdToStorageManager.values()) {
+            result.addRoot(new Document(manager, ROOT_PATH), getContext());
         }
 
         return result;
@@ -678,14 +680,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     }
 
     private FileDataStorageManager getStorageManager(String rootId) {
-        for(int i = 0; i < rootIdToStorageManager.size(); i++) {
-            FileDataStorageManager storageManager = rootIdToStorageManager.valueAt(i);
-            if (storageManager.getUser().nameEquals(rootId)) {
-                return storageManager;
-            }
-        }
+        return rootIdToStorageManager.get(rootId);
+    }
 
-        return null;
+    @VisibleForTesting
+    public static String rootIdForUser(User user) {
+        return HashUtil.md5Hash(user.getAccountName());
     }
 
     private void initiateStorageMap() {
@@ -696,7 +696,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
         for (User user : accountManager.getAllUsers()) {
             final FileDataStorageManager storageManager = new FileDataStorageManager(user, contentResolver);
-            rootIdToStorageManager.put(user.hashCode(), storageManager);
+            rootIdToStorageManager.put(rootIdForUser(user), storageManager);
         }
     }
 
@@ -725,7 +725,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             throw new FileNotFoundException("Invalid documentID " + documentId + "!");
         }
 
-        FileDataStorageManager storageManager = rootIdToStorageManager.get(Integer.parseInt(separated[0]));
+        FileDataStorageManager storageManager = rootIdToStorageManager.get(separated[0]);
         if (storageManager == null) {
             throw new FileNotFoundException("No storage manager associated for " + documentId + "!");
         }
@@ -803,9 +803,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         }
 
         public String getDocumentId() {
-            for(int i = 0; i < rootIdToStorageManager.size(); i++) {
-                if (Objects.equals(storageManager, rootIdToStorageManager.valueAt(i))) {
-                    return rootIdToStorageManager.keyAt(i) + DOCUMENTID_SEPARATOR + fileId;
+            for(String key: rootIdToStorageManager.keySet()) {
+                if (Objects.equals(storageManager, rootIdToStorageManager.get(key))) {
+                    return key + DOCUMENTID_SEPARATOR + fileId;
                 }
             }
             return null;

+ 53 - 0
app/src/test/java/com/nextcloud/client/utils/HashUtilTest.kt

@@ -0,0 +1,53 @@
+/*
+ *  Nextcloud Android Library is available under MIT license
+ *
+ *  @author Álvaro Brey Vilas
+ *  Copyright (C) 2022 Álvaro Brey Vilas
+ *  Copyright (C) 2022 Nextcloud GmbH
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ *  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ *  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ */
+
+package com.nextcloud.client.utils
+
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class HashUtilTest(private val input: String, private val expected: String) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun params(): List<Array<Any>> = listOf(
+            arrayOf("", "d41d8cd98f00b204e9800998ecf8427e"),
+            arrayOf("test", "098f6bcd4621d373cade4e832627b4f6"),
+            arrayOf("test@nextcloud.localhost", "12aa338095d171f307c3e3f724702ab1"),
+            arrayOf("tost@nextcloud.localhost", "e01e5301f90c123a65e872d68e84c4b2")
+        )
+    }
+
+    @Test
+    fun testMd5Hash() {
+        val hash = HashUtil.md5Hash(input)
+        Assert.assertEquals("Wrong hash for input", expected, hash)
+    }
+}