Skip to content

Commit

Permalink
Move compression and decompression into a service
Browse files Browse the repository at this point in the history
This does not handle decompressing multiple paths.

This fixes #735
  • Loading branch information
esensar committed Aug 29, 2023
1 parent 4a09d03 commit 86fc4ed
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 195 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ android {
}

dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:d1629c7f1a'
implementation 'com.github.esensar:Simple-Commons:ef5602aee2'
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
implementation 'com.github.Stericson:RootTools:df729dcb13'
implementation 'com.github.Stericson:RootShell:1.6'
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<uses-feature
android:name="android.hardware.faketouch"
Expand Down Expand Up @@ -127,6 +129,12 @@
</intent-filter>
</activity>

<service android:name=".services.CompressionService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="File decompression" />
</service>

<activity
android:name=".activities.SaveAsActivity"
android:configChanges="orientation"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.simplemobiletools.filemanager.pro.activities

import android.annotation.SuppressLint
import android.content.Intent
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
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.filemanager.pro.R
import com.simplemobiletools.filemanager.pro.adapters.DecompressItemsAdapter
import com.simplemobiletools.filemanager.pro.databinding.ActivityDecompressBinding
import com.simplemobiletools.filemanager.pro.extensions.config
import com.simplemobiletools.filemanager.pro.models.ListItem
import com.simplemobiletools.filemanager.pro.services.CompressionService
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.File

class DecompressActivity : SimpleActivity() {
companion object {
Expand Down Expand Up @@ -94,12 +93,17 @@ class DecompressActivity : SimpleActivity() {
currentPath = path
try {
val listItems = getFolderItems(currentPath)
DecompressItemsAdapter(this, listItems, binding.decompressList) {
if ((it as ListItem).isDirectory) {
updateCurrentPath(it.path)
val existingAdapter = (binding.decompressList.adapter as? DecompressItemsAdapter)
if (existingAdapter != null) {
existingAdapter.updateItems(listItems)
} else {
DecompressItemsAdapter(this, listItems, binding.decompressList) {
if ((it as ListItem).isDirectory) {
updateCurrentPath(it.path)
}
}.apply {
binding.decompressList.adapter = this
}
}.apply {
binding.decompressList.adapter = this
}
} catch (e: Exception) {
showErrorToast(e)
Expand All @@ -111,66 +115,18 @@ class DecompressActivity : SimpleActivity() {
FilePickerDialog(this, defaultFolder, false, config.showHidden, true, true, showFavoritesButton = true) { destination ->
handleSAFDialog(destination) {
if (it) {
ensureBackgroundThread {
decompressTo(destination)
Intent(this, CompressionService::class.java).apply {
action = CompressionService.ACTION_DECOMPRESS
putExtra(CompressionService.EXTRA_URI, uri!!)
putExtra(CompressionService.EXTRA_PASSWORD, password)
putExtra(CompressionService.EXTRA_DESTINATION, destination)
startService(this)
}
}
}
}
}

private fun decompressTo(destination: String) {
try {
val inputStream = contentResolver.openInputStream(uri!!)
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!))
if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
val buffer = ByteArray(1024)

zipInputStream.use {
while (true) {
val entry = zipInputStream.nextEntry ?: break
val filename = title.toString().substringBeforeLast(".")
val parent = "$destination/$filename"
val newPath = "$parent/${entry.fileName.trimEnd('/')}"

if (!getDoesFilePathExist(parent)) {
if (!createDirectorySync(parent)) {
continue
}
}

if (entry.isDirectory) {
continue
}

val isVulnerableForZipPathTraversal = !File(newPath).canonicalPath.startsWith(parent)
if (isVulnerableForZipPathTraversal) {
continue
}

val fos = getFileOutputStreamSync(newPath, newPath.getMimeType())
var count: Int
while (true) {
count = zipInputStream.read(buffer)
if (count == -1) {
break
}

fos!!.write(buffer, 0, count)
}
fos!!.close()
}

toast(R.string.decompression_successful)
finish()
}
} catch (e: Exception) {
showErrorToast(e)
}
}

private fun getFolderItems(parent: String): ArrayList<ListItem> {
return allFiles.filter {
val fileParent = if (it.path.contains("/")) {
Expand All @@ -185,47 +141,72 @@ class DecompressActivity : SimpleActivity() {

@SuppressLint("NewApi")
private fun fillAllListItems(uri: Uri) {
val inputStream = try {
contentResolver.openInputStream(uri)
} catch (e: Exception) {
showErrorToast(e)
return
}
binding.progressBar.beVisible()
ensureBackgroundThread {
val inputStream = try {
contentResolver.openInputStream(uri)
} catch (e: Exception) {
runOnUiThread {
showErrorToast(e)
binding.progressBar.beGone()
}
return@ensureBackgroundThread
}

val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
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) {
toast(getString(R.string.invalid_password))
passwordDialog?.clearPassword()
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
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) {
runOnUiThread {
toast(getString(R.string.invalid_password))
binding.progressBar.beGone()
if (passwordDialog == null) {
askForPassword()
} else {
passwordDialog?.clearPassword()
}
}
} else {
runOnUiThread {
binding.progressBar.beGone()
askForPassword()
}
}
return@ensureBackgroundThread
} else {
askForPassword()
break
}
return
} else {
} catch (ignored: Exception) {
break
}
} catch (ignored: Exception) {
break
}

if (zipEntry == null) {
break
}
if (zipEntry == null) {
break
}

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)
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)
runOnUiThread {
allFiles.add(listItem)
passwordDialog?.dismiss(notify = false)
passwordDialog = null
updateCurrentPath(currentPath)
}
}
runOnUiThread {
binding.progressBar.beGone()
passwordDialog?.dismiss(notify = false)
passwordDialog = null
}
}
passwordDialog?.dismiss(notify = false)
}

private fun askForPassword() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
Expand All @@ -21,6 +22,7 @@ import com.simplemobiletools.filemanager.pro.activities.SimpleActivity
import com.simplemobiletools.filemanager.pro.databinding.ItemDecompressionListFileDirBinding
import com.simplemobiletools.filemanager.pro.extensions.config
import com.simplemobiletools.filemanager.pro.models.ListItem
import java.util.ArrayList

class DecompressItemsAdapter(activity: SimpleActivity, var listItems: MutableList<ListItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
Expand Down Expand Up @@ -135,4 +137,23 @@ class DecompressItemsAdapter(activity: SimpleActivity, var listItems: MutableLis
fileDrawable = resources.getDrawable(R.drawable.ic_file_generic)
fileDrawables = getFilePlaceholderDrawables(activity)
}

fun updateItems(listItems: ArrayList<ListItem>) {
val diffResult = DiffUtil.calculateDiff(DiffCallback(this.listItems, listItems))
this.listItems.clear()
this.listItems.addAll(listItems)
diffResult.dispatchUpdatesTo(this)
}

class DiffCallback(private val oldList: List<ListItem>, private val newList: List<ListItem>) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].path == newList[newItemPosition].path

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]

}
}
Loading

0 comments on commit 86fc4ed

Please sign in to comment.