Skip to content

Commit 4be9909

Browse files
committed
ISO9660 and ZStandard archive support
Fixes #2988 Fixes #3318
1 parent 978fa99 commit 4be9909

33 files changed

+729
-29
lines changed

app/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ dependencies {
147147
implementation libs.androidX.multidex //Multiple dex files
148148
implementation libs.room.runtime
149149
implementation libs.room.rxjava2
150+
testImplementation "com.github.luben:zstd-jni:1.5.6-4"
150151

151152
ksp libs.room.compiler
152153
ksp libs.androidX.annotation
@@ -189,6 +190,11 @@ dependencies {
189190

190191
implementation libs.commons.compress
191192

193+
implementation "com.github.luben:zstd-jni:1.5.6-4@aar"
194+
implementation ("com.github.stephenc.java-iso-tools:loopy-core:1.2.2") {
195+
transitive = false
196+
}
197+
192198
implementation libs.materialdialogs.core
193199
implementation libs.materialdialogs.commons
194200

app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import org.apache.commons.compress.archivers.ArchiveEntry
2929
import org.apache.commons.compress.archivers.ArchiveException
3030
import org.apache.commons.compress.archivers.ArchiveInputStream
3131
import java.io.FileInputStream
32-
import java.io.IOException
3332
import java.io.InputStream
3433
import java.lang.ref.WeakReference
3534

@@ -52,7 +51,7 @@ abstract class AbstractCommonsArchiveHelperCallable(
5251
@Throws(ArchiveException::class)
5352
@Suppress("LabeledExpression")
5453
public override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
55-
try {
54+
runCatching {
5655
createFrom(FileInputStream(filePath)).use { tarInputStream ->
5756
var entry: ArchiveEntry?
5857
while (tarInputStream.nextEntry.also { entry = it } != null) {
@@ -88,8 +87,9 @@ abstract class AbstractCommonsArchiveHelperCallable(
8887
}
8988
}
9089
}
91-
} catch (e: IOException) {
92-
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath), e)
90+
}.onFailure {
91+
logger.error("Error enumerating archive entries", it)
92+
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath))
9393
}
9494
}
9595
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (C) 2014-2023 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.compress
22+
23+
import com.amaze.filemanager.adapters.data.CompressedObjectParcelable
24+
import com.amaze.filemanager.filesystem.compressed.CompressedHelper
25+
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor
26+
import net.didion.loopy.FileEntry
27+
import net.didion.loopy.iso9660.ISO9660FileEntry
28+
import net.didion.loopy.iso9660.ISO9660FileSystem
29+
import java.io.File
30+
import java.io.IOException
31+
import java.lang.reflect.Field
32+
33+
class Iso9660HelperCallable(
34+
private val filePath: String,
35+
private val relativePath: String,
36+
createBackItem: Boolean,
37+
) : CompressedHelperCallable(createBackItem) {
38+
private val SLASH = Regex("/")
39+
40+
// Hack. ISO9660FileEntry doesn't have getter for parentPath, we need to read it on our own
41+
private val parentPathField: Field =
42+
ISO9660FileEntry::class.java.getDeclaredField("parentPath").also {
43+
it.isAccessible = true
44+
}
45+
46+
override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
47+
val isoFile = ISO9660FileSystem(File(filePath), true)
48+
49+
val fileEntries: List<FileEntry> =
50+
runCatching {
51+
isoFile.entries?.let { isoFileEntries ->
52+
isoFileEntries.runCatching {
53+
isoFileEntries.toList().partition { entry ->
54+
CompressedHelper.isEntryPathValid((entry as FileEntry).path)
55+
}.let { pair ->
56+
pair.first as List<FileEntry>
57+
}
58+
}.onFailure {
59+
return
60+
}.getOrThrow()
61+
} ?: throw IOException("Empty archive or file is corrupt")
62+
}.onFailure {
63+
throw Extractor.BadArchiveNotice(it)
64+
}.getOrThrow().filter {
65+
it.name != "."
66+
}
67+
68+
val slashCount =
69+
if (relativePath == "") {
70+
0
71+
} else {
72+
SLASH.findAll("$relativePath/").count()
73+
}
74+
75+
fileEntries.filter {
76+
val parentPath = parentPathField.get(it)?.toString() ?: ""
77+
(
78+
if (slashCount == 0) {
79+
parentPath == ""
80+
} else {
81+
parentPath == "$relativePath/"
82+
}
83+
)
84+
}.forEach { entry ->
85+
elements.add(
86+
CompressedObjectParcelable(
87+
entry.name,
88+
entry.lastModifiedTime,
89+
entry.size.toLong(),
90+
entry.isDirectory,
91+
),
92+
)
93+
}
94+
}
95+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (C) 2014-2021 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.compress
22+
23+
import android.content.Context
24+
import org.apache.commons.compress.compressors.CompressorInputStream
25+
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream
26+
27+
class TarZstHelperCallable(
28+
context: Context,
29+
filePath: String,
30+
relativePath: String,
31+
goBack: Boolean,
32+
) :
33+
AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) {
34+
override fun getCompressorInputStreamClass(): Class<out CompressorInputStream> = ZstdCompressorInputStream::class.java
35+
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor;
3131
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Bzip2Extractor;
3232
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.GzipExtractor;
33+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Iso9660Extractor;
3334
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.LzmaExtractor;
3435
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.RarExtractor;
3536
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.SevenZipExtractor;
@@ -38,16 +39,20 @@
3839
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarGzExtractor;
3940
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarLzmaExtractor;
4041
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarXzExtractor;
42+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarZstExtractor;
4143
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.XzExtractor;
4244
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZipExtractor;
45+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZstdExtractor;
4346
import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor;
47+
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.Iso9660Decompressor;
4448
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.RarDecompressor;
4549
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.SevenZipDecompressor;
4650
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarBzip2Decompressor;
4751
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarDecompressor;
4852
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarGzDecompressor;
4953
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarLzmaDecompressor;
5054
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarXzDecompressor;
55+
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarZstDecompressor;
5156
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.UnknownCompressedFileDecompressor;
5257
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.ZipDecompressor;
5358
import com.amaze.filemanager.utils.Utils;
@@ -80,10 +85,14 @@ public abstract class CompressedHelper {
8085
public static final String fileExtension7zip = "7z";
8186
public static final String fileExtensionTarLzma = "tar.lzma";
8287
public static final String fileExtensionTarXz = "tar.xz";
88+
public static final String fileExtensionTarZst = "tar.zst";
8389
public static final String fileExtensionXz = "xz";
8490
public static final String fileExtensionLzma = "lzma";
8591
public static final String fileExtensionGz = "gz";
8692
public static final String fileExtensionBzip2 = "bz2";
93+
public static final String fileExtensionZst = "zst";
94+
95+
public static final String fileExtensionIso = "iso";
8796

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

@@ -114,6 +123,9 @@ public static Extractor getExtractorInstance(
114123
} else if (isLzippedTar(type)) {
115124
extractor =
116125
new TarLzmaExtractor(context, file.getPath(), outputPath, listener, updatePosition);
126+
} else if (isZstdTar(type)) {
127+
extractor =
128+
new TarZstExtractor(context, file.getPath(), outputPath, listener, updatePosition);
117129
} else if (is7zip(type)) {
118130
extractor =
119131
new SevenZipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
@@ -125,6 +137,11 @@ public static Extractor getExtractorInstance(
125137
extractor = new GzipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
126138
} else if (isBzip2(type)) {
127139
extractor = new Bzip2Extractor(context, file.getPath(), outputPath, listener, updatePosition);
140+
} else if (isZst(type)) {
141+
extractor = new ZstdExtractor(context, file.getPath(), outputPath, listener, updatePosition);
142+
} else if (isIso(type)) {
143+
extractor =
144+
new Iso9660Extractor(context, file.getPath(), outputPath, listener, updatePosition);
128145
} else {
129146
if (BuildConfig.DEBUG) {
130147
throw new IllegalArgumentException("The compressed file has no way of opening it: " + file);
@@ -156,9 +173,13 @@ public static Decompressor getCompressorInstance(@NonNull Context context, @NonN
156173
decompressor = new TarXzDecompressor(context);
157174
} else if (isLzippedTar(type)) {
158175
decompressor = new TarLzmaDecompressor(context);
176+
} else if (isZstdTar(type)) {
177+
decompressor = new TarZstDecompressor(context);
159178
} else if (is7zip(type)) {
160179
decompressor = new SevenZipDecompressor(context);
161-
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type)) {
180+
} else if (isIso(type)) {
181+
decompressor = new Iso9660Decompressor(context);
182+
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type) || isZst(type)) {
162183
// These 4 types are only compressing one single file.
163184
// Hence invoking this UnknownCompressedFileDecompressor which only returns the filename
164185
// without the compression extension
@@ -190,10 +211,13 @@ public static boolean isFileExtractable(String path) {
190211
|| isBzippedTar(type)
191212
|| isXzippedTar(type)
192213
|| isLzippedTar(type)
214+
|| isZstdTar(type)
193215
|| isBzip2(type)
194216
|| isGzip(type)
195217
|| isLzma(type)
196-
|| isXz(type);
218+
|| isXz(type)
219+
|| isZst(type)
220+
|| isIso(type);
197221
}
198222

199223
/**
@@ -216,12 +240,15 @@ public static String getFileName(String compressedName) {
216240
|| isGzip(compressedName)
217241
|| isBzip2(compressedName)
218242
|| isLzma(compressedName)
219-
|| isXz(compressedName))) {
243+
|| isXz(compressedName)
244+
|| isZst(compressedName)
245+
|| isIso(compressedName))) {
220246
return compressedName.substring(0, compressedName.lastIndexOf("."));
221247
} else if (hasFileName && isGzippedTar(compressedName)
222248
|| isXzippedTar(compressedName)
223249
|| isLzippedTar(compressedName)
224-
|| isBzippedTar(compressedName)) {
250+
|| isBzippedTar(compressedName)
251+
|| isZstdTar(compressedName)) {
225252
return compressedName.substring(0, Utils.nthToLastCharIndex(2, compressedName, '.'));
226253
} else {
227254
return compressedName;
@@ -267,6 +294,10 @@ private static boolean isLzippedTar(String type) {
267294
return type.endsWith(fileExtensionTarLzma);
268295
}
269296

297+
private static boolean isZstdTar(String type) {
298+
return type.endsWith(fileExtensionTarZst);
299+
}
300+
270301
private static boolean isXz(String type) {
271302
return type.endsWith(fileExtensionXz) && !isXzippedTar(type);
272303
}
@@ -283,6 +314,14 @@ private static boolean isBzip2(String type) {
283314
return type.endsWith(fileExtensionBzip2) && !isBzippedTar(type);
284315
}
285316

317+
private static boolean isZst(String type) {
318+
return type.endsWith(fileExtensionZst) && !isZstdTar(type);
319+
}
320+
321+
private static boolean isIso(String type) {
322+
return type.endsWith(fileExtensionIso);
323+
}
324+
286325
private static String getExtension(String path) {
287326
return path.substring(path.indexOf('.') + 1).toLowerCase();
288327
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,9 @@ public static class BadArchiveNotice extends IOException {
119119
public BadArchiveNotice(@NonNull Throwable reason) {
120120
super(reason);
121121
}
122+
123+
public BadArchiveNotice(@NonNull String reason) {
124+
super(reason);
125+
}
122126
}
123127
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsArchiveExtractor.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,22 @@ abstract class AbstractCommonsArchiveExtractor(
5151
*/
5252
abstract fun createFrom(inputStream: InputStream): ArchiveInputStream
5353

54+
protected open val onErrorHandler: (Throwable) -> Unit = { e ->
55+
if (e !is EmptyArchiveNotice) {
56+
throw BadArchiveNotice(e)
57+
} else {
58+
throw e
59+
}
60+
}
61+
5462
@Throws(IOException::class)
5563
@Suppress("EmptyWhileBlock")
5664
override fun extractWithFilter(filter: Filter) {
5765
var totalBytes: Long = 0
5866
val archiveEntries = ArrayList<ArchiveEntry>()
5967
var inputStream = createFrom(FileInputStream(filePath))
6068
var archiveEntry: ArchiveEntry?
61-
try {
69+
runCatching {
6270
while (inputStream.nextEntry.also { archiveEntry = it } != null) {
6371
archiveEntry?.run {
6472
if (filter.shouldExtract(name, isDirectory)) {
@@ -84,7 +92,7 @@ abstract class AbstractCommonsArchiveExtractor(
8492
} else {
8593
throw EmptyArchiveNotice()
8694
}
87-
} finally {
95+
}.onFailure(onErrorHandler).also {
8896
inputStream.close()
8997
}
9098
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCompressedTarArchiveExtractor.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,16 @@ abstract class AbstractCompressedTarArchiveExtractor(
5454
abstract fun getCompressorInputStreamClass(): Class<out CompressorInputStream>
5555

5656
override fun createFrom(inputStream: InputStream): TarArchiveInputStream {
57-
return runCatching {
58-
TarArchiveInputStream(compressorInputStreamConstructor.newInstance(inputStream))
59-
}.getOrElse {
60-
throw BadArchiveNotice(it)
57+
if (inputStream.available() < 1) {
58+
throw BadArchiveNotice(
59+
"Empty input stream - no valid compressed data can be found",
60+
)
61+
} else {
62+
return runCatching {
63+
TarArchiveInputStream(compressorInputStreamConstructor.newInstance(inputStream))
64+
}.getOrElse {
65+
throw BadArchiveNotice(it)
66+
}
6167
}
6268
}
6369
}

0 commit comments

Comments
 (0)