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

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

package com.xml.guard.model

import com.xml.guard.data.ResDefType
import com.xml.guard.entensions.GuardExtension
import com.xml.guard.utils.generateNumbers
import com.xml.guard.utils.inClassNameBlackList
import com.xml.guard.utils.inPackageNameBlackList
import com.xml.guard.utils.lowercaseFirstChar
import com.xml.guard.utils.randomizeCase
import com.xml.guard.utils.replaceLast
import com.xml.guard.utils.replaceWords
import com.xml.guard.utils.to26Long
import com.xml.guard.utils.toLetterStr
import groovy.util.Node
import groovy.xml.XmlParser
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.random.Random

class ResMapping(private val guardExtension: GuardExtension) {
    internal val resMapping by lazy { mutableMapOf() }
    internal val layoutMapping by lazy { mutableMapOf() }
    internal val valuesMapping by lazy { mutableMapOf() }

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

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

    // 遍历文件夹下的所有资源,混淆名称
    fun obfuscateAllRes(resFiles: List): MutableList {
        if (resFiles.isEmpty()) {
            return mutableListOf()
        }
        val infoList = mutableListOf()
        resFiles.forEach { resFile ->
            val fileRawName = resFile.nameWithoutExtension
            val fileDefType = resFile.parentFile.name.split("-").firstOrNull() ?: ""

            // 白名单
            if (inWhiteList(fileDefType, fileRawName)) {
                return@forEach
            }

            // 1、混淆资源文件名
            val obfuscateFile = obfuscateResFileName(resFile, fileDefType, infoList)

            // 2、混淆 layout 控件 id
            if (fileDefType == ResDefType.LAYOUT.defType) {
                obfuscateLayoutViewId(obfuscateFile, infoList)
            }

            // 3、混淆 values 下的资源标签名称
            if (fileDefType == ResDefType.VALUES.defType) {
                obfuscateLabel(obfuscateFile, infoList)
            }
        }
        return infoList
    }

    private fun obfuscateLabel(resFile: File, infoList: MutableList) {
        val labels = resFile.findLabels()
        if (labels.isNotEmpty()) {
            infoList.addAll(labels)

            var fileText = resFile.readText()
            labels.forEach { resInfo ->
                fileText = fileText.replaceWords(resInfo.rawName, resInfo.obfuscateName)
            }
            resFile.writeText(fileText)
        }
    }

    private fun obfuscateLayoutViewId(layoutFile: File, infoList: MutableList) {
        val viewIds = mutableMapOf()
        layoutFile.findViewIds().forEach viewId@{ viewId ->
            // 白名单
            if (inWhiteList(defType = ResDefType.ID.defType, viewId)) {
                return@viewId
            }
            // 已混淆
            if (layoutMapping.containsValue(viewId)) {
                return@viewId
            }

            val obfuscateViewId = obfuscateViewId(viewId)
            viewIds[viewId] = obfuscateViewId

            ResInfo(layoutFile, defType = ResDefType.ID.defType, viewId, obfuscateViewId)
                .let(infoList::add)
        }
        if (viewIds.isNotEmpty()) {
            var fileText = layoutFile.readText()
            viewIds.forEach viewId@{ (rawId, obfuscateId) ->
                fileText = fileText.replaceWords(rawId, obfuscateId)
            }
            layoutFile.writeText(fileText)
        }
    }

    private fun obfuscateResFileName(
        resFile: File,
        fileDefType: String,
        infoList: MutableList
    ): File {
        val fileRawName = resFile.nameWithoutExtension

        // 已混淆
        if (resMapping.containsValue(fileRawName)) {
            return resFile
        }

        var obfuscateFile = resFile
        val fileObfuscateName = obfuscateResName(fileRawName, resMapping)
        val newFileName = "${fileObfuscateName}.${resFile.extension}"
        val newFile = File(resFile.parent + File.separator + newFileName)
        if (resFile.renameTo(newFile)) {
            obfuscateFile = newFile
            ResInfo(resFile, fileDefType, fileRawName, fileObfuscateName)
                .let(infoList::add)
        }
        return obfuscateFile
    }

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

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

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

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

        writer.close()
    }

    // 获取标签名称
    private fun File.findLabels(): MutableList {
        val infoList = mutableListOf()
        val childrenList = XmlParser(false, false).parse(this).breadthFirst()
        for (children in childrenList) {
            val childNode = children as? Node ?: continue
            val nodeName = childNode.name() as? String ?: ""
            val rawName = childNode.attribute("name") as? String ?: ""

            // 白名单
            if (inWhiteList(nodeName, rawName)) {
                continue
            }
            // 已混淆
            if (valuesMapping.containsValue(rawName)) {
                continue
            }

            ResDefType.values().forEach { resDefType ->
                if (resDefType.nodeNames.any { nodeName == it }) {
                    val defType = resDefType.defType
                    val obfuscateName = obfuscateResName(rawName, valuesMapping)
                    ResInfo(
                        resFile = this,
                        defType = defType,
                        rawName = rawName,
                        obfuscateName = obfuscateName
                    ).let(infoList::add)

                    if (defType == ResDefType.STYLEABLE.defType) {
                        childNode.obfuscateStyleableChild(this, infoList, rawName, obfuscateName)
                    }
                }
            }
        }
        return infoList
    }

    // 混淆 styleable 标签子属性名称
    private fun Node?.obfuscateStyleableChild(
        resFile: File,
        infoList: MutableList,
        rawNamePrefix: String,
        obfuscateNamePrefix: String
    ) {
        val childrenList = this?.children()
        if (childrenList.isNullOrEmpty()) return
        for (children in childrenList) {
            val childNode = children as? Node ?: continue
            val rawName = childNode.attribute("name") as? String ?: ""
            if (!valuesMapping.containsValue(rawName)) {
                val obfuscateName = obfuscateResName(rawName, valuesMapping)
                ResInfo(
                    resFile,
                    defType = ResDefType.STYLEABLE.defType,
                    rawName,
                    obfuscateName,
                    rawNamePrefix,
                    obfuscateNamePrefix
                ).let(infoList::add)
            }

            childNode.obfuscateStyleableChild(
                resFile,
                infoList,
                rawNamePrefix,
                obfuscateNamePrefix,
            )
        }
    }

    // 获取控件 id
    private fun File.findViewIds(): Set {
        val set = HashSet()
        val childrenList = XmlParser(false, false).parse(this).breadthFirst()
        for (children in childrenList) {
            val childNode = children as? Node ?: continue
            val viewId = childNode.attribute("android:id")?.toString()
            if (viewId.isNullOrBlank()) continue
            set.add(viewId.replace("@+id/", ""))
        }
        return set
    }

    private fun inWhiteList(defType: String, rawName: String): Boolean {
        val str = "R.${defType}.${rawName}"
        return resWhiteList.any {
            (it == str) || (it.endsWith("*") && str.startsWith(it.replaceLast("*", "")))
        }
    }

    private fun obfuscateViewId(rawName: String): String {
        var obfuscateName = layoutMapping[rawName]
        if (obfuscateName == null) {
            obfuscateName = generateObfuscateViewId()

            val len = (Random.nextDouble() * 3).roundToInt()
            for (i in 0 until len) {
                val index = (generateNumbers().toDouble()
                    .pow(Random.nextDouble(1.0, 3.0))).toLong()
                obfuscateName += "_${index.toLetterStr()}"
            }
            layoutMapping[rawName] = obfuscateName
        }
        return obfuscateName
    }

    private fun obfuscateResName(
        rawName: String,
        mapping: MutableMap
    ): String {
        var obfuscateName = mapping[rawName]
        if (obfuscateName == null) {
            obfuscateName = generateObfuscateRes()

            val len = (Random.nextDouble() * 3).roundToInt()
            for (i in 0 until len) {
                val index = (generateNumbers().toDouble()
                    .pow(Random.nextDouble(1.0, 3.0))).toLong()
                obfuscateName += "_${index.toLetterStr()}"
            }
            mapping[rawName] = obfuscateName
        }
        return obfuscateName
    }

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

    // 生成混淆的资源名
    private fun generateObfuscateRes(): String {
        while (true) {
            obfuscateIndex += (generateNumbers().toDouble()
                .pow(Random.nextDouble(1.0, 3.0))).toLong()
            val obfuscateName = obfuscateIndex.toLetterStr()
            if (obfuscateName.length > 1 // 保证至少两位字母以上
                && !obfuscateName.inPackageNameBlackList() // 过滤黑名单
            ) {
                return obfuscateName
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy