diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt index cedc3a6e..1e321087 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt @@ -102,6 +102,7 @@ object Extension { jarSavePath } } + apkUrl != null -> { installAPK { val apkName = Uri.parse(apkUrl).lastPathSegment!! @@ -112,7 +113,10 @@ object Extension { apkSavePath } } - else -> throw NullPointerException("Could not find extension url for $pkgName") + + else -> { + throw NullPointerException("Could not find extension url for $pkgName") + } } } @@ -247,7 +251,7 @@ object Extension { pkgName, packageInfo.versionName, packageInfo.versionCode, - contentWarning + contentWarning, ) } catch (e: Throwable) { // free up the file descriptor if exists @@ -270,16 +274,19 @@ object Extension { ): String { val jarFile = Path(fetcher()) - val jarZip = ZipFile.builder() - .setPath(jarFile) - .get() - + val jarZip = + ZipFile + .builder() + .setPath(jarFile) + .get() return jarZip.use { jarZip -> - val manifest = jarZip.getInputStream(jarZip.getEntry("AndroidManifest.xml")) - .use { - AndroidManifestParser.parse(it) - } + val manifest = + jarZip + .getInputStream(jarZip.getEntry("AndroidManifest.xml")) + .use { + AndroidManifestParser.parse(it) + } val pkgName = manifest.packageName // check if we don't have the extension already installed @@ -316,20 +323,23 @@ object Extension { // throw Exception("This apk is not a signed with the official tachiyomi signature") // } - fun List.getString(name: String): String? { - return this.find { it.name == name }?.value - } + fun List.getString(name: String): String? = this.find { it.name == name }?.value - var contentWarning = manifest.application!!.metaData.getString(METADATA_CONTENT_WARNING) - ?.toIntOrNull() + var contentWarning = + manifest.application!! + .metaData + .getString(METADATA_CONTENT_WARNING) + ?.toIntOrNull() if (contentWarning == null) { - contentWarning = manifest.application.metaData.getString(METADATA_NSFW) + contentWarning = manifest.application.metaData + .getString(METADATA_NSFW) ?.toIntOrNull() ?: 0 } val sourceClass = - manifest.application.metaData.getString(METADATA_SOURCE_CLASS)!! + manifest.application.metaData + .getString(METADATA_SOURCE_CLASS)!! .trim() val className = @@ -364,7 +374,7 @@ object Extension { pkgName, manifest.versionName, manifest.versionCode!!, - contentWarning + contentWarning, ) } catch (e: Throwable) { // free up the file descriptor if exists @@ -373,7 +383,8 @@ object Extension { try { uninstallExtension(pkgName) - } catch (_: Exception) {} + } catch (_: Exception) { + } throw e } } @@ -409,7 +420,6 @@ object Extension { else -> "all" } - // update extension info transaction { if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/AndroidManifestParser.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/AndroidManifestParser.kt index fc419cf4..d68d8794 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/AndroidManifestParser.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/AndroidManifestParser.kt @@ -22,21 +22,16 @@ object AndroidManifestParser { data class AndroidManifest( @XmlSerialName("package", "", "") val packageName: String, - @XmlSerialName("versionCode", ANDROID_NS, "android") val versionCode: Int? = null, - @XmlSerialName("versionName", ANDROID_NS, "android") val versionName: String? = null, - @XmlElement(true) @XmlSerialName("uses-sdk", "", "") val usesSdk: UsesSdk? = null, - @XmlElement(true) @XmlSerialName("uses-feature", "", "") val usesFeatures: List = emptyList(), - @XmlElement(true) @XmlSerialName("application", "", "") val application: Application? = null, @@ -47,7 +42,6 @@ object AndroidManifestParser { data class UsesSdk( @XmlSerialName("minSdkVersion", ANDROID_NS, "android") val minSdkVersion: Int? = null, - @XmlSerialName("targetSdkVersion", ANDROID_NS, "android") val targetSdkVersion: Int? = null, ) @@ -64,20 +58,15 @@ object AndroidManifestParser { data class Application( @XmlSerialName("label", ANDROID_NS, "android") val label: String? = null, - @XmlSerialName("icon", ANDROID_NS, "android") val icon: String? = null, - @XmlSerialName("allowBackup", ANDROID_NS, "android") val allowBackup: Boolean? = null, - @XmlSerialName("extractNativeLibs", ANDROID_NS, "android") val extractNativeLibs: Boolean? = null, - @XmlElement(true) @XmlSerialName("meta-data", "", "") val metaData: List = emptyList(), - @XmlElement(true) @XmlSerialName("activity", "", "") val activities: List = emptyList(), @@ -88,10 +77,8 @@ object AndroidManifestParser { data class MetaData( @XmlSerialName("name", ANDROID_NS, "android") val name: String, - @XmlSerialName("value", ANDROID_NS, "android") val value: String? = null, - @XmlSerialName("resource", ANDROID_NS, "android") val resource: String? = null, ) @@ -101,13 +88,10 @@ object AndroidManifestParser { data class Activity( @XmlSerialName("name", ANDROID_NS, "android") val name: String, - @XmlSerialName("exported", ANDROID_NS, "android") val exported: Boolean? = null, - @XmlSerialName("theme", ANDROID_NS, "android") val theme: String? = null, - @XmlElement(true) @XmlSerialName("intent-filter", "", "") val intentFilters: List = emptyList(), @@ -119,11 +103,9 @@ object AndroidManifestParser { @XmlElement(true) @XmlSerialName("action", "", "") val actions: List = emptyList(), - @XmlElement(true) @XmlSerialName("category", "", "") val categories: List = emptyList(), - @XmlElement(true) @XmlSerialName("data", "", "") val data: List = emptyList(), @@ -148,24 +130,22 @@ object AndroidManifestParser { data class Data( @XmlSerialName("scheme", ANDROID_NS, "android") val scheme: String? = null, - @XmlSerialName("host", ANDROID_NS, "android") val host: String? = null, - @XmlSerialName("pathPattern", ANDROID_NS, "android") val pathPattern: String? = null, ) - private val xml = XML { - autoPolymorphic = false - repairNamespaces = true - xmlDeclMode = XmlDeclMode.Minimal - defaultPolicy { - unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() } + private val xml = + XML { + autoPolymorphic = false + repairNamespaces = true + xmlDeclMode = XmlDeclMode.Minimal + defaultPolicy { + unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() } + } } - } @OptIn(ExperimentalXmlUtilApi::class) - fun parse(input: InputStream): AndroidManifest = - xml.decodeFromReader(AndroidManifest.serializer(), KtXmlReader(input)) + fun parse(input: InputStream): AndroidManifest = xml.decodeFromReader(AndroidManifest.serializer(), KtXmlReader(input)) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 41a1778c..061fd388 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -94,8 +94,8 @@ object PackageTools { } /** A modified version of `xyz.nulldev.androidcompat.pm.InstalledPackage.info` */ - fun getPackageInfo(apkFile: Path): PackageInfo { - return ApkParsers.getMetaInfo(apkFile.toFile()).toPackageInfo(apkFile.toFile()).apply { + fun getPackageInfo(apkFile: Path): PackageInfo = + ApkParsers.getMetaInfo(apkFile.toFile()).toPackageInfo(apkFile.toFile()).apply { val parsed = ApkFile(apkFile.toFile()) val dbFactory = DocumentBuilderFactory.newInstance() val dBuilder = dbFactory.newDocumentBuilder() @@ -137,7 +137,6 @@ object PackageTools { .map { Signature(it.data) } .toTypedArray() } - } fun getSignatureHash(pkgInfo: PackageInfo): String? { val signatures = pkgInfo.signatures diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ResourceArscIconParser.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ResourceArscIconParser.kt index 67ec0916..850abff5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ResourceArscIconParser.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ResourceArscIconParser.kt @@ -18,19 +18,23 @@ object ResourceArscIconParser { fun extractIcon( jar: Path, - iconPath: Path + iconPath: Path, ) { - ZipFile.builder() + ZipFile + .builder() .setPath(jar) .get() .use { zip -> - val packages = zip.getInputStream(zip.getEntry("resources.arsc")) - .use { ArscParser(it.readBytes()).parse() } + val packages = + zip + .getInputStream(zip.getEntry("resources.arsc")) + .use { ArscParser(it.readBytes()).parse() } - val icon = packages - .flatMap { it.iconCandidates() } - .maxByOrNull { it.density } - ?: return + val icon = + packages + .flatMap { it.iconCandidates() } + .maxByOrNull { it.density } + ?: return val entry = zip.getEntry(icon.path) ?: return @@ -42,19 +46,21 @@ object ResourceArscIconParser { } } - fun extractIcon( - zip: ZipFile, - ): InputStream { - val packages = zip.getInputStream(zip.getEntry("resources.arsc")) - .use { ArscParser(it.readBytes()).parse() } + fun extractIcon(zip: ZipFile): InputStream { + val packages = + zip + .getInputStream(zip.getEntry("resources.arsc")) + .use { ArscParser(it.readBytes()).parse() } - val icon = packages - .flatMap { it.iconCandidates() } - .maxByOrNull { it.density } - ?: throw NullPointerException("No valid icons") + val icon = + packages + .flatMap { it.iconCandidates() } + .maxByOrNull { it.density } + ?: throw NullPointerException("No valid icons") - val entry = zip.getEntry(icon.path) - ?: throw NullPointerException("Icon ${icon.path} missing") + val entry = + zip.getEntry(icon.path) + ?: throw NullPointerException("Icon ${icon.path} missing") return zip.getInputStream(entry) } @@ -76,19 +82,22 @@ object ResourceArscIconParser { } private fun Config.density(): Int = - ByteBuffer.wrap(id) + ByteBuffer + .wrap(id) .order(ByteOrder.LITTLE_ENDIAN) .getShort(14) .toInt() and 0xffff - private val rasterExtensions = setOf( - "png", - "webp", - "jpg", - "jpeg", - ) + private val rasterExtensions = + setOf( + "png", + "webp", + "jpg", + "jpeg", + ) private fun isRasterImage(path: String): Boolean = - path.substringAfterLast('.', "") + path + .substringAfterLast('.', "") .lowercase() in rasterExtensions } diff --git a/server/src/test/kotlin/JarTest.kt b/server/src/test/kotlin/JarTest.kt deleted file mode 100644 index 8f640930..00000000 --- a/server/src/test/kotlin/JarTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -import org.apache.commons.compress.archivers.zip.ZipFile -import pxb.android.arsc.ArscDumper -import pxb.android.arsc.ArscParser -import pxb.android.axml.AxmlParser -import suwayomi.tachidesk.manga.impl.util.AndroidManifestParser -import suwayomi.tachidesk.manga.impl.util.ResourceArscIconParser -import kotlin.io.path.Path -import kotlin.io.path.absolutePathString -import kotlin.test.Test - -class JarTest { - - @Test - fun jarTest() { - println(Path("./").absolutePathString()) - val jar = Path("./build/tachiyomi-all.ahottie-v1.4.3.jar") - - val zip = ZipFile.builder() - .setPath(jar) - .get() - val manifest = zip.getInputStream(zip.getEntry("AndroidManifest.xml")) - .use { - AndroidManifestParser.parse(it) - } - // - // reader.parse() - // println(reader.resourceTable) - // val resourceTable = reader.resourceTable - // println(resourceTable.stringPool) - // //println(table.stringPool.get(1)) - // // table::class.members.forEach { - // // if (it.name == "packageMap") { - // // it.isAccessible = true - // // val map = it.call(table) as Map - // // map.forEach { short, packagz -> - // // println(short) - // // println(packagz.name to packagz.id) - // // } - // // } - // // } - // val apkTranslator = ApkMetaTranslator(resourceTable, null) - // val xmlTranslator = XmlTranslator() - // val xmlStreamer = CompositeXmlStreamer(xmlTranslator, apkTranslator) - // val data = zip.getInputStream(zip.getEntry("AndroidManifest.xml")) - // .use { - // it.readBytes() - // } - // - // - // val buffer = ByteBuffer.wrap(data) - // val binaryXmlParser = BinaryXmlParser(buffer, resourceTable) - // //binaryXmlParser.locale = preferredLocale - // binaryXmlParser.xmlStreamer = xmlStreamer - // binaryXmlParser.parse() - // val manifestXml = xmlTranslator.xml - // println(manifestXml) - // val iconPaths = apkTranslator.iconPaths - // iconPaths.forEach { - // println(it.path) - // } - - // val parser = zip.getInputStream(zip.getEntry("resources.arsc")) - // .use { - // ArscParser(it.readBytes()) - // } - // val parsed = parser.parse() - // ArscDumper.dump(parsed) - // - // parsed.forEach { - // print("pkg.name: ") - // println(it.name) - // it.types.toList().forEachIndexed { t, (i, type) -> - // print("pkg.types.$t.type.name: ") - // println(type.name) - // print("pkg.types.$t.type.id: ") - // println(type.id) - // type.specs.forEachIndexed { y, spec -> - // print("pkg.types.$t.type.specs.$y.id: ") - // println(spec.id) - // print("pkg.types.$t.type.specs.$y.name: ") - // println(spec.name) - // print("pkg.types.$t.type.specs.$y.flags: ") - // println(spec.flags) - // } - // type.configs.forEachIndexed { y, config -> - // print("pkg.types.$t.type.configs.$y.id: ") - // println(config.id.toHexString(HexFormat.UpperCase)) - // print("pkg.types.$t.type.configs.$y.entryCount: ") - // println(config.entryCount) - // config.resources.forEach { - // print("pkg.types.$t.type.configs.$y.resources.${it.key}.flag: ") - // println(it.value.flag) - // print("pkg.types.$t.type.configs.$y.resources.${it.key}.value: ") - // println(it.value.value) - // print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.id: ") - // println(it.value.spec.id) - // print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.name: ") - // println(it.value.spec.name) - // print("pkg.types.$t.type.configs.$y.resources.${it.key}.spec.flags: ") - // println(it.value.spec.flags) - // } - // } - // } - // } - - // val fileTest = Path("./build/icon.png") - // ResourceArscIconParser.extractIcon(jar, fileTest) - - println(manifest.packageName) - } -}