更换到json-simple类库

This commit is contained in:
yushijinhun 2018-04-15 09:57:55 +08:00
parent 5c816c2e90
commit da81ef6f60
No known key found for this signature in database
GPG Key ID: 5BC167F73EA558E4
7 changed files with 137 additions and 76 deletions

View File

@ -10,6 +10,7 @@ import static org.to2mbn.authlibinjector.util.LoggingUtils.debug;
import static org.to2mbn.authlibinjector.util.LoggingUtils.info; import static org.to2mbn.authlibinjector.util.LoggingUtils.info;
import static org.to2mbn.authlibinjector.util.LoggingUtils.isDebugOn; import static org.to2mbn.authlibinjector.util.LoggingUtils.isDebugOn;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.ClassFileTransformer;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; import java.util.Optional;
@ -81,7 +82,7 @@ public final class AuthlibInjector {
YggdrasilConfiguration configuration; YggdrasilConfiguration configuration;
try { try {
configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse); configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse);
} catch (IOException e) { } catch (UncheckedIOException e) {
info("unable to parse metadata: {0}\n" info("unable to parse metadata: {0}\n"
+ "metadata to parse:\n" + "metadata to parse:\n"
+ "{1}", + "{1}",

View File

@ -1,69 +1,59 @@
package org.to2mbn.authlibinjector; package org.to2mbn.authlibinjector;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull; 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 java.util.Optional.ofNullable;
import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey; import static java.util.stream.Collectors.toList;
import static org.to2mbn.authlibinjector.util.KeyUtils.loadX509PublicKey; import static org.to2mbn.authlibinjector.util.JsonUtils.asArray;
import java.io.IOException; import static org.to2mbn.authlibinjector.util.JsonUtils.asObject;
import java.security.GeneralSecurityException; import static org.to2mbn.authlibinjector.util.JsonUtils.parseJson;
import java.io.UncheckedIOException;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap; import java.util.TreeMap;
import org.to2mbn.authlibinjector.internal.org.json.JSONException; import org.to2mbn.authlibinjector.internal.org.json.simple.JSONObject;
import org.to2mbn.authlibinjector.internal.org.json.JSONObject; import org.to2mbn.authlibinjector.util.JsonUtils;
import org.to2mbn.authlibinjector.util.KeyUtils;
public class YggdrasilConfiguration { public class YggdrasilConfiguration {
public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws IOException { public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws UncheckedIOException {
if (!apiRoot.endsWith("/")) apiRoot += "/"; if (!apiRoot.endsWith("/")) apiRoot += "/";
try { JSONObject response = asObject(parseJson(metadataResponse));
JSONObject response = new JSONObject(metadataResponse);
List<String> skinDomains = new ArrayList<>(); List<String> skinDomains =
ofNullable(response.optJSONArray("skinDomains")) ofNullable(response.get("skinDomains"))
.ifPresent(it -> it.forEach(domain -> { .map(it -> asArray(it).stream()
if (domain instanceof String) .map(JsonUtils::asString)
skinDomains.add((String) domain); .collect(toList()))
})); .orElse(emptyList());
Optional<PublicKey> decodedPublickey; Optional<PublicKey> decodedPublickey =
String publickeyString = response.optString("signaturePublickey"); ofNullable(response.get("signaturePublickey"))
if (publickeyString == null) { .map(JsonUtils::asString)
decodedPublickey = empty(); .map(KeyUtils::parseSignaturePublicKey);
} else {
try {
decodedPublickey = of(loadX509PublicKey(decodePublicKey(publickeyString)));
} catch (IllegalArgumentException | GeneralSecurityException e) {
throw new IOException("Bad signature publickey", e);
}
}
Map<String, String> meta = new TreeMap<>(); Map<String, Object> meta =
ofNullable(response.optJSONObject("meta")) ofNullable(response.get("meta"))
.map(JSONObject::toMap) .map(it -> (Map<String, Object>) new TreeMap<>(asObject(it)))
.ifPresent(it -> it.forEach((k, v) -> meta.put(k, String.valueOf(v)))); .orElse(emptyMap());
return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey); return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey);
} catch (JSONException e) {
throw new IOException("Invalid json", e);
}
} }
private String apiRoot; private String apiRoot;
private List<String> skinDomains; private List<String> skinDomains;
private Optional<PublicKey> decodedPublickey; private Optional<PublicKey> decodedPublickey;
private Map<String, String> meta; private Map<String, Object> meta;
public YggdrasilConfiguration(String apiRoot, List<String> skinDomains, Map<String, String> meta, Optional<PublicKey> decodedPublickey) { public YggdrasilConfiguration(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) {
this.apiRoot = requireNonNull(apiRoot); this.apiRoot = requireNonNull(apiRoot);
this.skinDomains = requireNonNull(skinDomains); this.skinDomains = requireNonNull(skinDomains);
this.meta = requireNonNull(meta); this.meta = requireNonNull(meta);
@ -78,7 +68,7 @@ public class YggdrasilConfiguration {
return skinDomains; return skinDomains;
} }
public Map<String, String> getMeta() { public Map<String, Object> getMeta() {
return meta; return meta;
} }

View File

@ -1,11 +1,18 @@
package org.to2mbn.authlibinjector.httpd; package org.to2mbn.authlibinjector.httpd;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singleton;
import static java.util.Optional.empty; import static java.util.Optional.empty;
import static java.util.Optional.of; import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static org.to2mbn.authlibinjector.util.IOUtils.asString; import static org.to2mbn.authlibinjector.util.IOUtils.asString;
import static org.to2mbn.authlibinjector.util.IOUtils.getURL; import static org.to2mbn.authlibinjector.util.IOUtils.getURL;
import static org.to2mbn.authlibinjector.util.IOUtils.newUncheckedIOException;
import static org.to2mbn.authlibinjector.util.IOUtils.postURL; import static org.to2mbn.authlibinjector.util.IOUtils.postURL;
import static org.to2mbn.authlibinjector.util.JsonUtils.asArray;
import static org.to2mbn.authlibinjector.util.JsonUtils.asObject;
import static org.to2mbn.authlibinjector.util.JsonUtils.asString;
import static org.to2mbn.authlibinjector.util.JsonUtils.parseJson;
import static org.to2mbn.authlibinjector.util.LoggingUtils.debug; import static org.to2mbn.authlibinjector.util.LoggingUtils.debug;
import static org.to2mbn.authlibinjector.util.LoggingUtils.info; import static org.to2mbn.authlibinjector.util.LoggingUtils.info;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -16,9 +23,9 @@ import java.util.Optional;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.to2mbn.authlibinjector.YggdrasilConfiguration; import org.to2mbn.authlibinjector.YggdrasilConfiguration;
import org.to2mbn.authlibinjector.internal.org.json.JSONArray; import org.to2mbn.authlibinjector.internal.org.json.simple.JSONArray;
import org.to2mbn.authlibinjector.internal.org.json.JSONException; import org.to2mbn.authlibinjector.internal.org.json.simple.JSONObject;
import org.to2mbn.authlibinjector.internal.org.json.JSONObject; import org.to2mbn.authlibinjector.util.JsonUtils;
import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.NanoHTTPD.Response.Status; import fi.iki.elonen.NanoHTTPD.Response.Status;
@ -53,7 +60,7 @@ public class DeprecatedApiHttpd extends NanoHTTPD {
.flatMap(uuid -> queryCharacterProperty(uuid, "textures")) .flatMap(uuid -> queryCharacterProperty(uuid, "textures"))
.map(encoded -> asString(Base64.getDecoder().decode(encoded))) .map(encoded -> asString(Base64.getDecoder().decode(encoded)))
.flatMap(texturesPayload -> obtainTextureUrl(texturesPayload, "SKIN")); .flatMap(texturesPayload -> obtainTextureUrl(texturesPayload, "SKIN"));
} catch (UncheckedIOException | JSONException e) { } catch (UncheckedIOException e) {
info("[httpd] unable to fetch skin for {0}: {1}", username, e); info("[httpd] unable to fetch skin for {0}: {1}", username, e);
return of(newFixedLengthResponse(Status.INTERNAL_ERROR, null, null)); return of(newFixedLengthResponse(Status.INTERNAL_ERROR, null, null));
} }
@ -77,29 +84,30 @@ public class DeprecatedApiHttpd extends NanoHTTPD {
} }
} }
private Optional<String> queryCharacterUUID(String username) throws UncheckedIOException, JSONException { private Optional<String> queryCharacterUUID(String username) throws UncheckedIOException {
String responseText; String responseText;
try { try {
responseText = asString(postURL( responseText = asString(postURL(
configuration.getApiRoot() + "api/profiles/minecraft", configuration.getApiRoot() + "api/profiles/minecraft",
CONTENT_TYPE_JSON, CONTENT_TYPE_JSON,
new JSONArray(new String[] { username }) JSONArray.toJSONString(singleton(username)).getBytes(UTF_8)));
.toString().getBytes(UTF_8)));
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
debug("[httpd] query uuid of username {0}, response: {1}", username, responseText); debug("[httpd] query uuid of username {0}, response: {1}", username, responseText);
JSONArray response = new JSONArray(responseText);
if (response.length() == 0) { JSONArray response = asArray(parseJson(responseText));
if (response.size() == 0) {
return empty(); return empty();
} else if (response.length() == 1) { } else if (response.size() == 1) {
return of(response.getJSONObject(0).getString("id")); JSONObject profile = asObject(response.get(0));
return of(asString(profile.get("id")));
} else { } else {
throw new JSONException("Unexpected response length"); throw newUncheckedIOException("Invalid JSON: Unexpected response length");
} }
} }
private Optional<String> queryCharacterProperty(String uuid, String property) throws UncheckedIOException, JSONException { private Optional<String> queryCharacterProperty(String uuid, String propertyName) throws UncheckedIOException {
String responseText; String responseText;
try { try {
responseText = asString(getURL( responseText = asString(getURL(
@ -112,23 +120,24 @@ public class DeprecatedApiHttpd extends NanoHTTPD {
return empty(); return empty();
} }
debug("[httpd] query profile of {0}, response: {1}", uuid, responseText); debug("[httpd] query profile of {0}, response: {1}", uuid, responseText);
JSONObject response = new JSONObject(responseText);
for (Object element_ : response.getJSONArray("properties")) { JSONObject response = asObject(parseJson(responseText));
JSONObject element = (JSONObject) element_; return asArray(response.get("properties")).stream()
if (property.equals(element.getString("name"))) { .map(JsonUtils::asObject)
return of(element.getString("value")); .filter(property -> asString(property.get("name")).equals(propertyName))
} .findFirst()
} .map(property -> asString(property.get("value")));
return empty();
} }
private Optional<String> obtainTextureUrl(String texturesPayload, String textureType) throws JSONException { private Optional<String> obtainTextureUrl(String texturesPayload, String textureType) throws UncheckedIOException {
JSONObject textures = new JSONObject(texturesPayload).getJSONObject("textures"); JSONObject payload = asObject(parseJson(texturesPayload));
if (textures.has(textureType)) { JSONObject textures = asObject(payload.get("textures"));
return of(textures.getJSONObject(textureType).getString("url"));
} else { return ofNullable(textures.get(textureType))
return empty(); .map(JsonUtils::asObject)
} .map(it -> ofNullable(it.get("url"))
.map(JsonUtils::asString)
.orElseThrow(() -> newUncheckedIOException("Invalid JSON: missing texture url")));
} }
} }

View File

@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
@ -54,6 +55,14 @@ public final class IOUtils {
.replace("\r", ""); .replace("\r", "");
} }
public static UncheckedIOException newUncheckedIOException(String message) throws UncheckedIOException {
return new UncheckedIOException(new IOException(message));
}
public static UncheckedIOException newUncheckedIOException(String message, Throwable cause) throws UncheckedIOException {
return new UncheckedIOException(new IOException(message, cause));
}
private IOUtils() {} private IOUtils() {}
} }

View File

@ -0,0 +1,42 @@
package org.to2mbn.authlibinjector.util;
import static org.to2mbn.authlibinjector.util.IOUtils.newUncheckedIOException;
import java.io.UncheckedIOException;
import org.to2mbn.authlibinjector.internal.org.json.simple.JSONArray;
import org.to2mbn.authlibinjector.internal.org.json.simple.JSONObject;
import org.to2mbn.authlibinjector.internal.org.json.simple.JSONValue;
import org.to2mbn.authlibinjector.internal.org.json.simple.parser.ParseException;
public final class JsonUtils {
public static Object parseJson(String jsonText) throws UncheckedIOException {
try {
return JSONValue.parse(jsonText);
} catch (ParseException e) {
throw newUncheckedIOException("Invalid JSON", e);
}
}
public static JSONObject asObject(Object json) throws UncheckedIOException {
return assertJson(json, JSONObject.class, "an object");
}
public static JSONArray asArray(Object json) throws UncheckedIOException {
return assertJson(json, JSONArray.class, "an array");
}
public static String asString(Object json) throws UncheckedIOException {
return assertJson(json, String.class, "a string");
}
@SuppressWarnings("unchecked")
private static <T> T assertJson(Object json, Class<T> type, String message) {
if (type.isInstance(json)) {
return (T) json;
}
throw newUncheckedIOException("Invalid JSON: not " + message + ": " + json);
}
private JsonUtils() {}
}

View File

@ -1,6 +1,8 @@
package org.to2mbn.authlibinjector.util; package org.to2mbn.authlibinjector.util;
import static org.to2mbn.authlibinjector.util.IOUtils.newUncheckedIOException;
import static org.to2mbn.authlibinjector.util.IOUtils.removeNewLines; import static org.to2mbn.authlibinjector.util.IOUtils.removeNewLines;
import java.io.UncheckedIOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.PublicKey; import java.security.PublicKey;
@ -9,7 +11,7 @@ import java.util.Base64;
public final class KeyUtils { public final class KeyUtils {
public static byte[] decodePublicKey(String pem) throws IllegalArgumentException { public static byte[] decodePEMPublicKey(String pem) throws IllegalArgumentException {
pem = removeNewLines(pem); pem = removeNewLines(pem);
final String header = "-----BEGIN PUBLIC KEY-----"; final String header = "-----BEGIN PUBLIC KEY-----";
final String end = "-----END PUBLIC KEY-----"; final String end = "-----END PUBLIC KEY-----";
@ -21,10 +23,18 @@ public final class KeyUtils {
} }
} }
public static PublicKey loadX509PublicKey(byte[] encodedKey) throws GeneralSecurityException { public static PublicKey parseX509PublicKey(byte[] encodedKey) throws GeneralSecurityException {
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encodedKey)); return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encodedKey));
} }
public static PublicKey parseSignaturePublicKey(String pem) throws UncheckedIOException {
try {
return parseX509PublicKey(decodePEMPublicKey(pem));
} catch (IllegalArgumentException | GeneralSecurityException e) {
throw newUncheckedIOException("Bad signature public key", e);
}
}
private KeyUtils() {} private KeyUtils() {}
} }

View File

@ -1,7 +1,7 @@
package org.to2mbn.authlibinjector.test; package org.to2mbn.authlibinjector.test;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.to2mbn.authlibinjector.util.KeyUtils.decodePublicKey; import static org.to2mbn.authlibinjector.util.KeyUtils.decodePEMPublicKey;
import org.junit.Test; import org.junit.Test;
public class KeyUtilsTest { public class KeyUtilsTest {
@ -9,23 +9,23 @@ public class KeyUtilsTest {
@Test @Test
public void testDecodePublicKey1() { public void testDecodePublicKey1() {
assertArrayEquals(new byte[] { 127, 127, 127, 127 }, assertArrayEquals(new byte[] { 127, 127, 127, 127 },
decodePublicKey("-----BEGIN PUBLIC KEY-----f39/fw==-----END PUBLIC KEY-----")); decodePEMPublicKey("-----BEGIN PUBLIC KEY-----f39/fw==-----END PUBLIC KEY-----"));
} }
@Test @Test
public void testDecodePublicKey2() { public void testDecodePublicKey2() {
assertArrayEquals(new byte[] { 127, 127, 127, 127 }, assertArrayEquals(new byte[] { 127, 127, 127, 127 },
decodePublicKey("-----BEGIN PUBLIC KEY-----\nf\n39/fw==\n-----END PUBLIC KEY-----\n")); decodePEMPublicKey("-----BEGIN PUBLIC KEY-----\nf\n39/fw==\n-----END PUBLIC KEY-----\n"));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testDecodePublicKey3() { public void testDecodePublicKey3() {
decodePublicKey("-----BEGIN PUBLIC KEY----- f39/fw== -----END PUBLIC KEY-----"); decodePEMPublicKey("-----BEGIN PUBLIC KEY----- f39/fw== -----END PUBLIC KEY-----");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testDecodePublicKey4() { public void testDecodePublicKey4() {
decodePublicKey("-----BEGIN PUBLIC KEY-----f39/fw==-----END NOT A PUBLIC KEY-----"); decodePEMPublicKey("-----BEGIN PUBLIC KEY-----f39/fw==-----END NOT A PUBLIC KEY-----");
} }
} }