修复 Zip 路径遍历漏洞
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-7
@@ -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()
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-8
@@ -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<File>,
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user