Skip to content

Commit

Permalink
ISO9660 and ZStandard archive support
Browse files Browse the repository at this point in the history
Fixes #2988
Fixes #3318
  • Loading branch information
TranceLove committed Jul 28, 2024
1 parent 978fa99 commit 4be9909
Show file tree
Hide file tree
Showing 33 changed files with 729 additions and 29 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ dependencies {
implementation libs.androidX.multidex //Multiple dex files
implementation libs.room.runtime
implementation libs.room.rxjava2
testImplementation "com.github.luben:zstd-jni:1.5.6-4"

ksp libs.room.compiler
ksp libs.androidX.annotation
Expand Down Expand Up @@ -189,6 +190,11 @@ dependencies {

implementation libs.commons.compress

implementation "com.github.luben:zstd-jni:1.5.6-4@aar"
implementation ("com.github.stephenc.java-iso-tools:loopy-core:1.2.2") {
transitive = false
}

implementation libs.materialdialogs.core
implementation libs.materialdialogs.commons

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import org.apache.commons.compress.archivers.ArchiveEntry
import org.apache.commons.compress.archivers.ArchiveException
import org.apache.commons.compress.archivers.ArchiveInputStream
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.lang.ref.WeakReference

Expand All @@ -52,7 +51,7 @@ abstract class AbstractCommonsArchiveHelperCallable(
@Throws(ArchiveException::class)
@Suppress("LabeledExpression")
public override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
try {
runCatching {
createFrom(FileInputStream(filePath)).use { tarInputStream ->
var entry: ArchiveEntry?
while (tarInputStream.nextEntry.also { entry = it } != null) {
Expand Down Expand Up @@ -88,8 +87,9 @@ abstract class AbstractCommonsArchiveHelperCallable(
}
}
}
} catch (e: IOException) {
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath), e)
}.onFailure {
logger.error("Error enumerating archive entries", it)
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2014-2023 Arpit Khurana <[email protected]>, Vishal Nehra <[email protected]>,
* Emmanuel Messulam<[email protected]>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
*
* This file is part of Amaze File Manager.
*
* Amaze File Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.amaze.filemanager.asynchronous.asynctasks.compress

import com.amaze.filemanager.adapters.data.CompressedObjectParcelable
import com.amaze.filemanager.filesystem.compressed.CompressedHelper
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor
import net.didion.loopy.FileEntry
import net.didion.loopy.iso9660.ISO9660FileEntry
import net.didion.loopy.iso9660.ISO9660FileSystem
import java.io.File
import java.io.IOException
import java.lang.reflect.Field

class Iso9660HelperCallable(
private val filePath: String,
private val relativePath: String,
createBackItem: Boolean,
) : CompressedHelperCallable(createBackItem) {
private val SLASH = Regex("/")

// Hack. ISO9660FileEntry doesn't have getter for parentPath, we need to read it on our own
private val parentPathField: Field =
ISO9660FileEntry::class.java.getDeclaredField("parentPath").also {
it.isAccessible = true
}

override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
val isoFile = ISO9660FileSystem(File(filePath), true)

val fileEntries: List<FileEntry> =
runCatching {
isoFile.entries?.let { isoFileEntries ->
isoFileEntries.runCatching {
isoFileEntries.toList().partition { entry ->
CompressedHelper.isEntryPathValid((entry as FileEntry).path)
}.let { pair ->
pair.first as List<FileEntry>
}
}.onFailure {
return
}.getOrThrow()
} ?: throw IOException("Empty archive or file is corrupt")
}.onFailure {
throw Extractor.BadArchiveNotice(it)
}.getOrThrow().filter {
it.name != "."
}

val slashCount =
if (relativePath == "") {
0
} else {
SLASH.findAll("$relativePath/").count()
}

fileEntries.filter {
val parentPath = parentPathField.get(it)?.toString() ?: ""
(
if (slashCount == 0) {
parentPath == ""
} else {
parentPath == "$relativePath/"
}
)
}.forEach { entry ->
elements.add(
CompressedObjectParcelable(
entry.name,
entry.lastModifiedTime,
entry.size.toLong(),
entry.isDirectory,
),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2014-2021 Arpit Khurana <[email protected]>, Vishal Nehra <[email protected]>,
* Emmanuel Messulam<[email protected]>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
*
* This file is part of Amaze File Manager.
*
* Amaze File Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.amaze.filemanager.asynchronous.asynctasks.compress

import android.content.Context
import org.apache.commons.compress.compressors.CompressorInputStream
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream

class TarZstHelperCallable(
context: Context,
filePath: String,
relativePath: String,
goBack: Boolean,
) :
AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) {
override fun getCompressorInputStreamClass(): Class<out CompressorInputStream> = ZstdCompressorInputStream::class.java
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Bzip2Extractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.GzipExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Iso9660Extractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.LzmaExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.RarExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.SevenZipExtractor;
Expand All @@ -38,16 +39,20 @@
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarGzExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarLzmaExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarXzExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarZstExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.XzExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZipExtractor;
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZstdExtractor;
import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.Iso9660Decompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.RarDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.SevenZipDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarBzip2Decompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarGzDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarLzmaDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarXzDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarZstDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.UnknownCompressedFileDecompressor;
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.ZipDecompressor;
import com.amaze.filemanager.utils.Utils;
Expand Down Expand Up @@ -80,10 +85,14 @@ public abstract class CompressedHelper {
public static final String fileExtension7zip = "7z";
public static final String fileExtensionTarLzma = "tar.lzma";
public static final String fileExtensionTarXz = "tar.xz";
public static final String fileExtensionTarZst = "tar.zst";
public static final String fileExtensionXz = "xz";
public static final String fileExtensionLzma = "lzma";
public static final String fileExtensionGz = "gz";
public static final String fileExtensionBzip2 = "bz2";
public static final String fileExtensionZst = "zst";

public static final String fileExtensionIso = "iso";

private static final String TAG = CompressedHelper.class.getSimpleName();

Expand Down Expand Up @@ -114,6 +123,9 @@ public static Extractor getExtractorInstance(
} else if (isLzippedTar(type)) {
extractor =
new TarLzmaExtractor(context, file.getPath(), outputPath, listener, updatePosition);
} else if (isZstdTar(type)) {
extractor =
new TarZstExtractor(context, file.getPath(), outputPath, listener, updatePosition);
} else if (is7zip(type)) {
extractor =
new SevenZipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
Expand All @@ -125,6 +137,11 @@ public static Extractor getExtractorInstance(
extractor = new GzipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
} else if (isBzip2(type)) {
extractor = new Bzip2Extractor(context, file.getPath(), outputPath, listener, updatePosition);
} else if (isZst(type)) {
extractor = new ZstdExtractor(context, file.getPath(), outputPath, listener, updatePosition);
} else if (isIso(type)) {
extractor =
new Iso9660Extractor(context, file.getPath(), outputPath, listener, updatePosition);
} else {
if (BuildConfig.DEBUG) {
throw new IllegalArgumentException("The compressed file has no way of opening it: " + file);
Expand Down Expand Up @@ -156,9 +173,13 @@ public static Decompressor getCompressorInstance(@NonNull Context context, @NonN
decompressor = new TarXzDecompressor(context);
} else if (isLzippedTar(type)) {
decompressor = new TarLzmaDecompressor(context);
} else if (isZstdTar(type)) {
decompressor = new TarZstDecompressor(context);
} else if (is7zip(type)) {
decompressor = new SevenZipDecompressor(context);
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type)) {
} else if (isIso(type)) {
decompressor = new Iso9660Decompressor(context);
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type) || isZst(type)) {
// These 4 types are only compressing one single file.
// Hence invoking this UnknownCompressedFileDecompressor which only returns the filename
// without the compression extension
Expand Down Expand Up @@ -190,10 +211,13 @@ public static boolean isFileExtractable(String path) {
|| isBzippedTar(type)
|| isXzippedTar(type)
|| isLzippedTar(type)
|| isZstdTar(type)
|| isBzip2(type)
|| isGzip(type)
|| isLzma(type)
|| isXz(type);
|| isXz(type)
|| isZst(type)
|| isIso(type);
}

/**
Expand All @@ -216,12 +240,15 @@ public static String getFileName(String compressedName) {
|| isGzip(compressedName)
|| isBzip2(compressedName)
|| isLzma(compressedName)
|| isXz(compressedName))) {
|| isXz(compressedName)
|| isZst(compressedName)
|| isIso(compressedName))) {
return compressedName.substring(0, compressedName.lastIndexOf("."));
} else if (hasFileName && isGzippedTar(compressedName)
|| isXzippedTar(compressedName)
|| isLzippedTar(compressedName)
|| isBzippedTar(compressedName)) {
|| isBzippedTar(compressedName)
|| isZstdTar(compressedName)) {
return compressedName.substring(0, Utils.nthToLastCharIndex(2, compressedName, '.'));
} else {
return compressedName;
Expand Down Expand Up @@ -267,6 +294,10 @@ private static boolean isLzippedTar(String type) {
return type.endsWith(fileExtensionTarLzma);
}

private static boolean isZstdTar(String type) {
return type.endsWith(fileExtensionTarZst);
}

private static boolean isXz(String type) {
return type.endsWith(fileExtensionXz) && !isXzippedTar(type);
}
Expand All @@ -283,6 +314,14 @@ private static boolean isBzip2(String type) {
return type.endsWith(fileExtensionBzip2) && !isBzippedTar(type);
}

private static boolean isZst(String type) {
return type.endsWith(fileExtensionZst) && !isZstdTar(type);
}

private static boolean isIso(String type) {
return type.endsWith(fileExtensionIso);
}

private static String getExtension(String path) {
return path.substring(path.indexOf('.') + 1).toLowerCase();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,9 @@ public static class BadArchiveNotice extends IOException {
public BadArchiveNotice(@NonNull Throwable reason) {
super(reason);
}

public BadArchiveNotice(@NonNull String reason) {
super(reason);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,22 @@ abstract class AbstractCommonsArchiveExtractor(
*/
abstract fun createFrom(inputStream: InputStream): ArchiveInputStream

protected open val onErrorHandler: (Throwable) -> Unit = { e ->
if (e !is EmptyArchiveNotice) {
throw BadArchiveNotice(e)
} else {
throw e
}
}

@Throws(IOException::class)
@Suppress("EmptyWhileBlock")
override fun extractWithFilter(filter: Filter) {
var totalBytes: Long = 0
val archiveEntries = ArrayList<ArchiveEntry>()
var inputStream = createFrom(FileInputStream(filePath))
var archiveEntry: ArchiveEntry?
try {
runCatching {
while (inputStream.nextEntry.also { archiveEntry = it } != null) {
archiveEntry?.run {
if (filter.shouldExtract(name, isDirectory)) {
Expand All @@ -84,7 +92,7 @@ abstract class AbstractCommonsArchiveExtractor(
} else {
throw EmptyArchiveNotice()
}
} finally {
}.onFailure(onErrorHandler).also {
inputStream.close()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,16 @@ abstract class AbstractCompressedTarArchiveExtractor(
abstract fun getCompressorInputStreamClass(): Class<out CompressorInputStream>

override fun createFrom(inputStream: InputStream): TarArchiveInputStream {
return runCatching {
TarArchiveInputStream(compressorInputStreamConstructor.newInstance(inputStream))
}.getOrElse {
throw BadArchiveNotice(it)
if (inputStream.available() < 1) {
throw BadArchiveNotice(
"Empty input stream - no valid compressed data can be found",
)
} else {
return runCatching {
TarArchiveInputStream(compressorInputStreamConstructor.newInstance(inputStream))
}.getOrElse {
throw BadArchiveNotice(it)
}
}
}
}
Loading

0 comments on commit 4be9909

Please sign in to comment.