Skip to content

Commit f46fd8f

Browse files
authored
Merge pull request #720 from esensar/feature/password-protected-zips
Add support for decompressing password protected zips
2 parents 4d59b24 + 6b7206f commit f46fd8f

File tree

4 files changed

+77
-18
lines changed

4 files changed

+77
-18
lines changed

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ android {
6464
}
6565

6666
dependencies {
67-
implementation 'com.github.SimpleMobileTools:Simple-Commons:a8693482e8'
67+
implementation 'com.github.SimpleMobileTools:Simple-Commons:f54d4f7606'
6868
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
6969
implementation 'com.github.Stericson:RootTools:df729dcb13'
7070
implementation 'com.github.Stericson:RootShell:1.6'
7171
implementation 'com.alexvasilkov:gesture-views:2.5.2'
7272
implementation 'androidx.documentfile:documentfile:1.0.1'
7373
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
7474
implementation 'me.grantland:autofittextview:0.2.1'
75+
implementation 'net.lingala.zip4j:zip4j:2.11.5'
7576
}

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/DecompressActivity.kt

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.activities
33
import android.annotation.SuppressLint
44
import android.net.Uri
55
import android.os.Bundle
6+
import com.simplemobiletools.commons.dialogs.EnterPasswordDialog
67
import com.simplemobiletools.commons.dialogs.FilePickerDialog
78
import com.simplemobiletools.commons.extensions.*
89
import com.simplemobiletools.commons.helpers.NavigationIcon
@@ -13,14 +14,22 @@ import com.simplemobiletools.filemanager.pro.adapters.DecompressItemsAdapter
1314
import com.simplemobiletools.filemanager.pro.extensions.config
1415
import com.simplemobiletools.filemanager.pro.models.ListItem
1516
import kotlinx.android.synthetic.main.activity_decompress.*
17+
import net.lingala.zip4j.exception.ZipException
18+
import net.lingala.zip4j.exception.ZipException.Type
19+
import net.lingala.zip4j.io.inputstream.ZipInputStream
20+
import net.lingala.zip4j.model.LocalFileHeader
1621
import java.io.BufferedInputStream
17-
import java.util.zip.ZipEntry
18-
import java.util.zip.ZipInputStream
1922

2023
class DecompressActivity : SimpleActivity() {
24+
companion object {
25+
private const val PASSWORD = "password"
26+
}
27+
2128
private val allFiles = ArrayList<ListItem>()
2229
private var currentPath = ""
2330
private var uri: Uri? = null
31+
private var password: String? = null
32+
private var passwordDialog: EnterPasswordDialog? = null
2433

2534
override fun onCreate(savedInstanceState: Bundle?) {
2635
isMaterialActivity = true
@@ -36,17 +45,23 @@ class DecompressActivity : SimpleActivity() {
3645
return
3746
}
3847

48+
password = savedInstanceState?.getString(PASSWORD, null)
49+
3950
val realPath = getRealPathFromURI(uri!!)
4051
decompress_toolbar.title = realPath?.getFilenameFromPath() ?: Uri.decode(uri.toString().getFilenameFromPath())
41-
fillAllListItems(uri!!)
42-
updateCurrentPath("")
52+
setupFilesList()
4353
}
4454

4555
override fun onResume() {
4656
super.onResume()
4757
setupToolbar(decompress_toolbar, NavigationIcon.Arrow)
4858
}
4959

60+
override fun onSaveInstanceState(outState: Bundle) {
61+
super.onSaveInstanceState(outState)
62+
outState.putString(PASSWORD, password)
63+
}
64+
5065
private fun setupOptionsMenu() {
5166
decompress_toolbar.setOnMenuItemClickListener { menuItem ->
5267
when (menuItem.itemId) {
@@ -57,6 +72,11 @@ class DecompressActivity : SimpleActivity() {
5772
}
5873
}
5974

75+
private fun setupFilesList() {
76+
fillAllListItems(uri!!)
77+
updateCurrentPath("")
78+
}
79+
6080
override fun onBackPressed() {
6181
if (currentPath.isEmpty()) {
6282
super.onBackPressed()
@@ -99,14 +119,17 @@ class DecompressActivity : SimpleActivity() {
99119
try {
100120
val inputStream = contentResolver.openInputStream(uri!!)
101121
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!))
122+
if (password != null) {
123+
zipInputStream.setPassword(password?.toCharArray())
124+
}
102125
val buffer = ByteArray(1024)
103126

104127
zipInputStream.use {
105128
while (true) {
106129
val entry = zipInputStream.nextEntry ?: break
107130
val filename = title.toString().substringBeforeLast(".")
108131
val parent = "$destination/$filename"
109-
val newPath = "$parent/${entry.name.trimEnd('/')}"
132+
val newPath = "$parent/${entry.fileName.trimEnd('/')}"
110133

111134
if (!getDoesFilePathExist(parent)) {
112135
if (!createDirectorySync(parent)) {
@@ -161,10 +184,25 @@ class DecompressActivity : SimpleActivity() {
161184
}
162185

163186
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
164-
var zipEntry: ZipEntry?
187+
if (password != null) {
188+
zipInputStream.setPassword(password?.toCharArray())
189+
}
190+
var zipEntry: LocalFileHeader?
165191
while (true) {
166192
try {
167193
zipEntry = zipInputStream.nextEntry
194+
} catch (passwordException: ZipException) {
195+
if (passwordException.type == Type.WRONG_PASSWORD) {
196+
if (password != null) {
197+
toast(getString(R.string.invalid_password))
198+
passwordDialog?.clearPassword()
199+
} else {
200+
askForPassword()
201+
}
202+
return
203+
} else {
204+
break
205+
}
168206
} catch (ignored: Exception) {
169207
break
170208
}
@@ -173,10 +211,24 @@ class DecompressActivity : SimpleActivity() {
173211
break
174212
}
175213

176-
val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime.toMillis() else 0
177-
val filename = zipEntry.name.removeSuffix("/")
214+
val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime else 0
215+
val filename = zipEntry.fileName.removeSuffix("/")
178216
val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false)
179217
allFiles.add(listItem)
180218
}
219+
passwordDialog?.dismiss(notify = false)
220+
}
221+
222+
private fun askForPassword() {
223+
passwordDialog = EnterPasswordDialog(
224+
this,
225+
callback = { newPassword ->
226+
password = newPassword
227+
setupFilesList()
228+
},
229+
cancelCallback = {
230+
finish()
231+
}
232+
)
181233
}
182234
}

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/activities/MainActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,8 @@ class MainActivity : SimpleActivity() {
632632
}
633633

634634
private fun launchAbout() {
635-
val licenses = LICENSE_GLIDE or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GESTURE_VIEWS or LICENSE_PDF_VIEWER or LICENSE_AUTOFITTEXTVIEW
635+
val licenses =
636+
LICENSE_GLIDE or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GESTURE_VIEWS or LICENSE_PDF_VIEWER or LICENSE_AUTOFITTEXTVIEW or LICENSE_ZIP4J
636637

637638
val faqItems = arrayListOf(
638639
FAQItem(R.string.faq_3_title_commons, R.string.faq_3_text_commons),

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ import kotlinx.android.synthetic.main.item_file_dir_list.view.item_icon
4444
import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name
4545
import kotlinx.android.synthetic.main.item_file_grid.view.*
4646
import kotlinx.android.synthetic.main.item_section.view.*
47+
import net.lingala.zip4j.exception.ZipException
48+
import net.lingala.zip4j.io.inputstream.ZipInputStream
49+
import net.lingala.zip4j.model.LocalFileHeader
4750
import java.io.BufferedInputStream
4851
import java.io.Closeable
4952
import java.io.File
5053
import java.util.*
5154
import java.util.zip.ZipEntry
52-
import java.util.zip.ZipInputStream
5355
import java.util.zip.ZipOutputStream
5456

5557
class ItemsAdapter(
@@ -547,13 +549,11 @@ class ItemsAdapter(
547549
val fileDirItems = ArrayList<FileDirItem>()
548550
var entry = zipInputStream.nextEntry
549551
while (entry != null) {
550-
val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.name}"
551-
val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size)
552+
val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.fileName}"
553+
val fileDirItem = FileDirItem(currPath, entry.fileName, entry.isDirectory, 0, entry.uncompressedSize)
552554
fileDirItems.add(fileDirItem)
553-
zipInputStream.closeEntry()
554555
entry = zipInputStream.nextEntry
555556
}
556-
zipInputStream.closeEntry()
557557
val destinationPath = fileDirItems.first().getParentPath().trimEnd('/')
558558
activity.runOnUiThread {
559559
activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) {
@@ -562,6 +562,12 @@ class ItemsAdapter(
562562
}
563563
}
564564
}
565+
} catch (zipException: ZipException) {
566+
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
567+
activity.showErrorToast(activity.getString(R.string.invalid_password))
568+
} else {
569+
activity.showErrorToast(zipException)
570+
}
565571
} catch (exception: Exception) {
566572
activity.showErrorToast(exception)
567573
}
@@ -579,7 +585,7 @@ class ItemsAdapter(
579585
val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4)
580586
while (entry != null) {
581587
val parentPath = path.getParentPath()
582-
val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}"
588+
val newPath = "$parentPath/$newFolderName/${entry.fileName.trimEnd('/')}"
583589

584590
val resolution = getConflictResolution(conflictResolutions, newPath)
585591
val doesPathExist = activity.getDoesFilePathExist(newPath)
@@ -606,7 +612,6 @@ class ItemsAdapter(
606612
extractEntry(newPath, entry, zipInputStream)
607613
}
608614

609-
zipInputStream.closeEntry()
610615
entry = zipInputStream.nextEntry
611616
}
612617
callback(true)
@@ -618,7 +623,7 @@ class ItemsAdapter(
618623
}
619624
}
620625

621-
private fun extractEntry(newPath: String, entry: ZipEntry, zipInputStream: ZipInputStream) {
626+
private fun extractEntry(newPath: String, entry: LocalFileHeader, zipInputStream: ZipInputStream) {
622627
if (entry.isDirectory) {
623628
if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) {
624629
val error = String.format(activity.getString(R.string.could_not_create_file), newPath)

0 commit comments

Comments
 (0)