修复 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.model.remote.RemoteBookWebDav
|
||||||
import io.legado.app.ui.widget.dialog.WaitDialog
|
import io.legado.app.ui.widget.dialog.WaitDialog
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import io.legado.app.utils.compress.ZipUtils
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.ensureActive
|
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.help.coroutine.Coroutine
|
||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import io.legado.app.utils.compress.ZipUtils
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
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.config.ThemeConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import io.legado.app.utils.compress.ZipUtils
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.withContext
|
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.help.config.ThemeConfig
|
||||||
import io.legado.app.model.localBook.LocalBook
|
import io.legado.app.model.localBook.LocalBook
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import io.legado.app.utils.compress.ZipUtils
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.delay
|
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.document.HandleFileContract
|
||||||
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
|
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import io.legado.app.utils.compress.ZipUtils
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package io.legado.app.utils
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
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 splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|||||||
@@ -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 androidx.annotation.Keep
|
||||||
import com.github.junrar.Archive
|
import com.github.junrar.Archive
|
||||||
@@ -9,20 +9,30 @@ import java.io.FileOutputStream
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@Suppress("unused","MemberVisibilityCanBePrivate")
|
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
object RarUtils {
|
object RarUtils {
|
||||||
fun unRarToPath(inputStream: InputStream,path:String){
|
|
||||||
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
|
fun unRarToPath(inputStream: InputStream, path: String) {
|
||||||
unRarToPath(inputStream, File(path))
|
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))
|
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))
|
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))
|
unRarToPath(file, File(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
fun unRarToPath(inputStream: InputStream, destDir: File?) {
|
fun unRarToPath(inputStream: InputStream, destDir: File?) {
|
||||||
Archive(inputStream).use {
|
Archive(inputStream).use {
|
||||||
unRarToPath(it, destDir)
|
unRarToPath(it, destDir)
|
||||||
@@ -30,28 +40,36 @@ object RarUtils {
|
|||||||
inputStream.close()
|
inputStream.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
fun unRarToPath(byteArray: ByteArray, destDir: File?) {
|
fun unRarToPath(byteArray: ByteArray, destDir: File?) {
|
||||||
Archive(ByteArrayInputStream(byteArray)).use {
|
Archive(ByteArrayInputStream(byteArray)).use {
|
||||||
unRarToPath(it, destDir)
|
unRarToPath(it, destDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unRarToPath(filePath:String, destDir: File?) {
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
|
fun unRarToPath(filePath: String, destDir: File?) {
|
||||||
Archive(File(filePath)).use {
|
Archive(File(filePath)).use {
|
||||||
unRarToPath(it, destDir)
|
unRarToPath(it, destDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
fun unRarToPath(file: File, destDir: File?) {
|
fun unRarToPath(file: File, destDir: File?) {
|
||||||
Archive(file).use {
|
Archive(file).use {
|
||||||
unRarToPath(it, destDir)
|
unRarToPath(it, destDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(NullPointerException::class, SecurityException::class)
|
||||||
fun unRarToPath(archive: Archive, destDir: File?) {
|
fun unRarToPath(archive: Archive, destDir: File?) {
|
||||||
|
destDir ?: throw NullPointerException("解决路径不能为空")
|
||||||
var entry: FileHeader?
|
var entry: FileHeader?
|
||||||
while (archive.nextFileHeader().also { entry = it } != null) {
|
while (archive.nextFileHeader().also { entry = it } != null) {
|
||||||
val entryFile = File(destDir, entry!!.fileName)
|
val entryFile = File(destDir, entry!!.fileName)
|
||||||
|
if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) {
|
||||||
|
throw SecurityException("压缩文件只能解压到指定路径")
|
||||||
|
}
|
||||||
if (entry!!.isDirectory) {
|
if (entry!!.isDirectory) {
|
||||||
if (!entryFile.exists()) {
|
if (!entryFile.exists()) {
|
||||||
entryFile.mkdirs()
|
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.Dispatchers.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.*
|
import java.io.*
|
||||||
@@ -174,15 +176,21 @@ object ZipUtils {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(SecurityException::class)
|
||||||
fun unZipToPath(inputStream: InputStream, path: String) {
|
fun unZipToPath(inputStream: InputStream, path: String) {
|
||||||
val zipInputStream = ZipInputStream(inputStream)
|
ZipInputStream(inputStream).use {
|
||||||
unZipToPath(zipInputStream, path)
|
unZipToPath(it, path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(SecurityException::class)
|
||||||
fun unZipToPath(zipInputStream: ZipInputStream, path: String) {
|
fun unZipToPath(zipInputStream: ZipInputStream, path: String) {
|
||||||
var entry: ZipEntry?
|
var entry: ZipEntry?
|
||||||
while (zipInputStream.nextEntry.also { entry = it } != null) {
|
while (zipInputStream.nextEntry.also { entry = it } != null) {
|
||||||
val entryFile = File(path, entry!!.name)
|
val entryFile = File(path, entry!!.name)
|
||||||
|
if (!entryFile.canonicalPath.startsWith(path)) {
|
||||||
|
throw SecurityException("压缩文件只能解压到指定路径")
|
||||||
|
}
|
||||||
if (entry!!.isDirectory) {
|
if (entry!!.isDirectory) {
|
||||||
if (!entryFile.exists()) {
|
if (!entryFile.exists()) {
|
||||||
entryFile.mkdirs()
|
entryFile.mkdirs()
|
||||||
@@ -201,7 +209,6 @@ object ZipUtils {
|
|||||||
zipInputStream.copyTo(it)
|
zipInputStream.copyTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zipInputStream.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -302,7 +309,7 @@ object ZipUtils {
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class, SecurityException::class)
|
||||||
private fun unzipChildFile(
|
private fun unzipChildFile(
|
||||||
destDir: File,
|
destDir: File,
|
||||||
files: MutableList<File>,
|
files: MutableList<File>,
|
||||||
@@ -311,14 +318,17 @@ object ZipUtils {
|
|||||||
name: String
|
name: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val file = File(destDir, name)
|
val file = File(destDir, name)
|
||||||
|
if (!file.canonicalPath.startsWith(destDir.canonicalPath)) {
|
||||||
|
throw SecurityException("压缩文件只能解压到指定路径")
|
||||||
|
}
|
||||||
files.add(file)
|
files.add(file)
|
||||||
if (entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
return createOrExistsDir(file)
|
return createOrExistsDir(file)
|
||||||
} else {
|
} else {
|
||||||
if (!createOrExistsFile(file)) return false
|
if (!createOrExistsFile(file)) return false
|
||||||
BufferedInputStream(zip.getInputStream(entry)).use { `in` ->
|
zip.getInputStream(entry).use { input ->
|
||||||
BufferedOutputStream(FileOutputStream(file)).use { out ->
|
FileOutputStream(file).use { out ->
|
||||||
out.write(`in`.readBytes())
|
input.copyTo(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user