Fix signed jar class caching (#241)

This commit is contained in:
IzzelAliz 2021-04-24 13:27:32 +08:00
parent 0e02e12b3f
commit d9370b4f65
2 changed files with 50 additions and 15 deletions

View File

@ -6,6 +6,7 @@ import io.izzel.arclight.common.bridge.bukkit.JavaPluginLoaderBridge;
import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper;
import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper;
import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader;
import io.izzel.tools.product.Product2;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPluginLoader;
@ -16,11 +17,9 @@ import org.spongepowered.asm.mixin.Shadow;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.Map;
import java.util.concurrent.Callable;
@ -74,16 +73,10 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
if (url != null) {
URLConnection connection;
CodeSigner[] signers;
Callable<byte[]> byteSource;
try {
connection = url.openConnection();
connection.connect();
if (connection instanceof JarURLConnection) {
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
signers = new CodeSigner[0];
}
byteSource = () -> {
try (InputStream is = connection.getInputStream()) {
byte[] classBytes = ByteStreams.toByteArray(is);
@ -96,7 +89,7 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
throw new ClassNotFoundException(name, e);
}
byte[] classBytes = this.getRemapper().remapClass(name, byteSource, connection);
Product2<byte[], CodeSource> classBytes = this.getRemapper().remapClass(name, byteSource, connection);
int dot = name.lastIndexOf('.');
if (dot != -1) {
@ -116,9 +109,7 @@ public class PluginClassLoaderMixin extends URLClassLoader implements RemappingC
}
}
CodeSource source = new CodeSource(this.url, signers);
result = defineClass(name, classBytes, 0, classBytes.length, source);
result = defineClass(name, classBytes._1, 0, classBytes._1.length, classBytes._2);
}
if (result == null) {

View File

@ -4,9 +4,12 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.common.mod.util.remapper.generated.ArclightReflectionHandler;
import io.izzel.arclight.i18n.ArclightConfig;
import io.izzel.tools.product.Product;
import io.izzel.tools.product.Product2;
import net.md_5.specialsource.JarMapping;
import net.md_5.specialsource.JarRemapper;
import net.md_5.specialsource.RemappingClassAdapter;
@ -28,8 +31,12 @@ import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@ -39,6 +46,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarFile;
public class ClassLoaderRemapper extends LenientJarRemapper {
@ -52,6 +60,7 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
private final String generatedHandler;
private final Class<?> generatedHandlerClass;
private final GeneratedHandlerAdapter generatedHandlerAdapter;
private final Map<String, Boolean> secureJarInfo = new ConcurrentHashMap<>();
public ClassLoaderRemapper(JarMapping jarMapping, JarMapping toBukkitMapping, ClassLoader classLoader) {
super(jarMapping);
@ -279,7 +288,19 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
return Maps.immutableEntry(owner, mapped);
}
public byte[] remapClass(String className, Callable<byte[]> byteSource, URLConnection connection) throws ClassNotFoundException {
private boolean isSecureJar(JarFile jarFile) {
return this.secureJarInfo.computeIfAbsent(jarFile.getName(), key ->
jarFile.stream().anyMatch(it -> {
if (it.isDirectory()) return false;
String name = it.getName();
return name.startsWith("META-INF") && (name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.endsWith(".SF"));
}));
}
public Product2<byte[], CodeSource> remapClass(String className, Callable<byte[]> byteSource, URLConnection connection) throws ClassNotFoundException {
try {
ArclightClassCache.CacheSegment segment = ArclightClassCache.instance().makeSegment(connection);
Optional<byte[]> optional = segment.findByName(className);
@ -287,7 +308,21 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
byte[] bytes = optional.get();
ClassWriter cw = new ClassWriter(0);
new ClassReader(bytes).accept(new ClassRemapper(cw, generatedHandlerAdapter), 0);
return cw.toByteArray();
URL url;
CodeSigner[] signers;
if (connection instanceof JarURLConnection) {
url = ((JarURLConnection) connection).getJarFileURL();
if (isSecureJar(((JarURLConnection) connection).getJarFile())) {
ByteStreams.exhaust(connection.getInputStream()); // must read before asking signers
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
signers = null;
}
} else {
url = connection.getURL();
signers = null;
}
return Product.of(cw.toByteArray(), new CodeSource(url, signers));
} else {
byte[] bytes = remapClassFile(byteSource.call(), GlobalClassRepo.INSTANCE);
if (ArclightConfig.spec().getOptimization().isCachePluginClass()) {
@ -296,7 +331,16 @@ public class ClassLoaderRemapper extends LenientJarRemapper {
byte[] store = cw.toByteArray();
segment.addToCache(className, store);
}
return bytes;
URL url;
CodeSigner[] signers;
if (connection instanceof JarURLConnection) {
url = ((JarURLConnection) connection).getJarFileURL();
signers = ((JarURLConnection) connection).getJarEntry().getCodeSigners();
} else {
url = connection.getURL();
signers = null;
}
return Product.of(bytes, new CodeSource(url, signers));
}
} catch (Exception e) {
throw new ClassNotFoundException(className, e);