Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISO9660 and ZStandard archive support #3959

Open
wants to merge 1 commit into
base: release/4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading