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" };


Reply via email to