From 213f1e19f12bfc34a0fd72af1c3074c5eaa8f132 Mon Sep 17 00:00:00 2001 From: sk89q Date: Sat, 22 Jan 2011 15:38:04 -0800 Subject: [PATCH] Improved scripting support. --- .../sk89q/worldedit/WorldEditController.java | 17 +- .../scripting/CraftScriptContext.java | 170 +++++++++++++++++- .../scripting/CraftScriptEngine.java | 2 +- .../scripting/SunRhinoCraftScriptEngine.java | 6 +- 4 files changed, 181 insertions(+), 14 deletions(-) diff --git a/src/com/sk89q/worldedit/WorldEditController.java b/src/com/sk89q/worldedit/WorldEditController.java index 84ff44bc3..7e2355248 100644 --- a/src/com/sk89q/worldedit/WorldEditController.java +++ b/src/com/sk89q/worldedit/WorldEditController.java @@ -2114,8 +2114,10 @@ public class WorldEditController { * @param player * @param filename * @param args + * @throws WorldEditException */ - public void runScript(LocalPlayer player, String filename, String[] args) { + public void runScript(LocalPlayer player, String filename, String[] args) + throws WorldEditException { File dir = new File("craftscripts"); File f = new File(dir, filename); @@ -2174,7 +2176,7 @@ public class WorldEditController { LocalSession session = getSession(player); CraftScriptContext scriptContext = - new CraftScriptContext(this, server, config, session, player); + new CraftScriptContext(this, server, config, session, player, args); CraftScriptEngine engine = null; @@ -2199,10 +2201,17 @@ public class WorldEditController { try { engine.evaluate(script, filename, vars); } catch (ScriptException e) { - player.printError("Failed to execute:"); + player.printError("Failed to execute:");; player.printRaw(e.getMessage()); + } catch (NumberFormatException e) { + throw e; + } catch (WorldEditException e) { + throw e; + } catch (Throwable e) { + player.printError("Failed to execute (exception):"); + player.printRaw(e.getClass().getCanonicalName()); } finally { - for (EditSession editSession : scriptContext._getEditSessions()) { + for (EditSession editSession : scriptContext.getEditSessions()) { session.remember(editSession); } } diff --git a/src/com/sk89q/worldedit/scripting/CraftScriptContext.java b/src/com/sk89q/worldedit/scripting/CraftScriptContext.java index 8d2da49ea..e055dcc67 100644 --- a/src/com/sk89q/worldedit/scripting/CraftScriptContext.java +++ b/src/com/sk89q/worldedit/scripting/CraftScriptContext.java @@ -19,10 +19,15 @@ 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.InsufficientArgumentsException; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalSession; @@ -30,17 +35,31 @@ import com.sk89q.worldedit.ServerInterface; import com.sk89q.worldedit.UnknownItemException; import com.sk89q.worldedit.WorldEditController; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.patterns.Pattern; +/** + * The context given to scripts. + * + * @author sk89q + */ public class CraftScriptContext extends CraftScriptEnvironment { private List editSessions = new ArrayList(); + private String[] args; public CraftScriptContext(WorldEditController controller, ServerInterface server, LocalConfiguration config, - LocalSession session, LocalPlayer player) { + LocalSession session, LocalPlayer player, String[] args) { super(controller, server, config, session, player); + this.args = args; } - public EditSession startEditSession() { + /** + * Get an edit session. Every subsequent call returns a new edit session. + * Usually you only need to use one edit session. + * + * @return + */ + public EditSession remember() { EditSession editSession = new EditSession(server, player.getWorld(), session.getBlockChangeLimit(), session.getBlockBag(player)); @@ -48,35 +67,170 @@ public class CraftScriptContext extends CraftScriptEnvironment { return editSession; } + /** + * Get the player. + * + * @return + */ public LocalPlayer getPlayer() { return player; } + /** + * Get the player's session. + * + * @return + */ public LocalSession getSession() { return session; } + /** + * Get the configuration for WorldEdit. + * + * @return + */ public LocalConfiguration getConfiguration() { return config; } - public List _getEditSessions() { - return editSessions; + /** + * Get a list of edit sessions that have been created. + * + * @return + */ + public List getEditSessions() { + return Collections.unmodifiableList(editSessions); } + /** + * Print a regular message to the user. + * + * @param msg + */ public void print(String msg) { player.print(msg); } + /** + * Print an error message to the user. + * + * @param msg + */ public void error(String msg) { player.printError(msg); } - public BaseBlock getBlock(String arg) throws UnknownItemException, DisallowedItemException { - return controller.getBlock(player, arg, false); + /** + * Print an raw message to the user. + * + * @param msg + */ + public void printRaw(String msg) { + player.printRaw(msg); + } + + /** + * Checks to make sure that there are enough but not too many arguments. + * + * @param args + * @param min + * @param max -1 for no maximum + * @param usage usage string + * @throws InsufficientArgumentsException + */ + public void checkArgs(int min, int max, String usage) + throws InsufficientArgumentsException { + if (args.length <= min || (max != -1 && args.length - 1 > max)) { + throw new InsufficientArgumentsException("Usage: " + usage); + } + } + + /** + * Get an item ID from an item name or an item ID number. + * + * @param arg + * @param allAllowed true to ignore blacklists + * @return + * @throws UnknownItemException + * @throws DisallowedItemException + */ + public BaseBlock getBlock(String arg, boolean allAllowed) + throws UnknownItemException, DisallowedItemException { + return controller.getBlock(player, arg, allAllowed); + } + + /** + * Get a block. + * + * @param id + * @return + * @throws UnknownItemException + * @throws DisallowedItemException + */ + public BaseBlock getBlock(String id) + throws UnknownItemException, DisallowedItemException { + return controller.getBlock(player, id, false); } - public BaseBlock getAnyBlock(String arg) throws UnknownItemException, DisallowedItemException { - return controller.getBlock(player, arg, true); + /** + * Get a list of blocks as a set. This returns a Pattern. + * + * @param list + * @return pattern + */ + public Pattern getBlockPattern(String list) + throws UnknownItemException, DisallowedItemException { + return controller.getBlockPattern(player, list); + } + + /** + * Get a list of blocks as a set. + * + * @param list + * @param allBlocksAllowed + * @return set + */ + public Set getBlockIDs(String list, boolean allBlocksAllowed) + throws UnknownItemException, DisallowedItemException { + return controller.getBlockIDs(player, list, allBlocksAllowed); + } + + /** + * 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. + * + *

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

+ * + * @param folder subdirectory to look in + * @param filename filename (user-submitted) + * @return + */ + 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; + } } } diff --git a/src/com/sk89q/worldedit/scripting/CraftScriptEngine.java b/src/com/sk89q/worldedit/scripting/CraftScriptEngine.java index 32ac9c41d..04d24e2ba 100644 --- a/src/com/sk89q/worldedit/scripting/CraftScriptEngine.java +++ b/src/com/sk89q/worldedit/scripting/CraftScriptEngine.java @@ -26,5 +26,5 @@ public interface CraftScriptEngine { public void setTimeLimit(int milliseconds); public int getTimeLimit(); public Object evaluate(String script, String filename, Map args) - throws ScriptException; + throws ScriptException, Throwable; } diff --git a/src/com/sk89q/worldedit/scripting/SunRhinoCraftScriptEngine.java b/src/com/sk89q/worldedit/scripting/SunRhinoCraftScriptEngine.java index 10726fcf7..6f7e1dfb4 100644 --- a/src/com/sk89q/worldedit/scripting/SunRhinoCraftScriptEngine.java +++ b/src/com/sk89q/worldedit/scripting/SunRhinoCraftScriptEngine.java @@ -40,7 +40,7 @@ public class SunRhinoCraftScriptEngine implements CraftScriptEngine { @Override public Object evaluate(final String script, final String filename, final Map args) - throws ScriptException { + throws ScriptException, Throwable { SunRhinoContextFactory factory = new SunRhinoContextFactory(timeLimit); factory.initApplicationClassLoader(WorldEditController.class.getClassLoader()); @@ -62,6 +62,10 @@ public class SunRhinoCraftScriptEngine implements CraftScriptEngine { } catch (Error e) { throw new ScriptException(e.getMessage()); } catch (RhinoException e) { + if (e instanceof WrappedException) { + throw ((WrappedException)e).getCause(); + } + String msg; int line = (line = e.lineNumber()) == 0 ? -1 : line;