From ff6012776280dc5bf8ead949aa2dfa8a5995fd3c Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 25 Nov 2019 19:57:11 -0800 Subject: [PATCH] Sanitizes blueprint and bundle names https://github.com/BentoBoxWorld/BentoBox/issues/1038 Allows spaces to exist in the bundle names. Added tests to check for bad chars and foreign chars in names. --- .../managers/BlueprintClipboardManager.java | 10 +- .../bentobox/managers/BlueprintsManager.java | 21 +++- .../BlueprintClipboardManagerTest.java | 106 +++++++++++++++--- 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 7464496da..a14612107 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -91,13 +91,13 @@ public class BlueprintClipboardManager { * @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); + File zipFile = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX); if (!zipFile.exists()) { plugin.logError(LOAD_ERROR + zipFile.getName()); throw new IOException(LOAD_ERROR + zipFile.getName()); } unzip(zipFile.getAbsolutePath()); - File file = new File(blueprintFolder, fileName); + File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName)); if (!file.exists()) { plugin.logError(LOAD_ERROR + file.getName()); throw new IOException(LOAD_ERROR + file.getName() + " temp file"); @@ -114,7 +114,7 @@ public class BlueprintClipboardManager { 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 + " had no bedrock block in it so one was added automatically in the center. You should check it."); + plugin.logWarning("Blueprint " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); } return bp; } @@ -130,7 +130,7 @@ public class BlueprintClipboardManager { load(fileName); } catch (IOException e1) { user.sendMessage("commands.admin.blueprint.could-not-load"); - plugin.logError("Could not load blueprint file: " + fileName + " " + e1.getMessage()); + plugin.logError("Could not load blueprint file: " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); return false; } user.sendMessage("general.success"); @@ -163,7 +163,7 @@ public class BlueprintClipboardManager { plugin.logError("Blueprint name was empty - could not save it"); return false; } - File file = new File(blueprintFolder, blueprint.getName()); + File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(blueprint.getName())); String toStore = gson.toJson(blueprint, Blueprint.class); try (FileWriter fileWriter = new FileWriter(file)) { fileWriter.write(toStore); diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java index e8a8bda4b..2bcfce6c9 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java @@ -347,7 +347,7 @@ public class BlueprintsManager { if (!bpf.exists()) { bpf.mkdirs(); } - File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); String toStore = gson.toJson(bb, BlueprintBundle.class); try (FileWriter fileWriter = new FileWriter(fileName)) { fileWriter.write(toStore); @@ -357,6 +357,21 @@ public class BlueprintsManager { }); } + /** + * Sanitizes a filename as much as possible retaining the original name + * @param name - filename to sanitize + * @return sanitized name + */ + public static String sanitizeFileName(String name) { + return name + .chars() + .mapToObj(i -> (char) i) + .map(c -> Character.isWhitespace(c) ? '_' : c) + .filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_') + .map(String::valueOf) + .collect(Collectors.joining()); + } + /** * Saves all the blueprint bundles */ @@ -506,7 +521,7 @@ public class BlueprintsManager { blueprintBundles.get(addon).removeIf(k -> k.getUniqueId().equals(bb.getUniqueId())); } File bpf = getBlueprintsFolder(addon); - File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); try { Files.deleteIfExists(fileName.toPath()); } catch (IOException e) { @@ -528,7 +543,7 @@ public class BlueprintsManager { } File bpf = getBlueprintsFolder(addon); // Get the filename - File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX); + File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX); // Delete the old file try { Files.deleteIfExists(fileName.toPath()); diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java index 6ea762717..d2b540394 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java @@ -3,6 +3,7 @@ package world.bentobox.bentobox.managers; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,7 +29,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @@ -46,6 +46,8 @@ import world.bentobox.bentobox.blueprints.BlueprintClipboard; @PrepareForTest( {Bukkit.class, BentoBox.class} ) public class BlueprintClipboardManagerTest { + private static final String BLUEPRINT = "blueprint"; + @Mock private BentoBox plugin; @Mock @@ -120,9 +122,11 @@ public class BlueprintClipboardManagerTest { @Before public void setUp() throws Exception { blueprintFolder = new File("blueprints"); + // Clear any residual files + tearDown(); PowerMockito.mockStatic(Bukkit.class); BlockData blockData = mock(BlockData.class); - when(Bukkit.createBlockData(Mockito.any(Material.class))).thenReturn(blockData); + when(Bukkit.createBlockData(any(Material.class))).thenReturn(blockData); when(blockData.getAsString()).thenReturn("test123"); } @@ -131,6 +135,7 @@ public class BlueprintClipboardManagerTest { */ @After public void tearDown() throws Exception { + if (blueprintFolder.exists()) { // Clean up file system Files.walk(blueprintFolder.toPath()) @@ -198,7 +203,7 @@ public class BlueprintClipboardManagerTest { assertTrue(configFile.exists()); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); try { - bcm.loadBlueprint("blueprint"); + bcm.loadBlueprint(BLUEPRINT); } catch (Exception e) { assertTrue(e instanceof IOException); } finally { @@ -216,13 +221,13 @@ public class BlueprintClipboardManagerTest { // Make a blueprint file YamlConfiguration config = new YamlConfiguration(); config.set("hello", "this is a test"); - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); config.save(configFile); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); try { - bcm.loadBlueprint("blueprint"); + bcm.loadBlueprint(BLUEPRINT); } catch (Exception e) { assertTrue(e instanceof IOException); } finally { @@ -238,13 +243,13 @@ public class BlueprintClipboardManagerTest { public void testLoadBlueprintFileInZipNoBedrock() throws IOException { blueprintFolder.mkdirs(); // Make a blueprint file - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); byte[] bytes = jsonNoBedrock.getBytes(StandardCharsets.UTF_8); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); - Blueprint bp = bcm.loadBlueprint("blueprint"); + Blueprint bp = bcm.loadBlueprint(BLUEPRINT); verify(plugin).logWarning("Blueprint blueprint had no bedrock block in it so one was added automatically in the center. You should check it."); // Verify bedrock was placed in the center of the blueprint assertEquals(5, bp.getBedrock().getBlockX()); @@ -260,13 +265,13 @@ public class BlueprintClipboardManagerTest { public void testLoadBlueprintFileInZip() throws IOException { blueprintFolder.mkdirs(); // Make a blueprint file - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); - Blueprint bp = bcm.loadBlueprint("blueprint"); + Blueprint bp = bcm.loadBlueprint(BLUEPRINT); assertEquals(-2, bp.getBedrock().getBlockX()); assertEquals(-16, bp.getBedrock().getBlockY()); assertEquals(-1, bp.getBedrock().getBlockZ()); @@ -283,13 +288,13 @@ public class BlueprintClipboardManagerTest { public void testLoadString() throws IOException { blueprintFolder.mkdirs(); // Make a blueprint file - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); - bcm.load("blueprint"); + bcm.load(BLUEPRINT); Blueprint bp = bcm.getClipboard().getBlueprint(); assertEquals(-2, bp.getBedrock().getBlockX()); assertEquals(-16, bp.getBedrock().getBlockY()); @@ -307,14 +312,14 @@ public class BlueprintClipboardManagerTest { public void testLoadUserString() throws IOException { blueprintFolder.mkdirs(); // Make a blueprint file - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); User user = mock(User.class); - assertTrue(bcm.load(user, "blueprint")); + assertTrue(bcm.load(user, BLUEPRINT)); verify(user).sendMessage("general.success"); } @@ -326,7 +331,7 @@ public class BlueprintClipboardManagerTest { public void testLoadUserStringFail() throws IOException { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); User user = mock(User.class); - assertFalse(bcm.load(user, "blueprint")); + assertFalse(bcm.load(user, BLUEPRINT)); verify(user).sendMessage("commands.admin.blueprint.could-not-load"); verify(plugin).logError("Could not load blueprint file - does not exist : blueprint.blu"); } @@ -340,13 +345,13 @@ public class BlueprintClipboardManagerTest { // Load a blueprint, then save it blueprintFolder.mkdirs(); // Make a blueprint file - File configFile = new File(blueprintFolder, "blueprint"); + File configFile = new File(blueprintFolder, BLUEPRINT); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); // Zip it zip(configFile); BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); - bcm.load("blueprint"); + bcm.load(BLUEPRINT); User user = mock(User.class); assertTrue(bcm.save(user, "test1234")); File bp = new File(blueprintFolder, "test1234.blu"); @@ -354,6 +359,75 @@ public class BlueprintClipboardManagerTest { verify(user).sendMessage("general.success"); } + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testSaveBadChars() throws IOException { + // Load a blueprint, then save it + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, BLUEPRINT); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + bcm.load(BLUEPRINT); + User user = mock(User.class); + assertTrue(bcm.save(user, "test.1234/../../film")); + File bp = new File(blueprintFolder, "test1234film.blu"); + assertTrue(bp.exists()); + verify(user).sendMessage("general.success"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testSaveForeignChars() throws IOException { + // Load a blueprint, then save it + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, BLUEPRINT); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + bcm.load(BLUEPRINT); + User user = mock(User.class); + assertTrue(bcm.save(user, "日本語の言葉")); + File bp = new File(blueprintFolder, "日本語の言葉.blu"); + assertTrue(bp.exists()); + verify(user).sendMessage("general.success"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testSaveForeignBadChars() throws IOException { + // Load a blueprint, then save it + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, BLUEPRINT); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + bcm.load(BLUEPRINT); + User user = mock(User.class); + assertTrue(bcm.save(user, "日本語の言葉/../../../config")); + File bp = new File(blueprintFolder, "日本語の言葉config.blu"); + assertTrue(bp.exists()); + verify(user).sendMessage("general.success"); + } + /** * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#saveBlueprint(world.bentobox.bentobox.blueprints.Blueprint)}. */