/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.shell.syntax; import java.io.File; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; import org.jnode.driver.console.CompletionInfo; import org.jnode.shell.CommandLine.Token; import sun.security.action.GetPropertyAction; /** * This argument class performs completion against the file system namespace. This * Argument class understands the {@link Argument#EXISTING} and {@link Argument#NONEXISTENT} * flags when accepting argument values. Neither {@link Argument#EXISTING} or * {@link Argument#NONEXISTENT} currently affect completion. (You might expect that * {@link Argument#NONEXISTENT} would suppress completion, but consider that the user * may want to complete the directory path for some file to be created by a command.) * <p> * FileArgument normally treats pathname components starting with a "-" as invalid pathnames * and won't accept them. (The rationale is that they are probably a misplaced or unknown * option names.) This behavior can be changed using the {@link #ALLOW_DODGY_NAMES} flag. * <p> * Some commands use "-" to denote (for example) "standard input" instead of a file named * "-". To support this, FileArgument provides a {@link #HYPHEN_IS_SPECIAL} flag which * suppresses the {@link Argument#EXISTING} and {@link Argument#NONEXISTENT} flags so that * a "-" argument is always accepted. It is up to the command to deal with the resulting * {@code File("-")} instance, which of course should not be opened in the normal way. * (Note: this is an experimental feature, and may be replaced with a conceptually cleaner * solution in the future.) * * @author crawley@jnode.org */ public class FileArgument extends Argument<File> { /** * This Argument flag tells the FileArgument to accept filenames that, * while strictly legal, will cause problems. At the moment, this means * pathnames where one or more component names starts with a '-'. (Such * names may be problematic for some commands, and are probably entered * by mistake.) */ public static final int ALLOW_DODGY_NAMES = 0x00010000; /** * This Argument flag tells the FileArgument that the command will * interpret {@code File("-")} as meaning something other than a regular * pathname, and that FileArgument should allow "-" as a valid argument * or completion, not withstanding the existence of a real file with * that name. This flag cannot be set by a Syntax. */ public static final int HYPHEN_IS_SPECIAL = 0x01000000; public FileArgument(String label, int flags, String description) { super(label, flags, new File[0], description); } public FileArgument(String label, int flags) { this(label, flags, null); } @Override protected File doAccept(final Token token, final int flags) throws CommandSyntaxException { if (token.text.length() > 0) { try { return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { @Override public File run() throws Exception { File file = new File(token.text); if ((flags & HYPHEN_IS_SPECIAL) == 0 || !file.getPath().equals("-")) { if (isExisting(flags) && !file.exists()) { throw new CommandSyntaxException("this file or directory does not exist"); } if (isNonexistent(flags) && file.exists()) { throw new CommandSyntaxException("this file or directory already exist"); } if ((flags & ALLOW_DODGY_NAMES) == 0) { File f = file; do { // This assumes that option names start with '-'. if (f.getName().startsWith("-")) { if (f == file && !file.isAbsolute() && f.getParent() == null) { // The user most likely meant this to be an option name ... throw new CommandSyntaxException("unexpected or unknown option"); } else { throw new CommandSyntaxException( "file or directory name starts with a '-'"); } } f = f.getParentFile(); } while (f != null); } } return file; } }); } catch (PrivilegedActionException x) { Exception e = x.getException(); if (e instanceof CommandSyntaxException) { throw (CommandSyntaxException) e; } else { throw new RuntimeException(e); } } } else { throw new CommandSyntaxException("invalid file name"); } } @Override public void doComplete(final CompletionInfo completions, final String partial, final int flags) { // Get last full directory from the partial pathname. final int idx = partial.lastIndexOf(File.separatorChar); final String dir; if (idx == 0) { dir = String.valueOf(File.separatorChar); } else if (idx > 0) { dir = partial.substring(0, idx); } else { dir = ""; } // Get the contents of that directory. (Note that the call to getProperty() // is needed because new File("").exists() returns false. According to Sun, this // behavior is "not a bug".) String user_dir = AccessController.doPrivileged(new GetPropertyAction("user.dir")); final File f = dir.isEmpty() ? new File(user_dir) : new File(dir); final String[] names = AccessController.doPrivileged( new PrivilegedAction<String[]>() { public String[] run() { if (!f.exists()) { return null; } else { return f.list(); } } }); if (names == null) { // The dir (or user.dir) denotes a non-existent directory. // No completions are possible for this path name. return; } final String prefix = (dir.length() == 0) ? "" : dir.equals("/") ? "/" : dir + File.separatorChar; AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { for (String n : names) { String name = prefix + n; if (name.startsWith(partial)) { if (new File(f, n).isDirectory()) { completions.addCompletion(name + File.separatorChar, true); } else { completions.addCompletion(name); } } } return null; } }); // Completion of "." and ".." as the last pathname component have to be dealt with // explicitly. The 'f.list()' call does not include "." and ".." in the result array. int tmp = partial.length() - idx; if ((tmp == 3 && partial.endsWith("..")) || (tmp == 2 && partial.endsWith("."))) { completions.addCompletion(partial + File.separatorChar, true); } // Add "-" as a possible completion? if (partial.length() == 0 && (flags & HYPHEN_IS_SPECIAL) != 0) { completions.addCompletion("-"); } } @Override protected String argumentKind() { return "file"; } @Override public int nameToFlag(String name) throws IllegalArgumentException { if (name.equals("ALLOW_DODGY_NAMES")) { return ALLOW_DODGY_NAMES; } else if (name.equals("HYPHEN_IS_SPECIAL")) { return HYPHEN_IS_SPECIAL; } else { return super.nameToFlag(name); } } }