Fix error handling and fix skin server response handling

This commit is contained in:
Sollace 2022-11-25 18:42:05 +00:00
parent 89e24f3047
commit 9b90e9bfb6
12 changed files with 113 additions and 414 deletions

View file

@ -51,13 +51,7 @@ import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -123,7 +117,14 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
for (SkinServer server : skinServers) { for (SkinServer server : skinServers) {
try { try {
if (!server.getFeatures().contains(Feature.SYNTHETIC)) { if (!server.getFeatures().contains(Feature.SYNTHETIC)) {
server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent); server.loadProfileData(profile).getTextures().forEach((k, v) -> {
try {
Type t = Type.valueOf(k.toUpperCase(Locale.ROOT));
if (t != null) {
textureMap.put(t, v);
}
} catch (Exception e) {}
});
if (textureMap.size() == Type.values().length) { if (textureMap.size() == Type.values().length) {
break; break;
} }

View file

@ -232,7 +232,7 @@ public class SkinUploader implements Closeable {
if (throwable instanceof AuthenticationUnavailableException) { if (throwable instanceof AuthenticationUnavailableException) {
offline = true; offline = true;
} else if (throwable instanceof InvalidCredentialsException) { } else if (throwable instanceof InvalidCredentialsException) {
setError("hdskins.error.session"); setError("Invalid session: Please try restarting Minecraft");
} else if (throwable instanceof AuthenticationException) { } else if (throwable instanceof AuthenticationException) {
throttlingNeck = true; throttlingNeck = true;
} else if (throwable instanceof HttpException) { } else if (throwable instanceof HttpException) {

View file

@ -2,13 +2,14 @@ package com.voxelmodpack.hdskins.resources;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.voxelmodpack.hdskins.resources.texture.ISkinAvailableCallback; import com.voxelmodpack.hdskins.resources.texture.ISkinAvailableCallback;
import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD; import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD;
import com.voxelmodpack.hdskins.server.TexturePayload;
import net.minecraft.client.resources.SkinManager; import net.minecraft.client.resources.SkinManager;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -19,19 +20,22 @@ import javax.annotation.Nullable;
*/ */
public class PreviewTextureManager { public class PreviewTextureManager {
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures; private final Map<String, MinecraftProfileTexture> textures;
public PreviewTextureManager(MinecraftTexturesPayload payload) { public PreviewTextureManager(TexturePayload payload) {
this.textures = payload.getTextures(); this.textures = payload.getTextures();
} }
@Nullable @Nullable
public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def, @Nullable SkinManager.SkinAvailableCallback callback) { public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def, @Nullable SkinManager.SkinAvailableCallback callback) {
if (!textures.containsKey(type)) {
String key = type.name().toLowerCase(Locale.ROOT);
if (!textures.containsKey(key)) {
return null; return null;
} }
MinecraftProfileTexture texture = textures.get(type); MinecraftProfileTexture texture = textures.get(key);
ISkinAvailableCallback buff = new ImageBufferDownloadHD(type, () -> { ISkinAvailableCallback buff = new ImageBufferDownloadHD(type, () -> {
if (callback != null) { if (callback != null) {
callback.skinAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap())); callback.skinAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap()));

View file

@ -1,222 +0,0 @@
package com.voxelmodpack.hdskins.resources.texture;
import com.voxelmodpack.hdskins.util.MoreHttpResponses;
import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
/**
* @deprecated
* Do not use. This will be removed in a later update.
* Now that legacy includes the etag in the hash, it is no longer required to save it to disk.
*/
@Deprecated
public class ThreadDownloadImageETag extends SimpleTexture implements IBufferedTexture {
private static final Logger LOGGER = LogManager.getLogger();
private static final AtomicInteger THREAD_ID = new AtomicInteger(0);
private static CloseableHttpClient client = HttpClients.createSystem();
@Nonnull
private final Path cacheFile;
private final Path eTagFile;
private final String imageUrl;
@Nullable
private final IImageBuffer imageBuffer;
@Nullable
private BufferedImage bufferedImage;
@Nullable
private Thread imageThread;
private boolean textureUploaded;
public ThreadDownloadImageETag(@Nonnull File cacheFileIn, String imageUrlIn, ResourceLocation defLocation, @Nullable IImageBuffer imageBufferIn) {
super(defLocation);
this.cacheFile = cacheFileIn.toPath();
this.eTagFile = cacheFile.resolveSibling(cacheFile.getFileName() + ".etag");
this.imageUrl = imageUrlIn;
this.imageBuffer = imageBufferIn;
}
private void checkTextureUploaded() {
if (!this.textureUploaded) {
if (this.bufferedImage != null) {
if (this.textureLocation != null) {
this.deleteGlTexture();
}
TextureUtil.uploadTextureImage(super.getGlTextureId(), this.bufferedImage);
this.textureUploaded = true;
}
}
}
@Override
public int getGlTextureId() {
this.checkTextureUploaded();
return super.getGlTextureId();
}
private void setBufferedImage(@Nonnull BufferedImage bufferedImageIn) {
this.bufferedImage = bufferedImageIn;
if (this.imageBuffer != null) {
this.imageBuffer.skinAvailable();
}
}
@Override
@Nullable
public BufferedImage getBufferedImage() {
return bufferedImage;
}
@Override
public void loadTexture(IResourceManager resourceManager) throws IOException {
if (this.bufferedImage == null && this.textureLocation != null) {
super.loadTexture(resourceManager);
}
if (this.imageThread == null) {
this.imageThread = new Thread(this::loadTexture, "Texture Downloader #" + THREAD_ID.incrementAndGet());
this.imageThread.setDaemon(true);
this.imageThread.start();
}
}
private void loadTexture() {
switch (checkLocalCache()) {
case GONE:
clearCache();
break;
case OK:
case NOPE:
LOGGER.debug("Loading http texture from local cache ({})", cacheFile);
try {
// e-tag check passed. Load the local file
setLocalCache();
break;
} catch (IOException e) {
// Nope. Local cache is corrupt. Re-download it.
// fallthrough to load from network
LOGGER.error("Couldn't load skin {}", cacheFile, e);
}
case OUTDATED:
loadTextureFromServer();
}
}
private void setLocalCache() throws IOException {
if (Files.isRegularFile(cacheFile)) {
try (InputStream in = Files.newInputStream(cacheFile)) {
BufferedImage image = ImageIO.read(in);
if (imageBuffer != null) {
image = imageBuffer.parseUserSkin(image);
}
setBufferedImage(image);
}
}
}
private void clearCache() {
try {
Files.deleteIfExists(this.cacheFile);
Files.deleteIfExists(this.eTagFile);
} catch (IOException e) {
// ignore
}
}
private enum State {
OUTDATED,
GONE,
NOPE,
OK
}
private State checkLocalCache() {
try (CloseableHttpResponse response = client.execute(new HttpHead(imageUrl))) {
int code = response.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_NOT_FOUND) {
return State.GONE;
}
if (code != HttpStatus.SC_OK) {
return State.NOPE;
}
return checkETag(response) ? State.OK : State.OUTDATED;
} catch (IOException e) {
LOGGER.error("Couldn't load skin {} ", imageUrl, e);
return State.NOPE;
}
}
private boolean checkETag(HttpResponse response) {
try {
if (Files.isRegularFile(cacheFile)) {
String localETag = Files.lines(eTagFile).limit(1).findFirst().orElse("");
Header remoteETag = response.getFirstHeader(HttpHeaders.ETAG);
// true if no remote etag or does match
return remoteETag == null || localETag.equals(remoteETag.getValue());
}
return false;
} catch (IOException e) {
// it failed, so re-fetch.
return false;
}
}
private void loadTextureFromServer() {
LOGGER.debug("Downloading http texture from {} to {}", imageUrl, cacheFile);
try (MoreHttpResponses resp = MoreHttpResponses.execute(client, new HttpGet(imageUrl))) {
if (resp.ok()) {
// write the image to disk
Files.createDirectories(cacheFile.getParent());
Files.copy(resp.inputStream(), cacheFile);
try (InputStream in = Files.newInputStream(cacheFile)) {
BufferedImage bufferedimage = ImageIO.read(in);
// maybe write the etag to disk
Header eTag = resp.response().getFirstHeader(HttpHeaders.ETAG);
if (eTag != null) {
Files.write(eTagFile, Collections.singleton(eTag.getValue()));
}
if (imageBuffer != null) {
bufferedimage = imageBuffer.parseUserSkin(bufferedimage);
}
setBufferedImage(bufferedimage);
}
}
} catch (Exception exception) {
LOGGER.error("Couldn\'t download http texture", exception);
}
}
}

View file

@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.gui.Feature; import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
@ -43,13 +42,13 @@ public class BethlehemSkinServer implements SkinServer {
} }
@Override @Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException { public TexturePayload loadProfileData(GameProfile profile) throws IOException {
try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) { try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) {
if (!response.ok()) { if (!response.ok()) {
throw new HttpException(response.response()); throw new HttpException(response.response());
} }
return response.unwrapAsJson(MinecraftTexturesPayload.class); return response.requireOk().json(TexturePayload.class, "Invalid texture payload");
} }
} }
@ -66,9 +65,7 @@ public class BethlehemSkinServer implements SkinServer {
} }
try (MoreHttpResponses response = client.send()) { try (MoreHttpResponses response = client.send()) {
if (!response.ok()) { response.requireOk();
throw response.exception();
}
} }
} }

View file

@ -8,13 +8,11 @@ import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.gui.Feature; import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
import com.voxelmodpack.hdskins.util.MoreHttpResponses; import com.voxelmodpack.hdskins.util.MoreHttpResponses;
import com.voxelmodpack.hdskins.util.NetClient; import com.voxelmodpack.hdskins.util.NetClient;
import com.voxelmodpack.hdskins.util.TexturesPayloadBuilder;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header; import org.apache.http.Header;
@ -53,39 +51,39 @@ public class LegacySkinServer implements SkinServer {
} }
@Override @Override
public MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException { public TexturePayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID); SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID);
if (Strings.isNullOrEmpty(gateway)) { if (Strings.isNullOrEmpty(gateway)) {
throw gatewayUnsupported(); throw gatewayUnsupported();
} }
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class); Map<String, MinecraftProfileTexture> map = new HashMap<>();
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) { for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null)); map.put(type.name(), new MinecraftProfileTexture(getPath(gateway, type, profile), null));
} }
return TexturesPayloadBuilder.createTexturesPayload(profile, map); return new TexturePayload(profile, map);
} }
@Override @Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException { public TexturePayload loadProfileData(GameProfile profile) throws IOException {
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder(); ImmutableMap.Builder<String, MinecraftProfileTexture> builder = ImmutableMap.builder();
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) { for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
String url = getPath(address, type, profile); String url = getPath(address, type, profile);
try { try {
builder.put(type, loadProfileTexture(profile, url)); builder.put(type.name(), loadProfileTexture(profile, url));
} catch (IOException e) { } catch (IOException e) {
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e); logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
} }
} }
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build(); Map<String, MinecraftProfileTexture> map = builder.build();
if (map.isEmpty()) { if (map.isEmpty()) {
throw new HttpException(String.format("No textures found for %s at %s", profile, this.address), 404, null); throw new HttpException(String.format("No textures found for %s at %s", profile, this.address), 404, null);
} }
return TexturesPayloadBuilder.createTexturesPayload(profile, map); return new TexturePayload(profile, map);
} }
private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException { private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException {
@ -125,7 +123,7 @@ public class LegacySkinServer implements SkinServer {
} }
MoreHttpResponses resp = client.send(); MoreHttpResponses resp = client.send();
String response = resp.text(); String response = resp.reader().readLine();
if (response.startsWith("ERROR: ")) { if (response.startsWith("ERROR: ")) {
response = response.substring(7); response = response.substring(7);

View file

@ -3,7 +3,6 @@ package com.voxelmodpack.hdskins.server;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mumfrey.liteloader.modconfig.Exposable; import com.mumfrey.liteloader.modconfig.Exposable;
import com.voxelmodpack.hdskins.gui.Feature; import com.voxelmodpack.hdskins.gui.Feature;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -29,7 +28,7 @@ public interface SkinServer extends Exposable {
* *
* @throws IOException If any authentication or network error occurs. * @throws IOException If any authentication or network error occurs.
*/ */
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException; TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException;
/** /**
* Synchronously uploads a skin to this server. * Synchronously uploads a skin to this server.
@ -51,7 +50,7 @@ public interface SkinServer extends Exposable {
* @throws AuthenticationException * @throws AuthenticationException
* @throws IOException * @throws IOException
*/ */
default MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException { default TexturePayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
return loadProfileData(profile); return loadProfileData(profile);
} }

View file

@ -11,7 +11,6 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import com.voxelmodpack.hdskins.HDSkinManager;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Type; import java.lang.reflect.Type;

View file

@ -6,7 +6,6 @@ import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.gui.Feature; import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
@ -54,14 +53,9 @@ public class ValhallaSkinServer implements SkinServer {
} }
@Override @Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException { public TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, new HttpGet(getTexturesURI(profile)))) { try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, new HttpGet(getTexturesURI(profile)))) {
return response.requireOk().json(TexturePayload.class, "Invalid texture payload");
if (response.ok()) {
return response.unwrapAsJson(MinecraftTexturesPayload.class);
}
throw new HttpException(response.response());
} }
} }
@ -131,9 +125,7 @@ public class ValhallaSkinServer implements SkinServer {
private void upload(HttpUriRequest request) throws IOException { private void upload(HttpUriRequest request) throws IOException {
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, request)) { try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, request)) {
if (!response.ok()) { response.requireOk();
throw response.exception();
}
} }
} }
@ -163,7 +155,7 @@ public class ValhallaSkinServer implements SkinServer {
.setUri(getHandshakeURI()) .setUri(getHandshakeURI())
.addParameter("name", name) .addParameter("name", name)
.build())) { .build())) {
return resp.unwrapAsJson(AuthHandshake.class); return resp.requireOk().json(AuthHandshake.class, "Invalid handshake response");
} }
} }
@ -173,7 +165,7 @@ public class ValhallaSkinServer implements SkinServer {
.addParameter("name", name) .addParameter("name", name)
.addParameter("verifyToken", String.valueOf(verifyToken)) .addParameter("verifyToken", String.valueOf(verifyToken))
.build())) { .build())) {
return resp.unwrapAsJson(AuthResponse.class); return resp.requireOk().json(AuthResponse.class, "Invalid auth response");
} }
} }

View file

@ -15,7 +15,6 @@ import com.google.gson.Gson;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.*; import com.mojang.authlib.minecraft.*;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.gui.Feature; import com.voxelmodpack.hdskins.gui.Feature;
@ -53,9 +52,7 @@ public class YggdrasilSkinServer implements SkinServer {
} }
@Override @Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException { public TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = new HashMap<>();
Minecraft client = Minecraft.getMinecraft(); Minecraft client = Minecraft.getMinecraft();
MinecraftSessionService session = client.getSessionService(); MinecraftSessionService session = client.getSessionService();
@ -68,13 +65,16 @@ public class YggdrasilSkinServer implements SkinServer {
} }
profile = newProfile; profile = newProfile;
Map<String, MinecraftProfileTexture> textures = new HashMap<>();
try { try {
textures.putAll(session.getTextures(profile, requireSecure)); session.getTextures(profile, requireSecure).forEach((k, v) -> {
textures.put(k.name(), v);
});
} catch (InsecureTextureException e) { } catch (InsecureTextureException e) {
HDSkinManager.logger.error(e); HDSkinManager.logger.error(e);
} }
return TexturesPayloadBuilder.createTexturesPayload(profile, textures); return new TexturePayload(profile, textures);
} }
@Override @Override

View file

@ -1,10 +1,10 @@
package com.voxelmodpack.hdskins.util; package com.voxelmodpack.hdskins.util;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.gson.*; import com.google.gson.*;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.server.HttpException;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
@ -17,12 +17,9 @@ import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Stream;
/** /**
* Utility class for getting different response types from a http response. * Utility class for getting different response types from a http response.
@ -33,89 +30,6 @@ public interface MoreHttpResponses extends AutoCloseable {
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) .registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
.create(); .create();
CloseableHttpResponse response();
default boolean ok() {
return responseCode() < HttpStatus.SC_MULTIPLE_CHOICES;
}
default boolean json() {
return "application/json".contentEquals(contentType().getMimeType());
}
default int responseCode() {
return response().getStatusLine().getStatusCode();
}
default Optional<HttpEntity> entity() {
return Optional.ofNullable(response().getEntity());
}
default ContentType contentType() {
return entity()
.map(ContentType::get)
.orElse(ContentType.DEFAULT_TEXT);
}
default InputStream inputStream() throws IOException {
return response().getEntity().getContent();
}
default BufferedReader reader() throws IOException {
return new BufferedReader(new InputStreamReader(inputStream(), StandardCharsets.UTF_8));
}
default byte[] bytes() throws IOException {
try (InputStream input = inputStream()) {
return ByteStreams.toByteArray(input);
}
}
default String text() throws IOException {
try (BufferedReader reader = reader()) {
return CharStreams.toString(reader);
}
}
default Stream<String> lines() throws IOException {
try (BufferedReader reader = reader()) {
return reader.lines();
}
}
default <T> T json(Class<T> type, String errorMessage) throws IOException {
return json((Type)type, errorMessage);
}
default <T> T json(Type type, String errorMessage) throws IOException {
if (!json()) {
String text = text();
HDSkinManager.logger.error(errorMessage, text);
throw new IOException(text);
}
try (BufferedReader reader = reader()) {
return GSON.fromJson(reader, type);
}
}
default <T> T unwrapAsJson(Type type) throws IOException {
if (ok()) {
return json(type, "Server returned a non-json response!");
}
throw exception();
}
default IOException exception() throws IOException {
return new IOException(json(JsonObject.class, "Server error wasn't in json: {}").get("message").getAsString());
}
@Override
default void close() throws IOException {
response().close();
}
static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException { static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException {
CloseableHttpResponse response = client.execute(request); CloseableHttpResponse response = client.execute(request);
return () -> response; return () -> response;
@ -128,4 +42,71 @@ public interface MoreHttpResponses extends AutoCloseable {
) )
.toArray(NameValuePair[]::new); .toArray(NameValuePair[]::new);
} }
CloseableHttpResponse response();
default boolean contentTypeMatches(String contentType) {
return contentType.contentEquals(entity()
.map(ContentType::get)
.orElse(ContentType.DEFAULT_TEXT)
.getMimeType()
);
}
default int responseCode() {
return response().getStatusLine().getStatusCode();
}
default Optional<HttpEntity> entity() {
return Optional.ofNullable(response().getEntity());
}
default BufferedReader reader() throws IOException {
return new BufferedReader(new InputStreamReader(response().getEntity().getContent(), StandardCharsets.UTF_8));
}
default String text() throws IOException {
try (BufferedReader reader = reader()) {
return CharStreams.toString(reader);
}
}
default <T> T json(Class<T> type, String errorMessage) throws IOException {
if (!contentTypeMatches("application/json")) {
String text = text();
HDSkinManager.logger.error(errorMessage, text);
throw new HttpException(text, responseCode(), null);
}
String text = text();
T t = GSON.fromJson(text, type);
if (t == null) {
throw new HttpException(errorMessage + "\n " + text, responseCode(), null);
}
return t;
}
default boolean ok() {
return responseCode() < HttpStatus.SC_MULTIPLE_CHOICES;
}
default MoreHttpResponses requireOk() throws IOException {
if (!ok()) {
JsonObject json = json(JsonObject.class, "Server did not respond correctly. Status Code " + responseCode());
if (json.has("message")) {
throw new HttpException(json.get("message").getAsString(), responseCode(), null);
} else {
throw new HttpException(json.toString(), responseCode(), null);
}
}
return this;
}
@Override
default void close() throws IOException {
response().close();
}
} }

View file

@ -1,50 +0,0 @@
package com.voxelmodpack.hdskins.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Use this to build a {@link MinecraftTexturesPayload} object. This is
* required because it has no useful constructor. This uses reflection
* via Gson to create a new instance and populate the fields.
*/
@SuppressWarnings("unused")
public class TexturesPayloadBuilder {
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
public static MinecraftTexturesPayload createTexturesPayload(GameProfile profile, Map<Type, MinecraftProfileTexture> textures) {
// This worked fine as is before I started using sub-classes.
MinecraftTexturesPayload payload = gson.fromJson(gson.toJson(new TexturesPayloadBuilder(profile)), MinecraftTexturesPayload.class);
payload.getTextures().putAll(textures);
return payload;
}
private long timestamp;
private UUID profileId;
private String profileName;
private boolean isPublic;
private Map<Type, MinecraftProfileTexture> textures;
private TexturesPayloadBuilder(GameProfile profile) {
profileId = profile.getId();
profileName = profile.getName();
timestamp = System.currentTimeMillis();
isPublic = true;
this.textures = new HashMap<>();
}
}