diff --git a/app/src/main/java/io/legado/app/help/AppWebDav.kt b/app/src/main/java/io/legado/app/help/AppWebDav.kt index d4d27999f..a5878dfc3 100644 --- a/app/src/main/java/io/legado/app/help/AppWebDav.kt +++ b/app/src/main/java/io/legado/app/help/AppWebDav.kt @@ -21,6 +21,7 @@ import io.legado.app.lib.webdav.WebDavFile import io.legado.app.model.remote.RemoteBookWebDav import io.legado.app.ui.widget.dialog.WaitDialog import io.legado.app.utils.* +import io.legado.app.utils.compress.ZipUtils import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ensureActive diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index f77598223..076ef047f 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -13,6 +13,7 @@ import io.legado.app.help.DefaultData import io.legado.app.help.coroutine.Coroutine import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.* +import io.legado.app.utils.compress.ZipUtils import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.withContext import splitties.init.appCtx diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index f8dda491a..69a8a1884 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -14,6 +14,7 @@ import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ThemeConfig import io.legado.app.help.coroutine.Coroutine import io.legado.app.utils.* +import io.legado.app.utils.compress.ZipUtils import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 0161e6502..4690e77df 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -20,6 +20,7 @@ import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ThemeConfig import io.legado.app.model.localBook.LocalBook import io.legado.app.utils.* +import io.legado.app.utils.compress.ZipUtils import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.delay diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 8c59f7c65..1d745502d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -34,6 +34,7 @@ import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.document.HandleFileContract import io.legado.app.ui.widget.seekbar.SeekBarChangeListener import io.legado.app.utils.* +import io.legado.app.utils.compress.ZipUtils import io.legado.app.utils.viewbindingdelegate.viewBinding import splitties.init.appCtx diff --git a/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt b/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt index 2ab8db9cc..afa70c8dc 100644 --- a/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ArchiveUtils.kt @@ -2,6 +2,9 @@ package io.legado.app.utils import android.net.Uri import androidx.documentfile.provider.DocumentFile +import io.legado.app.utils.compress.RarUtils +import io.legado.app.utils.compress.SevenZipUtils +import io.legado.app.utils.compress.ZipUtils import splitties.init.appCtx import java.io.File @@ -52,7 +55,7 @@ object ArchiveUtils { val workPath = workPathFileDoc.toString() archiveFileDoc.uri.inputStream(appCtx).getOrThrow().use { when { - name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath(it, workPath) + name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath(it, workPath) name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath(it, workPath) name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath(it, workPath) else -> throw IllegalArgumentException("Unexpected archive format") diff --git a/app/src/main/java/io/legado/app/utils/SevenZipUtils.kt b/app/src/main/java/io/legado/app/utils/SevenZipUtils.kt deleted file mode 100644 index fb71b77e0..000000000 --- a/app/src/main/java/io/legado/app/utils/SevenZipUtils.kt +++ /dev/null @@ -1,75 +0,0 @@ -package io.legado.app.utils - -import android.annotation.SuppressLint -import android.os.ParcelFileDescriptor -import androidx.annotation.Keep -import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry -import org.apache.commons.compress.archivers.sevenz.SevenZFile -import org.apache.commons.compress.utils.SeekableInMemoryByteChannel -import java.io.File -import java.io.FileOutputStream -import java.io.InputStream -import java.nio.channels.FileChannel - -@Keep -@Suppress("unused","MemberVisibilityCanBePrivate") -object SevenZipUtils { - - fun un7zToPath(inputStream: InputStream, path:String){ - un7zToPath(inputStream,File(path)) - } - fun un7zToPath(byteArray: ByteArray, path:String){ - un7zToPath(byteArray,File(path)) - } - fun un7zToPath(pfd: ParcelFileDescriptor, path:String){ - un7zToPath(pfd,File(path)) - } - fun un7zToPath(fileChannel: FileChannel, path:String){ - un7zToPath(fileChannel,File(path)) - } - - fun un7zToPath(inputStream: InputStream, destDir: File?){ - un7zToPath(SevenZFile(SeekableInMemoryByteChannel(inputStream.readBytes())),destDir) - } - fun un7zToPath(byteArray: ByteArray, destDir: File?){ - un7zToPath(SevenZFile(SeekableInMemoryByteChannel(byteArray)),destDir) - } - fun un7zToPath(pfd: ParcelFileDescriptor, destDir: File?){ - un7zToPath(SevenZFile(ParcelFileDescriptorChannel(pfd)),destDir) - } - @SuppressLint("NewApi") - fun un7zToPath(fileChannel: FileChannel, destDir: File?){ - un7zToPath(SevenZFile(fileChannel),destDir) - } - fun un7zToPath(file: File, destDir: File?){ - un7zToPath(SevenZFile(file),destDir) - } - fun un7zToPath(filePath: String, destDir: File?){ - un7zToPath(SevenZFile(File(filePath)),destDir) - } - - - fun un7zToPath(sevenZFile:SevenZFile, destDir: File?){ - var entry: SevenZArchiveEntry? - while (sevenZFile.nextEntry.also { entry=it }!=null) { - val entryFile = File(destDir, entry!!.name) - if (entry!!.isDirectory) { - if (!entryFile.exists()) { - entryFile.mkdirs() - } - continue - } - if (entryFile.parentFile?.exists() != true) { - entryFile.parentFile?.mkdirs() - } - if (!entryFile.exists()) { - entryFile.createNewFile() - entryFile.setReadable(true) - entryFile.setExecutable(true) - } - FileOutputStream(entryFile).use { - sevenZFile.getInputStream(entry).copyTo(it) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/RarUtils.kt b/app/src/main/java/io/legado/app/utils/compress/RarUtils.kt similarity index 60% rename from app/src/main/java/io/legado/app/utils/RarUtils.kt rename to app/src/main/java/io/legado/app/utils/compress/RarUtils.kt index db091cb33..b2645971e 100644 --- a/app/src/main/java/io/legado/app/utils/RarUtils.kt +++ b/app/src/main/java/io/legado/app/utils/compress/RarUtils.kt @@ -1,4 +1,4 @@ -package io.legado.app.utils +package io.legado.app.utils.compress import androidx.annotation.Keep import com.github.junrar.Archive @@ -9,20 +9,30 @@ import java.io.FileOutputStream import java.io.InputStream @Keep -@Suppress("unused","MemberVisibilityCanBePrivate") +@Suppress("unused", "MemberVisibilityCanBePrivate") object RarUtils { - fun unRarToPath(inputStream: InputStream,path:String){ + + @Throws(NullPointerException::class, SecurityException::class) + fun unRarToPath(inputStream: InputStream, path: String) { unRarToPath(inputStream, File(path)) } - fun unRarToPath(byteArray: ByteArray,path:String){ + + @Throws(NullPointerException::class, SecurityException::class) + fun unRarToPath(byteArray: ByteArray, path: String) { unRarToPath(byteArray, File(path)) } - fun unRarToPath(zipPath:String,path:String){ + + @Throws(NullPointerException::class, SecurityException::class) + fun unRarToPath(zipPath: String, path: String) { unRarToPath(zipPath, File(path)) } - fun unRarToPath(file: File,path:String){ + + @Throws(NullPointerException::class, SecurityException::class) + fun unRarToPath(file: File, path: String) { unRarToPath(file, File(path)) } + + @Throws(NullPointerException::class, SecurityException::class) fun unRarToPath(inputStream: InputStream, destDir: File?) { Archive(inputStream).use { unRarToPath(it, destDir) @@ -30,28 +40,36 @@ object RarUtils { inputStream.close() } + @Throws(NullPointerException::class, SecurityException::class) fun unRarToPath(byteArray: ByteArray, destDir: File?) { Archive(ByteArrayInputStream(byteArray)).use { unRarToPath(it, destDir) } } - fun unRarToPath(filePath:String, destDir: File?) { + @Throws(NullPointerException::class, SecurityException::class) + fun unRarToPath(filePath: String, destDir: File?) { Archive(File(filePath)).use { unRarToPath(it, destDir) } } + @Throws(NullPointerException::class, SecurityException::class) fun unRarToPath(file: File, destDir: File?) { Archive(file).use { unRarToPath(it, destDir) } } + @Throws(NullPointerException::class, SecurityException::class) fun unRarToPath(archive: Archive, destDir: File?) { + destDir ?: throw NullPointerException("解决路径不能为空") var entry: FileHeader? while (archive.nextFileHeader().also { entry = it } != null) { val entryFile = File(destDir, entry!!.fileName) + if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) { + throw SecurityException("压缩文件只能解压到指定路径") + } if (entry!!.isDirectory) { if (!entryFile.exists()) { entryFile.mkdirs() diff --git a/app/src/main/java/io/legado/app/utils/compress/SevenZipUtils.kt b/app/src/main/java/io/legado/app/utils/compress/SevenZipUtils.kt new file mode 100644 index 000000000..0b1782b49 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/compress/SevenZipUtils.kt @@ -0,0 +1,98 @@ +package io.legado.app.utils.compress + +import android.annotation.SuppressLint +import android.os.ParcelFileDescriptor +import androidx.annotation.Keep +import io.legado.app.utils.ParcelFileDescriptorChannel +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry +import org.apache.commons.compress.archivers.sevenz.SevenZFile +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.nio.channels.FileChannel + +@Keep +@Suppress("unused", "MemberVisibilityCanBePrivate") +object SevenZipUtils { + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(inputStream: InputStream, path: String) { + un7zToPath(inputStream, File(path)) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(byteArray: ByteArray, path: String) { + un7zToPath(byteArray, File(path)) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(pfd: ParcelFileDescriptor, path: String) { + un7zToPath(pfd, File(path)) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(fileChannel: FileChannel, path: String) { + un7zToPath(fileChannel, File(path)) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(inputStream: InputStream, destDir: File?) { + un7zToPath(SevenZFile(SeekableInMemoryByteChannel(inputStream.readBytes())), destDir) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(byteArray: ByteArray, destDir: File?) { + un7zToPath(SevenZFile(SeekableInMemoryByteChannel(byteArray)), destDir) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(pfd: ParcelFileDescriptor, destDir: File?) { + un7zToPath(SevenZFile(ParcelFileDescriptorChannel(pfd)), destDir) + } + + @SuppressLint("NewApi") + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(fileChannel: FileChannel, destDir: File?) { + un7zToPath(SevenZFile(fileChannel), destDir) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(file: File, destDir: File?) { + un7zToPath(SevenZFile(file), destDir) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(filePath: String, destDir: File?) { + un7zToPath(SevenZFile(File(filePath)), destDir) + } + + @Throws(NullPointerException::class, SecurityException::class) + fun un7zToPath(sevenZFile: SevenZFile, destDir: File?) { + destDir ?: throw NullPointerException("解决路径不能为空") + var entry: SevenZArchiveEntry? + while (sevenZFile.nextEntry.also { entry = it } != null) { + val entryFile = File(destDir, entry!!.name) + if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) { + throw SecurityException("压缩文件只能解压到指定路径") + } + if (entry!!.isDirectory) { + if (!entryFile.exists()) { + entryFile.mkdirs() + } + continue + } + if (entryFile.parentFile?.exists() != true) { + entryFile.parentFile?.mkdirs() + } + if (!entryFile.exists()) { + entryFile.createNewFile() + entryFile.setReadable(true) + entryFile.setExecutable(true) + } + FileOutputStream(entryFile).use { + sevenZFile.getInputStream(entry).copyTo(it) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/ZipUtils.kt b/app/src/main/java/io/legado/app/utils/compress/ZipUtils.kt similarity index 94% rename from app/src/main/java/io/legado/app/utils/ZipUtils.kt rename to app/src/main/java/io/legado/app/utils/compress/ZipUtils.kt index a92eb35d6..f5278bf25 100644 --- a/app/src/main/java/io/legado/app/utils/ZipUtils.kt +++ b/app/src/main/java/io/legado/app/utils/compress/ZipUtils.kt @@ -1,5 +1,7 @@ -package io.legado.app.utils +package io.legado.app.utils.compress +import io.legado.app.utils.DebugLog +import io.legado.app.utils.printOnDebug import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.withContext import java.io.* @@ -174,15 +176,21 @@ object ZipUtils { return true } + @Throws(SecurityException::class) fun unZipToPath(inputStream: InputStream, path: String) { - val zipInputStream = ZipInputStream(inputStream) - unZipToPath(zipInputStream, path) + ZipInputStream(inputStream).use { + unZipToPath(it, path) + } } + @Throws(SecurityException::class) fun unZipToPath(zipInputStream: ZipInputStream, path: String) { var entry: ZipEntry? while (zipInputStream.nextEntry.also { entry = it } != null) { val entryFile = File(path, entry!!.name) + if (!entryFile.canonicalPath.startsWith(path)) { + throw SecurityException("压缩文件只能解压到指定路径") + } if (entry!!.isDirectory) { if (!entryFile.exists()) { entryFile.mkdirs() @@ -201,7 +209,6 @@ object ZipUtils { zipInputStream.copyTo(it) } } - zipInputStream.close() } /** @@ -302,7 +309,7 @@ object ZipUtils { return files } - @Throws(IOException::class) + @Throws(IOException::class, SecurityException::class) private fun unzipChildFile( destDir: File, files: MutableList, @@ -311,14 +318,17 @@ object ZipUtils { name: String ): Boolean { val file = File(destDir, name) + if (!file.canonicalPath.startsWith(destDir.canonicalPath)) { + throw SecurityException("压缩文件只能解压到指定路径") + } files.add(file) if (entry.isDirectory) { return createOrExistsDir(file) } else { if (!createOrExistsFile(file)) return false - BufferedInputStream(zip.getInputStream(entry)).use { `in` -> - BufferedOutputStream(FileOutputStream(file)).use { out -> - out.write(`in`.readBytes()) + zip.getInputStream(entry).use { input -> + FileOutputStream(file).use { out -> + input.copyTo(out) } } }