MOOOOAAAAAAR KOTLIN
Signed-off-by: androidacy-user <opensource@androidacy.com>pull/27/head
parent
3096b89e28
commit
f3bd95b251
@ -1,36 +1,39 @@
|
||||
package com.fox2code.mmm;
|
||||
package com.fox2code.mmm
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum Constants {
|
||||
@Suppress("unused")
|
||||
enum class Constants {
|
||||
;
|
||||
public static final int MAGISK_VER_CODE_FLAT_MODULES = 19000;
|
||||
public static final int MAGISK_VER_CODE_UTIL_INSTALL = 20400;
|
||||
public static final int MAGISK_VER_CODE_PATH_SUPPORT = 21000;
|
||||
public static final int MAGISK_VER_CODE_INSTALL_COMMAND = 21200;
|
||||
public static final int MAGISK_VER_CODE_MAGISK_ZYGOTE = 24000;
|
||||
public static final String INTENT_INSTALL_INTERNAL =
|
||||
BuildConfig.APPLICATION_ID + ".intent.action.INSTALL_MODULE_INTERNAL";
|
||||
public static final String INTENT_ANDROIDACY_INTERNAL =
|
||||
BuildConfig.APPLICATION_ID + ".intent.action.OPEN_ANDROIDACY_INTERNAL";
|
||||
public static final String EXTRA_INSTALL_PATH = "extra_install_path";
|
||||
public static final String EXTRA_INSTALL_NAME = "extra_install_name";
|
||||
public static final String EXTRA_INSTALL_CONFIG = "extra_install_config";
|
||||
public static final String EXTRA_INSTALL_CHECKSUM = "extra_install_checksum";
|
||||
public static final String EXTRA_INSTALL_MMT_REBORN = "extra_install_mmt_reborn";
|
||||
public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions";
|
||||
public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless";
|
||||
public static final String EXTRA_ANDROIDACY_COMPAT_LEVEL = "extra_androidacy_compat_level";
|
||||
public static final String EXTRA_ANDROIDACY_ALLOW_INSTALL = "extra_androidacy_allow_install";
|
||||
public static final String EXTRA_ANDROIDACY_ACTIONBAR_TITLE = "extra_androidacy_actionbar_title";
|
||||
public static final String EXTRA_ANDROIDACY_ACTIONBAR_CONFIG = "extra_androidacy_actionbar_config";
|
||||
public static final String EXTRA_MARKDOWN_URL = "extra_markdown_url";
|
||||
public static final String EXTRA_MARKDOWN_TITLE = "extra_markdown_title";
|
||||
public static final String EXTRA_MARKDOWN_CONFIG = "extra_markdown_config";
|
||||
public static final String EXTRA_MARKDOWN_CHANGE_BOOT = "extra_markdown_change_boot";
|
||||
public static final String EXTRA_MARKDOWN_NEEDS_RAMDISK = "extra_markdown_needs_ramdisk";
|
||||
public static final String EXTRA_MARKDOWN_MIN_MAGISK = "extra_markdown_min_magisk";
|
||||
public static final String EXTRA_MARKDOWN_MIN_API = "extra_markdown_min_api";
|
||||
public static final String EXTRA_MARKDOWN_MAX_API = "extra_markdown_max_api";
|
||||
public static final String EXTRA_FADE_OUT = "extra_fade_out";
|
||||
public static final String EXTRA_FROM_MANAGER = "extra_from_manager";
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAGISK_VER_CODE_FLAT_MODULES = 19000
|
||||
const val MAGISK_VER_CODE_UTIL_INSTALL = 20400
|
||||
const val MAGISK_VER_CODE_PATH_SUPPORT = 21000
|
||||
const val MAGISK_VER_CODE_INSTALL_COMMAND = 21200
|
||||
const val MAGISK_VER_CODE_MAGISK_ZYGOTE = 24000
|
||||
const val INTENT_INSTALL_INTERNAL =
|
||||
BuildConfig.APPLICATION_ID + ".intent.action.INSTALL_MODULE_INTERNAL"
|
||||
const val INTENT_ANDROIDACY_INTERNAL =
|
||||
BuildConfig.APPLICATION_ID + ".intent.action.OPEN_ANDROIDACY_INTERNAL"
|
||||
const val EXTRA_INSTALL_PATH = "extra_install_path"
|
||||
const val EXTRA_INSTALL_NAME = "extra_install_name"
|
||||
const val EXTRA_INSTALL_CONFIG = "extra_install_config"
|
||||
const val EXTRA_INSTALL_CHECKSUM = "extra_install_checksum"
|
||||
const val EXTRA_INSTALL_MMT_REBORN = "extra_install_mmt_reborn"
|
||||
const val EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions"
|
||||
const val EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless"
|
||||
const val EXTRA_ANDROIDACY_COMPAT_LEVEL = "extra_androidacy_compat_level"
|
||||
const val EXTRA_ANDROIDACY_ALLOW_INSTALL = "extra_androidacy_allow_install"
|
||||
const val EXTRA_ANDROIDACY_ACTIONBAR_TITLE = "extra_androidacy_actionbar_title"
|
||||
const val EXTRA_ANDROIDACY_ACTIONBAR_CONFIG = "extra_androidacy_actionbar_config"
|
||||
const val EXTRA_MARKDOWN_URL = "extra_markdown_url"
|
||||
const val EXTRA_MARKDOWN_TITLE = "extra_markdown_title"
|
||||
const val EXTRA_MARKDOWN_CONFIG = "extra_markdown_config"
|
||||
const val EXTRA_MARKDOWN_CHANGE_BOOT = "extra_markdown_change_boot"
|
||||
const val EXTRA_MARKDOWN_NEEDS_RAMDISK = "extra_markdown_needs_ramdisk"
|
||||
const val EXTRA_MARKDOWN_MIN_MAGISK = "extra_markdown_min_magisk"
|
||||
const val EXTRA_MARKDOWN_MIN_API = "extra_markdown_min_api"
|
||||
const val EXTRA_MARKDOWN_MAX_API = "extra_markdown_max_api"
|
||||
const val EXTRA_FADE_OUT = "extra_fade_out"
|
||||
const val EXTRA_FROM_MANAGER = "extra_from_manager"
|
||||
}
|
||||
}
|
@ -1,283 +1,303 @@
|
||||
package com.fox2code.mmm.utils.io;
|
||||
package com.fox2code.mmm.utils.io
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import com.fox2code.mmm.MainApplication
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||
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 org.apache.commons.io.FileUtils
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.Objects
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.fox2code.mmm.MainApplication;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
|
||||
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 org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/** @noinspection ResultOfMethodCallIgnored*/
|
||||
public enum Files {
|
||||
/** @noinspection ResultOfMethodCallIgnored
|
||||
*/
|
||||
enum class Files {
|
||||
;
|
||||
private static final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
|
||||
|
||||
// stolen from https://stackoverflow.com/a/25005243
|
||||
public static @NonNull String getFileName(Context context, Uri uri) {
|
||||
String result = null;
|
||||
if (Objects.equals(uri.getScheme(), "content")) {
|
||||
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (index != -1) {
|
||||
result = cursor.getString(index);
|
||||
companion object {
|
||||
private val is64bit = Build.SUPPORTED_64_BIT_ABIS.isNotEmpty()
|
||||
|
||||
// stolen from https://stackoverflow.com/a/25005243
|
||||
@JvmStatic
|
||||
fun getFileName(context: Context, uri: Uri): String {
|
||||
var result: String? = null
|
||||
if (uri.scheme == "content") {
|
||||
context.contentResolver.query(uri, null, null, null, null).use { cursor ->
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (index != -1) {
|
||||
result = cursor.getString(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = Objects.requireNonNull(result).lastIndexOf('/');
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1);
|
||||
if (result == null) {
|
||||
result = uri.path
|
||||
val cut = Objects.requireNonNull<String?>(result).lastIndexOf('/')
|
||||
if (cut != -1) {
|
||||
result = result!!.substring(cut + 1)
|
||||
}
|
||||
}
|
||||
return result!!
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// based on https://stackoverflow.com/a/63018108
|
||||
public static @Nullable Long getFileSize(Context context, Uri uri) {
|
||||
Long result = null;
|
||||
try {
|
||||
String scheme = uri.getScheme();
|
||||
if (Objects.equals(scheme, "content")) {
|
||||
Cursor returnCursor = context.getContentResolver().
|
||||
query(uri, null, null, null, null);
|
||||
int sizeIndex = Objects.requireNonNull(returnCursor).getColumnIndex(OpenableColumns.SIZE);
|
||||
returnCursor.moveToFirst();
|
||||
|
||||
long size = returnCursor.getLong(sizeIndex);
|
||||
returnCursor.close();
|
||||
|
||||
result = size;
|
||||
}
|
||||
if (Objects.equals(scheme, "file")) {
|
||||
result = new File(Objects.requireNonNull(uri.getPath())).length();
|
||||
// based on https://stackoverflow.com/a/63018108
|
||||
@JvmStatic
|
||||
fun getFileSize(context: Context, uri: Uri): Long? {
|
||||
var result: Long? = null
|
||||
try {
|
||||
val scheme = uri.scheme
|
||||
if (scheme == "content") {
|
||||
val returnCursor = context.contentResolver.query(uri, null, null, null, null)
|
||||
val sizeIndex =
|
||||
returnCursor?.getColumnIndex(OpenableColumns.SIZE)
|
||||
returnCursor!!.moveToFirst()
|
||||
val size = sizeIndex.let { it?.let { it1 -> returnCursor.getLong(it1) } }
|
||||
returnCursor.close()
|
||||
result = size
|
||||
}
|
||||
if (scheme == "file") {
|
||||
result = File(Objects.requireNonNull(uri.path)).length()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(Log.getStackTraceString(e))
|
||||
return null
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e(Log.getStackTraceString(e));
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void write(File file, byte[] bytes) throws IOException {
|
||||
// make the dir if necessary
|
||||
Objects.requireNonNull(file.getParentFile()).mkdirs();
|
||||
try (OutputStream outputStream = new FileOutputStream(file)) {
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun write(file: File, bytes: ByteArray?) {
|
||||
// make the dir if necessary
|
||||
Objects.requireNonNull(file.parentFile).mkdirs()
|
||||
FileOutputStream(file).use { outputStream ->
|
||||
outputStream.write(bytes)
|
||||
outputStream.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] read(File file) throws IOException {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return readAllBytes(inputStream);
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun read(file: File?): ByteArray {
|
||||
FileInputStream(file).use { inputStream -> return readAllBytes(inputStream) }
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeSU(File file, byte[] bytes) throws IOException {
|
||||
// make the dir if necessary
|
||||
Objects.requireNonNull(file.getParentFile()).mkdirs();
|
||||
try (OutputStream outputStream = SuFileOutputStream.open(file)) {
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun writeSU(file: File, bytes: ByteArray?) {
|
||||
// make the dir if necessary
|
||||
Objects.requireNonNull(file.parentFile).mkdirs()
|
||||
SuFileOutputStream.open(file).use { outputStream ->
|
||||
outputStream.write(bytes)
|
||||
outputStream.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readSU(File file) throws IOException {
|
||||
if (file.isFile() && file.canRead()) {
|
||||
try { // Read as app if su not required
|
||||
return read(file);
|
||||
} catch (IOException ignored) {
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun readSU(file: File): ByteArray {
|
||||
if (file.isFile && file.canRead()) {
|
||||
try { // Read as app if su not required
|
||||
return read(file)
|
||||
} catch (ignored: IOException) {
|
||||
}
|
||||
}
|
||||
SuFileInputStream.open(file).use { inputStream -> return readAllBytes(inputStream) }
|
||||
}
|
||||
try (InputStream inputStream = SuFileInputStream.open(file)) {
|
||||
return readAllBytes(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean existsSU(File file) {
|
||||
return file.exists() || new SuFile(file.getAbsolutePath()).exists();
|
||||
}
|
||||
@JvmStatic
|
||||
fun existsSU(file: File): Boolean {
|
||||
return file.exists() || SuFile(file.absolutePath).exists()
|
||||
}
|
||||
|
||||
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
|
||||
int nRead;
|
||||
byte[] data = new byte[16384];
|
||||
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
|
||||
outputStream.write(data, 0, nRead);
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun copy(inputStream: InputStream, outputStream: OutputStream) {
|
||||
var nRead: Int
|
||||
val data = ByteArray(16384)
|
||||
while (inputStream.read(data, 0, data.size).also { nRead = it } != -1) {
|
||||
outputStream.write(data, 0, nRead)
|
||||
}
|
||||
outputStream.flush()
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
public static void closeSilently(Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) closeable.close();
|
||||
} catch (IOException ignored) {
|
||||
@JvmStatic
|
||||
fun closeSilently(closeable: Closeable?) {
|
||||
try {
|
||||
closeable?.close()
|
||||
} catch (ignored: IOException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream makeBuffer(long capacity) {
|
||||
// Cap buffer to 1 Gib (or 512 Mib for 32bit) to avoid memory errors
|
||||
return Files.makeBuffer((int) Math.min(capacity, is64bit ? 0x40000000 : 0x20000000));
|
||||
}
|
||||
@JvmStatic
|
||||
fun makeBuffer(capacity: Long): ByteArrayOutputStream {
|
||||
// Cap buffer to 1 Gib (or 512 Mib for 32bit) to avoid memory errors
|
||||
return makeBuffer(
|
||||
capacity.coerceAtMost((if (is64bit) 0x40000000 else 0x20000000).toLong()).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream makeBuffer(int capacity) {
|
||||
return new ByteArrayOutputStream(Math.max(0x20, capacity)) {
|
||||
@NonNull
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return this.buf.length == this.count ?
|
||||
this.buf : super.toByteArray();
|
||||
private fun makeBuffer(capacity: Int): ByteArrayOutputStream {
|
||||
return object : ByteArrayOutputStream(0x20.coerceAtLeast(capacity)) {
|
||||
override fun toByteArray(): ByteArray {
|
||||
return if (buf.size == count) buf else super.toByteArray()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readAllBytes(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream buffer = Files.makeBuffer(inputStream.available());
|
||||
copy(inputStream, buffer);
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun readAllBytes(inputStream: InputStream): ByteArray {
|
||||
val buffer = makeBuffer(inputStream.available())
|
||||
copy(inputStream, buffer)
|
||||
return buffer.toByteArray()
|
||||
}
|
||||
|
||||
public static void fixJavaZipHax(byte[] bytes) {
|
||||
if (bytes.length > 8 && bytes[0x6] == 0x0 && bytes[0x7] == 0x0 && bytes[0x8] == 0x8)
|
||||
bytes[0x7] = 0x8; // Known hax to prevent java zip file read
|
||||
}
|
||||
@JvmStatic
|
||||
fun fixJavaZipHax(bytes: ByteArray) {
|
||||
if (bytes.size > 8 && bytes[0x6].toInt() == 0x0 && bytes[0x7].toInt() == 0x0 && bytes[0x8].toInt() == 0x8) bytes[0x7] =
|
||||
0x8 // Known hax to prevent java zip file read
|
||||
}
|
||||
|
||||
public static void patchModuleSimple(byte[] bytes, OutputStream outputStream) throws IOException {
|
||||
fixJavaZipHax(bytes);
|
||||
patchModuleSimple(new ByteArrayInputStream(bytes), outputStream);
|
||||
}
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun patchModuleSimple(bytes: ByteArray, outputStream: OutputStream?) {
|
||||
fixJavaZipHax(bytes)
|
||||
patchModuleSimple(ByteArrayInputStream(bytes), outputStream)
|
||||
}
|
||||
|
||||
public static void patchModuleSimple(InputStream inputStream, OutputStream outputStream) throws IOException {
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
|
||||
int nRead;
|
||||
byte[] data = new byte[16384];
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
String name = zipEntry.getName();
|
||||
int i = name.indexOf('/', 1);
|
||||
if (i == -1) continue;
|
||||
String newName = name.substring(i + 1);
|
||||
if (newName.startsWith(".git")) continue; // Skip metadata
|
||||
zipOutputStream.putNextEntry(new ZipEntry(newName));
|
||||
while ((nRead = zipInputStream.read(data, 0, data.length)) != -1) {
|
||||
zipOutputStream.write(data, 0, nRead);
|
||||
@Throws(IOException::class)
|
||||
fun patchModuleSimple(inputStream: InputStream?, outputStream: OutputStream?) {
|
||||
val zipInputStream = ZipInputStream(inputStream)
|
||||
val zipOutputStream = ZipOutputStream(outputStream)
|
||||
var nRead: Int
|
||||
val data = ByteArray(16384)
|
||||
var zipEntry: ZipEntry
|
||||
while (zipInputStream.nextEntry.also { zipEntry = it } != null) {
|
||||
val name = zipEntry.name
|
||||
val i = name.indexOf('/', 1)
|
||||
if (i == -1) continue
|
||||
val newName = name.substring(i + 1)
|
||||
if (newName.startsWith(".git")) continue // Skip metadata
|
||||
zipOutputStream.putNextEntry(ZipEntry(newName))
|
||||
while (zipInputStream.read(data, 0, data.size).also { nRead = it } != -1) {
|
||||
zipOutputStream.write(data, 0, nRead)
|
||||
}
|
||||
zipOutputStream.flush()
|
||||
zipOutputStream.closeEntry()
|
||||
zipInputStream.closeEntry()
|
||||
}
|
||||
zipOutputStream.flush();
|
||||
zipOutputStream.closeEntry();
|
||||
zipInputStream.closeEntry();
|
||||
zipOutputStream.finish()
|
||||
zipOutputStream.flush()
|
||||
zipOutputStream.close()
|
||||
zipInputStream.close()
|
||||
}
|
||||
zipOutputStream.finish();
|
||||
zipOutputStream.flush();
|
||||
zipOutputStream.close();
|
||||
zipInputStream.close();
|
||||
}
|
||||
|
||||
public static void fixSourceArchiveShit(byte[] rawModule) {
|
||||
// unzip the module, check if it has just one folder within. if so, switch to the folder and zip up contents, and replace the original file with that
|
||||
try {
|
||||
File tempDir = new File(MainApplication.getINSTANCE().getCacheDir(), "temp");
|
||||
if (tempDir.exists()) {
|
||||
FileUtils.deleteDirectory(tempDir);
|
||||
}
|
||||
if (!tempDir.mkdirs()) {
|
||||
throw new IOException("Unable to create temp dir");
|
||||
}
|
||||
File tempFile = new File(tempDir, "module.zip");
|
||||
Files.write(tempFile, rawModule);
|
||||
File tempUnzipDir = new File(tempDir, "unzip");
|
||||
if (!tempUnzipDir.mkdirs()) {
|
||||
throw new IOException("Unable to create temp unzip dir");
|
||||
}
|
||||
// unzip
|
||||
Timber.d("Unzipping module to %s", tempUnzipDir.getAbsolutePath());
|
||||
try (ZipFile zipFile = new ZipFile(tempFile)) {
|
||||
Enumeration<ZipArchiveEntry> files = zipFile.getEntries();
|
||||
// check if there is only one folder in the top level
|
||||
int folderCount = 0;
|
||||
while (files.hasMoreElements()) {
|
||||
ZipArchiveEntry entry = files.nextElement();
|
||||
if (entry.isDirectory()) {
|
||||
folderCount++;
|
||||
}
|
||||
@JvmStatic
|
||||
fun fixSourceArchiveShit(rawModule: ByteArray?) {
|
||||
// unzip the module, check if it has just one folder within. if so, switch to the folder and zip up contents, and replace the original file with that
|
||||
try {
|
||||
val tempDir = File(MainApplication.getINSTANCE().cacheDir, "temp")
|
||||
if (tempDir.exists()) {
|
||||
FileUtils.deleteDirectory(tempDir)
|
||||
}
|
||||
if (folderCount == 1) {
|
||||
files = zipFile.getEntries();
|
||||
while (files.hasMoreElements()) {
|
||||
ZipArchiveEntry entry = files.nextElement();
|
||||
if (entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
File file = new File(tempUnzipDir, entry.getName());
|
||||
if (!Objects.requireNonNull(file.getParentFile()).exists()) {
|
||||
if (!file.getParentFile().mkdirs()) {
|
||||
throw new IOException("Unable to create parent dir");
|
||||
if (!tempDir.mkdirs()) {
|
||||
throw IOException("Unable to create temp dir")
|
||||
}
|
||||
val tempFile = File(tempDir, "module.zip")
|
||||
write(tempFile, rawModule)
|
||||
val tempUnzipDir = File(tempDir, "unzip")
|
||||
if (!tempUnzipDir.mkdirs()) {
|
||||
throw IOException("Unable to create temp unzip dir")
|
||||
}
|
||||
// unzip
|
||||
Timber.d("Unzipping module to %s", tempUnzipDir.absolutePath)
|
||||
try {
|
||||
ZipFile(tempFile).use { zipFile ->
|
||||
var files = zipFile.entries
|
||||
// check if there is only one folder in the top level
|
||||
var folderCount = 0
|
||||
while (files.hasMoreElements()) {
|
||||
val entry = files.nextElement()
|
||||
if (entry.isDirectory) {
|
||||
folderCount++
|
||||
}
|
||||
}
|
||||
try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(file)) {
|
||||
zipArchiveOutputStream.putArchiveEntry(entry);
|
||||
try (InputStream inputStream = zipFile.getInputStream(entry)) {
|
||||
copy(inputStream, zipArchiveOutputStream);
|
||||
if (folderCount == 1) {
|
||||
files = zipFile.entries
|
||||
while (files.hasMoreElements()) {
|
||||
val entry = files.nextElement()
|
||||
if (entry.isDirectory) {
|
||||
continue
|
||||
}
|
||||
val file = File(tempUnzipDir, entry.name)
|
||||
if (!Objects.requireNonNull(file.parentFile).exists()) {
|
||||
if (!file.parentFile?.mkdirs()!!) {
|
||||
throw IOException("Unable to create parent dir")
|
||||
}
|
||||
}
|
||||
ZipArchiveOutputStream(file).use { zipArchiveOutputStream ->
|
||||
zipArchiveOutputStream.putArchiveEntry(entry)
|
||||
zipFile.getInputStream(entry).use { inputStream ->
|
||||
copy(
|
||||
inputStream,
|
||||
zipArchiveOutputStream
|
||||
)
|
||||
}
|
||||
zipArchiveOutputStream.closeArchiveEntry()
|
||||
}
|
||||
}
|
||||
zipArchiveOutputStream.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
// zip up the contents of the folder but not the folder itself
|
||||
File[] filesInFolder = Objects.requireNonNull(tempUnzipDir.listFiles());
|
||||
// create a new zip file
|
||||
try (ZipArchiveOutputStream archive = new ZipArchiveOutputStream(new FileOutputStream("new.zip"))) {
|
||||
for (File files2 : filesInFolder) {
|
||||
// create a new ZipArchiveEntry and add it to the ZipArchiveOutputStream
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(files2, files2.getName());
|
||||
archive.putArchiveEntry(entry);
|
||||
try (InputStream input = new FileInputStream(files2)) {
|
||||
copy(input, archive);
|
||||
// zip up the contents of the folder but not the folder itself
|
||||
val filesInFolder = Objects.requireNonNull(tempUnzipDir.listFiles())
|
||||
// create a new zip file
|
||||
try {
|
||||
ZipArchiveOutputStream(FileOutputStream("new.zip")).use { archive ->
|
||||
for (files2 in filesInFolder) {
|
||||
// create a new ZipArchiveEntry and add it to the ZipArchiveOutputStream
|
||||
val entry = ZipArchiveEntry(files2, files2.name)
|
||||
archive.putArchiveEntry(entry)
|
||||
FileInputStream(files2).use { input ->
|
||||
copy(
|
||||
input,
|
||||
archive
|
||||
)
|
||||
}
|
||||
archive.closeArchiveEntry()
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "Unable to zip up module")
|
||||
}
|
||||
archive.closeArchiveEntry();
|
||||
} else {
|
||||
Timber.d("Module does not have a single folder in the top level, skipping")
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Unable to zip up module");
|
||||
}
|
||||
} else {
|
||||
Timber.d("Module does not have a single folder in the top level, skipping");
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "Unable to unzip module")
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Unable to unzip module");
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "Unable to create temp dir")
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Unable to create temp dir");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,129 +1,130 @@
|
||||
package com.fox2code.mmm.utils.io;
|
||||
@file:Suppress("UNUSED_PARAMETER")
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
package com.fox2code.mmm.utils.io
|
||||
|
||||
import timber.log.Timber;
|
||||
import timber.log.Timber
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public enum Hashes {
|
||||
@Suppress("UNUSED_EXPRESSION")
|
||||
enum class Hashes {
|
||||
;
|
||||
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
|
||||
private static final Pattern nonAlphaNum = Pattern.compile("[^a-zA-Z0-9]");
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
companion object {
|
||||
private val HEX_ARRAY = "0123456789abcdef".toCharArray()
|
||||
private val nonAlphaNum = Pattern.compile("[^a-zA-Z0-9]")
|
||||
@JvmStatic
|
||||
fun bytesToHex(bytes: ByteArray): String {
|
||||
val hexChars = CharArray(bytes.size * 2)
|
||||
for (j in bytes.indices) {
|
||||
val v = bytes[j].toInt() and 0xFF
|
||||
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
||||
}
|
||||
return String(hexChars)
|
||||
}
|
||||
return String.valueOf(hexChars);
|
||||
}
|
||||
|
||||
public static String hashMd5(byte[] input) {
|
||||
throw new SecurityException("MD5 is not secure");
|
||||
}
|
||||
@JvmStatic
|
||||
fun hashMd5(ignoredInput: ByteArray?): String {
|
||||
throw SecurityException("MD5 is not secure")
|
||||
}
|
||||
|
||||
public static String hashSha1(byte[] input) {
|
||||
throw new SecurityException("SHA-1 is not secure");
|
||||
}
|
||||
@JvmStatic
|
||||
fun hashSha1(ignoredInput: ByteArray?): String {
|
||||
throw SecurityException("SHA-1 is not secure")
|
||||
}
|
||||
|
||||
public static String hashSha256(byte[] input) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
@JvmStatic
|
||||
fun hashSha256(input: ByteArray?): String {
|
||||
input ?: return ""
|
||||
return try {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
bytesToHex(md.digest(input))
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
return bytesToHex(md.digest(input));
|
||||
} catch (
|
||||
NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
@JvmStatic
|
||||
fun hashSha512(input: ByteArray?): String {
|
||||
input ?: return ""
|
||||
return try {
|
||||
val md = MessageDigest.getInstance("SHA-512")
|
||||
bytesToHex(md.digest(input))
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String hashSha512(byte[] input) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
/**
|
||||
* Check if the checksum match a file by picking the correct
|
||||
* hashing algorithm depending on the length of the checksum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun checkSumMatch(data: ByteArray?, checksum: String?): Boolean {
|
||||
if (checksum == null) return false
|
||||
val hash: String = when (checksum.length) {
|
||||
0 -> {
|
||||
return true // No checksum
|
||||
}
|
||||
|
||||
return bytesToHex(md.digest(input));
|
||||
} catch (
|
||||
NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
32 -> hashMd5(data)
|
||||
40 -> hashSha1(data)
|
||||
64 -> hashSha256(data)
|
||||
128 -> hashSha512(data)
|
||||
else -> {
|
||||
Timber.e("No hash algorithm for " + checksum.length * 8 + "bit checksums")
|
||||
return false
|
||||
}
|
||||
}
|
||||
Timber.i("Checksum result (data: $hash,expected: $checksum)")
|
||||
return hash == checksum.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the checksum match a file by picking the correct
|
||||
* hashing algorithm depending on the length of the checksum
|
||||
*/
|
||||
public static boolean checkSumMatch(byte[] data, String checksum) {
|
||||
String hash;
|
||||
if (checksum == null)
|
||||
return false;
|
||||
switch (checksum.length()) {
|
||||
case 0:
|
||||
return true; // No checksum
|
||||
case 32:
|
||||
hash = Hashes.hashMd5(data);
|
||||
break;
|
||||
case 40:
|
||||
hash = Hashes.hashSha1(data);
|
||||
break;
|
||||
case 64:
|
||||
hash = Hashes.hashSha256(data);
|
||||
break;
|
||||
case 128:
|
||||
hash = Hashes.hashSha512(data);
|
||||
break;
|
||||
default:
|
||||
Timber.e("No hash algorithm for " + checksum.length() * 8 + "bit checksums");
|
||||
return false;
|
||||
}
|
||||
Timber.i("Checksum result (data: " + hash + ",expected: " + checksum + ")");
|
||||
return hash.equals(checksum.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
@JvmStatic
|
||||
fun checkSumValid(checksum: String?): Boolean {
|
||||
return if (checksum == null) false else when (checksum.length) {
|
||||
32, 40, 64, 128 -> {
|
||||
val len = checksum.length
|
||||
for (i in 0 until len) {
|
||||
val c = checksum[i]
|
||||
if (c < '0' || c > 'f') return false
|
||||
if (c > '9' && // Easier working with bits
|
||||
c.code and 95 < 'A'.code
|
||||
) return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public static boolean checkSumValid(String checksum) {
|
||||
if (checksum == null)
|
||||
return false;
|
||||
switch (checksum.length()) {
|
||||
case 0:
|
||||
default:
|
||||
return false;
|
||||
case 32:
|
||||
case 40:
|
||||
case 64:
|
||||
case 128:
|
||||
final int len = checksum.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = checksum.charAt(i);
|
||||
if (c < '0' || c > 'f')
|
||||
return false;
|
||||
if (c > '9' && // Easier working with bits
|
||||
(c & 0b01011111) < 'A')
|
||||
return false;
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String checkSumName(String checksum) {
|
||||
if (checksum == null)
|
||||
return null;
|
||||
return switch (checksum.length()) {
|
||||
default -> null;
|
||||
case 32 -> "MD5";
|
||||
case 40 -> "SHA-1";
|
||||
case 64 -> "SHA-256";
|
||||
case 128 -> "SHA-512";
|
||||
};
|
||||
}
|
||||
@JvmStatic
|
||||
fun checkSumName(checksum: String?): String? {
|
||||
return if (checksum == null) null else when (checksum.length) {
|
||||
32 -> "MD5"
|
||||
40 -> "SHA-1"
|
||||
64 -> "SHA-256"
|
||||
128 -> "SHA-512"
|
||||
else -> {
|
||||
null
|
||||
"MD5"
|
||||
"SHA-1"
|
||||
"SHA-256"
|
||||
"SHA-512"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String checkSumFormat(String checksum) {
|
||||
if (checksum == null)
|
||||
return null;
|
||||
// Remove all non-alphanumeric characters
|
||||
return nonAlphaNum.matcher(checksum.trim()).replaceAll("");
|
||||
@JvmStatic
|
||||
fun checkSumFormat(checksum: String?): String? {
|
||||
return if (checksum == null) null else nonAlphaNum.matcher(checksum.trim { it <= ' ' })
|
||||
.replaceAll("")
|
||||
// Remove all non-alphanumeric characters
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue