diff --git a/api/src/main/java/com/velocitypowered/api/util/Favicon.java b/api/src/main/java/com/velocitypowered/api/util/Favicon.java index a3ce28164..9ee159991 100644 --- a/api/src/main/java/com/velocitypowered/api/util/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/util/Favicon.java @@ -1,15 +1,11 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; import java.util.Objects; -import javax.imageio.ImageIO; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -65,23 +61,17 @@ public final class Favicon { } /** - * Creates a new {@code Favicon} from the specified {@code image}. + * Creates a new {@code Favicon} from the specified {@code buffer}. * - * @param image the image to use for the favicon + * @param buffer the buffer to use for the favicon * @return the created {@link Favicon} instance + * @throws IOException if the file could not be read from the path */ - public static Favicon create(BufferedImage image) { - Preconditions.checkNotNull(image, "image"); - Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, - "Image is not 64x64 (found %sx%s)", image.getWidth(),image.getHeight()); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - ImageIO.write(image, "PNG", os); - } catch (IOException e) { - throw new AssertionError(e); + public static Favicon create(byte[] buffer) throws IOException { + if (!FaviconChecker.check(buffer)) { + throw new IllegalArgumentException("Image is not a PNG file or does not have 64x64 dimensions"); } - return new Favicon( - "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray())); + return new Favicon("data:image/png;base64," + Base64.getEncoder().encodeToString(buffer)); } /** @@ -92,12 +82,6 @@ public final class Favicon { * @throws IOException if the file could not be read from the path */ public static Favicon create(Path path) throws IOException { - try (InputStream stream = Files.newInputStream(path)) { - BufferedImage image = ImageIO.read(stream); - if (image == null) { - throw new IOException("Unable to read the image."); - } - return create(image); - } + return create(Files.readAllBytes(path)); } } diff --git a/api/src/main/java/com/velocitypowered/api/util/FaviconChecker.java b/api/src/main/java/com/velocitypowered/api/util/FaviconChecker.java new file mode 100644 index 000000000..4dcd5a374 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/util/FaviconChecker.java @@ -0,0 +1,34 @@ +package com.velocitypowered.api.util; + +import java.nio.ByteBuffer; + +class FaviconChecker { + private static final byte[] PNG_MAGIC = new byte[] { + (byte) 137, 80, 78, 71, 13, 10, 26, 10 + }; + private static final byte[] IHDR_NAME = new byte[] { + 73, 72, 68, 82 + }; + + public static boolean check(byte[] data) { + ByteBuffer buf = ByteBuffer.wrap(data); + + for (byte value : PNG_MAGIC) { + if (buf.get() != value) { + return false; + } + } + + buf.position(buf.position() + 4); + for (byte value : IHDR_NAME) { + if (buf.get() != value) { + return false; + } + } + + int width = buf.getInt(); + int height = buf.getInt(); + + return width == 64 && height == 64; + } +} diff --git a/api/src/test/java/com/velocitypowered/api/util/FaviconCheckerTest.java b/api/src/test/java/com/velocitypowered/api/util/FaviconCheckerTest.java new file mode 100644 index 000000000..b61b19323 --- /dev/null +++ b/api/src/test/java/com/velocitypowered/api/util/FaviconCheckerTest.java @@ -0,0 +1,33 @@ +package com.velocitypowered.api.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.io.Resources; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class FaviconCheckerTest { + + @Test + void handlesProperlyFormattedFavicon() throws IOException { + assertTrue(FaviconChecker.check(readFile("test_icon_dimensions_correct.png")), + "Correctly formatted favicon is not valid"); + } + + @Test + void handlesBadDimensionFavicon() throws IOException { + assertFalse(FaviconChecker.check(readFile("test_icon_dimensions_wrong.png")), + "Incorrect dimension favicon is valid?"); + } + + @Test + void handlesBadFormatFavicon() throws IOException { + assertFalse(FaviconChecker.check(readFile("test_icon_dimensions_wrong_format.jpg")), + "Incorrect format favicon is valid?"); + } + + private static byte[] readFile(String path) throws IOException { + return Resources.toByteArray(FaviconCheckerTest.class.getResource(path)); + } +} diff --git a/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_correct.png b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_correct.png new file mode 100644 index 000000000..b20eabf2f Binary files /dev/null and b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_correct.png differ diff --git a/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong.png b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong.png new file mode 100644 index 000000000..c3f0357b5 Binary files /dev/null and b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong.png differ diff --git a/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong_format.jpg b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong_format.jpg new file mode 100644 index 000000000..b86dbfcdc Binary files /dev/null and b/api/src/test/resources/com/velocitypowered/api/util/test_icon_dimensions_wrong_format.jpg differ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index ed8094cbe..85811abfd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -11,10 +11,6 @@ public class Velocity { private static final Logger logger = LogManager.getLogger(Velocity.class); static { - // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. - // How inconvenient. Force AWT to work with its head chopped off. - System.setProperty("java.awt.headless", "true"); - // By default, Netty allocates 16MiB arenas for the PooledByteBufAllocator. This is too much // memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more // sane default.