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

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

There is a newer version: 3.0.5
Show newest version
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