From ee1c92e36ff4b4edf35f68c73557c1bb58879b19 Mon Sep 17 00:00:00 2001 From: iceBear67 Date: Mon, 6 Jan 2025 20:30:47 +0800 Subject: [PATCH] add HTTPAPI --- build.gradle | 3 +- .../sfcraft/module/supervisor/WebModule.java | 96 +++++++++++++++++++ .../java/io/ib67/sfcraft/util/Helper.java | 14 +++ .../ib67/sfcraft/util/LitematicConverter.java | 4 +- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/ib67/sfcraft/module/supervisor/WebModule.java diff --git a/build.gradle b/build.gradle index ee60f96..db1f2b0 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,8 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" shadow('com.maxmind.geoip2:geoip2:4.2.0') - implementation("io.javalin:javalin:6.4.0") + implementation "io.javalin:javalin:6.4.0" + implementation 'com.github.houbb:pinyin:0.4.0' shadow('com.google.inject:guice:7.0.0') { exclude group: 'com.google.guava', module: 'guava' exclude group: 'com.google.guava', module: 'listenablefuture' diff --git a/src/main/java/io/ib67/sfcraft/module/supervisor/WebModule.java b/src/main/java/io/ib67/sfcraft/module/supervisor/WebModule.java new file mode 100644 index 0000000..4093e8a --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/module/supervisor/WebModule.java @@ -0,0 +1,96 @@ +package io.ib67.sfcraft.module.supervisor; + +import com.google.inject.Inject; +import io.ib67.sfcraft.ServerModule; +import io.ib67.sfcraft.config.SFConfig; +import io.ib67.sfcraft.module.SignatureService; +import io.ib67.sfcraft.util.Helper; +import io.ib67.sfcraft.util.LitematicConverter; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.UploadedFile; +import io.netty.buffer.Unpooled; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtSizeTracker; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.ForkJoinPool; +import java.util.regex.Pattern; + +@Log4j2 +public class WebModule extends ServerModule { + private static final int PERMISSION_UPLOAD_SCHEMATICS = 2; + private static final Path SCHEMATIC_DIR = Path.of("config/worldedit/schematics/"); + @Inject + SFConfig config; + @Inject + SignatureService signatureService; + + @Override + @SneakyThrows + public void onInitialize() { + Files.createDirectories(SCHEMATIC_DIR); + Thread.ofVirtual().name("SFCraft API Web").start(() -> { + Javalin.create(cfg -> cfg.useVirtualThreads = true) + .post("/api/schematics", this::uploadSchematic) + .start(config.httpPort); + }); + } + + private void uploadSchematic(@NotNull Context context) { + if (context.contentLength() > config.maxSchematicSize) { + context.result("Content is too large!"); + return; + } + var signRaw = Base64.getUrlDecoder().decode(context.pathParam("sign")); + var verifiedSign = signatureService.readSignature(Unpooled.wrappedBuffer(signRaw)); + if ((verifiedSign.permission() & PERMISSION_UPLOAD_SCHEMATICS) == 0) { + context.result("Permission denied!"); + return; + } + var files = context.uploadedFileMap(); + files.forEach((k, v) -> handleUploadSchematic(context, k, v)); + } + + @SneakyThrows + private void handleUploadSchematic(@NotNull Context context, String fileName, List v) { + if (v.size() != 1) { + return; + } + var file = v.getFirst(); + if (file.size() > config.maxSchematicSize) { + context.result("Content is too large!"); + return; + } + fileName = Helper.cleanFileName(fileName); + if (fileName.endsWith(".schematic")) { + Files.write(SCHEMATIC_DIR.resolve(fileName), file.content().readAllBytes()); + file.content().close(); + log.info("Saved "+fileName+" as a schematic."); + context.result("Success!"); + } else if (fileName.endsWith(".litematic")) { + log.error("Handling new {}", fileName); + var baseFileName = fileName.substring(0, fileName.length() - 10); + new LitematicConverter( + file.content(), + new NbtSizeTracker(config.maxSchematicSize, 16) + ).read((name, nbt) -> { + name = Helper.cleanFileName(baseFileName + "-" + name + ".schematic"); + try { + NbtIo.writeCompressed(nbt, SCHEMATIC_DIR.resolve(name)); + log.info("Schematic" + name+" has been saved!"); + } catch (IOException e) { + log.error("Error occurred when serializing .schematic from .litematic.", e); + } + }); + } + } +} diff --git a/src/main/java/io/ib67/sfcraft/util/Helper.java b/src/main/java/io/ib67/sfcraft/util/Helper.java index 6b75245..e507e31 100644 --- a/src/main/java/io/ib67/sfcraft/util/Helper.java +++ b/src/main/java/io/ib67/sfcraft/util/Helper.java @@ -1,5 +1,8 @@ package io.ib67.sfcraft.util; +import com.github.houbb.pinyin.api.impl.Pinyin; +import com.github.houbb.pinyin.constant.enums.PinyinStyleEnum; +import com.github.houbb.pinyin.util.PinyinHelper; import com.maxmind.geoip2.exception.GeoIp2Exception; import io.ib67.sfcraft.SFCraft; import io.ib67.sfcraft.config.SFConfig; @@ -29,8 +32,10 @@ import java.nio.file.Path; import java.util.Optional; import java.util.UUID; +import java.util.regex.Pattern; public class Helper { + private static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[./\\\\#%!@&*]"); public static final char COLOR = 'ยง'; public static boolean canBack(ServerPlayerEntity player) { @@ -43,6 +48,15 @@ public static boolean canBack(ServerPlayerEntity player) { return nearby != null; } + public static String cleanFileName(String filename) { + var name = PinyinHelper.toPinyin(filename, PinyinStyleEnum.NORMAL,"_"); + name = ILLEGAL_CHARACTERS.matcher(name).replaceAll(""); + if(name.length() > 32){ + name = name.substring(0,32); + } + return name; + } + public static boolean teleportSafely(ServerPlayerEntity player, ServerWorld world, int x, int y, int z, float yaw, float pitch) { var pos = new BlockPos(x, y, z); var stand = world.getBlockState(pos); diff --git a/src/main/java/io/ib67/sfcraft/util/LitematicConverter.java b/src/main/java/io/ib67/sfcraft/util/LitematicConverter.java index 509362e..d719091 100644 --- a/src/main/java/io/ib67/sfcraft/util/LitematicConverter.java +++ b/src/main/java/io/ib67/sfcraft/util/LitematicConverter.java @@ -20,7 +20,6 @@ public class LitematicConverter implements AutoCloseable { public static final String NBT_LITEMATICA_ROOT = ""; private final InputStream input; private final NbtSizeTracker sizeTracker; - private NbtCompound root; public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) { this.input = input; @@ -31,8 +30,7 @@ public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) { public void read( BiConsumer schematicOutput ) { - if (root != null) throw new IllegalStateException("Litematica file is already read"); - root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT); + var root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT); var dataVersion = root.getInt("MinecraftDataVersion"); var regionsNbt = root.getCompound("Regions"); var i = 0;