All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.com.xml.guard.model.XmlClassMapping.kt Maven / Gradle / Ivy

package com.xml.guard.model

import com.xml.guard.entensions.GuardExtension
import com.xml.guard.utils.KtFileParser
import com.xml.guard.utils.aidlDirs
import com.xml.guard.utils.findLocationProject
import com.xml.guard.utils.findPackage
import com.xml.guard.utils.generateNumbers
import com.xml.guard.utils.getClassPackage
import com.xml.guard.utils.getDirPath
import com.xml.guard.utils.inClassNameBlackList
import com.xml.guard.utils.inPackageNameBlackList
import com.xml.guard.utils.insertImportXxxIfAbsent
import com.xml.guard.utils.isAidl
import com.xml.guard.utils.isJava
import com.xml.guard.utils.isKt
import com.xml.guard.utils.javaDirs
import com.xml.guard.utils.randomizeCase
import com.xml.guard.utils.removeSuffix
import com.xml.guard.utils.replaceLast
import com.xml.guard.utils.to26Long
import com.xml.guard.utils.toLetterStr
import com.xml.guard.utils.toUpperLetterStr
import com.xml.guard.utils.uppercaseFirstChar
import org.gradle.api.Project
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import kotlin.math.pow
import kotlin.random.Random

/**
 * User: ljx
 * Date: 2022/3/16
 * Time: 22:02
 */
class XmlClassMapping(private val guardExtension: GuardExtension) {
    internal val dirMapping by lazy { mutableMapOf() }
    internal val classMapping by lazy { mutableMapOf() }

    // 内部类正则表达式
    private val innerClassRegex by lazy { "[a-zA-Z0-9_]+\\$[a-zA-Z0-9_]+".toRegex() }

    // 混淆名索引
    internal var obfuscateIndex = -1L

    // 白名单
    private val classWhiteList by lazy { guardExtension.classWhiteList }

    // 遍历文件夹下的所有直接子类,混淆文件名及移动目录
    fun obfuscateAllClass(project: Project, variantName: String): MutableMap {
        val classMapped = mutableMapOf()
        val iterator = dirMapping.iterator()
        while (iterator.hasNext()) {
            val entry = iterator.next()
            val rawDir = entry.key
            val locationProject = project.findLocationProject(rawDir, variantName)
            if (locationProject == null) {
                iterator.remove()
                continue
            }
            val manifestPackage = locationProject.findPackage()
            // 过滤目录的直接子文件
            val dirPath = rawDir.replace(".", File.separator) // xx.xx  不带文件名
            val dirs = mutableListOf()
            dirs.addAll(locationProject.javaDirs(variantName))
            dirs.addAll(locationProject.aidlDirs(variantName))
            val childFiles = dirs.flatMap {
                File(it, dirPath).listFiles { f ->
                    var bool = (f.isFile && (f.isJava || f.isKt || f.isAidl))
                    if (bool && f.isAidl) {
                        val classPackage = getClassPackage(locationProject, f)
                        if (!dirMapping.containsKey(classPackage)) {
                            bool = false
                        }
                    }
                    bool
                }?.toList() ?: emptyList()
            }
            if (childFiles.isEmpty()) continue
            for (file in childFiles) {
                file.insertImportXxxIfAbsent(manifestPackage)

                val rawClassPath = "${rawDir}.${file.name.removeSuffix()}" // 原始 xx.Xxx
                // 已经混淆
                if (isClassObfuscatedOrWhiteList(rawClassPath)) {
                    continue
                }

                val obfuscatePath = obfuscatePath(locationProject, rawClassPath)  // 混淆后 xx.Xxx
                val obfuscateRelativePath = obfuscatePath.replace(".", File.separator) // 混淆后 xx/Xxx
                val rawRelativePath = rawClassPath.replace(".", File.separator) // 原始 xx/Xxx
                // 替换原始类路径
                val newFile =
                    File(file.absolutePath.replace(rawRelativePath, obfuscateRelativePath))
                if (!newFile.exists()) newFile.parentFile.mkdirs()
                if (file.renameTo(newFile)) {
                    classMapped[rawClassPath] = obfuscatePath

                    // 处理顶级类、方法及变量
                    val obfuscateDir = obfuscatePath.getDirPath()
                    val filename = file.name.removeSuffix()
                    val ktParser = KtFileParser(newFile, filename)
                    val jvmName = ktParser.jvmName
                    if (jvmName != null && jvmName != filename) {
                        classMapped["$rawDir.$jvmName"] = "$obfuscateDir.$jvmName"
                    } else if (jvmName == null &&
                        (ktParser.topFunNames.isNotEmpty() || ktParser.topFieldNames.isNotEmpty())
                    ) {
                        classMapped["${rawClassPath}Kt"] = "${obfuscatePath}Kt"
                    }
                    ktParser.getTopClassOrFunOrFieldNames().forEach {
                        classMapped["$rawDir.$it"] = "$obfuscateDir.$it"
                    }
                }
            }
        }
        return classMapped
    }

    fun isClassObfuscatedOrWhiteList(rawClassPath: String): Boolean {
        // 白名单 || 已混淆
        return inWhiteList(rawClassPath) || classMapping.containsValue(rawClassPath)
    }

    // 需要混淆的类文件包
    fun obfuscateOtherDir(androidProjects: List, variantName: String) {
        androidProjects.forEach {
            val dirs = mutableListOf()
            dirs.addAll(it.aidlDirs(variantName))
            dirs.addAll(it.javaDirs(variantName))
            it.files(dirs).asFileTree.forEach asFileTree@{ file ->
                val rawPackage = getClassPackage(it, file).trim()
                val rawClassPath = "${rawPackage}.${file.nameWithoutExtension}"
                // 白名单
                if (inWhiteList(rawClassPath)) {
                    return@asFileTree
                }
                if (!dirMapping.containsKey(rawPackage)
                    && !dirMapping.containsValue(rawPackage)
                ) {
                    obfuscatePackage(it, rawPackage)
                }
            }
        }
    }

    // 混淆包名+类名,返回混淆后的包名+类名
    fun obfuscatePath(project: Project, classPath: String): String {
        var innerClassName: String? = null //内部类类名
        val rawClassPath = if (isInnerClass(classPath)) {
            val arr = classPath.split("$")
            innerClassName = arr[1]
            arr[0]
        } else {
            classPath
        }
        // TODO 内部类混淆有问题,暂时禁止混淆
        if (isClassObfuscatedOrWhiteList(rawClassPath)) {
            return classPath
        }
        var obfuscateClassPath = classMapping[rawClassPath]
        if (obfuscateClassPath == null) {
            val rawPackage = rawClassPath.getDirPath()
            val obfuscatePackage = obfuscatePackage(project, rawPackage)
            val obfuscateClassName = generateObfuscateClassName()
            obfuscateClassPath = "${obfuscatePackage}.${obfuscateClassName}"
            classMapping[rawClassPath] = obfuscateClassPath
        }
        return if (innerClassName != null) "$obfuscateClassPath\$$innerClassName" else obfuscateClassPath
    }

    fun writeMappingToFile(mappingFile: File) {
        val writer = BufferedWriter(FileWriter(mappingFile, false))

        writer.write("${MappingParser.DIR_MAPPING}\n")
        for ((key, value) in dirMapping) {
            writer.write(String.format("\t%s -> %s\n", key, value))
        }
        writer.write("\n")
        writer.flush()

        writer.write("${MappingParser.CLASS_MAPPING}\n")
        for ((key, value) in classMapping.entries) {
            writer.write(String.format("\t%s -> %s\n", key, value))
        }
        writer.flush()
        writer.close()
    }

    /**
     * 白名单
     */
    fun inWhiteList(rawClassPath: String): Boolean {
        return classWhiteList.any {
            return classWhiteList.any {
                (it == rawClassPath
                        || it == rawClassPath.getDirPath()
                        || (it.endsWith("*") && rawClassPath.startsWith(it.replaceLast("*", ""))))
            }
        }
    }

    fun inWhiteList2(rawDirPath: String): Boolean {
        return classWhiteList.any {
            (it == rawDirPath
                    || it == rawDirPath.getDirPath()
                    || (it.endsWith("*") && "${rawDirPath}.".startsWith(it.replaceLast("*", ""))))
        }
    }

    // 混淆包名,返回混淆后的包名
    private fun obfuscatePackage(project: Project, rawPackage: String): String {
        var obfuscatePackage = dirMapping[rawPackage]
        if (obfuscatePackage == null) {
            val prefix = project.findPackage() + "."
            obfuscatePackage = prefix + generateObfuscatePackageName()
            dirMapping[rawPackage] = obfuscatePackage
        }
        return obfuscatePackage
    }

    private fun isInnerClass(classPath: String): Boolean {
        return classPath.contains(innerClassRegex)
    }

    // 生成混淆的包名
    private fun generateObfuscatePackageName(): String {
        while (true) {
            obfuscateIndex += (generateNumbers().toDouble()
                .pow(Random.nextDouble(1.0, 3.0))).toLong()
            val obfuscatePackage = obfuscateIndex.toLetterStr()
            if (obfuscatePackage.length > 1
                && !obfuscatePackage.inPackageNameBlackList()
            ) { // 过滤黑名单
                return obfuscatePackage
            }
        }
    }

    // 生成混淆的类名
    private fun generateObfuscateClassName(): String {
        while (true) {
            obfuscateIndex += (generateNumbers().toDouble()
                .pow(Random.nextDouble(1.0, 3.0))).toLong()
            val obfuscateClassName =
                obfuscateIndex.toUpperLetterStr().randomizeCase().uppercaseFirstChar()
            if (obfuscateClassName.length > 1 // 保证至少两位字母以上
                && !obfuscateClassName.inClassNameBlackList() // 过滤黑名单
            ) {
                val minimumValue = obfuscateClassName.to26Long()
                obfuscateIndex = obfuscateIndex.coerceAtLeast(minimumValue)
                return obfuscateClassName
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy