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:
Ursprung
76a62abd27
Commit
36709143c7
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren