diff --git a/.gitignore b/.gitignore index 110682f6..ef45fb00 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ bin/ doc/ logs/ +.run/ run/ node_modules/ diff --git a/BlueMapCommon/BlueMapVue b/BlueMapCommon/BlueMapVue index f3dc8d5d..4bef101c 160000 --- a/BlueMapCommon/BlueMapVue +++ b/BlueMapCommon/BlueMapVue @@ -1 +1 @@ -Subproject commit f3dc8d5dd478475cf8a1f0f1b166843821109ad2 +Subproject commit 4bef101c6ee6ddef23049287cc1a3736d954979e diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java index 0222ae68..939f9328 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java @@ -31,7 +31,6 @@ import de.bluecolored.bluemap.api.marker.Marker; import ninja.leaping.configurate.ConfigurationNode; -import java.util.Objects; import java.util.Optional; public abstract class MarkerImpl implements Marker { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index 223e321f..c562eb94 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -24,24 +24,7 @@ */ package de.bluecolored.bluemap.common.plugin; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import de.bluecolored.bluemap.common.BlueMapService; -import de.bluecolored.bluemap.common.InterruptableReentrantLock; -import de.bluecolored.bluemap.common.MapType; -import de.bluecolored.bluemap.common.MissingResourcesException; -import de.bluecolored.bluemap.common.RenderManager; +import de.bluecolored.bluemap.common.*; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; @@ -59,6 +42,14 @@ import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.world.World; +import java.io.*; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + public class Plugin { public static final String PLUGIN_ID = "bluemap"; @@ -207,7 +198,10 @@ public void load() throws IOException, ParseResourceException { //start skin updater if (pluginConfig.isLiveUpdatesEnabled()) { - this.skinUpdater = new PlayerSkinUpdater(new File(renderConfig.getWebRoot(), "assets" + File.separator + "playerheads")); + this.skinUpdater = new PlayerSkinUpdater( + new File(renderConfig.getWebRoot(), "assets" + File.separator + "playerheads"), + new File(renderConfig.getWebRoot(), "assets" + File.separator + "steve.png") + ); serverInterface.registerListener(skinUpdater); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java index 386f47d0..836b3230 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkin.java @@ -24,7 +24,13 @@ */ package de.bluecolored.bluemap.common.plugin.skins; -import java.awt.Graphics2D; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import de.bluecolored.bluemap.core.logger.Logger; + +import javax.imageio.ImageIO; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -33,19 +39,7 @@ import java.net.URL; import java.util.Base64; import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.imageio.ImageIO; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; - -import de.bluecolored.bluemap.core.logger.Logger; +import java.util.concurrent.*; public class PlayerSkin { @@ -57,23 +51,32 @@ public PlayerSkin(UUID uuid) { this.lastUpdate = -1; } - public void update(File storageFolder) { + public void update(File storageFolder, File fallback) { long now = System.currentTimeMillis(); if (lastUpdate > 0 && lastUpdate + 600000 > now) return; // only update if skin is older than 10 minutes lastUpdate = now; new Thread(() -> { + BufferedImage head = null; + try { Future futureSkin = loadSkin(); BufferedImage skin = futureSkin.get(10, TimeUnit.SECONDS); - BufferedImage head = createHead(skin); - ImageIO.write(head, "png", new File(storageFolder, uuid.toString() + ".png")); + head = createHead(skin); } catch (ExecutionException | TimeoutException e) { Logger.global.logDebug("Failed to load player-skin from mojang-servers: " + e); + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + return; + } + + try { + if (head == null) head = ImageIO.read(fallback); + ImageIO.write(head, "png", new File(storageFolder, uuid.toString() + ".png")); } catch (IOException e) { Logger.global.logError("Failed to write player-head image!", e); - } catch (InterruptedException ignore) { Thread.currentThread().interrupt(); } + } }).start(); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java index 2332a3be..9e2d430d 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/skins/PlayerSkinUpdater.java @@ -24,24 +24,28 @@ */ package de.bluecolored.bluemap.common.plugin.skins; +import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; +import org.apache.commons.io.FileUtils; + import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; - public class PlayerSkinUpdater implements ServerEventListener { private File storageFolder; + private File defaultSkin; - private Map skins; + private final Map skins; - public PlayerSkinUpdater(File storageFolder) { + public PlayerSkinUpdater(File storageFolder, File defaultSkin) throws IOException { this.storageFolder = storageFolder; + this.defaultSkin = defaultSkin; this.skins = new ConcurrentHashMap<>(); - - this.storageFolder.mkdirs(); + + FileUtils.forceMkdir(this.storageFolder); } public void updateSkin(UUID playerUuid) { @@ -52,12 +56,28 @@ public void updateSkin(UUID playerUuid) { skins.put(playerUuid, skin); } - skin.update(storageFolder); + skin.update(storageFolder, defaultSkin); } @Override public void onPlayerJoin(UUID playerUuid) { updateSkin(playerUuid); } - + + public File getStorageFolder() { + return storageFolder; + } + + public void setStorageFolder(File storageFolder) { + this.storageFolder = storageFolder; + } + + public File getDefaultSkin() { + return defaultSkin; + } + + public void setDefaultSkin(File defaultSkin) { + this.defaultSkin = defaultSkin; + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java index cd2f9a81..f26f447c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/FileRequestHandler.java @@ -24,12 +24,14 @@ */ package de.bluecolored.bluemap.core.web; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; +import de.bluecolored.bluemap.core.webserver.HttpRequest; +import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; +import de.bluecolored.bluemap.core.webserver.HttpResponse; +import de.bluecolored.bluemap.core.webserver.HttpStatusCode; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.io.*; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.GregorianCalendar; @@ -40,26 +42,22 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.time.DateFormatUtils; - -import de.bluecolored.bluemap.core.webserver.HttpRequest; -import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; -import de.bluecolored.bluemap.core.webserver.HttpResponse; -import de.bluecolored.bluemap.core.webserver.HttpStatusCode; - public class FileRequestHandler implements HttpRequestHandler { private static final long DEFLATE_MIN_SIZE = 10L * 1024L; private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L; private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L; - private Path webRoot; - private String serverName; + private final Path webRoot; + private final String serverName; + + private final File emptyTileFile; public FileRequestHandler(Path webRoot, String serverName) { - this.webRoot = webRoot; + this.webRoot = webRoot.normalize(); this.serverName = serverName; + + this.emptyTileFile = webRoot.resolve("assets").resolve("emptyTile.json").toFile(); } @Override @@ -84,10 +82,10 @@ public HttpResponse handle(HttpRequest request) { private HttpResponse generateResponse(HttpRequest request) { String path = request.getPath(); - //normalize path + // normalize path if (path.startsWith("/")) path = path.substring(1); if (path.endsWith("/")) path = path.substring(0, path.length() - 1); - + Path filePath = webRoot; try { filePath = webRoot.resolve(path); @@ -95,12 +93,12 @@ private HttpResponse generateResponse(HttpRequest request) { return new HttpResponse(HttpStatusCode.NOT_FOUND); } - //can we use deflation? + // can we use deflation? boolean isDeflationPossible = request.getLowercaseHeader("Accept-Encoding").contains("gzip"); boolean isDeflated = false; - //check if file is in web-root - if (!filePath.normalize().startsWith(webRoot.normalize())){ + // check if file is in web-root + if (!filePath.normalize().startsWith(webRoot)){ return new HttpResponse(HttpStatusCode.FORBIDDEN); } @@ -128,7 +126,12 @@ private HttpResponse generateResponse(HttpRequest request) { isDeflated = true; } - if (!file.exists()){ + if (!file.exists() && file.toPath().startsWith(webRoot.resolve("data"))){ + file = emptyTileFile; + isDeflated = false; + } + + if (!file.exists() || file.isDirectory()) { return new HttpResponse(HttpStatusCode.NOT_FOUND); } @@ -140,12 +143,12 @@ private HttpResponse generateResponse(HttpRequest request) { } } - //check if file is still in web-root - if (!file.toPath().normalize().startsWith(webRoot.normalize())){ + // check if file is still in web-root and is not a directory + if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){ return new HttpResponse(HttpStatusCode.FORBIDDEN); } - //check modified + // check modified long lastModified = file.lastModified(); Set modStringSet = request.getHeader("If-Modified-Since"); if (!modStringSet.isEmpty()){ @@ -154,7 +157,7 @@ private HttpResponse generateResponse(HttpRequest request) { if (since + 1000 >= lastModified){ return new HttpResponse(HttpStatusCode.NOT_MODIFIED); } - } catch (IllegalArgumentException e){} + } catch (IllegalArgumentException ignored){} } //check ETag @@ -174,7 +177,7 @@ private HttpResponse generateResponse(HttpRequest request) { response.addHeader("Cache-Control", "max-age=" + TimeUnit.HOURS.toSeconds(1)); //add content type header - String filetype = file.getName().toString(); + String filetype = file.getName(); if (filetype.endsWith(".gz")) filetype = filetype.substring(0, filetype.length() - 3); int pointIndex = filetype.lastIndexOf('.'); if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);