com.reandroid.apk.PathSanitizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ARSCLib Show documentation
Show all versions of ARSCLib Show documentation
Android binary resources read/write library
/*
* 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.utils.HexUtil;
import com.reandroid.identifiers.Identifier;
import java.util.*;
public class PathSanitizer {
private final Collection extends InputSource> sourceList;
private final boolean sanitizeResourceFiles;
private Collection resFileList;
private APKLogger apkLogger;
private final Set mSanitizedPaths;
private boolean mCaseInsensitive;
private int mUniqueName;
public PathSanitizer(Collection extends InputSource> sourceList, boolean sanitizeResourceFiles){
this.sourceList = sourceList;
this.mSanitizedPaths = new HashSet<>();
this.sanitizeResourceFiles = sanitizeResourceFiles;
this.mCaseInsensitive = Identifier.CASE_INSENSITIVE_FS;
}
public PathSanitizer(Collection extends InputSource> sourceList){
this(sourceList, false);
}
public void sanitize(){
mSanitizedPaths.clear();
logMessage("Sanitizing paths ...");
sanitizeCaseInsensitiveOs();
sanitizeResFiles();
for(InputSource inputSource:sourceList){
sanitize(inputSource, 1, false);
}
}
public void setResourceFileList(Collection resFileList){
this.resFileList = resFileList;
}
public boolean isCaseInsensitive(){
return mCaseInsensitive;
}
public void setCaseInsensitive(boolean caseInsensitive){
mCaseInsensitive = caseInsensitive;
}
private void sanitizeCaseInsensitiveOs(){
if(!Identifier.CASE_INSENSITIVE_FS){
return;
}
logMessage("[WIN/MAC] Checking duplicate case insensitive paths ...");
mUniqueName = 0;
Map uniqueMap = new HashMap<>();
for(InputSource inputSource : sourceList){
String path = inputSource.getAlias().toLowerCase();
InputSource exist = uniqueMap.get(path);
if(exist == null){
uniqueMap.put(path, inputSource);
continue;
}
sanitizeCaseInsensitiveOs(inputSource);
sanitizeCaseInsensitiveOs(exist);
uniqueMap.remove(path);
}
}
private void sanitizeCaseInsensitiveOs(InputSource inputSource){
String path = inputSource.getAlias();
mUniqueName++;
String uniqueName = createUniqueName(mUniqueName + path);
String alias;
int i = path.lastIndexOf('/');
if(i > 0){
alias = path.substring(0, i) + "/" + uniqueName;
}else {
alias = uniqueName;
}
inputSource.setAlias(alias);
String msg = "'" + path + "' -> '" + alias + "'";
if(mUniqueName < 10){
logMessage("Case sensitive path renamed: " + msg);
}else {
logVerbose(msg);
}
}
private void sanitizeResFiles(){
Collection resFileList = this.resFileList;
if(resFileList == null){
return;
}
boolean sanitizeRes = this.sanitizeResourceFiles;
Set sanitizedPaths = this.mSanitizedPaths;
if(sanitizeRes){
logMessage("Sanitizing resource files ...");
}
for(ResFile resFile:resFileList){
if(sanitizeRes){
sanitize(resFile);
}else {
sanitizedPaths.add(resFile.getFilePath());
}
}
}
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();
if(shouldIgnore(alias)){
return null;
}
String replace = sanitize(alias, depth, fixedDepth);
if(alias.equals(replace)){
return null;
}
inputSource.setAlias(replace);
if(alias.length() > 20){
alias = ".. " + alias.substring(alias.length()-20);
}
logVerbose("'" + 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=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();
}
private boolean shouldIgnore(String path){
return path.startsWith("lib/") && path.endsWith(".so");
}
public void setApkLogger(APKLogger apkLogger) {
this.apkLogger = apkLogger;
}
private String getLogTag(){
return "[SANITIZE]: ";
}
void logMessage(String msg){
APKLogger logger = this.apkLogger;
if(logger!=null){
logger.logMessage(getLogTag()+msg);
}
}
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 "alias_" + HexUtil.toHexNoPrefix8(hash);
}
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=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 == '#';
}
private static boolean isGoodFileNameChar(char ch){
return ch == '_'
|| (ch >= '0' && ch <= '9')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= 'a' && ch <= 'z');
}
public static PathSanitizer create(ApkModule apkModule){
PathSanitizer pathSanitizer = new PathSanitizer(
apkModule.getZipEntryMap().listInputSources());
pathSanitizer.setApkLogger(apkModule.getApkLogger());
pathSanitizer.setResourceFileList(apkModule.listResFiles());
return pathSanitizer;
}
private static final int MAX_NAME_LENGTH = 75;
private static final int MAX_PATH_LENGTH = 100;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy