package world.bentobox.bentobox.managers; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.bukkit.Material; import org.bukkit.util.Vector; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory; /** * @author tastybento * @since 1.5.0 */ public class BlueprintClipboardManager { private static final String LOAD_ERROR = "Could not load blueprint file - does not exist : "; private final File blueprintFolder; private BlueprintClipboard clipboard; private Gson gson; private final BentoBox plugin; public BlueprintClipboardManager(BentoBox plugin, File blueprintFolder) { this(plugin, blueprintFolder, null); } public BlueprintClipboardManager(BentoBox plugin, File blueprintFolder, BlueprintClipboard clipboard) { super(); this.plugin = plugin; if (!blueprintFolder.exists()) { blueprintFolder.mkdirs(); } this.blueprintFolder = blueprintFolder; this.clipboard = clipboard; getGson(); } /** * @return the clipboard */ public BlueprintClipboard getClipboard() { return clipboard; } private void getGson() { GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().enableComplexMapKeySerialization(); // Disable <>'s escaping etc. builder.disableHtmlEscaping(); // Register adapter factory builder.registerTypeAdapterFactory(new BentoboxTypeAdapterFactory(plugin)); gson = builder.create(); } /** * Load a file to clipboard * @param fileName - filename in blueprints folder * @throws IOException - if there's a load error with unzipping or name */ public void load(String fileName) throws IOException { clipboard = new BlueprintClipboard(loadBlueprint(fileName)); } /** * Loads a blueprint * @param fileName - the sanitized filename without the suffix * @return the blueprint * @throws IOException exception if there's an issue loading or unzipping */ public Blueprint loadBlueprint(String fileName) throws IOException { File zipFile = new File(blueprintFolder, fileName + BlueprintsManager.BLUEPRINT_SUFFIX); if (!zipFile.exists()) { plugin.logError(LOAD_ERROR + zipFile.getName()); throw new IOException(LOAD_ERROR + zipFile.getName()); } unzip(zipFile.getCanonicalPath()); File file = new File(blueprintFolder, fileName); if (!file.exists()) { plugin.logError(LOAD_ERROR + file.getName()); throw new IOException(LOAD_ERROR + file.getName() + " temp file"); } Blueprint bp; try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8)) { bp = gson.fromJson(fr, Blueprint.class); } catch (Exception e) { plugin.logError("Blueprint has JSON error: " + zipFile.getName()); throw new IOException("Blueprint has JSON error: " + zipFile.getName()); } Files.delete(file.toPath()); // Bedrock check and set if (bp.getBedrock() == null) { bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2)); bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString())); plugin.logWarning("Blueprint " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); } return bp; } /** * Load a blueprint to the clipboard for a user * @param user - user trying to load * @param fileName - filename * @return - true if load is successful, false if not */ public boolean load(User user, String fileName) { try { load(fileName); } catch (IOException e1) { user.sendMessage("commands.admin.blueprint.could-not-load"); plugin.logError("Could not load blueprint file: " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); return false; } user.sendMessage("general.success"); return true; } /** * Save the clipboard to a file * @param user - user who is copying * @param newName - new name of this blueprint * @return - true if successful, false if error */ public boolean save(User user, String newName, String displayName) { if (this.clipboard.isFull()) { this.clipboard.getBlueprint().setName(newName); this.clipboard.getBlueprint().setDisplayName(displayName); if (this.saveBlueprint(this.clipboard.getBlueprint())) { user.sendMessage("general.success"); return true; } } user.sendMessage("commands.admin.blueprint.could-not-save", "[message]", "Could not save temp blueprint file."); return false; } /** * Save a blueprint * @param blueprint - blueprint * @return true if successful, false if not */ public boolean saveBlueprint(Blueprint blueprint) { if (blueprint.getName().isEmpty()) { plugin.logError("Blueprint name was empty - could not save it"); return false; } File file = new File(blueprintFolder, blueprint.getName()); String toStore = gson.toJson(blueprint, Blueprint.class); try (FileWriter fileWriter = new FileWriter(file, StandardCharsets.UTF_8)) { fileWriter.write(toStore); } catch (IOException e) { plugin.logError("Could not save temporary blueprint file: " + file.getName()); return false; } try { zip(file); } catch (IOException e) { plugin.logError("Could not zip temporary blueprint file: " + file.getName()); return false; } return true; } private void unzip(final String zipFilePath) throws IOException { Path path = Paths.get(zipFilePath); if (!(path.toFile().exists())) { throw new IOException("No file exists!"); } try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath))) { ZipEntry entry = zipInputStream.getNextEntry(); while (entry != null) { Path filePath = Paths.get(path.getParent().toString(), entry.getName()); if (!entry.isDirectory()) { unzipFiles(zipInputStream, filePath); } else { if (!filePath.startsWith(blueprintFolder.getCanonicalPath())) { throw new IOException("Entry is outside of the target directory"); } Files.createDirectories(filePath); } zipInputStream.closeEntry(); entry = zipInputStream.getNextEntry(); } } } private void unzipFiles(final ZipInputStream zipInputStream, final Path unzipFilePath) throws IOException { // Prevent directory traversal attacks by normalizing the path if (!unzipFilePath.startsWith(blueprintFolder.getCanonicalFile().toPath().normalize())) { throw new IOException( "Blueprint file is trying to write outside of the target directory! Blocked attempt to write to " + unzipFilePath.toString()); } try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(unzipFilePath.toFile().getCanonicalPath()))) { byte[] bytesIn = new byte[1024]; int read; while ((read = zipInputStream.read(bytesIn)) != -1) { bos.write(bytesIn, 0, read); } } } private void zip(File targetFile) throws IOException { try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetFile.getCanonicalPath() + BlueprintsManager.BLUEPRINT_SUFFIX))) { zipOutputStream.putNextEntry(new ZipEntry(targetFile.getName())); try (FileInputStream inputStream = new FileInputStream(targetFile)) { final byte[] buffer = new byte[1024]; int length; while((length = inputStream.read(buffer)) >= 0) { zipOutputStream.write(buffer, 0, length); } } try { Files.delete(targetFile.toPath()); } catch (Exception e) { plugin.logError(e.getMessage()); } } } }