Support 7z extraction, and fix .xz extraction which would have crashed at runtime

Commons Compress declares its LZMA/LZMA2 support against org.tukaani:xz as an *optional* dependency, so nothing pulled it in transitively and nothing failed at build time. The app nevertheless advertised .xz as extractable, so the first user to open one would have got NoClassDefFoundError: org/tukaani/xz/FilterOptions. XZCompressorInputStream references org/tukaani/xz/XZInputStream directly. The library is now declared, and a round-trip test covers the path that was silently missing.

That same library is what 7z needs, and Commons Compress already ships a pure-Java 7z reader, so 7z extraction costs no native code and no further dependency. The docs had assumed the format was off limits and said so in three places, including the customer-facing store copy.

7z is the only format here that cannot arrive through a stream: its header sits at the end of the file, so the reader has to seek. That is now a capability the backend advertises, StorageSource.openSeekable: the all-files backend answers with a real FileChannel, the SAF fallback with null, so folder mode reports needs-a-seekable-file instead of failing obscurely. Entry names go through the same ArchivePath.safeRelativePath zip-slip guard. 7z creation stays deferred.
This commit is contained in:
2026-10-15 08:47:37 +02:00
parent a461be2a66
commit ba92cf392d
13 changed files with 326 additions and 51 deletions
+8
View File
@@ -22,6 +22,12 @@ coroutines = "1.9.0"
# Archives. Commons Compress is pure Java (zip/tar/gzip/bzip2/xz, 7z read),
# so it adds no native code and therefore no NDK dependency to the build.
commonsCompress = "1.27.1"
# Commons Compress declares its LZMA/LZMA2 support — the `.xz` compressor and the 7z
# format — against this library, as an *optional* dependency. Optional means it is NOT
# pulled in transitively, so leaving it out breaks `.xz` and `.7z` at **runtime** with
# NoClassDefFoundError rather than failing the build. 1.10 is the version Commons
# Compress 1.27.1 itself compiles against.
xz = "1.10"
# Testing
junit = "4.13.2"
@@ -51,6 +57,8 @@ kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commonsCompress" }
# Commons Compress's LZMA/LZMA2 support: the `.xz` compressor and the 7z format.
xz = { group = "org.tukaani", name = "xz", version.ref = "xz" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }