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

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.getRandomString
import com.xml.guard.utils.lowercaseFirstChar
import com.xml.guard.utils.replaceLast
import com.xml.guard.utils.replaceWords
import groovy.util.Node
import groovy.xml.XmlParser
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter

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 }

    /**
     * 遍历文件夹下的所有资源,混淆名称
     */
    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
    }

    /**
     * 将映射写入文件
     */
    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 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 { (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
    }

    // 获取标签名称
    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.entries.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) {
            val value = MappingParser.checkObfuscate(rawName)
            obfuscateName = value.ifBlank {
                getRandomString { text ->
                    val result = text.lowercaseFirstChar()
                    MappingParser.verifyObfuscateName(result)
                }
            }
            layoutMapping[rawName] = obfuscateName
        }
        return obfuscateName
    }

    private fun obfuscateResName(
        rawName: String,
        mapping: MutableMap
    ): String {
        var obfuscateName = mapping[rawName]
        if (obfuscateName == null) {
            val value = MappingParser.checkObfuscate(rawName, isLowercase = true)
            obfuscateName = value.ifBlank {
                getRandomString { text ->
                    val result = text.lowercase()
                    MappingParser.verifyObfuscateName(result)
                }
            }
            mapping[rawName] = obfuscateName
        }
        return obfuscateName
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy