bentobox/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java

251 lines
9.2 KiB
Java

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 - <tt>true</tt> if load is successful, <tt>false</tt> 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());
}
}
}
}