Support zip files with unsupported compresssion method

This commit is contained in:
topjohnwu
2024-12-11 16:09:34 -08:00
committed by John Wu
parent dc2ae7cfd1
commit 3414415907
4 changed files with 133 additions and 73 deletions

View File

@@ -29,9 +29,8 @@ import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.forEach
import com.topjohnwu.magisk.core.ktx.set
import com.topjohnwu.magisk.core.ktx.withStreams
import com.topjohnwu.magisk.core.ktx.withInOut
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
@@ -43,14 +42,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
/**
* This class drives the execution of file downloads and notification management.
@@ -99,33 +97,35 @@ class DownloadEngine(
}
}
private fun createIntent(context: Context, subject: Subject) =
if (Build.VERSION.SDK_INT >= 34) {
context.intent<com.topjohnwu.magisk.core.Receiver>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
} else {
context.intent<com.topjohnwu.magisk.core.Service>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
}
private fun createBroadcastIntent(context: Context, subject: Subject) =
context.intent<com.topjohnwu.magisk.core.Receiver>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
private fun createServiceIntent(context: Context, subject: Subject) =
context.intent<com.topjohnwu.magisk.core.Service>()
.setAction(ACTION)
.putExtra(SUBJECT_KEY, subject)
@SuppressLint("InlinedApi")
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_ONE_SHOT
val intent = createIntent(context, subject)
return if (Build.VERSION.SDK_INT >= 34) {
// On API 34+, download tasks are handled with a user-initiated job.
// However, there is no way to schedule a new job directly with a pending intent.
// As a workaround, we send the subject to a broadcast receiver and have it
// schedule the job for us.
val intent = createBroadcastIntent(context, subject)
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
} else if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
val intent = createServiceIntent(context, subject)
if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
}
}
}
@@ -152,10 +152,13 @@ class DownloadEngine(
.setTransientExtras(extras)
.build()
scheduler.schedule(info)
} else if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(createIntent(context, subject))
} else {
context.startService(createIntent(context, subject))
val intent = createServiceIntent(context, subject)
if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
}
@@ -285,11 +288,11 @@ class DownloadEngine(
}
// Extract stub
val zf = ZipFile(updateApk)
val apk = context.cachedFile("stub.apk")
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close()
ZipFile.Builder().setFile(updateApk).get().use { zf ->
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
}
// Patch and install
subject.intent = AppMigration.upgradeStub(context, apk)
@@ -308,29 +311,36 @@ class DownloadEngine(
}
private suspend fun handleModule(src: InputStream, file: Uri) {
val input = ZipInputStream(src)
val output = ZipOutputStream(file.outputStream())
val tmp = context.cachedFile("module.zip")
try {
// First download the entire zip into cache so we can process it
src.writeTo(tmp)
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
val input = ZipFile.Builder().setFile(tmp).get()
val output = ZipArchiveOutputStream(file.outputStream())
withInOut(input, output) { zin, zout ->
zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
zout.closeArchiveEntry()
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
zout.closeArchiveEntry()
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray())
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
zout.closeArchiveEntry()
zin.forEach { entry ->
val path = entry.name
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyAll(zout)
}
}
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray())
zout.closeArchiveEntry()
// Then simply copy all entries to output
zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
}
} finally {
tmp.delete()
}
}

View File

@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InputStream
@@ -17,24 +18,14 @@ import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
var entry: ZipEntry? = nextEntry
while (entry != null) {
callback(entry)
entry = nextEntry
}
}
inline fun <In : InputStream, Out : OutputStream> withStreams(
inStream: In,
outStream: Out,
inline fun <In : Closeable, Out : Closeable> withInOut(
input: In,
output: Out,
withBoth: (In, Out) -> Unit
) {
inStream.use { reader ->
outStream.use { writer ->
input.use { reader ->
output.use { writer ->
withBoth(reader, writer)
}
}
@@ -64,7 +55,7 @@ suspend inline fun InputStream.copyAndClose(
out: OutputStream,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
@Throws(IOException::class)
suspend inline fun InputStream.writeTo(

View File

@@ -2,6 +2,10 @@ package com.topjohnwu.magisk.core.utils;
import android.os.Build;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipUtil;
import java.nio.file.attribute.FileTime;
import java.util.zip.ZipEntry;
@@ -29,4 +33,15 @@ public class Desugar {
return null;
}
}
/**
* Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call
* {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage
* of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},
* which does not need to actually understand the content of the zip entry. By removing
* this feature check, we can modify zip files using unsupported compression methods.
*/
public static void checkRequestedFeatures(final ZipArchiveEntry ze) {
// No-op
}
}