Author: gnodet Date: Thu May 6 09:51:02 2010 New Revision: 941637 URL: http://svn.apache.org/viewvc?rev=941637&view=rev Log: SSHD-86: SFTP folder browsing
Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java?rev=941637&r1=941636&r2=941637&view=diff ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java (original) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java Thu May 6 09:51:02 2010 @@ -330,6 +330,10 @@ public final class Buffer { wpos += r; } + /** + * Writes 32 bits + * @param i + */ public void putInt(long i) { ensureCapacity(4); data[wpos++] = (byte) (i >> 24); @@ -338,6 +342,22 @@ public final class Buffer { data[wpos++] = (byte) (i ); } + /** + * Writes 64 bits + * @param i + */ + public void putLong(long i) { + ensureCapacity(8); + data[wpos++] = (byte) (i >> 56); + data[wpos++] = (byte) (i >> 48); + data[wpos++] = (byte) (i >> 40); + data[wpos++] = (byte) (i >> 32); + data[wpos++] = (byte) (i >> 24); + data[wpos++] = (byte) (i >> 16); + data[wpos++] = (byte) (i >> 8); + data[wpos++] = (byte) (i ); + } + public void putBoolean(boolean b) { putByte(b ? (byte) 1 : (byte) 0); } Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java?rev=941637&r1=941636&r2=941637&view=diff ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java (original) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java Thu May 6 09:51:02 2010 @@ -19,6 +19,7 @@ package org.apache.sshd.common.util; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; @@ -527,6 +528,43 @@ public final class SelectorUtils { return ret; } + + /** + * Normalizes the path by removing '.', '..' and double separators (e.g. '//') + * @param path + * @param separator + * @return normalized path + * @throws IOException when the path is invalid (e.g. '/first/../..') + */ + public static String normalizePath(String path, String separator) throws IOException { + boolean startsWithSeparator = path.startsWith(separator); + // tokenize + List<String> tokens = tokenizePath(path, separator); + + // clean up + for (int i = tokens.size() - 1; i >= 0; i--) { + String t = tokens.get(i); + if (t.length() == 0 || t.equals(".")) { + tokens.remove(i); + } else if (t.equals("..")) { + tokens.remove(i); + if (i > 1) { + tokens.remove(--i); + } + } + } + + // serialize + StringBuffer buffer = new StringBuffer(path.length()); + + for (int i = 0; i < tokens.size(); i++) { + if (i > 0 || (i == 0 && startsWithSeparator)) buffer.append(separator); + buffer.append(tokens.get(i)); + } + + return buffer.toString(); + } + /** * Returns dependency information on these two files. If src has been Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java?rev=941637&r1=941636&r2=941637&view=diff ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java (original) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java Thu May 6 09:51:02 2010 @@ -34,6 +34,7 @@ import java.util.UUID; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.util.Buffer; +import org.apache.sshd.common.util.SelectorUtils; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; import org.apache.sshd.server.ExitCallback; @@ -52,8 +53,19 @@ public class SftpSubsystem implements Co protected final Logger log = LoggerFactory.getLogger(getClass()); public static class Factory implements NamedFactory<Command> { + + private File root; + + public Factory() { + this(new File(".")); + } + + public Factory(File root) { + this.root = root; + } + public Command create() { - return new SftpSubsystem(); + return new SftpSubsystem(this.root); } public String getName() { @@ -131,6 +143,7 @@ public class SftpSubsystem implements Co public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; //v3 naming convention public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; @@ -224,6 +237,7 @@ public class SftpSubsystem implements Co private ServerSession session; private boolean closed = false; + private File root; private int version; private Map<String, Handle> handles = new HashMap<String, Handle>(); @@ -285,6 +299,10 @@ public class SftpSubsystem implements Co } } + public SftpSubsystem(File root) { + this.root = root.getAbsoluteFile(); + } + public void setSession(ServerSession session) { this.session = session; } @@ -379,7 +397,7 @@ public class SftpSubsystem implements Co int pflags = buffer.getInt(); // attrs try { - File file = new File(path); + File file = resolveFile(path); RandomAccessFile raf; if (file.exists()) { if (((pflags & SSH_FXF_CREAT) != 0) && ((pflags & SSH_FXF_EXCL) != 0)) { @@ -411,7 +429,7 @@ public class SftpSubsystem implements Co int flags = buffer.getInt(); // attrs try { - File file = new File(path); + File file = resolveFile(path); RandomAccessFile raf; switch (flags & SSH_FXF_ACCESS_DISPOSITION) { case SSH_FXF_CREATE_NEW: { @@ -544,7 +562,7 @@ public class SftpSubsystem implements Co case SSH_FXP_STAT: { String path = buffer.getString(); try { - File p = new File(path); + File p = resolveFile(path); sendAttrs(id, p); } catch (FileNotFoundException e) { sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); @@ -572,7 +590,7 @@ public class SftpSubsystem implements Co case SSH_FXP_OPENDIR: { String path = buffer.getString(); try { - File p = new File(path); + File p = resolveFile(path); if (!p.exists()) { sendStatus(id, SSH_FX_NO_SUCH_FILE, path); } else if (!p.isDirectory()) { @@ -615,7 +633,7 @@ public class SftpSubsystem implements Co case SSH_FXP_REMOVE: { String path = buffer.getString(); try { - File p = new File(path); + File p = resolveFile(path); if (!p.exists()) { sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getPath()); } else if (p.isDirectory()) { @@ -634,7 +652,7 @@ public class SftpSubsystem implements Co String path = buffer.getString(); // attrs try { - File p = new File(path); + File p = resolveFile(path); if (p.exists()) { if (p.isDirectory()) { sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.getPath()); @@ -643,6 +661,8 @@ public class SftpSubsystem implements Co } } else if (!p.mkdir()) { throw new IOException("Error creating dir " + path); + } else { + sendStatus(id, SSH_FX_OK, ""); } } catch (IOException e) { sendStatus(id, SSH_FX_FAILURE, e.getMessage()); @@ -653,7 +673,7 @@ public class SftpSubsystem implements Co String path = buffer.getString(); // attrs try { - File p = new File(path); + File p = resolveFile(path); if (p.isDirectory()) { if (p.exists()) { if (p.listFiles().length == 0) { @@ -684,8 +704,8 @@ public class SftpSubsystem implements Co // TODO: handle optional args try { log.info("path=" + path); - File p = new File(path).getCanonicalFile(); - sendAbsoluteName(id, p); + File p = resolveFile(path); + sendPath(id, path, p); } catch (FileNotFoundException e) { e.printStackTrace(); sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); @@ -695,7 +715,26 @@ public class SftpSubsystem implements Co } break; } - + case SSH_FXP_RENAME: { + String oldPath = buffer.getString(); + String newPath = buffer.getString(); + try { + File o = resolveFile(oldPath); + File n = resolveFile(newPath); + if (!o.exists()) { + sendStatus(id, SSH_FX_NO_SUCH_FILE, o.getPath()); + } else if (n.exists()) { + sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, n.getPath()); + } else if (!o.renameTo(n)) { + sendStatus(id, SSH_FX_FAILURE, "Failed to rename file"); + } else { + sendStatus(id, SSH_FX_OK, ""); + } + } catch (IOException e) { + sendStatus(id, SSH_FX_FAILURE, e.getMessage()); + } + break; + } case SSH_FXP_SETSTAT: case SSH_FXP_FSETSTAT: { // This is required for WinSCP / Cyberduck to upload properly @@ -737,21 +776,27 @@ public class SftpSubsystem implements Co } - protected void sendAbsoluteName(int id, File file) throws IOException { + protected void sendPath(int id, String path, File f) throws IOException { Buffer buffer = new Buffer(); buffer.putByte((byte) SSH_FXP_NAME); buffer.putInt(id); buffer.putInt(1); - String name = file.getPath(); - if (File.separatorChar != '/') { - name = name.replace(File.separatorChar, '/'); + //normalize the given path, use *nix style separator + String normalizedPath = SelectorUtils.normalizePath(path, "/"); + if (normalizedPath.length() == 0) { + normalizedPath = "/"; + } + buffer.putString(normalizedPath); + f = new File(normalizedPath); + if (f.getName().length() == 0) { + f = new File(f, "."); } - if (!name.startsWith("/")) { - name = "/" + name; + if (version <= 3) { + buffer.putString(getLongName(f)); // Format specified in the specs + } else { + buffer.putString(f.getName()); // Supposed to be UTF-8 } - buffer.putString(name); - buffer.putString(name); - writeAttrs(buffer, file); + writeAttrs(buffer, f); send(buffer); } @@ -869,11 +914,16 @@ public class SftpSubsystem implements Co } */ if (file.isFile()) { - buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS); + buffer.putInt(SSH_FILEXFER_ATTR_SIZE| SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + buffer.putLong(file.length()); buffer.putInt(p); + buffer.putInt(file.lastModified()/1000); + buffer.putInt(file.lastModified()/1000); } else if (file.isDirectory()) { - buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS); + buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); buffer.putInt(p); + buffer.putInt(file.lastModified()/1000); + buffer.putInt(file.lastModified()/1000); } else { buffer.putInt(0); } @@ -905,6 +955,9 @@ public class SftpSubsystem implements Co closed = true; } + private File resolveFile(String path) { + return new File(this.root, path); + } private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };