Merge pull request #721 from esensar/feature/password-protected-zips-compress

Add support for creating password protected zips
This commit is contained in:
Tibor Kaputa 2023-07-15 19:58:04 +02:00 committed by GitHub
commit f73688c0b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 18 deletions

View File

@ -64,7 +64,7 @@ android {
} }
dependencies { dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:2d4e07e5f4' implementation 'com.github.SimpleMobileTools:Simple-Commons:79b117a9ba'
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'

View File

@ -46,13 +46,14 @@ import kotlinx.android.synthetic.main.item_file_grid.view.*
import kotlinx.android.synthetic.main.item_section.view.* import kotlinx.android.synthetic.main.item_section.view.*
import net.lingala.zip4j.exception.ZipException import net.lingala.zip4j.exception.ZipException
import net.lingala.zip4j.io.inputstream.ZipInputStream import net.lingala.zip4j.io.inputstream.ZipInputStream
import net.lingala.zip4j.io.outputstream.ZipOutputStream
import net.lingala.zip4j.model.LocalFileHeader import net.lingala.zip4j.model.LocalFileHeader
import net.lingala.zip4j.model.ZipParameters
import net.lingala.zip4j.model.enums.EncryptionMethod
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class ItemsAdapter( class ItemsAdapter(
activity: SimpleActivity, var listItems: MutableList<ListItem>, val listener: ItemOperationsListener?, recyclerView: MyRecyclerView, activity: SimpleActivity, var listItems: MutableList<ListItem>, val listener: ItemOperationsListener?, recyclerView: MyRecyclerView,
@ -484,8 +485,7 @@ class ItemsAdapter(
return return
} }
CompressAsDialog(activity, firstPath) { CompressAsDialog(activity, firstPath) { destination, password ->
val destination = it
activity.handleAndroidSAFDialog(firstPath) { granted -> activity.handleAndroidSAFDialog(firstPath) { granted ->
if (!granted) { if (!granted) {
return@handleAndroidSAFDialog return@handleAndroidSAFDialog
@ -498,7 +498,7 @@ class ItemsAdapter(
activity.toast(R.string.compressing) activity.toast(R.string.compressing)
val paths = getSelectedFileDirItems().map { it.path } val paths = getSelectedFileDirItems().map { it.path }
ensureBackgroundThread { ensureBackgroundThread {
if (compressPaths(paths, destination)) { if (compressPaths(paths, destination, password)) {
activity.runOnUiThread { activity.runOnUiThread {
activity.toast(R.string.compression_successful) activity.toast(R.string.compression_successful)
listener?.refreshFragment() listener?.refreshFragment()
@ -648,13 +648,21 @@ class ItemsAdapter(
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean { private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean {
val queue = LinkedList<String>() val queue = LinkedList<String>()
val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
val zout = ZipOutputStream(fos) val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos)
var res: Closeable = fos var res: Closeable = fos
fun zipEntry(name: String) = ZipParameters().also {
it.fileNameInZip = name
if (password != null) {
it.isEncryptFiles = true
it.encryptionMethod = EncryptionMethod.AES
}
}
try { try {
sourcePaths.forEach { currentPath -> sourcePaths.forEach { currentPath ->
var name: String var name: String
@ -664,7 +672,11 @@ class ItemsAdapter(
queue.push(mainFilePath) queue.push(mainFilePath)
if (activity.getIsPathDirectory(mainFilePath)) { if (activity.getIsPathDirectory(mainFilePath)) {
name = "${mainFilePath.getFilenameFromPath()}/" name = "${mainFilePath.getFilenameFromPath()}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(
ZipParameters().also {
it.fileNameInZip = name
}
)
} }
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
@ -677,9 +689,9 @@ class ItemsAdapter(
if (activity.getIsPathDirectory(file.path)) { if (activity.getIsPathDirectory(file.path)) {
queue.push(file.path) queue.push(file.path)
name = "${name.trimEnd('/')}/" name = "${name.trimEnd('/')}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
} else { } else {
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(file.path)!!.copyTo(zout) activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
@ -692,9 +704,9 @@ class ItemsAdapter(
if (activity.getIsPathDirectory(file.absolutePath)) { if (activity.getIsPathDirectory(file.absolutePath)) {
queue.push(file.absolutePath) queue.push(file.absolutePath)
name = "${name.trimEnd('/')}/" name = "${name.trimEnd('/')}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
} else { } else {
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(file.path)!!.copyTo(zout) activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
@ -703,7 +715,7 @@ class ItemsAdapter(
} else { } else {
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base) name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout) activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }

View File

@ -7,10 +7,9 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.filemanager.pro.R import com.simplemobiletools.filemanager.pro.R
import com.simplemobiletools.filemanager.pro.extensions.config import com.simplemobiletools.filemanager.pro.extensions.config
import kotlinx.android.synthetic.main.dialog_compress_as.view.filename_value import kotlinx.android.synthetic.main.dialog_compress_as.view.*
import kotlinx.android.synthetic.main.dialog_compress_as.view.folder
class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String) -> Unit) { class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String, password: String?) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_compress_as, null) private val view = activity.layoutInflater.inflate(R.layout.dialog_compress_as, null)
init { init {
@ -29,6 +28,10 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
realPath = it realPath = it
} }
} }
password_protect.setOnCheckedChangeListener { _, _ ->
enter_password_hint.beVisibleIf(password_protect.isChecked)
}
} }
activity.getAlertDialogBuilder() activity.getAlertDialogBuilder()
@ -39,6 +42,14 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
alertDialog.showKeyboard(view.filename_value) alertDialog.showKeyboard(view.filename_value)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
val name = view.filename_value.value val name = view.filename_value.value
var password: String? = null
if (view.password_protect.isChecked) {
password = view.password.value
if (password.isEmpty()) {
activity.toast(R.string.empty_password_new)
return@OnClickListener
}
}
when { when {
name.isEmpty() -> activity.toast(R.string.empty_name) name.isEmpty() -> activity.toast(R.string.empty_name)
name.isAValidFilename() -> { name.isAValidFilename() -> {
@ -49,8 +60,9 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
} }
alertDialog.dismiss() alertDialog.dismiss()
callback(newPath) callback(newPath, password)
} }
else -> activity.toast(R.string.invalid_name) else -> activity.toast(R.string.invalid_name)
} }
}) })

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dialog_holder" android:id="@+id/dialog_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -39,4 +40,35 @@
android:textSize="@dimen/bigger_text_size" /> android:textSize="@dimen/bigger_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout> </com.simplemobiletools.commons.views.MyTextInputLayout>
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/password_protect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_hint"
android:layout_marginTop="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
android:paddingBottom="@dimen/normal_margin"
android:text="@string/add_password" />
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/enter_password_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/password_protect"
app:passwordToggleEnabled="true"
android:hint="@string/password"
android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textPassword"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
</RelativeLayout> </RelativeLayout>