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