3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-20 15:00:11 +01:00

start on proper url checking (application type/size)

Dieser Commit ist enthalten in:
onebeastchris 2023-10-24 07:50:37 +02:00
Ursprung 76a62abd27
Commit 36709143c7
4 geänderte Dateien mit 95 neuen und 39 gelöschten Zeilen

Datei anzeigen

@ -50,6 +50,7 @@ import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -280,6 +281,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
if (!GameProtocol.isPre1_20_30(this.session)) {
// TODO: Proper pack checking - could be that the remote url is offline, the pack changed, or.. something?
GeyserImpl.getInstance().getLogger().warning("Received ResourcePackChunkRequestPacket for URL pack " + urlPackCodec.url());
WebUtils.checkRemotePackUrl(urlPackCodec.url());
}
}

Datei anzeigen

@ -25,6 +25,7 @@
package org.geysermc.geyser.pack.url;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.UrlPackCodec;
@ -33,25 +34,25 @@ import org.geysermc.geyser.registry.loader.ResourcePackLoader;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
public class GeyserUrlPackCodec extends UrlPackCodec {
private final String url;
private final String contentKey;
public final GeyserPathPackCodec fallback;
@Getter
private final GeyserPathPackCodec fallback;
public GeyserUrlPackCodec(String url) {
public GeyserUrlPackCodec(String url) throws IllegalArgumentException {
this(url, "");
}
public GeyserUrlPackCodec(String url, String contentKey) {
public GeyserUrlPackCodec(String url, String contentKey) throws IllegalArgumentException {
this.url = url;
this.contentKey = contentKey;
this.fallback = new GeyserPathPackCodec(getCachePath(url));
}
private static Path getCachePath(String url) {
return ResourcePackLoader.downloadPack(url);
try {
this.fallback = new GeyserPathPackCodec(ResourcePackLoader.downloadPack(url).get());
} catch (Exception e) {
throw new IllegalArgumentException("Unable to download pack from " + url, e);
}
}
@Override

Datei anzeigen

@ -26,6 +26,7 @@
package org.geysermc.geyser.registry.loader;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
@ -49,6 +50,7 @@ import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -149,7 +151,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
}
public static ResourcePack loadDownloadedPack(GeyserUrlPackCodec codec) throws IllegalArgumentException {
Path path = codec.fallback.path();
Path path = codec.getFallback().path();
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
throw new IllegalArgumentException("The url " + codec.url() + " did not provide a valid resource pack! Please check the url and try again.");
}
@ -214,11 +216,11 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
for (String url: remotePackUrls) {
try {
ResourcePack pack = new GeyserUrlPackCodec(url).create();
GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url);
ResourcePack pack = codec.create();
packMap.put(pack.manifest().header().uuid().toString(), pack);
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url));
GeyserImpl.getInstance().getLogger().error(e.getMessage());
if (GeyserImpl.getInstance().getLogger().isDebug()) {
e.printStackTrace();
}
@ -227,32 +229,39 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
return packMap;
}
public static Path downloadPack(String url) throws IllegalArgumentException {
int packHash = url.hashCode();
Path cachedPath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs").resolve(packHash + ".zip");
WebUtils.downloadFile(url, cachedPath.toString());
if (!PACK_MATCHER.matches(cachedPath)) {
throw new IllegalArgumentException("Invalid pack! Not a .zip or .mcpack file.");
}
try {
ZipFile zip = new ZipFile(cachedPath.toFile());
if (zip.stream().noneMatch(x -> x.getName().contains("manifest.json"))) {
throw new IllegalArgumentException(url + " does not contain a manifest file.");
public static CompletableFuture<@Nullable Path> downloadPack(String url) throws IllegalArgumentException {
CompletableFuture<Path> future = WebUtils.checkRemotePackUrl(url);
AtomicReference<Path> pathAtomicReference = new AtomicReference<>();
future.whenCompleteAsync((cachedPath, throwable) -> {
if (cachedPath == null || throwable != null) {
return;
}
// Check if a "manifest.json" or "pack_manifest.json" file is located directly in the zip... does not work otherwise.
// (something like MyZip.zip/manifest.json) will not, but will if it's a subfolder (MyPack.zip/MyPack/manifest.json)
if (zip.getEntry("manifest.json") != null || zip.getEntry("pack_manifest.json") != null) {
GeyserImpl.getInstance().getLogger().warning("The remote resource pack from " + url + " contains a manifest.json file at the root of the zip file. " +
"This is not supported for remote packs, and will cause Bedrock clients to fall back to request the pack from the server. " +
"Please put the pack file in a subfolder, and provide that zip in the URL.");
// Check if the pack is a .zip or .mcpack file
if (!PACK_MATCHER.matches(cachedPath)) {
throw new IllegalArgumentException("Invalid pack! Not a .zip or .mcpack file.");
}
} catch (IOException e) {
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
}
return cachedPath;
try {
ZipFile zip = new ZipFile(cachedPath.toFile());
if (zip.stream().noneMatch(x -> x.getName().contains("manifest.json"))) {
throw new IllegalArgumentException(url + " does not contain a manifest file.");
}
// Check if a "manifest.json" or "pack_manifest.json" file is located directly in the zip... does not work otherwise.
// (something like MyZip.zip/manifest.json) will not, but will if it's a subfolder (MyPack.zip/MyPack/manifest.json)
if (zip.getEntry("manifest.json") != null || zip.getEntry("pack_manifest.json") != null) {
GeyserImpl.getInstance().getLogger().warning("The remote resource pack from " + url + " contains a manifest.json file at the root of the zip file. " +
"This is not supported for remote packs, and will cause Bedrock clients to fall back to request the pack from the server. " +
"Please put the pack file in a subfolder, and provide that zip in the URL.");
}
} catch (IOException e) {
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
}
pathAtomicReference.set(cachedPath);
});
return future.thenApplyAsync(x -> pathAtomicReference.get());
}
}

Datei anzeigen

@ -26,20 +26,23 @@
package org.geysermc.geyser.util;
import com.fasterxml.jackson.databind.JsonNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import javax.annotation.Nullable;
import javax.naming.directory.Attribute;
import javax.naming.directory.InitialDirContext;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class WebUtils {
@ -95,6 +98,47 @@ public class WebUtils {
}
}
/**
* Checks a remote pack URL to see if it is valid
* If it is, it will download the pack file and return a path to it
*
* @param url The URL to check
* @return Path to the downloaded pack file
*/
public static CompletableFuture<@Nullable Path> checkRemotePackUrl(String url) {
return CompletableFuture.supplyAsync(() -> {
try {
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
int size = con.getContentLength();
String type = con.getContentType();
InputStream in = con.getInputStream();
if (size < 1 || !type.equals("application/zip")) {
GeyserImpl.getInstance().getLogger().error("Invalid resource pack: " + url + " (" + type + ", " + size + " bytes)");
//return null;
}
Path fileLocation = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs").resolve(url.hashCode() + ".zip");
Files.copy(in, fileLocation, StandardCopyOption.REPLACE_EXISTING);
if (Files.size(fileLocation) != size) {
GeyserImpl.getInstance().getLogger().error("Server sent " + Files.size(fileLocation) + " bytes, expected " + size + " bytes");
Files.delete(fileLocation);
return null;
}
return fileLocation;
} catch (MalformedURLException e) {
GeyserImpl.getInstance().getLogger().error("Malformed URL: " + url);
return null;
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Unable to download and save file: " + url + ")");
return null;
}
});
}
/**
* Post a string to the given URL
*
@ -169,15 +213,14 @@ public class WebUtils {
try (OutputStream out = con.getOutputStream()) {
// Write the form data to the output
for (Map.Entry<String, String> field : fields.entrySet()) {
out.write((field.getKey() + "=" + URLEncoder.encode(field.getValue(), StandardCharsets.UTF_8.toString()) + "&").getBytes(StandardCharsets.UTF_8));
out.write((field.getKey() + "=" + URLEncoder.encode(field.getValue(), StandardCharsets.UTF_8) + "&").getBytes(StandardCharsets.UTF_8));
}
}
return connectionToString(con);
}
@Nullable
public static String[] findSrvRecord(GeyserImpl geyser, String remoteAddress) {
public static String @Nullable [] findSrvRecord(GeyserImpl geyser, String remoteAddress) {
try {
// Searches for a server address and a port from a SRV record of the specified host name
InitialDirContext ctx = new InitialDirContext();