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.
This commit is contained in:
Ensar Sarajčić
2023-07-07 12:41:47 +02:00
parent 2ac5648037
commit 15dc3d094c
2 changed files with 61 additions and 9 deletions

View File

@@ -64,7 +64,7 @@ android {
} }
dependencies { dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:84c71fdcc1' implementation 'com.github.SimpleMobileTools:Simple-Commons:db25f91be3'
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b' implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
implementation 'com.github.Stericson:RootTools:df729dcb13' implementation 'com.github.Stericson:RootTools:df729dcb13'
implementation 'com.github.Stericson:RootShell:1.6' implementation 'com.github.Stericson:RootShell:1.6'
@@ -72,4 +72,5 @@ dependencies {
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'me.grantland:autofittextview:0.2.1' implementation 'me.grantland:autofittextview:0.2.1'
implementation 'net.lingala.zip4j:zip4j:2.11.5'
} }

View File

@@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.simplemobiletools.commons.dialogs.EnterPasswordDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.NavigationIcon 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.extensions.config
import com.simplemobiletools.filemanager.pro.models.ListItem import com.simplemobiletools.filemanager.pro.models.ListItem
import kotlinx.android.synthetic.main.activity_decompress.* 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.io.BufferedInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
class DecompressActivity : SimpleActivity() { class DecompressActivity : SimpleActivity() {
companion object {
private const val PASSWORD = "password"
}
private val allFiles = ArrayList<ListItem>() private val allFiles = ArrayList<ListItem>()
private var currentPath = "" private var currentPath = ""
private var uri: Uri? = null private var uri: Uri? = null
private var password: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true isMaterialActivity = true
@@ -36,10 +44,11 @@ class DecompressActivity : SimpleActivity() {
return return
} }
password = savedInstanceState?.getString(PASSWORD, null)
val realPath = getRealPathFromURI(uri!!) val realPath = getRealPathFromURI(uri!!)
decompress_toolbar.title = realPath?.getFilenameFromPath() ?: Uri.decode(uri.toString().getFilenameFromPath()) decompress_toolbar.title = realPath?.getFilenameFromPath() ?: Uri.decode(uri.toString().getFilenameFromPath())
fillAllListItems(uri!!) setupFilesList()
updateCurrentPath("")
} }
override fun onResume() { override fun onResume() {
@@ -47,6 +56,11 @@ class DecompressActivity : SimpleActivity() {
setupToolbar(decompress_toolbar, NavigationIcon.Arrow) setupToolbar(decompress_toolbar, NavigationIcon.Arrow)
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(PASSWORD, password)
}
private fun setupOptionsMenu() { private fun setupOptionsMenu() {
decompress_toolbar.setOnMenuItemClickListener { menuItem -> decompress_toolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {
@@ -57,6 +71,12 @@ class DecompressActivity : SimpleActivity() {
} }
} }
private fun setupFilesList() {
fillAllListItems(uri!!)
updateCurrentPath("")
}
override fun onBackPressed() { override fun onBackPressed() {
if (currentPath.isEmpty()) { if (currentPath.isEmpty()) {
super.onBackPressed() super.onBackPressed()
@@ -99,6 +119,9 @@ class DecompressActivity : SimpleActivity() {
try { try {
val inputStream = contentResolver.openInputStream(uri!!) val inputStream = contentResolver.openInputStream(uri!!)
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!)) val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!))
if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
val buffer = ByteArray(1024) val buffer = ByteArray(1024)
zipInputStream.use { zipInputStream.use {
@@ -106,7 +129,7 @@ class DecompressActivity : SimpleActivity() {
val entry = zipInputStream.nextEntry ?: break val entry = zipInputStream.nextEntry ?: break
val filename = title.toString().substringBeforeLast(".") val filename = title.toString().substringBeforeLast(".")
val parent = "$destination/$filename" val parent = "$destination/$filename"
val newPath = "$parent/${entry.name.trimEnd('/')}" val newPath = "$parent/${entry.fileName.trimEnd('/')}"
if (!getDoesFilePathExist(parent)) { if (!getDoesFilePathExist(parent)) {
if (!createDirectorySync(parent)) { if (!createDirectorySync(parent)) {
@@ -161,10 +184,25 @@ class DecompressActivity : SimpleActivity() {
} }
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream)) val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
var zipEntry: ZipEntry? if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
var zipEntry: LocalFileHeader?
while (true) { while (true) {
try { try {
zipEntry = zipInputStream.nextEntry 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) { } catch (ignored: Exception) {
break break
} }
@@ -173,10 +211,23 @@ class DecompressActivity : SimpleActivity() {
break break
} }
val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime.toMillis() else 0 val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime else 0
val filename = zipEntry.name.removeSuffix("/") val filename = zipEntry.fileName.removeSuffix("/")
val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false) val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false)
allFiles.add(listItem) allFiles.add(listItem)
} }
} }
private fun askForPassword() {
EnterPasswordDialog(
this,
callback = { newPassword ->
password = newPassword
setupFilesList()
},
cancelCallback = {
finish()
}
)
}
} }