Unified file selection and checking API.

This commit is contained in:
sk89q 2011-01-30 20:40:22 -08:00
parent 2bc75dd5db
commit d5173a8763
9 changed files with 252 additions and 83 deletions

View File

@ -0,0 +1,32 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit;
public class FileSelectionAbortedException extends FilenameException {
private static final long serialVersionUID = 7377072269988014886L;
public FileSelectionAbortedException() {
super("");
}
public FileSelectionAbortedException(String msg) {
super("", msg);
}
}

View File

@ -0,0 +1,40 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,32 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,32 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit; package com.sk89q.worldedit;
import java.io.File;
import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.bags.BlockBag;
import com.sk89q.worldedit.blocks.BlockType; import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.util.TargetBlock; import com.sk89q.worldedit.util.TargetBlock;
@ -536,6 +537,17 @@ public abstract class LocalPlayer {
*/ */
public abstract boolean hasPermission(String perm); 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. * Returns true if the player can destroy bedrock.
* *

View File

@ -345,6 +345,56 @@ public class WorldEdit {
} }
return blocks; 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 <code>java.io.File</code> 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. * Checks to see if the specified radius is within bounds.
@ -774,6 +824,14 @@ public class WorldEdit {
player.printError(e.getMessage()); player.printError(e.getMessage());
} catch (EmptyClipboardException e) { } catch (EmptyClipboardException e) {
player.printError("Your clipboard is empty. Use //copy first."); 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) { } catch (WorldEditException e) {
player.printError(e.getMessage()); player.printError(e.getMessage());
} catch (Throwable excp) { } catch (Throwable excp) {
@ -793,16 +851,9 @@ public class WorldEdit {
* @param args * @param args
* @throws WorldEditException * @throws WorldEditException
*/ */
public void runScript(LocalPlayer player, String filename, String[] args) public void runScript(LocalPlayer player, File f, String[] args)
throws WorldEditException { throws WorldEditException {
File dir = getWorkingDirectoryFile(config.scriptsDir); String filename = f.getPath();
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;
}
int index = filename.lastIndexOf("."); int index = filename.lastIndexOf(".");
String ext = filename.substring(index + 1, filename.length()); String ext = filename.substring(index + 1, filename.length());
@ -810,19 +861,6 @@ public class WorldEdit {
player.printError("Only .js scripts are currently supported"); player.printError("Only .js scripts are currently supported");
return; 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; String script;

View File

@ -179,15 +179,9 @@ public class ClipboardCommands {
LocalConfiguration config = we.getConfiguration(); LocalConfiguration config = we.getConfiguration();
String filename = args.getString(0).replace("\0", "") + ".schematic"; String filename = args.getString(0);
File dir = we.getWorkingDirectoryFile(config.saveDir); File dir = we.getWorkingDirectoryFile(config.saveDir);
File f = new File(dir, filename); File f = we.getSafeFile(player, dir, filename, "schematic");
if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) {
player.printError("Valid characters: A-Z, a-z, 0-9, spaces, "
+ "./\'$@~!%^*()[]+{},?");
return;
}
try { try {
String filePath = f.getCanonicalPath(); String filePath = f.getCanonicalPath();
@ -221,41 +215,28 @@ public class ClipboardCommands {
LocalConfiguration config = we.getConfiguration(); 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 dir = we.getWorkingDirectoryFile(config.saveDir);
File f = new File(dir, filename); File f = we.getSafeFile(player, dir, filename, "schematic");
if (!dir.exists()) { if (!dir.exists()) {
if (!dir.mkdir()) { if (!dir.mkdir()) {
player.printError("A schematics/ folder could not be created."); player.printError("The storage folder could not be created.");
return; return;
} }
} }
try { try {
String filePath = f.getCanonicalPath(); // Create parent directories
String dirPath = dir.getCanonicalPath(); File parent = f.getParentFile();
if (parent != null && !parent.exists()) {
if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { parent.mkdirs();
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.");
} }
session.getClipboard().saveSchematic(f);
WorldEdit.logger.info(player.getName() + " saved " + f.getCanonicalPath());
player.print(filename + " saved.");
} catch (DataException se) { } catch (DataException se) {
player.printError("Save error: " + se.getMessage()); player.printError("Save error: " + se.getMessage());
} catch (IOException e) { } catch (IOException e) {

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.commands; package com.sk89q.worldedit.commands;
import java.io.File;
import com.sk89q.util.commands.Command; import com.sk89q.util.commands.Command;
import com.sk89q.util.commands.CommandContext; import com.sk89q.util.commands.CommandContext;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
@ -45,8 +46,11 @@ public class ScriptingCommands {
String[] scriptArgs = args.getSlice(1); String[] scriptArgs = args.getSlice(1);
session.setLastScript(args.getString(0)); 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( @Command(
@ -70,8 +74,11 @@ public class ScriptingCommands {
} }
String[] scriptArgs = args.getSlice(0); 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);
} }
} }

View File

@ -20,13 +20,13 @@
package com.sk89q.worldedit.scripting; package com.sk89q.worldedit.scripting;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import com.sk89q.worldedit.DisallowedItemException; import com.sk89q.worldedit.DisallowedItemException;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.FilenameException;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.LocalSession; 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 * 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 * has valid characters and has an extension. It also prevents directory
* traversal exploits by checking the root directory and the file directory. * traversal exploits by checking the root directory and the file directory.
* On success, a <code>java.io.File</code> object will be returned, * On success, a <code>java.io.File</code> object will be returned.
* otherwise a null will be returned and the player will be informed.
* *
* <p>Use this method if you need to read a file from a directory.</p> * <p>Use this method if you need to read a file from a directory.</p>
* *
* @param folder subdirectory to look in * @param folder sub-directory to look in
* @param filename filename (user-submitted) * @param filename filename (user-submitted)
* @return * @return
* @throws FilenameException
*/ */
public File getSafeFile(String folder, String filename) { public File getSafeFile(String folder, String filename) throws FilenameException {
File dir = new File(folder); File dir = controller.getWorkingDirectoryFile(folder);
File f = new File(dir, filename); return controller.getSafeFile(player, dir, filename, null);
}
if (!filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$")) {
player.printError("Invalid filename specified."); /**
return null; * This version will append an extension if one doesn't exist.
} *
* @param folder sub-directory to look in
try { * @param filename filename (user-submitted)
String filePath = f.getCanonicalPath(); * @param defaultExt default extension to append if there is none
String dirPath = dir.getCanonicalPath(); * @return
* @throws FilenameException
if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { */
player.printError("File could not read or it does not exist."); public File getSafeFile(String folder, String filename, String defaultExt)
return null; throws FilenameException {
} File dir = controller.getWorkingDirectoryFile(folder);
return controller.getSafeFile(player, dir, filename, defaultExt);
return f;
} catch (IOException e) {
player.printError("File could not read or it does not exist: " + e.getMessage());
return null;
}
} }
} }