From 15dc3d094cbb845e3da4c7ed0d6a1167a30c0027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 7 Jul 2023 12:41:47 +0200 Subject: [PATCH 1/6] Add support for decompressing password protected zips This includes Zip4j library which supports password protected ZIP archives and replaces default zip library in DecompressActivity. NOTE: This does not cover decompression of multiple ZIPs, since I am not completely sure what UX we would want in that case. --- app/build.gradle | 3 +- .../pro/activities/DecompressActivity.kt | 67 ++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6bf0bc9d..27fdbdfa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,7 +64,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:84c71fdcc1' + implementation 'com.github.SimpleMobileTools:Simple-Commons:db25f91be3' implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b' implementation 'com.github.Stericson:RootTools:df729dcb13' implementation 'com.github.Stericson:RootShell:1.6' @@ -72,4 +72,5 @@ dependencies { implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'me.grantland:autofittextview:0.2.1' + implementation 'net.lingala.zip4j:zip4j:2.11.5' } diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt index 284d434e..415f4255 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.activities import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle +import com.simplemobiletools.commons.dialogs.EnterPasswordDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.NavigationIcon @@ -13,14 +14,21 @@ import com.simplemobiletools.filemanager.pro.adapters.DecompressItemsAdapter import com.simplemobiletools.filemanager.pro.extensions.config import com.simplemobiletools.filemanager.pro.models.ListItem import kotlinx.android.synthetic.main.activity_decompress.* +import net.lingala.zip4j.exception.ZipException +import net.lingala.zip4j.exception.ZipException.Type +import net.lingala.zip4j.io.inputstream.ZipInputStream +import net.lingala.zip4j.model.LocalFileHeader import java.io.BufferedInputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream class DecompressActivity : SimpleActivity() { + companion object { + private const val PASSWORD = "password" + } + private val allFiles = ArrayList() private var currentPath = "" private var uri: Uri? = null + private var password: String? = null override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true @@ -36,10 +44,11 @@ class DecompressActivity : SimpleActivity() { return } + password = savedInstanceState?.getString(PASSWORD, null) + val realPath = getRealPathFromURI(uri!!) decompress_toolbar.title = realPath?.getFilenameFromPath() ?: Uri.decode(uri.toString().getFilenameFromPath()) - fillAllListItems(uri!!) - updateCurrentPath("") + setupFilesList() } override fun onResume() { @@ -47,6 +56,11 @@ class DecompressActivity : SimpleActivity() { setupToolbar(decompress_toolbar, NavigationIcon.Arrow) } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(PASSWORD, password) + } + private fun setupOptionsMenu() { decompress_toolbar.setOnMenuItemClickListener { menuItem -> when (menuItem.itemId) { @@ -57,6 +71,12 @@ class DecompressActivity : SimpleActivity() { } } + private fun setupFilesList() { + fillAllListItems(uri!!) + updateCurrentPath("") + } + + override fun onBackPressed() { if (currentPath.isEmpty()) { super.onBackPressed() @@ -99,6 +119,9 @@ class DecompressActivity : SimpleActivity() { try { val inputStream = contentResolver.openInputStream(uri!!) val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!)) + if (password != null) { + zipInputStream.setPassword(password?.toCharArray()) + } val buffer = ByteArray(1024) zipInputStream.use { @@ -106,7 +129,7 @@ class DecompressActivity : SimpleActivity() { val entry = zipInputStream.nextEntry ?: break val filename = title.toString().substringBeforeLast(".") val parent = "$destination/$filename" - val newPath = "$parent/${entry.name.trimEnd('/')}" + val newPath = "$parent/${entry.fileName.trimEnd('/')}" if (!getDoesFilePathExist(parent)) { if (!createDirectorySync(parent)) { @@ -161,10 +184,25 @@ class DecompressActivity : SimpleActivity() { } val zipInputStream = ZipInputStream(BufferedInputStream(inputStream)) - var zipEntry: ZipEntry? + if (password != null) { + zipInputStream.setPassword(password?.toCharArray()) + } + var zipEntry: LocalFileHeader? while (true) { try { zipEntry = zipInputStream.nextEntry + } catch (passwordException: ZipException) { + if (passwordException.type == Type.WRONG_PASSWORD) { + if (password != null) { + showErrorToast(getString(R.string.invalid_password)) + finish() + } else { + askForPassword() + } + return + } else { + break + } } catch (ignored: Exception) { break } @@ -173,10 +211,23 @@ class DecompressActivity : SimpleActivity() { break } - val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime.toMillis() else 0 - val filename = zipEntry.name.removeSuffix("/") + val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime else 0 + val filename = zipEntry.fileName.removeSuffix("/") val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false) allFiles.add(listItem) } } + + private fun askForPassword() { + EnterPasswordDialog( + this, + callback = { newPassword -> + password = newPassword + setupFilesList() + }, + cancelCallback = { + finish() + } + ) + } } From 131adb8c28be25923de22b712afe04c9d010b215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 7 Jul 2023 13:16:40 +0200 Subject: [PATCH 2/6] Add incorrect password toast when decompressing multiple protected zips --- .../filemanager/pro/adapters/ItemsAdapter.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt index ef24564b..04725d3c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt @@ -44,12 +44,14 @@ import kotlinx.android.synthetic.main.item_file_dir_list.view.item_icon import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name import kotlinx.android.synthetic.main.item_file_grid.view.* import kotlinx.android.synthetic.main.item_section.view.* +import net.lingala.zip4j.exception.ZipException +import net.lingala.zip4j.io.inputstream.ZipInputStream +import net.lingala.zip4j.model.LocalFileHeader import java.io.BufferedInputStream import java.io.Closeable import java.io.File import java.util.* import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream class ItemsAdapter( @@ -547,13 +549,11 @@ class ItemsAdapter( val fileDirItems = ArrayList() var entry = zipInputStream.nextEntry while (entry != null) { - val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.name}" - val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size) + val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.fileName}" + val fileDirItem = FileDirItem(currPath, entry.fileName, entry.isDirectory, 0, entry.uncompressedSize) fileDirItems.add(fileDirItem) - zipInputStream.closeEntry() entry = zipInputStream.nextEntry } - zipInputStream.closeEntry() val destinationPath = fileDirItems.first().getParentPath().trimEnd('/') activity.runOnUiThread { activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) { @@ -562,6 +562,12 @@ class ItemsAdapter( } } } + } catch (zipException: ZipException) { + if (zipException.type == ZipException.Type.WRONG_PASSWORD) { + activity.showErrorToast(activity.getString(R.string.invalid_password)) + } else { + activity.showErrorToast(zipException) + } } catch (exception: Exception) { activity.showErrorToast(exception) } @@ -579,7 +585,7 @@ class ItemsAdapter( val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4) while (entry != null) { val parentPath = path.getParentPath() - val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}" + val newPath = "$parentPath/$newFolderName/${entry.fileName.trimEnd('/')}" val resolution = getConflictResolution(conflictResolutions, newPath) val doesPathExist = activity.getDoesFilePathExist(newPath) @@ -606,7 +612,6 @@ class ItemsAdapter( extractEntry(newPath, entry, zipInputStream) } - zipInputStream.closeEntry() entry = zipInputStream.nextEntry } callback(true) @@ -618,7 +623,7 @@ class ItemsAdapter( } } - private fun extractEntry(newPath: String, entry: ZipEntry, zipInputStream: ZipInputStream) { + private fun extractEntry(newPath: String, entry: LocalFileHeader, zipInputStream: ZipInputStream) { if (entry.isDirectory) { if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) { val error = String.format(activity.getString(R.string.could_not_create_file), newPath) From 7776dd6af401655d505f398d45af9921f935a043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 7 Jul 2023 16:48:22 +0200 Subject: [PATCH 3/6] Make password dialog behavior the same as with protected PDFs --- app/build.gradle | 2 +- .../filemanager/pro/activities/DecompressActivity.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 27fdbdfa..ce63b924 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,7 +64,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:db25f91be3' + implementation 'com.github.SimpleMobileTools:Simple-Commons:a8693482e8' implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b' implementation 'com.github.Stericson:RootTools:df729dcb13' implementation 'com.github.Stericson:RootShell:1.6' diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt index 415f4255..a1e73c1c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt @@ -29,6 +29,7 @@ class DecompressActivity : SimpleActivity() { private var currentPath = "" private var uri: Uri? = null private var password: String? = null + private var passwordDialog: EnterPasswordDialog? = null override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true @@ -195,7 +196,7 @@ class DecompressActivity : SimpleActivity() { if (passwordException.type == Type.WRONG_PASSWORD) { if (password != null) { showErrorToast(getString(R.string.invalid_password)) - finish() + passwordDialog?.clearPassword() } else { askForPassword() } @@ -216,10 +217,11 @@ class DecompressActivity : SimpleActivity() { val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false) allFiles.add(listItem) } + passwordDialog?.dismiss(notify = false) } private fun askForPassword() { - EnterPasswordDialog( + passwordDialog = EnterPasswordDialog( this, callback = { newPassword -> password = newPassword From e56053966c671897d9e486115ca1ebf9b6e46eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 7 Jul 2023 17:01:51 +0200 Subject: [PATCH 4/6] Add LICENSE_ZIP4J to list of used licenses --- app/build.gradle | 2 +- .../filemanager/pro/activities/MainActivity.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ce63b924..12820f5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,7 +64,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:a8693482e8' + implementation 'com.github.SimpleMobileTools:Simple-Commons:f54d4f7606' implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b' implementation 'com.github.Stericson:RootTools:df729dcb13' implementation 'com.github.Stericson:RootShell:1.6' diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/MainActivity.kt index a179ad57..b8ce7e3f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/MainActivity.kt @@ -632,7 +632,8 @@ class MainActivity : SimpleActivity() { } private fun launchAbout() { - val licenses = LICENSE_GLIDE or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GESTURE_VIEWS or LICENSE_PDF_VIEWER or LICENSE_AUTOFITTEXTVIEW + val licenses = + LICENSE_GLIDE or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GESTURE_VIEWS or LICENSE_PDF_VIEWER or LICENSE_AUTOFITTEXTVIEW or LICENSE_ZIP4J val faqItems = arrayListOf( FAQItem(R.string.faq_3_title_commons, R.string.faq_3_text_commons), From 0e9cb91697df7bc417f194a1e56fe9afbfd6b9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 7 Jul 2023 18:18:33 +0200 Subject: [PATCH 5/6] Remove error prefix from incorrect password toast --- .../filemanager/pro/activities/DecompressActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt index a1e73c1c..a910d361 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt @@ -195,7 +195,7 @@ class DecompressActivity : SimpleActivity() { } catch (passwordException: ZipException) { if (passwordException.type == Type.WRONG_PASSWORD) { if (password != null) { - showErrorToast(getString(R.string.invalid_password)) + toast(getString(R.string.invalid_password)) passwordDialog?.clearPassword() } else { askForPassword() From 6b7206fc0d109afd9488a53ce7b95ed2b5cea6f2 Mon Sep 17 00:00:00 2001 From: Tibor Kaputa Date: Fri, 7 Jul 2023 19:08:56 +0200 Subject: [PATCH 6/6] removing an empty line --- .../filemanager/pro/activities/DecompressActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt index a910d361..f3a15dc0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt @@ -77,7 +77,6 @@ class DecompressActivity : SimpleActivity() { updateCurrentPath("") } - override fun onBackPressed() { if (currentPath.isEmpty()) { super.onBackPressed()