diff --git a/src/com/sk89q/worldedit/FileSelectionAbortedException.java b/src/com/sk89q/worldedit/FileSelectionAbortedException.java new file mode 100644 index 000000000..b7cf89e9c --- /dev/null +++ b/src/com/sk89q/worldedit/FileSelectionAbortedException.java @@ -0,0 +1,32 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +public class FileSelectionAbortedException extends FilenameException { + private static final long serialVersionUID = 7377072269988014886L; + + public FileSelectionAbortedException() { + super(""); + } + + public FileSelectionAbortedException(String msg) { + super("", msg); + } +} diff --git a/src/com/sk89q/worldedit/FilenameException.java b/src/com/sk89q/worldedit/FilenameException.java new file mode 100644 index 000000000..6892d6389 --- /dev/null +++ b/src/com/sk89q/worldedit/FilenameException.java @@ -0,0 +1,40 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +public class FilenameException extends WorldEditException { + private static final long serialVersionUID = 6072601657326106265L; + + private String filename; + + public FilenameException(String filename) { + super(); + this.filename = filename; + } + + public FilenameException(String filename, String msg) { + super(msg); + this.filename = filename; + } + + public String getFilename() { + return filename; + } +} diff --git a/src/com/sk89q/worldedit/FilenameNotFoundException.java b/src/com/sk89q/worldedit/FilenameNotFoundException.java new file mode 100644 index 000000000..4dae4857a --- /dev/null +++ b/src/com/sk89q/worldedit/FilenameNotFoundException.java @@ -0,0 +1,32 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +public class FilenameNotFoundException extends FilenameException { + private static final long serialVersionUID = 4673670296313383121L; + + public FilenameNotFoundException(String filename) { + super(filename); + } + + public FilenameNotFoundException(String filename, String msg) { + super(filename, msg); + } +} diff --git a/src/com/sk89q/worldedit/InvalidFilenameException.java b/src/com/sk89q/worldedit/InvalidFilenameException.java new file mode 100644 index 000000000..4431e8da3 --- /dev/null +++ b/src/com/sk89q/worldedit/InvalidFilenameException.java @@ -0,0 +1,32 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit; + +public class InvalidFilenameException extends FilenameException { + private static final long serialVersionUID = 7377072269988014886L; + + public InvalidFilenameException(String filename) { + super(filename); + } + + public InvalidFilenameException(String filename, String msg) { + super(filename, msg); + } +} diff --git a/src/com/sk89q/worldedit/LocalPlayer.java b/src/com/sk89q/worldedit/LocalPlayer.java index 405eb3af1..8ebc0bd34 100644 --- a/src/com/sk89q/worldedit/LocalPlayer.java +++ b/src/com/sk89q/worldedit/LocalPlayer.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit; +import java.io.File; import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.blocks.BlockType; import com.sk89q.worldedit.util.TargetBlock; @@ -536,6 +537,17 @@ public abstract class LocalPlayer { */ public abstract boolean hasPermission(String perm); + /** + * Open a file open dialog. + * + * @param extensions null to allow all + * @return + */ + public File openFileDialog(String[] extensions) { + printError("File dialogs are not supported in your environment."); + return null; + } + /** * Returns true if the player can destroy bedrock. * diff --git a/src/com/sk89q/worldedit/WorldEdit.java b/src/com/sk89q/worldedit/WorldEdit.java index 54e83aa0f..59da0ebee 100644 --- a/src/com/sk89q/worldedit/WorldEdit.java +++ b/src/com/sk89q/worldedit/WorldEdit.java @@ -345,6 +345,56 @@ public class WorldEdit { } return blocks; } + + /** + * Gets the path to a file. This method will check to see if the filename + * has valid characters and has an extension. It also prevents directory + * traversal exploits by checking the root directory and the file directory. + * On success, a java.io.File object will be returned. + * + * @param dir sub-directory to look in + * @param filename filename (user-submitted) + * @param defaultExt append an extension if missing one, null to not use + * @return + * @throws FilenameException + */ + public File getSafeFile(LocalPlayer player, File dir, String filename, + String defaultExt) throws FilenameException { + File f; + + if (filename.equals("#")) { + f = player.openFileDialog(null); + if (f == null) { + throw new FileSelectionAbortedException("No file selected"); + } + } else { + if (defaultExt != null && filename.lastIndexOf('.') == -1) { + filename += "." + defaultExt; + } + + if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$")) { + throw new InvalidFilenameException(filename, + "Invalid characters or extension missing"); + } + + f = new File(dir, filename); + } + + try { + String filePath = f.getCanonicalPath(); + String dirPath = dir.getCanonicalPath(); + + if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { + throw new FilenameNotFoundException(filename, + "Path is outside allowable root"); + } + + return f; + } catch (IOException e) { + throw new FilenameNotFoundException(filename, + "Failed to resolve path"); + } + } /** * Checks to see if the specified radius is within bounds. @@ -774,6 +824,14 @@ public class WorldEdit { player.printError(e.getMessage()); } catch (EmptyClipboardException e) { player.printError("Your clipboard is empty. Use //copy first."); + } catch (InvalidFilenameException e) { + player.printError("Filename '" + e.getFilename() + "' invalid: " + + e.getMessage()); + } catch (FilenameNotFoundException e) { + player.printError("File '" + e.getFilename() + "' not found: " + + e.getMessage()); + } catch (FileSelectionAbortedException e) { + player.printError("File selection aborted."); } catch (WorldEditException e) { player.printError(e.getMessage()); } catch (Throwable excp) { @@ -793,16 +851,9 @@ public class WorldEdit { * @param args * @throws WorldEditException */ - public void runScript(LocalPlayer player, String filename, String[] args) + public void runScript(LocalPlayer player, File f, String[] args) throws WorldEditException { - File dir = getWorkingDirectoryFile(config.scriptsDir); - File f = new File(dir, filename); - - if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$")) { - player.printError("Invalid filename. Don't forget the extension."); - return; - } - + String filename = f.getPath(); int index = filename.lastIndexOf("."); String ext = filename.substring(index + 1, filename.length()); @@ -810,19 +861,6 @@ public class WorldEdit { player.printError("Only .js scripts are currently supported"); return; } - - try { - String filePath = f.getCanonicalPath(); - String dirPath = dir.getCanonicalPath(); - - if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { - player.printError("Script could not read or it does not exist."); - return; - } - } catch (IOException e) { - player.printError("Script could not read or it does not exist: " + e.getMessage()); - return; - } String script; diff --git a/src/com/sk89q/worldedit/commands/ClipboardCommands.java b/src/com/sk89q/worldedit/commands/ClipboardCommands.java index 2d11cc979..403432f69 100644 --- a/src/com/sk89q/worldedit/commands/ClipboardCommands.java +++ b/src/com/sk89q/worldedit/commands/ClipboardCommands.java @@ -179,15 +179,9 @@ public class ClipboardCommands { LocalConfiguration config = we.getConfiguration(); - String filename = args.getString(0).replace("\0", "") + ".schematic"; + String filename = args.getString(0); File dir = we.getWorkingDirectoryFile(config.saveDir); - File f = new File(dir, filename); - - if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) { - player.printError("Valid characters: A-Z, a-z, 0-9, spaces, " - + "./\'$@~!%^*()[]+{},?"); - return; - } + File f = we.getSafeFile(player, dir, filename, "schematic"); try { String filePath = f.getCanonicalPath(); @@ -221,41 +215,28 @@ public class ClipboardCommands { LocalConfiguration config = we.getConfiguration(); - String filename = args.getString(0).replace("\0", "") + ".schematic"; + String filename = args.getString(0); - if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) { - player.printError("Valid characters: A-Z, a-z, 0-9, spaces, " - + "./\'$@~!%^*()[]+{},?"); - return; - } - File dir = we.getWorkingDirectoryFile(config.saveDir); - File f = new File(dir, filename); + File f = we.getSafeFile(player, dir, filename, "schematic"); if (!dir.exists()) { if (!dir.mkdir()) { - player.printError("A schematics/ folder could not be created."); + player.printError("The storage folder could not be created."); return; } } try { - String filePath = f.getCanonicalPath(); - String dirPath = dir.getCanonicalPath(); - - if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { - player.printError("Invalid path for Schematic."); - } else { - // Create parent directories - File parent = f.getParentFile(); - if (parent != null && !parent.exists()) { - parent.mkdirs(); - } - - session.getClipboard().saveSchematic(f); - WorldEdit.logger.info(player.getName() + " saved " + filePath); - player.print(filename + " saved."); + // Create parent directories + File parent = f.getParentFile(); + if (parent != null && !parent.exists()) { + parent.mkdirs(); } + + session.getClipboard().saveSchematic(f); + WorldEdit.logger.info(player.getName() + " saved " + f.getCanonicalPath()); + player.print(filename + " saved."); } catch (DataException se) { player.printError("Save error: " + se.getMessage()); } catch (IOException e) { diff --git a/src/com/sk89q/worldedit/commands/ScriptingCommands.java b/src/com/sk89q/worldedit/commands/ScriptingCommands.java index fbc4a7ae5..001fca03b 100644 --- a/src/com/sk89q/worldedit/commands/ScriptingCommands.java +++ b/src/com/sk89q/worldedit/commands/ScriptingCommands.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.commands; +import java.io.File; import com.sk89q.util.commands.Command; import com.sk89q.util.commands.CommandContext; import com.sk89q.worldedit.*; @@ -45,8 +46,11 @@ public class ScriptingCommands { String[] scriptArgs = args.getSlice(1); session.setLastScript(args.getString(0)); + + File dir = we.getWorkingDirectoryFile(we.getConfiguration().scriptsDir); + File f = we.getSafeFile(player, dir, args.getString(0), "js"); - we.runScript(player, args.getString(0), scriptArgs); + we.runScript(player, f, scriptArgs); } @Command( @@ -70,8 +74,11 @@ public class ScriptingCommands { } String[] scriptArgs = args.getSlice(0); + + File dir = we.getWorkingDirectoryFile(we.getConfiguration().scriptsDir); + File f = we.getSafeFile(player, dir, lastScript, "js"); - we.runScript(player, lastScript, scriptArgs); + we.runScript(player, f, scriptArgs); } } diff --git a/src/com/sk89q/worldedit/scripting/CraftScriptContext.java b/src/com/sk89q/worldedit/scripting/CraftScriptContext.java index 90399d3da..d63f0aa0b 100644 --- a/src/com/sk89q/worldedit/scripting/CraftScriptContext.java +++ b/src/com/sk89q/worldedit/scripting/CraftScriptContext.java @@ -20,13 +20,13 @@ package com.sk89q.worldedit.scripting; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import com.sk89q.worldedit.DisallowedItemException; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.FilenameException; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalSession; @@ -200,37 +200,32 @@ public class CraftScriptContext extends CraftScriptEnvironment { * Gets the path to a file. This method will check to see if the filename * has valid characters and has an extension. It also prevents directory * traversal exploits by checking the root directory and the file directory. - * On success, a java.io.File object will be returned, - * otherwise a null will be returned and the player will be informed. + * On success, a java.io.File object will be returned. * *

Use this method if you need to read a file from a directory.

* - * @param folder subdirectory to look in + * @param folder sub-directory to look in * @param filename filename (user-submitted) * @return + * @throws FilenameException */ - public File getSafeFile(String folder, String filename) { - File dir = new File(folder); - File f = new File(dir, filename); - - if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$")) { - player.printError("Invalid filename specified."); - return null; - } - - try { - String filePath = f.getCanonicalPath(); - String dirPath = dir.getCanonicalPath(); - - if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { - player.printError("File could not read or it does not exist."); - return null; - } - - return f; - } catch (IOException e) { - player.printError("File could not read or it does not exist: " + e.getMessage()); - return null; - } + public File getSafeFile(String folder, String filename) throws FilenameException { + File dir = controller.getWorkingDirectoryFile(folder); + return controller.getSafeFile(player, dir, filename, null); + } + + /** + * This version will append an extension if one doesn't exist. + * + * @param folder sub-directory to look in + * @param filename filename (user-submitted) + * @param defaultExt default extension to append if there is none + * @return + * @throws FilenameException + */ + public File getSafeFile(String folder, String filename, String defaultExt) + throws FilenameException { + File dir = controller.getWorkingDirectoryFile(folder); + return controller.getSafeFile(player, dir, filename, defaultExt); } }