forked from github-mirror/CoreProtect
Improve metadata sanitization
This commit is contained in:
1 parent
42a2965cd1
commit
99130fdcdb
1 file changed
+205
-49
@@ -7,10 +7,14 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@@ -36,6 +40,31 @@ public class ItemUtils {
|
||||
|
||||
private static final Object UNSERIALIZABLE_VALUE = new Object();
|
||||
private static final Logger LOGGER = Logger.getLogger("CoreProtect");
|
||||
private static final int MAX_SANITIZE_ATTEMPTS = 2;
|
||||
private static final Pattern TYPE_TOKEN_PATTERN = Pattern.compile("([A-Za-z_$][A-Za-z0-9_$\\.]*)\\{");
|
||||
private static final Pattern REGISTRY_LOCATION_PATTERN = Pattern.compile("location=([^,\\]\\s]+)");
|
||||
|
||||
private static final class FailureContext {
|
||||
|
||||
private final Set<String> typeTokens;
|
||||
private final String registryLocation;
|
||||
|
||||
private FailureContext(Set<String> typeTokens, String registryLocation) {
|
||||
this.typeTokens = typeTokens;
|
||||
this.registryLocation = registryLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SanitizationResult {
|
||||
|
||||
private final Object value;
|
||||
private final boolean modified;
|
||||
|
||||
private SanitizationResult(Object value, boolean modified) {
|
||||
this.value = value;
|
||||
this.modified = modified;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
@@ -350,25 +379,48 @@ public class ItemUtils {
|
||||
return serializeByteData(data);
|
||||
}
|
||||
catch (Exception initialException) {
|
||||
Object sanitizedData = sanitizeByteData(data);
|
||||
if (sanitizedData != UNSERIALIZABLE_VALUE) {
|
||||
Object workingData = data;
|
||||
Exception failure = initialException;
|
||||
Set<String> problematicTypes = new LinkedHashSet<>();
|
||||
String registryLocation = null;
|
||||
boolean sanitized = false;
|
||||
|
||||
for (int attempt = 0; attempt < MAX_SANITIZE_ATTEMPTS; attempt++) {
|
||||
FailureContext failureContext = parseSerializationFailure(failure);
|
||||
problematicTypes.addAll(failureContext.typeTokens);
|
||||
if (registryLocation == null && failureContext.registryLocation != null) {
|
||||
registryLocation = failureContext.registryLocation;
|
||||
}
|
||||
|
||||
if (problematicTypes.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
SanitizationResult sanitizationResult = sanitizeByteData(workingData, problematicTypes);
|
||||
if (!sanitizationResult.modified || sanitizationResult.value == UNSERIALIZABLE_VALUE) {
|
||||
break;
|
||||
}
|
||||
|
||||
sanitized = true;
|
||||
workingData = sanitizationResult.value;
|
||||
|
||||
try {
|
||||
byte[] result = serializeByteData(sanitizedData);
|
||||
byte[] result = serializeByteData(workingData);
|
||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
||||
LOGGER.warning("CoreProtect sanitized unsupported metadata during byte serialization; some nested values were omitted.");
|
||||
logSanitizedMetadata(problematicTypes, registryLocation);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception sanitizedException) {
|
||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
||||
sanitizedException.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
catch (Exception retryException) {
|
||||
failure = retryException;
|
||||
}
|
||||
}
|
||||
|
||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
||||
initialException.printStackTrace();
|
||||
if (sanitized) {
|
||||
LOGGER.warning("CoreProtect failed to serialize metadata after targeted type sanitization.");
|
||||
}
|
||||
failure.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,85 +435,189 @@ public class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canSerializeByteData(Object data) {
|
||||
try {
|
||||
serializeByteData(data);
|
||||
return true;
|
||||
private static FailureContext parseSerializationFailure(Throwable throwable) {
|
||||
Set<String> typeTokens = new LinkedHashSet<>();
|
||||
String registryLocation = null;
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
String message = current.getMessage();
|
||||
if (message != null) {
|
||||
Matcher typeMatcher = TYPE_TOKEN_PATTERN.matcher(message);
|
||||
while (typeMatcher.find()) {
|
||||
addTypeToken(typeTokens, typeMatcher.group(1));
|
||||
}
|
||||
|
||||
if (registryLocation == null) {
|
||||
Matcher locationMatcher = REGISTRY_LOCATION_PATTERN.matcher(message);
|
||||
if (locationMatcher.find()) {
|
||||
registryLocation = locationMatcher.group(1);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object sanitizeByteData(Object data) {
|
||||
if (data == null || canSerializeByteData(data)) {
|
||||
return data;
|
||||
for (StackTraceElement frame : current.getStackTrace()) {
|
||||
if ("serialize".equals(frame.getMethodName())) {
|
||||
addTypeToken(typeTokens, frame.getClassName());
|
||||
}
|
||||
}
|
||||
|
||||
current = current.getCause();
|
||||
}
|
||||
|
||||
return new FailureContext(typeTokens, registryLocation);
|
||||
}
|
||||
|
||||
private static void addTypeToken(Set<String> typeTokens, String token) {
|
||||
String normalizedToken = normalizeTypeToken(token);
|
||||
if (normalizedToken != null) {
|
||||
typeTokens.add(normalizedToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeTypeToken(String token) {
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int dotIndex = normalized.lastIndexOf('.');
|
||||
if (dotIndex >= 0 && dotIndex + 1 < normalized.length()) {
|
||||
normalized = normalized.substring(dotIndex + 1);
|
||||
}
|
||||
|
||||
int dollarIndex = normalized.lastIndexOf('$');
|
||||
if (dollarIndex >= 0 && dollarIndex + 1 < normalized.length()) {
|
||||
normalized = normalized.substring(dollarIndex + 1);
|
||||
}
|
||||
|
||||
StringBuilder safeToken = new StringBuilder(normalized.length());
|
||||
for (int i = 0; i < normalized.length(); i++) {
|
||||
char currentChar = normalized.charAt(i);
|
||||
if ((currentChar >= 'A' && currentChar <= 'Z') || (currentChar >= 'a' && currentChar <= 'z') || (currentChar >= '0' && currentChar <= '9') || currentChar == '_') {
|
||||
safeToken.append(currentChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (safeToken.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return safeToken.toString().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static boolean isProblematicType(Class<?> valueType, Set<String> problematicTypes) {
|
||||
String typeToken = normalizeTypeToken(valueType.getSimpleName());
|
||||
if (typeToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String problematicType : problematicTypes) {
|
||||
if (typeToken.equals(problematicType) || typeToken.startsWith(problematicType) || problematicType.startsWith(typeToken)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SanitizationResult sanitizeByteData(Object data, Set<String> problematicTypes) {
|
||||
if (data == null) {
|
||||
return new SanitizationResult(null, false);
|
||||
}
|
||||
|
||||
if (isProblematicType(data.getClass(), problematicTypes)) {
|
||||
return new SanitizationResult(UNSERIALIZABLE_VALUE, true);
|
||||
}
|
||||
|
||||
if (data instanceof Map<?, ?>) {
|
||||
Map<?, ?> sourceMap = (Map<?, ?>) data;
|
||||
Map<Object, Object> sanitizedMap = new LinkedHashMap<>();
|
||||
boolean modified = false;
|
||||
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
|
||||
Object sanitizedKey = sanitizeByteData(entry.getKey());
|
||||
Object sanitizedValue = sanitizeByteData(entry.getValue());
|
||||
if (sanitizedKey == UNSERIALIZABLE_VALUE || sanitizedValue == UNSERIALIZABLE_VALUE) {
|
||||
SanitizationResult sanitizedKey = sanitizeByteData(entry.getKey(), problematicTypes);
|
||||
SanitizationResult sanitizedValue = sanitizeByteData(entry.getValue(), problematicTypes);
|
||||
if (sanitizedKey.value == UNSERIALIZABLE_VALUE || sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||
modified = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
sanitizedMap.put(sanitizedKey, sanitizedValue);
|
||||
modified |= sanitizedKey.modified || sanitizedValue.modified;
|
||||
sanitizedMap.put(sanitizedKey.value, sanitizedValue.value);
|
||||
}
|
||||
|
||||
if (sanitizedMap.isEmpty() && !sourceMap.isEmpty()) {
|
||||
return UNSERIALIZABLE_VALUE;
|
||||
if (!modified) {
|
||||
return new SanitizationResult(data, false);
|
||||
}
|
||||
|
||||
return canSerializeByteData(sanitizedMap) ? sanitizedMap : UNSERIALIZABLE_VALUE;
|
||||
return new SanitizationResult(sanitizedMap, true);
|
||||
}
|
||||
|
||||
if (data instanceof List<?>) {
|
||||
List<?> sourceList = (List<?>) data;
|
||||
List<Object> sanitizedList = new ArrayList<>();
|
||||
List<Object> sanitizedList = new ArrayList<>(sourceList.size());
|
||||
boolean modified = false;
|
||||
for (Object value : sourceList) {
|
||||
Object sanitizedValue = sanitizeByteData(value);
|
||||
if (sanitizedValue == UNSERIALIZABLE_VALUE) {
|
||||
continue;
|
||||
SanitizationResult sanitizedValue = sanitizeByteData(value, problematicTypes);
|
||||
if (sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||
sanitizedList.add(null);
|
||||
modified = true;
|
||||
}
|
||||
else {
|
||||
sanitizedList.add(sanitizedValue.value);
|
||||
modified |= sanitizedValue.modified;
|
||||
}
|
||||
}
|
||||
|
||||
sanitizedList.add(sanitizedValue);
|
||||
if (!modified) {
|
||||
return new SanitizationResult(data, false);
|
||||
}
|
||||
|
||||
if (sanitizedList.isEmpty() && !sourceList.isEmpty()) {
|
||||
return UNSERIALIZABLE_VALUE;
|
||||
}
|
||||
|
||||
return canSerializeByteData(sanitizedList) ? sanitizedList : UNSERIALIZABLE_VALUE;
|
||||
return new SanitizationResult(sanitizedList, true);
|
||||
}
|
||||
|
||||
if (data.getClass().isArray()) {
|
||||
Class<?> componentType = data.getClass().getComponentType();
|
||||
if (componentType.isPrimitive()) {
|
||||
return new SanitizationResult(data, false);
|
||||
}
|
||||
|
||||
int length = Array.getLength(data);
|
||||
List<Object> sanitizedValues = new ArrayList<>();
|
||||
Object sanitizedArray = Array.newInstance(componentType, length);
|
||||
boolean modified = false;
|
||||
for (int index = 0; index < length; index++) {
|
||||
Object sanitizedValue = sanitizeByteData(Array.get(data, index));
|
||||
if (sanitizedValue == UNSERIALIZABLE_VALUE) {
|
||||
continue;
|
||||
SanitizationResult sanitizedValue = sanitizeByteData(Array.get(data, index), problematicTypes);
|
||||
if (sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||
Array.set(sanitizedArray, index, null);
|
||||
modified = true;
|
||||
}
|
||||
else {
|
||||
Array.set(sanitizedArray, index, sanitizedValue.value);
|
||||
modified |= sanitizedValue.modified;
|
||||
}
|
||||
}
|
||||
|
||||
sanitizedValues.add(sanitizedValue);
|
||||
if (!modified) {
|
||||
return new SanitizationResult(data, false);
|
||||
}
|
||||
|
||||
if (sanitizedValues.isEmpty() && length > 0) {
|
||||
return UNSERIALIZABLE_VALUE;
|
||||
return new SanitizationResult(sanitizedArray, true);
|
||||
}
|
||||
|
||||
Object sanitizedArray = Array.newInstance(data.getClass().getComponentType(), sanitizedValues.size());
|
||||
for (int index = 0; index < sanitizedValues.size(); index++) {
|
||||
Array.set(sanitizedArray, index, sanitizedValues.get(index));
|
||||
return new SanitizationResult(data, false);
|
||||
}
|
||||
|
||||
return canSerializeByteData(sanitizedArray) ? sanitizedArray : UNSERIALIZABLE_VALUE;
|
||||
private static void logSanitizedMetadata(Set<String> problematicTypes, String registryLocation) {
|
||||
StringBuilder message = new StringBuilder("CoreProtect sanitized unsupported metadata during byte serialization (types: ");
|
||||
message.append(String.join(", ", problematicTypes));
|
||||
if (registryLocation != null) {
|
||||
message.append(", missing key: ").append(registryLocation);
|
||||
}
|
||||
|
||||
return UNSERIALIZABLE_VALUE;
|
||||
message.append(").");
|
||||
LOGGER.warning(message.toString());
|
||||
}
|
||||
|
||||
public static ItemMeta deserializeItemMeta(Class<? extends ItemMeta> itemMetaClass, Map<String, Object> args) {
|
||||
|
||||
Reference in new issue
Block a user