forked from github-mirror/CoreProtect
Improve metadata sanitization
This commit is contained in:
1 parent
42a2965cd1
commit
99130fdcdb
1 file changed
+206
-50
@@ -7,10 +7,14 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -36,6 +40,31 @@ public class ItemUtils {
|
|||||||
|
|
||||||
private static final Object UNSERIALIZABLE_VALUE = new Object();
|
private static final Object UNSERIALIZABLE_VALUE = new Object();
|
||||||
private static final Logger LOGGER = Logger.getLogger("CoreProtect");
|
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() {
|
private ItemUtils() {
|
||||||
throw new IllegalStateException("Utility class");
|
throw new IllegalStateException("Utility class");
|
||||||
@@ -350,25 +379,48 @@ public class ItemUtils {
|
|||||||
return serializeByteData(data);
|
return serializeByteData(data);
|
||||||
}
|
}
|
||||||
catch (Exception initialException) {
|
catch (Exception initialException) {
|
||||||
Object sanitizedData = sanitizeByteData(data);
|
Object workingData = data;
|
||||||
if (sanitizedData != UNSERIALIZABLE_VALUE) {
|
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 {
|
try {
|
||||||
byte[] result = serializeByteData(sanitizedData);
|
byte[] result = serializeByteData(workingData);
|
||||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception sanitizedException) {
|
catch (Exception retryException) {
|
||||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
failure = retryException;
|
||||||
sanitizedException.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigHandler.EDITION_BRANCH.contains("-dev")) {
|
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) {
|
private static FailureContext parseSerializationFailure(Throwable throwable) {
|
||||||
try {
|
Set<String> typeTokens = new LinkedHashSet<>();
|
||||||
serializeByteData(data);
|
String registryLocation = null;
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (StackTraceElement frame : current.getStackTrace()) {
|
||||||
|
if ("serialize".equals(frame.getMethodName())) {
|
||||||
|
addTypeToken(typeTokens, frame.getClassName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.getCause();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
return false;
|
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 Object sanitizeByteData(Object data) {
|
private static String normalizeTypeToken(String token) {
|
||||||
if (data == null || canSerializeByteData(data)) {
|
if (token == null) {
|
||||||
return data;
|
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<?, ?>) {
|
if (data instanceof Map<?, ?>) {
|
||||||
Map<?, ?> sourceMap = (Map<?, ?>) data;
|
Map<?, ?> sourceMap = (Map<?, ?>) data;
|
||||||
Map<Object, Object> sanitizedMap = new LinkedHashMap<>();
|
Map<Object, Object> sanitizedMap = new LinkedHashMap<>();
|
||||||
|
boolean modified = false;
|
||||||
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
|
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
|
||||||
Object sanitizedKey = sanitizeByteData(entry.getKey());
|
SanitizationResult sanitizedKey = sanitizeByteData(entry.getKey(), problematicTypes);
|
||||||
Object sanitizedValue = sanitizeByteData(entry.getValue());
|
SanitizationResult sanitizedValue = sanitizeByteData(entry.getValue(), problematicTypes);
|
||||||
if (sanitizedKey == UNSERIALIZABLE_VALUE || sanitizedValue == UNSERIALIZABLE_VALUE) {
|
if (sanitizedKey.value == UNSERIALIZABLE_VALUE || sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||||
|
modified = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedMap.put(sanitizedKey, sanitizedValue);
|
modified |= sanitizedKey.modified || sanitizedValue.modified;
|
||||||
|
sanitizedMap.put(sanitizedKey.value, sanitizedValue.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedMap.isEmpty() && !sourceMap.isEmpty()) {
|
if (!modified) {
|
||||||
return UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canSerializeByteData(sanitizedMap) ? sanitizedMap : UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(sanitizedMap, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data instanceof List<?>) {
|
if (data instanceof List<?>) {
|
||||||
List<?> sourceList = (List<?>) data;
|
List<?> sourceList = (List<?>) data;
|
||||||
List<Object> sanitizedList = new ArrayList<>();
|
List<Object> sanitizedList = new ArrayList<>(sourceList.size());
|
||||||
|
boolean modified = false;
|
||||||
for (Object value : sourceList) {
|
for (Object value : sourceList) {
|
||||||
Object sanitizedValue = sanitizeByteData(value);
|
SanitizationResult sanitizedValue = sanitizeByteData(value, problematicTypes);
|
||||||
if (sanitizedValue == UNSERIALIZABLE_VALUE) {
|
if (sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||||
continue;
|
sanitizedList.add(null);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sanitizedList.add(sanitizedValue.value);
|
||||||
|
modified |= sanitizedValue.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedList.add(sanitizedValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedList.isEmpty() && !sourceList.isEmpty()) {
|
if (!modified) {
|
||||||
return UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return canSerializeByteData(sanitizedList) ? sanitizedList : UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(sanitizedList, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.getClass().isArray()) {
|
if (data.getClass().isArray()) {
|
||||||
|
Class<?> componentType = data.getClass().getComponentType();
|
||||||
|
if (componentType.isPrimitive()) {
|
||||||
|
return new SanitizationResult(data, false);
|
||||||
|
}
|
||||||
|
|
||||||
int length = Array.getLength(data);
|
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++) {
|
for (int index = 0; index < length; index++) {
|
||||||
Object sanitizedValue = sanitizeByteData(Array.get(data, index));
|
SanitizationResult sanitizedValue = sanitizeByteData(Array.get(data, index), problematicTypes);
|
||||||
if (sanitizedValue == UNSERIALIZABLE_VALUE) {
|
if (sanitizedValue.value == UNSERIALIZABLE_VALUE) {
|
||||||
continue;
|
Array.set(sanitizedArray, index, null);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Array.set(sanitizedArray, index, sanitizedValue.value);
|
||||||
|
modified |= sanitizedValue.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedValues.add(sanitizedValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedValues.isEmpty() && length > 0) {
|
if (!modified) {
|
||||||
return UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object sanitizedArray = Array.newInstance(data.getClass().getComponentType(), sanitizedValues.size());
|
return new SanitizationResult(sanitizedArray, true);
|
||||||
for (int index = 0; index < sanitizedValues.size(); index++) {
|
|
||||||
Array.set(sanitizedArray, index, sanitizedValues.get(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
return canSerializeByteData(sanitizedArray) ? sanitizedArray : UNSERIALIZABLE_VALUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UNSERIALIZABLE_VALUE;
|
return new SanitizationResult(data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
message.append(").");
|
||||||
|
LOGGER.warning(message.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemMeta deserializeItemMeta(Class<? extends ItemMeta> itemMetaClass, Map<String, Object> args) {
|
public static ItemMeta deserializeItemMeta(Class<? extends ItemMeta> itemMetaClass, Map<String, Object> args) {
|
||||||
|
|||||||
Reference in new issue
Block a user