001/*
002 * Copyright (C) 2015-2021 KeepSafe Software
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package com.getkeepsafe.dexcount;
017
018import org.apache.commons.io.FileUtils;
019
020import java.io.File;
021import java.io.IOException;
022import java.nio.charset.StandardCharsets;
023import java.util.Collections;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030/**
031 * An object that can produce an unobfuscated class name from a Proguard
032 * mapping file.
033 */
034public class Deobfuscator {
035    /**
036     * Proguard mapping files have the following syntax:
037     *
038     * ```
039     * line : comment | class_mapping | member_mapping
040     * comment: '#' ...
041     * class_mapping: type_name ' -> ' obfuscated_name ':'
042     * member_mapping: '    ' type_name ' ' member_name ' -> ' obfuscated_name
043     * ```
044     *
045     * Class mapping lines are easily distinguished because they're the only
046     * lines that start with an identifier character.  We can just pluck them
047     * out of the file with a regex.
048     */
049    private static final Pattern CLASS_LINE = Pattern.compile("^([a-zA-Z][^\\s]*) -> ([^:]+):$");
050
051    public static final Deobfuscator EMPTY = new Deobfuscator(Collections.emptyMap());
052
053    private final Map<String, String> mapping;
054
055    public Deobfuscator(Map<String, String> mapping) {
056        this.mapping = mapping;
057    }
058
059    public String deobfuscate(String name) {
060        return mapping.getOrDefault(name, name);
061    }
062
063    public static Deobfuscator create(File mappingFile) throws IOException {
064        if (mappingFile == null) {
065            return EMPTY;
066        }
067
068        if (!mappingFile.exists()) {
069            return EMPTY;
070        }
071
072        Map<String, String> mapping = new LinkedHashMap<>();
073
074        List<String> lines = FileUtils.readLines(mappingFile, StandardCharsets.UTF_8);
075        for (String line : lines) {
076            Matcher matcher = CLASS_LINE.matcher(line);
077            if (!matcher.matches()) {
078                continue;
079            }
080
081            String cleartext = matcher.group(1);
082            String obfuscated = matcher.group(2);
083            mapping.put(obfuscated, cleartext);
084        }
085
086        return new Deobfuscator(mapping);
087    }
088}