mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 22:34:24 +02:00
[XML,JSON] keep original apk file paths, fixes: REAndroid/APKEditor#15
This commit is contained in:
parent
c650910b2b
commit
43f299cc79
@ -39,6 +39,10 @@ public class ApkJsonDecoder {
|
|||||||
public ApkJsonDecoder(ApkModule apkModule){
|
public ApkJsonDecoder(ApkModule apkModule){
|
||||||
this(apkModule, false);
|
this(apkModule, false);
|
||||||
}
|
}
|
||||||
|
public void sanitizeFilePaths(){
|
||||||
|
PathSanitizer sanitizer = new PathSanitizer(apkModule);
|
||||||
|
sanitizer.sanitize();
|
||||||
|
}
|
||||||
public File writeToDirectory(File dir) throws IOException {
|
public File writeToDirectory(File dir) throws IOException {
|
||||||
this.decodedPaths.clear();
|
this.decodedPaths.clear();
|
||||||
writeUncompressed(dir);
|
writeUncompressed(dir);
|
||||||
@ -48,8 +52,15 @@ public class ApkJsonDecoder {
|
|||||||
//writePublicXml(dir);
|
//writePublicXml(dir);
|
||||||
writeResources(dir);
|
writeResources(dir);
|
||||||
writeRootFiles(dir);
|
writeRootFiles(dir);
|
||||||
|
writePathMap(dir);
|
||||||
return new File(dir, apkModule.getModuleName());
|
return new File(dir, apkModule.getModuleName());
|
||||||
}
|
}
|
||||||
|
private void writePathMap(File dir) throws IOException {
|
||||||
|
PathMap pathMap = new PathMap();
|
||||||
|
pathMap.add(apkModule.getApkArchive());
|
||||||
|
File file = toPathMapJsonFile(dir);
|
||||||
|
pathMap.toJson().write(file);
|
||||||
|
}
|
||||||
private void writeUncompressed(File dir) throws IOException {
|
private void writeUncompressed(File dir) throws IOException {
|
||||||
File file=toUncompressedJsonFile(dir);
|
File file=toUncompressedJsonFile(dir);
|
||||||
UncompressedFiles uncompressedFiles=new UncompressedFiles();
|
UncompressedFiles uncompressedFiles=new UncompressedFiles();
|
||||||
@ -77,17 +88,9 @@ public class ApkJsonDecoder {
|
|||||||
jsonObject.write(file);
|
jsonObject.write(file);
|
||||||
addDecoded(path);
|
addDecoded(path);
|
||||||
}
|
}
|
||||||
// TODO: temporary fix
|
|
||||||
private void writeRootFiles(File dir) throws IOException {
|
private void writeRootFiles(File dir) throws IOException {
|
||||||
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
|
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
|
||||||
try{
|
|
||||||
writeRootFile(dir, inputSource);
|
writeRootFile(dir, inputSource);
|
||||||
}catch (IOException ex){
|
|
||||||
APKLogger logger = apkModule.getApkLogger();
|
|
||||||
if(logger!=null){
|
|
||||||
logger.logMessage("ERROR: "+ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void writeRootFile(File dir, InputSource inputSource) throws IOException {
|
private void writeRootFile(File dir, InputSource inputSource) throws IOException {
|
||||||
@ -186,6 +189,10 @@ public class ApkJsonDecoder {
|
|||||||
String name = "public.xml";
|
String name = "public.xml";
|
||||||
return new File(file, name);
|
return new File(file, name);
|
||||||
}
|
}
|
||||||
|
private File toPathMapJsonFile(File dir){
|
||||||
|
File file = new File(dir, apkModule.getModuleName());
|
||||||
|
return new File(file, PathMap.JSON_FILE);
|
||||||
|
}
|
||||||
private File toUncompressedJsonFile(File dir){
|
private File toUncompressedJsonFile(File dir){
|
||||||
File file = new File(dir, apkModule.getModuleName());
|
File file = new File(dir, apkModule.getModuleName());
|
||||||
return new File(file, UncompressedFiles.JSON_FILE);
|
return new File(file, UncompressedFiles.JSON_FILE);
|
||||||
|
@ -19,8 +19,11 @@ import com.reandroid.archive.APKArchive;
|
|||||||
import com.reandroid.archive.FileInputSource;
|
import com.reandroid.archive.FileInputSource;
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
import com.reandroid.arsc.chunk.TableBlock;
|
||||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
||||||
|
import com.reandroid.json.JSONArray;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -40,8 +43,27 @@ public class ApkJsonEncoder {
|
|||||||
module.setAPKLogger(apkLogger);
|
module.setAPKLogger(apkLogger);
|
||||||
loadUncompressed(module, moduleDir);
|
loadUncompressed(module, moduleDir);
|
||||||
applyResourceId(module, moduleDir);
|
applyResourceId(module, moduleDir);
|
||||||
|
restorePathMap(moduleDir, module);
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
private void restorePathMap(File dir, ApkModule apkModule){
|
||||||
|
File file = new File(dir, PathMap.JSON_FILE);
|
||||||
|
if(!file.isFile()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage("Restoring file path ...");
|
||||||
|
PathMap pathMap = new PathMap();
|
||||||
|
FileInputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = new FileInputStream(file);
|
||||||
|
} catch (FileNotFoundException exception) {
|
||||||
|
logError("Failed to load path-map", exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONArray jsonArray = new JSONArray(inputStream);
|
||||||
|
pathMap.fromJson(jsonArray);
|
||||||
|
pathMap.restore(apkModule);
|
||||||
|
}
|
||||||
private void applyResourceId(ApkModule apkModule, File moduleDir) {
|
private void applyResourceId(ApkModule apkModule, File moduleDir) {
|
||||||
if(!apkModule.hasTableBlock()){
|
if(!apkModule.hasTableBlock()){
|
||||||
return;
|
return;
|
||||||
|
@ -48,6 +48,10 @@ import java.util.*;
|
|||||||
this.decodedEntries = new HashMap<>();
|
this.decodedEntries = new HashMap<>();
|
||||||
this.mDecodedPaths = new HashSet<>();
|
this.mDecodedPaths = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
public void sanitizeFilePaths(){
|
||||||
|
PathSanitizer sanitizer = new PathSanitizer(apkModule);
|
||||||
|
sanitizer.sanitize();
|
||||||
|
}
|
||||||
public void decodeTo(File outDir)
|
public void decodeTo(File outDir)
|
||||||
throws IOException, XMLException {
|
throws IOException, XMLException {
|
||||||
this.decodedEntries.clear();
|
this.decodedEntries.clear();
|
||||||
@ -74,6 +78,14 @@ import java.util.*;
|
|||||||
decodeValues(tableBlock, outDir, tableBlock);
|
decodeValues(tableBlock, outDir, tableBlock);
|
||||||
|
|
||||||
extractRootFiles(outDir);
|
extractRootFiles(outDir);
|
||||||
|
|
||||||
|
writePathMap(outDir);
|
||||||
|
}
|
||||||
|
private void writePathMap(File dir) throws IOException {
|
||||||
|
PathMap pathMap = new PathMap();
|
||||||
|
pathMap.add(apkModule.getApkArchive());
|
||||||
|
File file = new File(dir, PathMap.JSON_FILE);
|
||||||
|
pathMap.toJson().write(file);
|
||||||
}
|
}
|
||||||
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
|
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
||||||
@ -103,22 +115,21 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
private void decodeResRaw(File outDir, ResFile resFile)
|
private void decodeResRaw(File outDir, ResFile resFile)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Entry entry =resFile.pickOne();
|
Entry entry = resFile.pickOne();
|
||||||
PackageBlock packageBlock= entry.getPackageBlock();
|
PackageBlock packageBlock= entry.getPackageBlock();
|
||||||
|
|
||||||
File pkgDir=new File(outDir, getPackageDirName(packageBlock));
|
File pkgDir=new File(outDir, getPackageDirName(packageBlock));
|
||||||
File resDir=new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
||||||
String path=resFile.buildPath();
|
String path = alias.replace('/', File.separatorChar);
|
||||||
path=path.replace('/', File.separatorChar);
|
File file=new File(pkgDir, path);
|
||||||
File file=new File(resDir, path);
|
|
||||||
File dir=file.getParentFile();
|
File dir=file.getParentFile();
|
||||||
if(!dir.exists()){
|
if(!dir.exists()){
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOutputStream outputStream=new FileOutputStream(file);
|
FileOutputStream outputStream=new FileOutputStream(file);
|
||||||
resFile.getInputSource().write(outputStream);
|
resFile.getInputSource().write(outputStream);
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
|
resFile.setFilePath(alias);
|
||||||
|
|
||||||
addDecodedEntry(entry);
|
addDecodedEntry(entry);
|
||||||
}
|
}
|
||||||
@ -130,10 +141,10 @@ import java.util.*;
|
|||||||
resFile.getInputSource().getName());
|
resFile.getInputSource().getName());
|
||||||
|
|
||||||
File pkgDir=new File(outDir, getPackageDirName(packageBlock));
|
File pkgDir=new File(outDir, getPackageDirName(packageBlock));
|
||||||
File resDir=new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
||||||
String path=resFile.buildPath();
|
String path = alias.replace('/', File.separatorChar);
|
||||||
path=path.replace('/', File.separatorChar);
|
path=path.replace('/', File.separatorChar);
|
||||||
File file=new File(resDir, path);
|
File file=new File(pkgDir, path);
|
||||||
|
|
||||||
logVerbose("Decoding: "+path);
|
logVerbose("Decoding: "+path);
|
||||||
XMLNamespaceValidator namespaceValidator=new XMLNamespaceValidator(resXmlDocument);
|
XMLNamespaceValidator namespaceValidator=new XMLNamespaceValidator(resXmlDocument);
|
||||||
@ -141,6 +152,8 @@ import java.util.*;
|
|||||||
XMLDocument xmlDocument= resXmlDocument.decodeToXml(entryStore, packageBlock.getId());
|
XMLDocument xmlDocument= resXmlDocument.decodeToXml(entryStore, packageBlock.getId());
|
||||||
xmlDocument.save(file, true);
|
xmlDocument.save(file, true);
|
||||||
|
|
||||||
|
resFile.setFilePath(alias);
|
||||||
|
|
||||||
addDecodedEntry(resFile.pickOne());
|
addDecodedEntry(resFile.pickOne());
|
||||||
}
|
}
|
||||||
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
||||||
@ -304,14 +317,9 @@ import java.util.*;
|
|||||||
if(!dir.exists()){
|
if(!dir.exists()){
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
}
|
}
|
||||||
// TODO:Temporary fix
|
|
||||||
try{
|
|
||||||
FileOutputStream outputStream=new FileOutputStream(file);
|
FileOutputStream outputStream=new FileOutputStream(file);
|
||||||
inputSource.write(outputStream);
|
inputSource.write(outputStream);
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
}catch (IOException ex){
|
|
||||||
logMessage("ERROR: "+ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private boolean containsDecodedPath(String path){
|
private boolean containsDecodedPath(String path){
|
||||||
return mDecodedPaths.contains(path);
|
return mDecodedPaths.contains(path);
|
||||||
|
@ -19,9 +19,11 @@ package com.reandroid.apk;
|
|||||||
import com.reandroid.archive.FileInputSource;
|
import com.reandroid.archive.FileInputSource;
|
||||||
import com.reandroid.apk.xmlencoder.RESEncoder;
|
import com.reandroid.apk.xmlencoder.RESEncoder;
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
import com.reandroid.arsc.chunk.TableBlock;
|
||||||
|
import com.reandroid.json.JSONArray;
|
||||||
import com.reandroid.xml.XMLException;
|
import com.reandroid.xml.XMLException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -38,6 +40,18 @@ package com.reandroid.apk;
|
|||||||
resEncoder.scanDirectory(mainDirectory);
|
resEncoder.scanDirectory(mainDirectory);
|
||||||
File rootDir=new File(mainDirectory, "root");
|
File rootDir=new File(mainDirectory, "root");
|
||||||
scanRootDir(rootDir);
|
scanRootDir(rootDir);
|
||||||
|
restorePathMap(mainDirectory);
|
||||||
|
}
|
||||||
|
private void restorePathMap(File dir) throws IOException{
|
||||||
|
File file = new File(dir, PathMap.JSON_FILE);
|
||||||
|
if(!file.isFile()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PathMap pathMap = new PathMap();
|
||||||
|
FileInputStream inputStream = new FileInputStream(file);
|
||||||
|
JSONArray jsonArray = new JSONArray(inputStream);
|
||||||
|
pathMap.fromJson(jsonArray);
|
||||||
|
pathMap.restore(getApkModule());
|
||||||
}
|
}
|
||||||
public ApkModule getApkModule(){
|
public ApkModule getApkModule(){
|
||||||
return resEncoder.getApkModule();
|
return resEncoder.getApkModule();
|
||||||
|
206
src/main/java/com/reandroid/apk/PathMap.java
Normal file
206
src/main/java/com/reandroid/apk/PathMap.java
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 github.com/REAndroid
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.reandroid.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
import com.reandroid.archive.ZipArchive;
|
||||||
|
import com.reandroid.json.JSONArray;
|
||||||
|
import com.reandroid.json.JSONConvert;
|
||||||
|
import com.reandroid.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class PathMap implements JSONConvert<JSONArray> {
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
private final Map<String, String> mNameAliasMap;
|
||||||
|
private final Map<String, String> mAliasNameMap;
|
||||||
|
|
||||||
|
public PathMap(){
|
||||||
|
this.mNameAliasMap = new HashMap<>();
|
||||||
|
this.mAliasNameMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restore(ApkModule apkModule){
|
||||||
|
restoreResFile(apkModule.listResFiles());
|
||||||
|
restore(apkModule.getApkArchive().listInputSources());
|
||||||
|
}
|
||||||
|
public List<String> restoreResFile(Collection<ResFile> files){
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
if(files == null){
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
for(ResFile resFile:files){
|
||||||
|
String alias = restoreResFile(resFile);
|
||||||
|
if(alias==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.add(alias);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
public String restoreResFile(ResFile resFile){
|
||||||
|
InputSource inputSource = resFile.getInputSource();
|
||||||
|
String alias = restore(inputSource);
|
||||||
|
if(alias==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
resFile.setFilePath(alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
public List<String> restore(Collection<InputSource> sources){
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
if(sources == null){
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
for(InputSource inputSource:sources){
|
||||||
|
String alias = restore(inputSource);
|
||||||
|
if(alias==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.add(alias);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
public String restore(InputSource inputSource){
|
||||||
|
if(inputSource==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String name = inputSource.getName();
|
||||||
|
String alias = getName(name);
|
||||||
|
if(alias==null){
|
||||||
|
name = inputSource.getAlias();
|
||||||
|
alias = getName(name);
|
||||||
|
}
|
||||||
|
if(alias==null || alias.equals(inputSource.getAlias())){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
inputSource.setAlias(alias);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias(String name){
|
||||||
|
synchronized (mLock){
|
||||||
|
return mNameAliasMap.get(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String getName(String alias){
|
||||||
|
synchronized (mLock){
|
||||||
|
return mAliasNameMap.get(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int size(){
|
||||||
|
synchronized (mLock){
|
||||||
|
return mNameAliasMap.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void clear(){
|
||||||
|
synchronized (mLock){
|
||||||
|
mNameAliasMap.clear();
|
||||||
|
mAliasNameMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void add(ZipArchive archive){
|
||||||
|
if(archive == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
add(archive.listInputSources());
|
||||||
|
}
|
||||||
|
public void add(Collection<InputSource> sources){
|
||||||
|
if(sources==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(InputSource inputSource:sources){
|
||||||
|
add(inputSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void add(InputSource inputSource){
|
||||||
|
if(inputSource==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
add(inputSource.getName(), inputSource.getAlias());
|
||||||
|
}
|
||||||
|
public void add(String name, String alias){
|
||||||
|
if(name==null || alias==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(name.equals(alias)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (mLock){
|
||||||
|
mNameAliasMap.remove(name);
|
||||||
|
mNameAliasMap.put(name, alias);
|
||||||
|
mAliasNameMap.remove(alias);
|
||||||
|
mAliasNameMap.put(alias, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(JSONObject json){
|
||||||
|
if(json==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
add(json.optString(NAME_name), json.optString(NAME_alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray toJson() {
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
Map<String, String> nameMap = this.mNameAliasMap;
|
||||||
|
List<String> nameList = toSortedList(nameMap.keySet());
|
||||||
|
for(String name:nameList){
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put(NAME_name, name);
|
||||||
|
jsonObject.put(NAME_alias, nameMap.get(name));
|
||||||
|
jsonArray.put(jsonObject);
|
||||||
|
}
|
||||||
|
return jsonArray;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void fromJson(JSONArray json) {
|
||||||
|
clear();
|
||||||
|
if(json==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int length = json.length();
|
||||||
|
for(int i=0;i<length;i++){
|
||||||
|
add(json.optJSONObject(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
return "PathMap size="+size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> toSortedList(Collection<String> stringCollection){
|
||||||
|
List<String> results;
|
||||||
|
if(stringCollection instanceof List){
|
||||||
|
results = (List<String>) stringCollection;
|
||||||
|
}else {
|
||||||
|
results = new ArrayList<>(stringCollection);
|
||||||
|
}
|
||||||
|
Comparator<String> cmp = new Comparator<String>() {
|
||||||
|
@Override
|
||||||
|
public int compare(String s1, String s2) {
|
||||||
|
return s1.compareTo(s2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
results.sort(cmp);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String NAME_name = "name";
|
||||||
|
public static final String NAME_alias = "alias";
|
||||||
|
public static final String JSON_FILE = "path-map.json";
|
||||||
|
}
|
183
src/main/java/com/reandroid/apk/PathSanitizer.java
Normal file
183
src/main/java/com/reandroid/apk/PathSanitizer.java
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 github.com/REAndroid
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.reandroid.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class PathSanitizer {
|
||||||
|
private final ApkModule apkModule;
|
||||||
|
private APKLogger apkLogger;
|
||||||
|
private final Set<String> mSanitizedPaths;
|
||||||
|
public PathSanitizer(ApkModule apkModule){
|
||||||
|
this.apkModule = apkModule;
|
||||||
|
this.apkLogger = apkModule.getApkLogger();
|
||||||
|
this.mSanitizedPaths = new HashSet<>();
|
||||||
|
}
|
||||||
|
public void sanitize(){
|
||||||
|
mSanitizedPaths.clear();
|
||||||
|
logMessage("Sanitizing paths ...");
|
||||||
|
List<ResFile> resFileList = apkModule.listResFiles();
|
||||||
|
for(ResFile resFile:resFileList){
|
||||||
|
sanitize(resFile);
|
||||||
|
}
|
||||||
|
List<InputSource> sourceList = apkModule.getApkArchive().listInputSources();
|
||||||
|
for(InputSource inputSource:sourceList){
|
||||||
|
sanitize(inputSource, 1, false);
|
||||||
|
}
|
||||||
|
logMessage("DONE = "+mSanitizedPaths.size());
|
||||||
|
}
|
||||||
|
private void sanitize(ResFile resFile){
|
||||||
|
InputSource inputSource = resFile.getInputSource();
|
||||||
|
String replace = sanitize(inputSource, 3, true);
|
||||||
|
if(replace==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resFile.setFilePath(replace);
|
||||||
|
}
|
||||||
|
private String sanitize(InputSource inputSource, int depth, boolean fixedDepth){
|
||||||
|
String name = inputSource.getName();
|
||||||
|
if(mSanitizedPaths.contains(name)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mSanitizedPaths.add(name);
|
||||||
|
String alias = inputSource.getAlias();
|
||||||
|
String replace = sanitize(alias, depth, fixedDepth);
|
||||||
|
if(alias.equals(replace)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
inputSource.setAlias(replace);
|
||||||
|
logVerbose("REN: '"+alias+"' -> '"+replace+"'");
|
||||||
|
return replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sanitize(String name, int depth, boolean fixedDepth){
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
String[] nameSplit = name.split("/");
|
||||||
|
|
||||||
|
boolean pathIsLong = name.length() >= MAX_PATH_LENGTH;
|
||||||
|
int length = nameSplit.length;
|
||||||
|
for(int i=0;i<length;i++){
|
||||||
|
String split = nameSplit[i];
|
||||||
|
boolean good = isGoodSimpleName(split);
|
||||||
|
if(!good || (pathIsLong && i>=depth)){
|
||||||
|
split = createUniqueName(name);
|
||||||
|
appendPathName(builder, split);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(fixedDepth && i>=(depth-1)){
|
||||||
|
if(i < length-1){
|
||||||
|
split = createUniqueName(name);
|
||||||
|
}
|
||||||
|
appendPathName(builder, split);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
appendPathName(builder, split);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApkLogger(APKLogger apkLogger) {
|
||||||
|
this.apkLogger = apkLogger;
|
||||||
|
}
|
||||||
|
private String getLogTag(){
|
||||||
|
return "[SANITIZE]: ";
|
||||||
|
}
|
||||||
|
private void logMessage(String msg){
|
||||||
|
APKLogger logger = this.apkLogger;
|
||||||
|
if(logger!=null){
|
||||||
|
logger.logMessage(getLogTag()+msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void logVerbose(String msg){
|
||||||
|
APKLogger logger = this.apkLogger;
|
||||||
|
if(logger!=null){
|
||||||
|
logger.logVerbose(getLogTag()+msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendPathName(StringBuilder builder, String name){
|
||||||
|
if(builder.length()>0){
|
||||||
|
builder.append('/');
|
||||||
|
}
|
||||||
|
builder.append(name);
|
||||||
|
}
|
||||||
|
private static String createUniqueName(String name){
|
||||||
|
int hash = name.hashCode();
|
||||||
|
return String.format("alias_%08x", hash).toLowerCase();
|
||||||
|
}
|
||||||
|
private static boolean isGoodSimpleName(String name){
|
||||||
|
if(name==null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String alias = sanitizeSimpleName(name);
|
||||||
|
return name.equals(alias);
|
||||||
|
}
|
||||||
|
public static String sanitizeSimpleName(String name){
|
||||||
|
if(name==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
char[] chars = name.toCharArray();
|
||||||
|
boolean skipNext = true;
|
||||||
|
int length = 0;
|
||||||
|
int lengthMax = MAX_NAME_LENGTH;
|
||||||
|
for(int i=0;i<chars.length;i++){
|
||||||
|
if(length>=lengthMax){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char ch = chars[i];
|
||||||
|
if(isGoodFileNameSymbol(ch)){
|
||||||
|
if(!skipNext){
|
||||||
|
builder.append(ch);
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
skipNext=true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!isGoodFileNameChar(ch)){
|
||||||
|
skipNext = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.append(ch);
|
||||||
|
length++;
|
||||||
|
skipNext=false;
|
||||||
|
}
|
||||||
|
if(length==0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isGoodFileNameSymbol(char ch){
|
||||||
|
return ch == '.'
|
||||||
|
|| ch == '+'
|
||||||
|
|| ch == '-'
|
||||||
|
|| ch == '_'
|
||||||
|
|| ch == '#';
|
||||||
|
}
|
||||||
|
private static boolean isGoodFileNameChar(char ch){
|
||||||
|
return (ch >= '0' && ch <= '9')
|
||||||
|
|| (ch >= 'A' && ch <= 'Z')
|
||||||
|
|| (ch >= 'a' && ch <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MAX_NAME_LENGTH = 50;
|
||||||
|
private static final int MAX_PATH_LENGTH = 100;
|
||||||
|
}
|
@ -135,14 +135,23 @@ public class ResFile {
|
|||||||
return new File(dir, path);
|
return new File(dir, path);
|
||||||
}
|
}
|
||||||
public String buildPath(){
|
public String buildPath(){
|
||||||
Entry entry =pickOne();
|
return buildPath(null);
|
||||||
TypeBlock typeBlock= entry.getTypeBlock();
|
}
|
||||||
StringBuilder builder=new StringBuilder();
|
public String buildPath(String parent){
|
||||||
|
Entry entry = pickOne();
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if(parent!=null){
|
||||||
|
builder.append(parent);
|
||||||
|
if(!parent.endsWith("/")){
|
||||||
|
builder.append('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeBlock typeBlock = entry.getTypeBlock();
|
||||||
builder.append(typeBlock.getTypeName());
|
builder.append(typeBlock.getTypeName());
|
||||||
builder.append(typeBlock.getQualifiers());
|
builder.append(typeBlock.getQualifiers());
|
||||||
builder.append('/');
|
builder.append('/');
|
||||||
builder.append(entry.getName());
|
builder.append(entry.getName());
|
||||||
String ext=getFileExtension();
|
String ext = getFileExtension();
|
||||||
if(ext!=null){
|
if(ext!=null){
|
||||||
builder.append(ext);
|
builder.append(ext);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user