forked from MirrorHub/authlib-injector
support prefetched metadata & refactor
This commit is contained in:
parent
cd5b42140c
commit
a671ee8339
8 changed files with 86 additions and 238 deletions
|
@ -2,12 +2,16 @@ package org.to2mbn.authlibinjector;
|
|||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static org.to2mbn.authlibinjector.util.IOUtils.readURL;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import org.to2mbn.authlibinjector.transform.ClassTransformer;
|
||||
import org.to2mbn.authlibinjector.transform.SkinWhitelistTransformUnit;
|
||||
import org.to2mbn.authlibinjector.transform.YggdrasilApiTransformUnit;
|
||||
import org.to2mbn.authlibinjector.transform.YggdrasilKeyTransformUnit;
|
||||
|
||||
public final class AuthlibInjector {
|
||||
|
||||
|
@ -19,60 +23,82 @@ public final class AuthlibInjector {
|
|||
private AuthlibInjector() {}
|
||||
|
||||
private static boolean booted = false;
|
||||
private static boolean debug = "true".equals(System.getProperty("org.to2mbn.authlibinjector.debug"));
|
||||
|
||||
public static void log(String message, Object... args) {
|
||||
public static void info(String message, Object... args) {
|
||||
System.err.println("[authlib-injector] " + MessageFormat.format(message, args));
|
||||
}
|
||||
|
||||
public static void debug(String message, Object... args) {
|
||||
if (debug) {
|
||||
info(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void bootstrap(Consumer<ClassFileTransformer> transformerRegistry) {
|
||||
if (booted) {
|
||||
log("already booted, skipping");
|
||||
info("already booted, skipping");
|
||||
return;
|
||||
}
|
||||
booted = true;
|
||||
|
||||
Optional<InjectorConfig> optionalConfig = configure();
|
||||
Optional<YggdrasilConfiguration> optionalConfig = configure();
|
||||
if (!optionalConfig.isPresent()) {
|
||||
log("no config is found, exiting");
|
||||
info("no config available");
|
||||
return;
|
||||
}
|
||||
|
||||
InjectorConfig config = optionalConfig.get();
|
||||
transformerRegistry.accept(createTransformer(optionalConfig.get()));
|
||||
}
|
||||
|
||||
private static Optional<YggdrasilConfiguration> configure() {
|
||||
String apiRoot = System.getProperty("org.to2mbn.authlibinjector.config");
|
||||
if (apiRoot == null) return empty();
|
||||
info("api root: {0}", apiRoot);
|
||||
|
||||
String metadataResponse = System.getProperty("org.to2mbn.authlibinjector.config.prefetched");
|
||||
|
||||
if (metadataResponse == null) {
|
||||
info("fetching metadata");
|
||||
try {
|
||||
metadataResponse = readURL(apiRoot);
|
||||
} catch (IOException e) {
|
||||
info("unable to fetch metadata: {0}", e);
|
||||
return empty();
|
||||
}
|
||||
|
||||
} else {
|
||||
info("prefetched metadata detected");
|
||||
}
|
||||
|
||||
debug("metadata: {0}", metadataResponse);
|
||||
|
||||
YggdrasilConfiguration configuration;
|
||||
try {
|
||||
configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse);
|
||||
} catch (IOException e) {
|
||||
info("unable to parse metadata: {0}\n"
|
||||
+ "metadata to parse:\n"
|
||||
+ "{1}",
|
||||
e, metadataResponse);
|
||||
return empty();
|
||||
}
|
||||
debug("parsed metadata: {0}", configuration);
|
||||
return of(configuration);
|
||||
}
|
||||
|
||||
private static ClassTransformer createTransformer(YggdrasilConfiguration config) {
|
||||
ClassTransformer transformer = new ClassTransformer();
|
||||
|
||||
if (config.isDebug()) transformer.debug = true;
|
||||
|
||||
transformer.debugSaveClass = debug;
|
||||
for (String ignore : nonTransformablePackages)
|
||||
transformer.ignores.add(ignore);
|
||||
|
||||
config.applyTransformers(transformer.units);
|
||||
transformerRegistry.accept(transformer);
|
||||
}
|
||||
transformer.units.add(new YggdrasilApiTransformUnit(config.getApiRoot()));
|
||||
transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0])));
|
||||
config.getDecodedPublickey().ifPresent(
|
||||
key -> transformer.units.add(new YggdrasilKeyTransformUnit(key.getEncoded())));
|
||||
|
||||
private static Optional<InjectorConfig> configure() {
|
||||
String url = System.getProperty("org.to2mbn.authlibinjector.config");
|
||||
if (url == null) {
|
||||
return empty();
|
||||
}
|
||||
log("trying to config remotely: {0}", url);
|
||||
|
||||
InjectorConfig config = new InjectorConfig();
|
||||
config.setDebug("true".equals(System.getProperty("org.to2mbn.authlibinjector.debug")));
|
||||
|
||||
RemoteConfiguration remoteConfig;
|
||||
try {
|
||||
remoteConfig = RemoteConfiguration.fetch(url);
|
||||
} catch (IOException e) {
|
||||
log("unable to configure remotely: {0}", e);
|
||||
return empty();
|
||||
}
|
||||
|
||||
if (config.isDebug()) {
|
||||
log("fetched remote config: {0}", remoteConfig);
|
||||
}
|
||||
|
||||
remoteConfig.applyToInjectorConfig(config);
|
||||
return of(config);
|
||||
return transformer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package org.to2mbn.authlibinjector;
|
||||
|
||||
import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey;
|
||||
import java.util.List;
|
||||
import org.to2mbn.authlibinjector.transform.SkinWhitelistTransformUnit;
|
||||
import org.to2mbn.authlibinjector.transform.TransformUnit;
|
||||
import org.to2mbn.authlibinjector.transform.YggdrasilApiTransformUnit;
|
||||
import org.to2mbn.authlibinjector.transform.YggdrasilKeyTransformUnit;
|
||||
|
||||
public class InjectorConfig {
|
||||
|
||||
private String apiRoot;
|
||||
private List<String> skinWhitelistDomains;
|
||||
private String publicKey;
|
||||
private boolean debug;
|
||||
|
||||
public String getApiRoot() {
|
||||
return apiRoot;
|
||||
}
|
||||
|
||||
public void setApiRoot(String apiRoot) {
|
||||
this.apiRoot = apiRoot;
|
||||
}
|
||||
|
||||
public List<String> getSkinWhitelistDomains() {
|
||||
return skinWhitelistDomains;
|
||||
}
|
||||
|
||||
public void setSkinWhitelistDomains(List<String> skinWhitelistDomains) {
|
||||
this.skinWhitelistDomains = skinWhitelistDomains;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
public void applyTransformers(List<TransformUnit> units) {
|
||||
units.add(new YggdrasilApiTransformUnit(apiRoot));
|
||||
units.add(new SkinWhitelistTransformUnit(skinWhitelistDomains.toArray(new String[0])));
|
||||
if (publicKey != null) {
|
||||
units.add(new YggdrasilKeyTransformUnit(decodePublicKey(publicKey)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@ import static java.util.Objects.requireNonNull;
|
|||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static org.to2mbn.authlibinjector.util.HttpRequester.http;
|
||||
import static org.to2mbn.authlibinjector.util.IOUtils.asJson;
|
||||
import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey;
|
||||
import static org.to2mbn.authlibinjector.util.KeyUtils.loadX509PublicKey;
|
||||
import java.io.IOException;
|
||||
|
@ -21,13 +19,13 @@ import java.util.TreeMap;
|
|||
import org.to2mbn.authlibinjector.internal.org.json.JSONException;
|
||||
import org.to2mbn.authlibinjector.internal.org.json.JSONObject;
|
||||
|
||||
public class RemoteConfiguration {
|
||||
public class YggdrasilConfiguration {
|
||||
|
||||
public static RemoteConfiguration fetch(String apiRoot) throws IOException {
|
||||
public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws IOException {
|
||||
if (!apiRoot.endsWith("/")) apiRoot += "/";
|
||||
|
||||
try {
|
||||
JSONObject response = asJson(http.request("GET", apiRoot));
|
||||
JSONObject response = new JSONObject(metadataResponse);
|
||||
|
||||
List<String> skinDomains = new ArrayList<>();
|
||||
ofNullable(response.optJSONArray("skinDomains"))
|
||||
|
@ -54,7 +52,7 @@ public class RemoteConfiguration {
|
|||
.map(JSONObject::toMap)
|
||||
.ifPresent(it -> it.forEach((k, v) -> meta.put(k, String.valueOf(v))));
|
||||
|
||||
return new RemoteConfiguration(apiRoot, unmodifiableList(skinDomains), signaturePublickey, unmodifiableMap(meta), decodedPublickey);
|
||||
return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), signaturePublickey, unmodifiableMap(meta), decodedPublickey);
|
||||
} catch (JSONException e) {
|
||||
throw new IOException("Invalid json", e);
|
||||
}
|
||||
|
@ -66,7 +64,7 @@ public class RemoteConfiguration {
|
|||
private Optional<PublicKey> decodedPublickey;
|
||||
private Map<String, String> meta;
|
||||
|
||||
public RemoteConfiguration(String apiRoot, List<String> skinDomains, Optional<String> signaturePublickey, Map<String, String> meta, Optional<PublicKey> decodedPublickey) {
|
||||
public YggdrasilConfiguration(String apiRoot, List<String> skinDomains, Optional<String> signaturePublickey, Map<String, String> meta, Optional<PublicKey> decodedPublickey) {
|
||||
this.apiRoot = requireNonNull(apiRoot);
|
||||
this.skinDomains = requireNonNull(skinDomains);
|
||||
this.signaturePublickey = requireNonNull(signaturePublickey);
|
||||
|
@ -94,14 +92,8 @@ public class RemoteConfiguration {
|
|||
return decodedPublickey;
|
||||
}
|
||||
|
||||
public void applyToInjectorConfig(InjectorConfig config) {
|
||||
config.setApiRoot(apiRoot);
|
||||
config.setPublicKey(signaturePublickey.orElse(null));
|
||||
config.setSkinWhitelistDomains(skinDomains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoteConfiguration [apiRoot=" + apiRoot + ", skinDomains=" + skinDomains + ", signaturePublickey=" + signaturePublickey + ", meta=" + meta + "]";
|
||||
return "YggdrasilConfiguration [apiRoot=" + apiRoot + ", skinDomains=" + skinDomains + ", signaturePublickey=" + signaturePublickey + ", meta=" + meta + "]";
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package org.to2mbn.authlibinjector.javaagent;
|
||||
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.bootstrap;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.log;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.info;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
public class AuthlibInjectorPremain {
|
||||
|
||||
public static void premain(String arg, Instrumentation instrumentation) {
|
||||
try {
|
||||
log("launched from javaagent");
|
||||
info("launched from javaagent");
|
||||
if (arg != null && !arg.isEmpty()) {
|
||||
System.setProperty("org.to2mbn.authlibinjector.config", arg);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.to2mbn.authlibinjector.transform;
|
||||
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.log;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.debug;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.info;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
|
@ -21,7 +22,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
|
||||
public List<TransformUnit> units = new ArrayList<>();
|
||||
public Set<String> ignores = new HashSet<>();
|
||||
public boolean debug;
|
||||
public boolean debugSaveClass;
|
||||
|
||||
private static class TransformHandle {
|
||||
|
||||
|
@ -51,7 +52,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
ClassReader reader = new ClassReader(classBuffer);
|
||||
reader.accept(optionalVisitor.get(), 0);
|
||||
if (currentModified) {
|
||||
log("transform {0} using {1}", className, unit);
|
||||
info("transform {0} using {1}", className, unit);
|
||||
modified = true;
|
||||
classBuffer = writer.toByteArray();
|
||||
}
|
||||
|
@ -82,18 +83,16 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
units.forEach(handle::accept);
|
||||
if (handle.getResult().isPresent()) {
|
||||
byte[] classBuffer = handle.getResult().get();
|
||||
if (debug) {
|
||||
if (debugSaveClass) {
|
||||
saveClassFile(className, classBuffer);
|
||||
}
|
||||
return classBuffer;
|
||||
} else {
|
||||
if (debug) {
|
||||
log("no transform performed on {0}", className);
|
||||
}
|
||||
debug("no transform performed on {0}", className);
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log("unable to transform {0}: {1}", internalClassName, e);
|
||||
info("unable to transform {0}: {1}", internalClassName, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +103,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
try {
|
||||
Files.write(Paths.get(className + "_dump.class"), classBuffer, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
log("unable to dump class {0}: {1}", className, e);
|
||||
info("unable to dump class {0}: {1}", className, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.to2mbn.authlibinjector.transform;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ASM6;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.log;
|
||||
import static org.to2mbn.authlibinjector.AuthlibInjector.info;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
|
@ -29,7 +29,7 @@ public class LdcTransformUnit implements TransformUnit {
|
|||
Optional<String> transformed = ldcMapper.apply((String) cst);
|
||||
if (transformed.isPresent() && !transformed.get().equals(cst)) {
|
||||
modifiedCallback.run();
|
||||
log("transform [{0}] to [{1}]", cst, transformed.get());
|
||||
info("transform [{0}] to [{1}]", cst, transformed.get());
|
||||
super.visitLdcInsn(transformed.get());
|
||||
} else {
|
||||
super.visitLdcInsn(cst);
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
package org.to2mbn.authlibinjector.util;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.to2mbn.authlibinjector.util.IOUtils.asString;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpRequester {
|
||||
|
||||
/** Common http requester */
|
||||
public static final HttpRequester http = new HttpRequester();
|
||||
|
||||
private int timeout = 15000;
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String request(String method, String url) throws IOException {
|
||||
return request(method, url, null);
|
||||
}
|
||||
|
||||
public String request(String method, String url, Map<String, String> headers) throws IOException {
|
||||
HttpURLConnection conn = createConnection(url, headers);
|
||||
conn.setRequestMethod(method);
|
||||
try {
|
||||
conn.connect();
|
||||
try (InputStream in = conn.getInputStream()) {
|
||||
return asString(in);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
try (InputStream in = conn.getErrorStream()) {
|
||||
return readErrorStream(in, e);
|
||||
}
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public String requestWithPayload(String method, String url, Object payload, String contentType) throws IOException {
|
||||
return requestWithPayload(method, url, payload, contentType, null);
|
||||
}
|
||||
|
||||
public String requestWithPayload(String method, String url, Object payload, String contentType, Map<String, String> headers) throws IOException {
|
||||
byte[] bytePayload;
|
||||
if (payload instanceof byte[]) {
|
||||
bytePayload = (byte[]) payload;
|
||||
} else if (payload == null) {
|
||||
bytePayload = new byte[0];
|
||||
} else {
|
||||
bytePayload = String.valueOf(payload).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
HttpURLConnection conn = createConnection(url, headers);
|
||||
conn.setRequestMethod(method);
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
conn.setRequestProperty("Content-Length", String.valueOf(bytePayload.length));
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try {
|
||||
conn.connect();
|
||||
try (OutputStream out = conn.getOutputStream()) {
|
||||
out.write(bytePayload);
|
||||
}
|
||||
try (InputStream in = conn.getInputStream()) {
|
||||
return asString(in);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
try (InputStream in = conn.getErrorStream()) {
|
||||
return readErrorStream(in, e);
|
||||
}
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private String readErrorStream(InputStream in, IOException e) throws IOException {
|
||||
if (in == null)
|
||||
throw e;
|
||||
|
||||
try {
|
||||
return asString(in);
|
||||
} catch (IOException e1) {
|
||||
if (e != e1)
|
||||
e1.addSuppressed(e);
|
||||
|
||||
throw e1;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection createConnection(String url, Map<String, String> headers) throws IOException {
|
||||
HttpURLConnection conn = createConnection(new URL(url));
|
||||
if (headers != null)
|
||||
headers.forEach((key, value) -> conn.setRequestProperty(key, value));
|
||||
return conn;
|
||||
}
|
||||
|
||||
private HttpURLConnection createConnection(URL url) throws IOException {
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(timeout);
|
||||
conn.setReadTimeout(timeout);
|
||||
conn.setUseCaches(false);
|
||||
return conn;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,11 +6,16 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import org.to2mbn.authlibinjector.internal.org.json.JSONException;
|
||||
import org.to2mbn.authlibinjector.internal.org.json.JSONObject;
|
||||
import java.net.URL;
|
||||
|
||||
public final class IOUtils {
|
||||
|
||||
public static String readURL(String url) throws IOException {
|
||||
try (InputStream in = new URL(url).openStream()) {
|
||||
return asString(in);
|
||||
}
|
||||
}
|
||||
|
||||
public static String asString(InputStream in) throws IOException {
|
||||
CharArrayWriter w = new CharArrayWriter();
|
||||
Reader reader = new InputStreamReader(in, UTF_8);
|
||||
|
@ -22,10 +27,6 @@ public final class IOUtils {
|
|||
return new String(w.toCharArray());
|
||||
}
|
||||
|
||||
public static JSONObject asJson(String data) throws JSONException {
|
||||
return new JSONObject(data);
|
||||
}
|
||||
|
||||
private IOUtils() {}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue