mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-23 13:59:17 +01:00
Add a ZipUtils
class for unpacking zip files
This commit is contained in:
parent
b03f624191
commit
0d1c7965df
111
app/src/main/java/emu/skyline/utils/ZipUtils.kt
Normal file
111
app/src/main/java/emu/skyline/utils/ZipUtils.kt
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
* Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
*/
|
||||
|
||||
package emu.skyline.utils
|
||||
|
||||
import java.io.*
|
||||
import java.lang.reflect.Field
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
interface ZipUtils {
|
||||
companion object {
|
||||
/**
|
||||
* Extracts a zip file to the given target directory.
|
||||
* @exception IOException if unzipping fails for any reason
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun unzip(file : File, targetDirectory : File) {
|
||||
ZipFile(file).use { zipFile ->
|
||||
for (zipEntry in zipFile.entries()) {
|
||||
val destFile = createNewFile(targetDirectory, zipEntry)
|
||||
// If the zip entry is a file, we need to create its parent directories
|
||||
val destDirectory : File? = if (zipEntry.isDirectory) destFile else destFile.parentFile
|
||||
|
||||
// Create the destination directory
|
||||
if (destDirectory == null || (!destDirectory.isDirectory && !destDirectory.mkdirs()))
|
||||
throw FileNotFoundException("Failed to create destination directory: $destDirectory")
|
||||
|
||||
// If the entry is a directory we don't need to copy anything
|
||||
if (zipEntry.isDirectory)
|
||||
continue
|
||||
|
||||
// Copy bytes to destination
|
||||
try {
|
||||
zipFile.getInputStream(zipEntry).use { inputStream ->
|
||||
destFile.outputStream().use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
} catch (e : IOException) {
|
||||
if (destFile.exists())
|
||||
destFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a zip file from the given stream to the given target directory.
|
||||
*
|
||||
* This method is ~5x slower than [unzip], as [ZipInputStream] uses a fixed `512` bytes buffer for inflation,
|
||||
* instead of `8192` bytes or more used by input streams returned by [ZipFile].
|
||||
* This results in ~8x the amount of JNI calls, producing an increased number of array bounds checking, which kills performance.
|
||||
* Usage of this method is discouraged when possible, [unzip] should be used instead.
|
||||
* Nevertheless, it's the only option when extracting zips obtained from content URIs, as a [File] object cannot be obtained from them.
|
||||
* @exception IOException if unzipping fails for any reason
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun unzip(stream : InputStream, targetDirectory : File) {
|
||||
ZipInputStream(BufferedInputStream(stream)).use { zis ->
|
||||
do {
|
||||
// Get the next entry, break if we've reached the end
|
||||
val zipEntry = zis.nextEntry ?: break
|
||||
|
||||
val destFile = createNewFile(targetDirectory, zipEntry)
|
||||
// If the zip entry is a file, we need to create its parent directories
|
||||
val destDirectory : File? = if (zipEntry.isDirectory) destFile else destFile.parentFile
|
||||
|
||||
// Create the destination directory
|
||||
if (destDirectory == null || (!destDirectory.isDirectory && !destDirectory.mkdirs()))
|
||||
throw FileNotFoundException("Failed to create destination directory: $destDirectory")
|
||||
|
||||
// If the entry is a directory we don't need to copy anything
|
||||
if (zipEntry.isDirectory)
|
||||
continue
|
||||
|
||||
// Copy bytes to destination
|
||||
try {
|
||||
BufferedOutputStream(destFile.outputStream()).use { zis.copyTo(it) }
|
||||
} catch (e : IOException) {
|
||||
if (destFile.exists())
|
||||
destFile.delete()
|
||||
throw e
|
||||
}
|
||||
} while (true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely creates a new destination file where the given zip entry will be extracted to.
|
||||
*
|
||||
* @exception IOException if the file was being created outside of the target directory
|
||||
* **see:** [Zip Slip](https://github.com/snyk/zip-slip-vulnerability)
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun createNewFile(destinationDir : File, zipEntry : ZipEntry) : File {
|
||||
val destFile = File(destinationDir, zipEntry.name)
|
||||
val destDirPath = destinationDir.canonicalPath
|
||||
val destFilePath = destFile.canonicalPath
|
||||
|
||||
if (!destFilePath.startsWith(destDirPath + File.separator))
|
||||
throw IOException("Entry is outside of the target dir: " + zipEntry.name)
|
||||
|
||||
return destFile
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user