You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/07/13 16:26:41 UTC

[2/2] mina-sshd git commit: [SSHD-538] Handle correctly paths with double-slashes in them

[SSHD-538] Handle correctly paths with double-slashes in them


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/5da0d1a8
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/5da0d1a8
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/5da0d1a8

Branch: refs/heads/master
Commit: 5da0d1a86bff354e71916954a31257f9c5e45dbc
Parents: 14579eb
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jul 13 17:26:28 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jul 13 17:26:28 2015 +0300

----------------------------------------------------------------------
 .../org/apache/sshd/common/scp/ScpHelper.java   |  10 +-
 .../java/org/apache/sshd/common/util/Pair.java  |   7 +
 .../apache/sshd/common/util/SelectorUtils.java  |  34 +-
 .../server/subsystem/sftp/SftpSubsystem.java    | 209 ++++----
 .../org/apache/sshd/client/scp/ScpTest.java     | 482 +++++++++++--------
 .../subsystem/sftp/SftpFileSystemTest.java      |  16 +-
 .../sshd/client/subsystem/sftp/SftpTest.java    | 161 +++++--
 .../AbstractCheckFileExtensionTest.java         |   7 +-
 .../AbstractMD5HashExtensionTest.java           |   9 +-
 .../extensions/CopyDataExtensionImplTest.java   |   9 +-
 .../sftp/extensions/OpenSSHExtensionsTest.java  |   7 +-
 .../server/subsystem/sftp/SshFsMounter.java     | 318 ++++++++++++
 .../org/apache/sshd/util/BaseTestSupport.java   |  29 +-
 13 files changed, 893 insertions(+), 405 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 96efe39..88e604a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -46,6 +46,7 @@ import org.apache.sshd.common.file.util.MockPath;
 import org.apache.sshd.common.scp.ScpTransferEventListener.FileOperation;
 import org.apache.sshd.common.util.DirectoryScanner;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.LimitInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -473,7 +474,7 @@ public class ScpHelper extends AbstractLoggingBean {
         }
     }
 
-    public Path resolveLocalPath(String basedir, String subpath) {
+    public Path resolveLocalPath(String basedir, String subpath) throws IOException {
         if (GenericUtils.isEmpty(basedir)) {
             return resolveLocalPath(subpath);
         } else {
@@ -481,8 +482,11 @@ public class ScpHelper extends AbstractLoggingBean {
         }
     }
 
-    public Path resolveLocalPath(String path) {
-        String localPath = (path == null) ? null : path.replace('/', File.separatorChar);
+    public Path resolveLocalPath(String remotePath) throws IOException {
+        // In case double slashes and other patterns are used 
+        String path = SelectorUtils.normalizePath(remotePath, "/");
+        String localPath = SelectorUtils.translateToLocalPath(path);
+
         return fileSystem.getPath(localPath);
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/main/java/org/apache/sshd/common/util/Pair.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/Pair.java b/sshd-core/src/main/java/org/apache/sshd/common/util/Pair.java
index d885b14..21e23d6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/Pair.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/Pair.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd.common.util;
 
+import java.util.Objects;
+
 /**
  * Represents a pair of values
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -38,4 +40,9 @@ public class Pair<U,V> {
     public V getSecond() {
         return second;
     }
+
+    @Override
+    public String toString() {
+        return Objects.toString(getFirst()) + ", " + Objects.toString(getSecond());
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
index 0b0c19e..2ac0976 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
@@ -532,33 +532,51 @@ public final class SelectorUtils {
     
     /**
      * Normalizes the path by removing '.', '..' and double separators (e.g. '//')
-     * @param path
-     * @param separator
+     * @param path Original path - ignored if {@code null}/empty
+     * @param separator The separator used for the path components 
      * @return normalized path
      * @throws IOException when the path is invalid (e.g. '/first/../..')
      */
     public static String normalizePath(String path, String separator) throws IOException {
+        if (GenericUtils.isEmpty(path)) {
+            return path;
+        }
+
         boolean startsWithSeparator = path.startsWith(separator);
-        // tokenize
         List<String> tokens = tokenizePath(path, separator);
-
+        int removedDots = 0;
         // clean up
         for (int i = tokens.size() - 1; i >= 0; i--) {
             String t = tokens.get(i);
-            if (t.length() == 0 || t.equals(".")) {
+            if (GenericUtils.isEmpty(t)) {
+                tokens.remove(i);
+            } else if (t.equals(".")) {
                 tokens.remove(i);
+                removedDots++;
             } else if (t.equals("..")) {
                 tokens.remove(i);
+                removedDots++;
                 if (i >= 1) {
                     tokens.remove(--i);
+                    removedDots++;
                 }
             }
         }
 
+        if (GenericUtils.isEmpty(tokens)) {
+            if (removedDots > 0) {
+                return "";  // had some "." and ".." after which we remained with no path 
+            } else {
+                return separator;   // it was all separators
+            }
+        }
+
         // serialize
         StringBuilder buffer = new StringBuilder(path.length());
         for (int i = 0; i < tokens.size(); i++) {
-            if (i > 0 || (i == 0 && startsWithSeparator)) buffer.append(separator);
+            if ((i > 0) || ((i == 0) && startsWithSeparator)) {
+                buffer.append(separator);
+            }
             buffer.append(tokens.get(i));
         }
 
@@ -574,7 +592,7 @@ public final class SelectorUtils {
      * Converts a possibly '/' separated path to a local path. <B>Note:</B>
      * takes special care of Windows drive paths - e.g., {@code C:}
      * by converting them to &quot;C:\&quot;
-     * @param path The original path
+     * @param path The original path - ignored if {@code null}/empty
      * @return The local path
      */
     public static String translateToLocalPath(String path) {
@@ -582,7 +600,7 @@ public final class SelectorUtils {
             return path;
         }
         
-        // this means we are running on Windows
+        // This code is reached if we are running on Windows
         String  localPath=path.replace('/', File.separatorChar);
         if ((localPath.length() < 2) || (localPath.charAt(1) != ':')) {
             return localPath;   // assume a relative path

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index add23fe..a12811f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -40,6 +40,7 @@ import java.nio.file.FileSystem;
 import java.nio.file.FileSystemLoopException;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
 import java.nio.file.LinkOption;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.NotDirectoryException;
@@ -88,11 +89,12 @@ import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.file.FileSystemAware;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
 import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.Pair;
 import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -1531,7 +1533,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             log.debug("Received SSH_FXP_STAT (path={}, flags=0x{})", path, Integer.toHexString(flags));
         }
         Path p = resolveFile(path);
-        return resolveFileAttributes(p, flags, true);
+        return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false));
     }
 
     protected void doRealPath(Buffer buffer, int id) throws IOException {
@@ -1543,12 +1545,21 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
 
         Map<String,?> attrs = Collections.<String, Object>emptyMap();
-        Path p;
+        Pair<Path,Boolean> result;
         try {
+            LinkOption[] options = IoUtils.getLinkOptions(false);
             if (version < SFTP_V6) {
-                p = doRealPathV6(id, path);
+                /*
+                 * See http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt:
+                 * 
+                 *      The SSH_FXP_REALPATH request can be used to have the server
+                 *      canonicalize any given path name to an absolute path.
+                 * 
+                 * See also SSHD-294
+                 */
+                result = doRealPathV345(id, path, options);
             } else {
-                // Read control byte
+                // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13 section 8.9
                 int control = 0;
                 if (buffer.available() > 0) {
                     control = buffer.getUByte();
@@ -1559,18 +1570,35 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                     extraPaths.add(buffer.getString());
                 }
 
-                p = doRealPathV345(id, path, extraPaths);
+                result = doRealPathV6(id, path, extraPaths, options);
+
+                Path p = result.getFirst();
+                Boolean status = result.getSecond();
                 if (control == SSH_FXP_REALPATH_STAT_IF) {
-                    try {
-                        attrs = getAttributes(p, false);
-                    } catch (IOException e) {
+                    if (status == null) {
+                        attrs = handleUnknownStatusFileAttributes(p, SSH_FILEXFER_ATTR_ALL, options);
+                    } else if (status.booleanValue()) {
+                        try {
+                            attrs = getAttributes(p, IoUtils.getLinkOptions(false));
+                        } catch (IOException e) {
+                            if (log.isDebugEnabled()) {
+                                log.debug("Failed ({}) to retrieve attributes of {}: {}",
+                                          e.getClass().getSimpleName(), p, e.getMessage());
+                            }
+                        }
+                    } else {
                         if (log.isDebugEnabled()) {
-                            log.debug("Failed ({}) to retrieve attributes of {}: {}",
-                                      e.getClass().getSimpleName(), p, e.getMessage());
+                            log.debug("Dummy attributes for non-existing file: " + p);
                         }
                     }
                 } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
-                    attrs = getAttributes(p, false);
+                    if (status == null) {
+                        attrs = handleUnknownStatusFileAttributes(p, SSH_FILEXFER_ATTR_ALL, options);
+                    } else if (status.booleanValue()) {
+                        attrs = getAttributes(p, options);
+                    } else {
+                        throw new FileNotFoundException(p.toString());
+                    }
                 }
             }
         } catch (IOException | RuntimeException e) {
@@ -1578,52 +1606,46 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             return;
         }
 
-        sendPath(BufferUtils.clear(buffer), id, p, attrs);
+        sendPath(BufferUtils.clear(buffer), id, result.getFirst(), attrs);
     }
 
-    protected Path doRealPathV345(int id, String path, Collection<String> extraPaths) throws IOException {
+    protected Pair<Path,Boolean> doRealPathV6(int id, String path, Collection<String> extraPaths, LinkOption ... options) throws IOException {
         Path p = resolveFile(path);
-        
-        if (GenericUtils.size(extraPaths) > 0) {
+        int numExtra = GenericUtils.size(extraPaths); 
+        if (numExtra > 0) {
+            StringBuilder sb = new StringBuilder(GenericUtils.length(path) + numExtra * 8);
+            sb.append(path);
+
             for (String p2 : extraPaths) {
                 p = p.resolve(p2);
+                sb.append('/').append(p2);
             }
+            
+            path = sb.toString();
         }
 
-        p = p.toAbsolutePath();
-        return p.normalize();
+        return validateRealPath(id, path, p, options);
     }
 
-    protected Path doRealPathV6(int id, String path) throws IOException {
-        Path f = resolveFile(path);
-        Path abs = f.toAbsolutePath();
-        Path p = abs.normalize();
-        Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_LINK_OPTIONS);
-        if (status == null) {
-            return handleUnknownRealPathStatus(path, abs, p);
-        } else if (status.booleanValue()) {
-            return p;
-        } else {
-            throw new FileNotFoundException(path);
-        }
+    protected Pair<Path,Boolean> doRealPathV345(int id, String path, LinkOption ... options) throws IOException {
+        return validateRealPath(id, path, resolveFile(path), options);
     }
 
-    protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
-        switch(unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case Warn:
-                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
-                break;
-            case ThrowException:
-                throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
-            
-            default:
-                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
-                       + " - unknown policy: " + unsupportedAttributePolicy);
-        }
-        
-        return absolute;
+    /**
+     * @param id The request identifier
+     * @param path The original path
+     * @param f The resolve {@link Path}
+     * @param options The {@link LinkOption}s to use to verify file existence and access
+     * @return A {@link Pair} whose left-hand is the <U>absolute <B>normalized</B></U>
+     * {@link Path} and right-hand is a {@link Boolean} indicating its status
+     * @throws IOException If failed to validate the file
+     * @see IoUtils#checkFileExists(Path, LinkOption...)
+     */
+    protected Pair<Path,Boolean> validateRealPath(int id, String path, Path f, LinkOption ... options) throws IOException {
+        Path abs = f.toAbsolutePath();
+        Path p = abs.normalize();
+        Boolean status = IoUtils.checkFileExists(p, options);
+        return new Pair<Path, Boolean>(p, status);
     }
 
     protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
@@ -1727,9 +1749,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 throw new EOFException("Directory reading is done");
             }
 
-            Path            file = dh.getFile();
-            LinkOption[]    options = IoUtils.getLinkOptions(false);
-            Boolean         status = IoUtils.checkFileExists(file, options);
+            Path file = dh.getFile();
+            LinkOption[] options = IoUtils.getLinkOptions(false);
+            Boolean status = IoUtils.checkFileExists(file, options);
             if (status == null) {
                 throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
             }
@@ -1755,8 +1777,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
                 int count = doReadDir(id, dh, reply, FactoryManagerUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH));
                 BufferUtils.updateLengthPlaceholder(reply, lenPos, count);
-                if (log.isTraceEnabled()) {
-                    log.trace("doReadDir({})[{}] - sent {} entries", handle, h, Integer.valueOf(count));
+                if (log.isDebugEnabled()) {
+                    log.debug("doReadDir({})[{}] - sent {} entries", handle, h, Integer.valueOf(count));
                 }
                 if ((!dh.isSendDot()) && (!dh.isSendDotDot()) && (!dh.hasNext())) {
                     // if no more files to send
@@ -1879,7 +1901,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             log.debug("Received SSH_FXP_FSTAT (handle={}[{}], flags=0x{})", handle, h, Integer.toHexString(flags));
         }
         
-        return resolveFileAttributes(validateHandle(handle, h, Handle.class).getFile(), flags, true);
+        return resolveFileAttributes(validateHandle(handle, h, Handle.class).getFile(), flags, IoUtils.getLinkOptions(true));
     }
 
     protected void doLStat(Buffer buffer, int id) throws IOException {
@@ -1906,7 +1928,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             log.debug("Received SSH_FXP_LSTAT (path={}[{}], flags=0x{})", path, p, Integer.toHexString(flags));
         }
 
-        return resolveFileAttributes(p, flags, false);
+        return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false));
     }
 
     protected void doWrite(Buffer buffer, int id) throws IOException {
@@ -2389,16 +2411,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
      */
     protected int doReadDir(int id, DirectoryHandle dir, Buffer buffer, int maxSize) throws IOException {
         int nb = 0;
+        LinkOption[] options = IoUtils.getLinkOptions(false);
         while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && (buffer.wpos() < maxSize)) {
             if (dir.isSendDot()) {
-                writeDirEntry(id, dir, buffer, nb, dir.getFile(), ".");
+                writeDirEntry(id, dir, buffer, nb, dir.getFile(), ".", options);
                 dir.markDotSent();    // do not send it again
             } else if (dir.isSendDotDot()) {
-                writeDirEntry(id, dir, buffer, nb, dir.getFile().getParent(), "..");
+                writeDirEntry(id, dir, buffer, nb, dir.getFile().getParent(), "..", options);
                 dir.markDotDotSent(); // do not send it again
             } else {
                 Path f = dir.next();
-                writeDirEntry(id, dir, buffer, nb, f, getShortName(f));
+                writeDirEntry(id, dir, buffer, nb, f, getShortName(f), options);
             }
 
             nb++;
@@ -2414,34 +2437,36 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
      * @param index Zero-based index of the entry to be written
      * @param f The entry {@link Path}
      * @param shortName The entry short name
+     * @param options The {@link LinkOption}s to use for querying the entry-s attributes
      * @throws IOException If failed to generate the entry data
      */
-    protected void writeDirEntry(int id, DirectoryHandle dir, Buffer buffer, int index, Path f, String shortName) throws IOException {
+    protected void writeDirEntry(int id, DirectoryHandle dir, Buffer buffer, int index, Path f, String shortName, LinkOption ... options) throws IOException {
+        Map<String,?> attrs = resolveFileAttributes(f, SSH_FILEXFER_ATTR_ALL, options);
+
         buffer.putString(shortName);
         if (version == SFTP_V3) {
-            String  longName = getLongName(f);
+            String  longName = getLongName(f, options);
             buffer.putString(longName);
             if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName + " [" + longName + "]");
+                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName + " [" + longName + "]: " + attrs);
             }
         } else {
             if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName);
+                log.trace("writeDirEntry(id=" + id + ")[" + index + "] - " + shortName + ": " + attrs);
             }
         }
         
-        Map<String,?> attrs = resolveFileAttributes(f, SSH_FILEXFER_ATTR_ALL, false);
         writeAttrs(buffer, attrs);
     }
 
-    protected String getLongName(Path f) throws IOException {
-        return getLongName(f, true);
+    protected String getLongName(Path f, LinkOption ... options) throws IOException {
+        return getLongName(f, true, options);
     }
 
-    private String getLongName(Path f, boolean sendAttrs) throws IOException {
+    private String getLongName(Path f, boolean sendAttrs, LinkOption ... options) throws IOException {
         Map<String, Object> attributes;
         if (sendAttrs) {
-            attributes = getAttributes(f, false);
+            attributes = getAttributes(f, options);
         } else {
             attributes = Collections.emptyMap();
         }
@@ -2509,7 +2534,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return sb.toString();
     }
 
-    protected String getShortName(Path f) {
+    protected String getShortName(Path f) throws IOException {
         if (OsUtils.isUNIX()) {
             Path    name=f.getFileName();
             if (name == null) {
@@ -2578,15 +2603,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return pf;
     }
 
-    protected Map<String, Object> resolveFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
-        LinkOption[] options = IoUtils.getLinkOptions(followLinks);
-        Boolean      status = IoUtils.checkFileExists(file, options);
+    protected Map<String, Object> resolveFileAttributes(Path file, int flags, LinkOption ... options) throws IOException {
+        Boolean status = IoUtils.checkFileExists(file, options);
         if (status == null) {
-            return handleUnknownStatusFileAttributes(file, flags, followLinks);
+            return handleUnknownStatusFileAttributes(file, flags, options);
         } else if (!status.booleanValue()) {
             throw new FileNotFoundException(file.toString());
         } else {
-            return getAttributes(file, flags, followLinks);
+            return getAttributes(file, flags, options);
         }
     }
 
@@ -2674,13 +2698,13 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return (bool != null) && bool.booleanValue();
     }
 
-    protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
-        return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
+    protected Map<String, Object> getAttributes(Path file, LinkOption ... options) throws IOException {
+        return getAttributes(file, SSH_FILEXFER_ATTR_ALL, options);
     }
 
     public static final List<String>    DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
 
-    protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
+    protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, LinkOption ... options) throws IOException {
         switch(unsupportedAttributePolicy) {
             case Ignore:
                 break;
@@ -2693,13 +2717,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
         }
         
-        return getAttributes(file, flags, followLinks);
+        return getAttributes(file, flags, options);
     }
 
-    protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
+    protected Map<String, Object> getAttributes(Path file, int flags, LinkOption ... options) throws IOException {
         FileSystem          fs=file.getFileSystem();
         Collection<String>  supportedViews=fs.supportedFileAttributeViews();
-        LinkOption[]        opts=IoUtils.getLinkOptions(followLinks);
         Map<String,Object>  attrs=new HashMap<>();
         Collection<String>  views;
 
@@ -2715,7 +2738,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
 
         for (String v : views) {
-            Map<String, Object> ta=readFileAttributes(file, v, opts);
+            Map<String, Object> ta=readFileAttributes(file, v, options);
             attrs.putAll(ta);
         }
 
@@ -2728,15 +2751,15 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return attrs;
     }
 
-    protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
+    protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... options) throws IOException {
         try {
-            return Files.readAttributes(file, view, opts);
+            return Files.readAttributes(file, view, options);
         } catch(IOException e) {
-            return handleReadFileAttributesException(file, view, opts, e);
+            return handleReadFileAttributesException(file, view, options, e);
         }
     }
 
-    protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
+    protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] options, IOException e) throws IOException {
         switch(unsupportedAttributePolicy) {
             case Ignore:
                 break;
@@ -3167,6 +3190,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             return SSH_FX_OP_UNSUPPORTED;
         } else if (e instanceof IllegalArgumentException) {
             return SSH_FX_INVALID_PARAMETER;
+        } else if (e instanceof InvalidPathException) {
+            return SSH_FX_INVALID_FILENAME;
         } else {
             return SSH_FX_FAILURE;
         }
@@ -3239,23 +3264,29 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
     }
 
-    private Path resolveFile(String path) {
-        //in case we are running on Windows
+    protected Path resolveFile(String remotePath) throws IOException, InvalidPathException {
+        // In case double slashes and other patterns are used 
+        String path = SelectorUtils.normalizePath(remotePath, "/");
         String localPath = SelectorUtils.translateToLocalPath(path);
+
+        // In case we are running on Windows
         return defaultDir.resolve(localPath);
     }
 
-    private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
-            "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+    public static final List<String> MONTHS =
+        Collections.unmodifiableList(
+                Arrays.asList(
+                        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+                    ));
 
     /**
      * Get unix style date string.
      */
-    private static String getUnixDate(FileTime time) {
+    public static String getUnixDate(FileTime time) {
         return getUnixDate(time != null ? time.toMillis() : -1);
     }
 
-    private static String getUnixDate(long millis) {
+    public static String getUnixDate(long millis) {
         if (millis < 0) {
             return "------------";
         }
@@ -3265,7 +3296,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         cal.setTimeInMillis(millis);
 
         // month
-        sb.append(MONTHS[cal.get(Calendar.MONTH)]);
+        sb.append(MONTHS.get(cal.get(Calendar.MONTH)));
         sb.append(' ');
 
         // day

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
index 945f4ef..a1291a0 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -39,11 +39,14 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.scp.ScpHelper;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.command.ScpCommandFactory;
@@ -135,42 +138,104 @@ public class ScpTest extends BaseTestSupport {
     }
 
     @Test
-    public void testUploadAbsoluteDriveLetter() throws Exception {
+    public void testNormalizedScpRemotePaths() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(scpRoot);
+
+        Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path localFile = localDir.resolve("file.txt");
+        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+
+        Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+        Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
+        String localPath = localFile.toString();
+        String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+        String[] remoteComps = GenericUtils.split(remotePath, '/');
+
         try (SshClient client = SshClient.setUpDefaultClient()) {
             client.start();
 
-            try {
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
-
-                    ScpClient scp = createScpClient(session);
-                    Path targetPath = detectTargetFolder().toPath();
-                    Path parentPath = targetPath.getParent();
-                    Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
-                    Utils.deleteRecursive(scpRoot);
-
-                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-                    Path localFile = localDir.resolve(getCurrentTestName() + "-1.txt");
-                    byte[] data = writeFile(localFile, (getCurrentTestName() + "\n"));
-
-                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-                    Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
-                    String localPath = localFile.toString();
-                    String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
-                    scp.upload(localPath, remotePath);
-                    assertFileLength(remoteFile, data.length, 5000);
-
-                    Path secondRemote = remoteDir.resolve(getCurrentTestName() + "-2.txt");
-                    String secondPath = Utils.resolveRelativeRemotePath(parentPath, secondRemote);
-                    scp.upload(localPath, secondPath);
-                    assertFileLength(secondRemote, data.length, 5000);
+            Factory<? extends Random> factory = client.getRandomFactory();
+            Random rnd = factory.create();
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                StringBuilder sb = new StringBuilder(remotePath.length() + Long.SIZE);
+                for (int i = 0; i < Math.max(Long.SIZE, remoteComps.length); i++) {
+                    if (sb.length() > 0) {
+                        sb.setLength(0);    // start again
+                    }
                     
-                    Path pathRemote = remoteDir.resolve(getCurrentTestName() + "-path.txt");
-                    String pathPath = Utils.resolveRelativeRemotePath(parentPath, pathRemote);
-                    scp.upload(localFile, pathPath);
-                    assertFileLength(pathRemote, data.length, 5000);
+                    sb.append(remoteComps[0]);
+                    for (int j = 1; j < remoteComps.length; j++) {
+                        String name = remoteComps[j];
+                        slashify(sb, rnd);
+                        sb.append(name);
+                    }
+                    slashify(sb, rnd);
+                    
+                    String path = sb.toString();
+                    scp.upload(localPath, path);
+                    assertTrue("Remote file not ready for " + path, waitForFile(remoteFile, data.length, TimeUnit.SECONDS.toMillis(5L)));
+                    
+                    byte[] actual = Files.readAllBytes(remoteFile);
+                    assertArrayEquals("Mismatched uploaded data for " + path, data, actual);
+                    Files.delete(remoteFile);
+                    assertFalse("Remote file (" + remoteFile + ") not deleted for " + path, Files.exists(remoteFile));
                 }
+            }
+        }
+    }
+
+    private static int slashify(StringBuilder sb, Random rnd) {
+        int slashes = 1 /* at least one slash */ + rnd.random(Byte.SIZE);
+        for (int k=0; k < slashes; k++) {
+            sb.append('/');
+        }
+        
+        return slashes;
+    }
+
+    @Test
+    public void testUploadAbsoluteDriveLetter() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(scpRoot);
+
+        Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path localFile = localDir.resolve("file-1.txt");
+        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+
+        Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+        Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
+        String localPath = localFile.toString();
+        String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                scp.upload(localPath, remotePath);
+                assertFileLength(remoteFile, data.length, TimeUnit.SECONDS.toMillis(5L));
+
+                Path secondRemote = remoteDir.resolve("file-2.txt");
+                String secondPath = Utils.resolveRelativeRemotePath(parentPath, secondRemote);
+                scp.upload(localPath, secondPath);
+                assertFileLength(secondRemote, data.length, TimeUnit.SECONDS.toMillis(5L));
+                
+                Path pathRemote = remoteDir.resolve("file-path.txt");
+                String pathPath = Utils.resolveRelativeRemotePath(parentPath, pathRemote);
+                scp.upload(localFile, pathPath);
+                assertFileLength(pathRemote, data.length, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -182,31 +247,29 @@ public class ScpTest extends BaseTestSupport {
         try (SshClient client = SshClient.setUpDefaultClient()) {
             client.start();
 
-            try {
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                    ScpClient scp = createScpClient(session);
-                    String data = getCurrentTestName() + "\n";
+                ScpClient scp = createScpClient(session);
+                String data = getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator");
 
-                    Path targetPath = detectTargetFolder().toPath();
-                    Path parentPath = targetPath.getParent();
-                    Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
-                    Utils.deleteRecursive(scpRoot);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+                Utils.deleteRecursive(scpRoot);
 
-                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-                    Path localFile = localDir.resolve(getCurrentTestName() + ".txt");
-                    writeFile(localFile, data);
+                Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                Path localFile = localDir.resolve("file.txt");
+                writeFile(localFile, data);
 
-                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-                    Path remoteFile = remoteDir.resolve(localFile.getFileName());
-                    writeFile(remoteFile, data + data);
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                Path remoteFile = remoteDir.resolve(localFile.getFileName());
+                writeFile(remoteFile, data + data);
 
-                    String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
-                    scp.upload(localFile.toString(), remotePath);
-                    assertFileLength(remoteFile, data.length(), 5000);
-                }
+                String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+                scp.upload(localFile.toString(), remotePath);
+                assertFileLength(remoteFile, data.length(), TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -219,7 +282,7 @@ public class ScpTest extends BaseTestSupport {
         Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
         Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
         Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-        Path zeroLocal = localDir.resolve(getCurrentTestName());
+        Path zeroLocal = localDir.resolve("zero.txt");
 
         try (FileChannel fch = FileChannel.open(zeroLocal, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
             if (fch.size() > 0L) {
@@ -234,18 +297,16 @@ public class ScpTest extends BaseTestSupport {
         }
 
         try (SshClient client = SshClient.setUpDefaultClient()) {
-            try {
-                client.start();
+            client.start();
 
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                    ScpClient scp = createScpClient(session);
-                    String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
-                    scp.upload(zeroLocal.toString(), remotePath);
-                    assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
-                }
+                ScpClient scp = createScpClient(session);
+                String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+                scp.upload(zeroLocal.toString(), remotePath);
+                assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -272,18 +333,16 @@ public class ScpTest extends BaseTestSupport {
         assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote));
 
         try (SshClient client = SshClient.setUpDefaultClient()) {
-            try {
-                client.start();
+            client.start();
 
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                    ScpClient scp = createScpClient(session);
-                    String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
-                    scp.download(remotePath, zeroLocal.toString());
-                    assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
-                }
+                ScpClient scp = createScpClient(session);
+                String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+                scp.download(remotePath, zeroLocal.toString());
+                assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -292,51 +351,50 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnSingleFile() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
+        String data = getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator");
+
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(scpRoot);
 
-            try {
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
+        Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path localOutFile = localDir.resolve("file-1.txt");
+        Path remoteDir = scpRoot.resolve("remote");
+        Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
 
-                    ScpClient scp = createScpClient(session);
-                    String data = getCurrentTestName() + "\n";
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-                    Path targetPath = detectTargetFolder().toPath();
-                    Path parentPath = targetPath.getParent();
-                    Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
-                    Utils.deleteRecursive(scpRoot);
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-                    Path localOutFile = localDir.resolve(getCurrentTestName() + "-1.txt");
-                    writeFile(localOutFile, data);
+                ScpClient scp = createScpClient(session);
+                writeFile(localOutFile, data);
 
-                    Path remoteDir = scpRoot.resolve("remote");
-                    Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
-                    assertFalse("Remote folder already exists: " + remoteDir, Files.exists(remoteDir));
-                    
-                    String localOutPath = localOutFile.toString();
-                    String remoteOutPath = Utils.resolveRelativeRemotePath(parentPath, remoteOutFile);
-                    try {
-                        scp.upload(localOutPath, remoteOutPath);
-                        fail("Expected IOException for 1st time " + remoteOutPath);
-                    } catch(IOException e) {
-                        // ok
-                    }
-                    
-                    Files.createDirectories(remoteDir);
+                assertFalse("Remote folder already exists: " + remoteDir, Files.exists(remoteDir));
+                
+                String localOutPath = localOutFile.toString();
+                String remoteOutPath = Utils.resolveRelativeRemotePath(parentPath, remoteOutFile);
+                try {
                     scp.upload(localOutPath, remoteOutPath);
-                    assertFileLength(remoteOutFile, data.length(), 5000);
-
-                    Path secondLocal = localDir.resolve(localOutFile.getFileName());
-                    scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, secondLocal));
-                    assertFileLength(secondLocal, data.length(), 5000);
-                    
-                    Path localPath = localDir.resolve(getCurrentTestName() + "-path.txt");
-                    scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, localPath));
-                    assertFileLength(localPath, data.length(), 5000);
+                    fail("Expected IOException for 1st time " + remoteOutPath);
+                } catch(IOException e) {
+                    // ok
                 }
+                
+                assertHierarchyTargetFolderExists(remoteDir);
+                scp.upload(localOutPath, remoteOutPath);
+                assertFileLength(remoteOutFile, data.length(), TimeUnit.SECONDS.toMillis(5L));
+
+                Path secondLocal = localDir.resolve(localOutFile.getFileName());
+                scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, secondLocal));
+                assertFileLength(secondLocal, data.length(), TimeUnit.SECONDS.toMillis(5L));
+                
+                Path localPath = localDir.resolve("file-path.txt");
+                scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, localPath));
+                assertFileLength(localPath, data.length(), TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -348,78 +406,76 @@ public class ScpTest extends BaseTestSupport {
         try (SshClient client = SshClient.setUpDefaultClient()) {
             client.start();
 
-            try {
-                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                    session.addPasswordIdentity(getCurrentTestName());
-                    session.auth().verify(5L, TimeUnit.SECONDS);
-
-                    ScpClient scp = createScpClient(session);
-                    Path targetPath = detectTargetFolder().toPath();
-                    Path parentPath = targetPath.getParent();
-                    Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
-                    Utils.deleteRecursive(scpRoot);
-
-                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-                    Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
-                    byte[] data = writeFile(local1, getCurrentTestName() + "\n");
-
-                    Path local2 = localDir.resolve(getCurrentTestName() + "-2.txt");
-                    Files.write(local2, data);
-
-                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-                    Path remote1 = remoteDir.resolve(local1.getFileName());
-                    String remote1Path = Utils.resolveRelativeRemotePath(parentPath, remote1);
-                    String[] locals = { local1.toString(), local2.toString() };
-                    try {
-                        scp.upload(locals, remote1Path);
-                        fail("Unexpected upload success to missing remote file: " + remote1Path);
-                    } catch (IOException e) {
-                        // Ok
-                    }
-                    
-                    Files.write(remote1, data);
-                    try {
-                        scp.upload(locals, remote1Path);
-                        fail("Unexpected upload success to existing remote file: " + remote1Path);
-                    } catch (IOException e) {
-                        // Ok
-                    }
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                    Path remoteSubDir = assertHierarchyTargetFolderExists(remoteDir.resolve("dir"));
-                    scp.upload(locals, Utils.resolveRelativeRemotePath(parentPath, remoteSubDir));
-                    
-                    Path remoteSub1 = remoteSubDir.resolve(local1.getFileName());
-                    assertFileLength(remoteSub1, data.length, 5000);
-
-                    Path remoteSub2 = remoteSubDir.resolve(local2.getFileName());
-                    assertFileLength(remoteSub2, data.length, 5000);
-
-                    String[] remotes = {
-                            Utils.resolveRelativeRemotePath(parentPath, remoteSub1),
-                            Utils.resolveRelativeRemotePath(parentPath, remoteSub2),
-                        };
-
-                    try {
-                        scp.download(remotes, Utils.resolveRelativeRemotePath(parentPath, local1));
-                        fail("Unexpected download success to existing local file: " + local1);
-                    } catch (IOException e) {
-                        // Ok
-                    }
-                    
-                    Path localSubDir = localDir.resolve("dir");
-                    try {
-                        scp.download(remotes, localSubDir);
-                        fail("Unexpected download success to non-existing folder: " + localSubDir);
-                    } catch (IOException e) {
-                        // Ok
-                    }
+                ScpClient scp = createScpClient(session);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+                Utils.deleteRecursive(scpRoot);
 
-                    Files.createDirectories(localSubDir);
-                    scp.download(remotes, localSubDir);
+                Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                Path local1 = localDir.resolve("file-1.txt");
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+
+                Path local2 = localDir.resolve("file-2.txt");
+                Files.write(local2, data);
 
-                    assertFileLength(localSubDir.resolve(remoteSub1.getFileName()), data.length, 5000);
-                    assertFileLength(localSubDir.resolve(remoteSub2.getFileName()), data.length, 5000);
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                Path remote1 = remoteDir.resolve(local1.getFileName());
+                String remote1Path = Utils.resolveRelativeRemotePath(parentPath, remote1);
+                String[] locals = { local1.toString(), local2.toString() };
+                try {
+                    scp.upload(locals, remote1Path);
+                    fail("Unexpected upload success to missing remote file: " + remote1Path);
+                } catch (IOException e) {
+                    // Ok
+                }
+                
+                Files.write(remote1, data);
+                try {
+                    scp.upload(locals, remote1Path);
+                    fail("Unexpected upload success to existing remote file: " + remote1Path);
+                } catch (IOException e) {
+                    // Ok
                 }
+
+                Path remoteSubDir = assertHierarchyTargetFolderExists(remoteDir.resolve("dir"));
+                scp.upload(locals, Utils.resolveRelativeRemotePath(parentPath, remoteSubDir));
+                
+                Path remoteSub1 = remoteSubDir.resolve(local1.getFileName());
+                assertFileLength(remoteSub1, data.length, TimeUnit.SECONDS.toMillis(5L));
+
+                Path remoteSub2 = remoteSubDir.resolve(local2.getFileName());
+                assertFileLength(remoteSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
+
+                String[] remotes = {
+                        Utils.resolveRelativeRemotePath(parentPath, remoteSub1),
+                        Utils.resolveRelativeRemotePath(parentPath, remoteSub2),
+                    };
+
+                try {
+                    scp.download(remotes, Utils.resolveRelativeRemotePath(parentPath, local1));
+                    fail("Unexpected download success to existing local file: " + local1);
+                } catch (IOException e) {
+                    // Ok
+                }
+                
+                Path localSubDir = localDir.resolve("dir");
+                try {
+                    scp.download(remotes, localSubDir);
+                    fail("Unexpected download success to non-existing folder: " + localSubDir);
+                } catch (IOException e) {
+                    // Ok
+                }
+
+                assertHierarchyTargetFolderExists(localSubDir);
+                scp.download(remotes, localSubDir);
+
+                assertFileLength(localSubDir.resolve(remoteSub1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(localSubDir.resolve(remoteSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -443,23 +499,23 @@ public class ScpTest extends BaseTestSupport {
 
                 Path localDir = scpRoot.resolve("local");
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
-                Path localSub1 = localSubDir.resolve(getCurrentTestName() + "-1.txt");
-                byte[] data = writeFile(localSub1, getCurrentTestName() + "\n");
-                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Path localSub1 = localSubDir.resolve("file-1.txt");
+                byte[] data = writeFile(localSub1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
 
                 Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
                 scp.upload(localSubDir, Utils.resolveRelativeRemotePath(parentPath, remoteDir), ScpClient.Option.Recursive);
                 
                 Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
-                assertFileLength(remoteSubDir.resolve(localSub1.getFileName()), data.length, 5000);
-                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, 5000);
+                assertFileLength(remoteSubDir.resolve(localSub1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 Utils.deleteRecursive(localSubDir);
 
                 scp.download(Utils.resolveRelativeRemotePath(parentPath, remoteSubDir), localDir, ScpClient.Option.Recursive);
-                assertFileLength(localSub1, data.length, 5000);
-                assertFileLength(localSub2, data.length, 5000);
+                assertFileLength(localSub1, data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -482,22 +538,22 @@ public class ScpTest extends BaseTestSupport {
                 Utils.deleteRecursive(scpRoot);
 
                 Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
-                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
-                Path local2 = localDir.resolve(getCurrentTestName() + "-2.txt");
+                Path local1 = localDir.resolve("file-1.txt");
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                Path local2 = localDir.resolve("file-2.txt");
                 Files.write(local2, data);
 
                 Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
                 String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
                 scp.upload(localDir.toString() + File.separator + "*", remotePath);
-                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, 5000);
-                assertFileLength(remoteDir.resolve(local2.getFileName()), data.length, 5000);
+                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(remoteDir.resolve(local2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 Files.delete(local1);
                 Files.delete(local2);
                 scp.download(remotePath + "/*", localDir);
-                assertFileLength(local1, data.length, 5000);
-                assertFileLength(local2, data.length, 5000);
+                assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(local2, data.length, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -521,30 +577,30 @@ public class ScpTest extends BaseTestSupport {
 
                 Path localDir = scpRoot.resolve("local");
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
-                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
-                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
-                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Path local1 = localDir.resolve("file-1.txt");
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
 
                 Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
                 String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
                 scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive);
-                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, 5000);
+                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
-                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, 5000);
+                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 Files.delete(local1);
                 Utils.deleteRecursive(localSubDir);
 
                 scp.download(remotePath + "/*", localDir);
-                assertFileLength(local1, data.length, 5000);
+                assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
                 assertFalse("Unexpected recursive local file: " + localSub2, Files.exists(localSub2));
 
                 Files.delete(local1);
                 scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive);
-                assertFileLength(local1, data.length, 5000);
-                assertFileLength(localSub2, data.length, 5000);
+                assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
+                assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
             } finally {
                 client.stop();
             }
@@ -570,14 +626,14 @@ public class ScpTest extends BaseTestSupport {
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
                 // convert everything to seconds since this is the SCP timestamps granularity
                 long lastMod = TimeUnit.MILLISECONDS.toSeconds(Files.getLastModifiedTime(localSubDir).toMillis() - TimeUnit.DAYS.toMillis(1));
-                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
-                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
+                Path local1 = localDir.resolve("file-1.txt");
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
                 File lclFile1 = local1.toFile();
                 lclFile1.setLastModified(lastMod);
                 lclFile1.setExecutable(true, true);
                 lclFile1.setWritable(false, false);
 
-                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
                 File lclSubFile2 = localSub2.toFile();
                 lclSubFile2.setLastModified(lastMod);
@@ -587,25 +643,25 @@ public class ScpTest extends BaseTestSupport {
                 scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
 
                 Path remote1 = remoteDir.resolve(local1.getFileName());
-                assertFileLength(remote1, data.length, 5000);
+                assertFileLength(remote1, data.length, TimeUnit.SECONDS.toMillis(5L));
                 
                 File remFile1 = remote1.toFile();
                 assertLastModifiedTimeEquals(remFile1, lastMod);
 
                 Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
                 Path remoteSub2 = remoteSubDir.resolve(localSub2.getFileName());
-                assertFileLength(remoteSub2, data.length, 5000);
+                assertFileLength(remoteSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 File remSubFile2 = remoteSub2.toFile();
                 assertLastModifiedTimeEquals(remSubFile2, lastMod);
 
                 Utils.deleteRecursive(localDir);
-                Files.createDirectories(localDir);
+                assertHierarchyTargetFolderExists(localDir);
 
                 scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
-                assertFileLength(local1, data.length, 5000);
+                assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
                 assertLastModifiedTimeEquals(lclFile1, lastMod);
-                assertFileLength(localSub2, data.length, 5000);
+                assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
                 assertLastModifiedTimeEquals(lclSubFile2, lastMod);
             } finally {
                 client.stop();
@@ -629,7 +685,7 @@ public class ScpTest extends BaseTestSupport {
                 Utils.deleteRecursive(scpRoot);
 
                 Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-                Path remoteFile = remoteDir.resolve(getCurrentTestName() + ".txt");
+                Path remoteFile = remoteDir.resolve("file.txt");
                 String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
                 byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
                 scp.upload(data, remotePath, EnumSet.allOf(PosixFilePermission.class), null);
@@ -687,12 +743,12 @@ public class ScpTest extends BaseTestSupport {
             target.delete();
             assertFalse(target.exists());
             sendFile(unixPath, fileName, data);
-            assertFileLength(target, data.length(), 5000);
+            assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(5L));
     
             target.delete();
             assertFalse(target.exists());
             sendFile(unixDir, fileName, data);
-            assertFileLength(target, data.length(), 5000);
+            assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(5L));
     
             sendFileError("target", ScpHelper.SCP_COMMAND_PREFIX, data);
     
@@ -705,7 +761,7 @@ public class ScpTest extends BaseTestSupport {
             root.delete();
     
             sendDir("target", ScpHelper.SCP_COMMAND_PREFIX, fileName, data);
-            assertFileLength(target, data.length(), 5000);
+            assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(5L));
         } finally {
             session.disconnect();
         }
@@ -721,7 +777,7 @@ public class ScpTest extends BaseTestSupport {
         byte[] expected = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
         Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
         String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
-        String fileName = getCurrentTestName() + ".txt";
+        String fileName = "file.txt";
         Path remoteFile = remoteDir.resolve(fileName);
         String mode = ScpHelper.getOctalPermissions(EnumSet.of(
                     PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE,

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
index 02f2068..aee7420 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -147,9 +147,9 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
             Path parentPath = targetPath.getParent();
             Path clientFolder = lclSftp.resolve("client");
-            String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
+            String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
             Path file = fs.getPath(remFilePath);
-            Files.createDirectories(file.getParent());
+            assertHierarchyTargetFolderExists(file.getParent());
             Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
     
             Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
@@ -183,10 +183,10 @@ public class SftpFileSystemTest extends BaseTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path rootNative = targetPath.resolve("root").toAbsolutePath();
         Utils.deleteRecursive(rootNative);
-        Files.createDirectories(rootNative);
+        assertHierarchyTargetFolderExists(rootNative);
 
         try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
-            Path dir = Files.createDirectories(fs.getPath("test/foo"));
+            Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
             System.out.println("Created " + dir);
         }
     }
@@ -341,9 +341,9 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = lclSftp.resolve("client");
-        String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
+        String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
         Path file1 = fs.getPath(remFile1Path);
-        Files.createDirectories(file1.getParent());
+        assertHierarchyTargetFolderExists(file1.getParent());
 
         String  expected="Hello world: " + getCurrentTestName();
         {
@@ -352,9 +352,9 @@ public class SftpFileSystemTest extends BaseTestSupport {
             assertEquals("Mismatched read test data", expected, buf);
         }
 
-        String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
+        String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
         Path file2 = fs.getPath(remFile2Path);
-        String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
+        String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
         Path file3 = fs.getPath(remFile3Path);
         try {
             Files.move(file2, file3);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5da0d1a8/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index 81d1525..d39a6f3 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -113,23 +113,115 @@ public class SftpTest extends AbstractSftpClientTestSupport {
     }
 
     @Test
-    public void testOpen() throws Exception {
+    public void testNormalizeRemoteRootValues() throws Exception {
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                try(SftpClient sftp = session.createSftpClient()) {
+                    StringBuilder sb = new StringBuilder(Long.SIZE + 1);
+                    String expected = sftp.canonicalPath("/");
+                    for (int i = 0; i < Long.SIZE; i++) {
+                        if (sb.length() > 0) {
+                            sb.setLength(0);
+                        }
+                        
+                        for (int j = 1; j <= i; j++) {
+                            sb.append('/');
+                        }
+ 
+                        String remotePath = sb.toString();
+                        String actual = sftp.canonicalPath(remotePath);
+                        assertEquals("Mismatched roots for " + remotePath.length() + " slashes", expected, actual);
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testNormalizeRemotePathsValues() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+
+        Path testFile = assertHierarchyTargetFolderExists(lclSftp).resolve("file.txt");
+
+        String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
+        String[] comps = GenericUtils.split(file, '/');
         try(SshClient client = SshClient.setUpDefaultClient()) {
             client.start();
             
+            Factory<? extends Random> factory = client.getRandomFactory();
+            Random rnd = factory.create();
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
                 session.addPasswordIdentity(getCurrentTestName());
                 session.auth().verify(5L, TimeUnit.SECONDS);
 
-                Path targetPath = detectTargetFolder().toPath();
-                Path parentPath = targetPath.getParent();
-                Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-                Path clientFolder = lclSftp.resolve("client");
-                Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt");
-                String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
+                try(SftpClient sftp = session.createSftpClient()) {
+                    StringBuilder sb = new StringBuilder(file.length() + comps.length);
+                    String expected = sftp.canonicalPath(file);
+                    for (int i = 0; i < file.length(); i++) {
+                        if (sb.length() > 0) {
+                            sb.setLength(0);
+                        }
+                        
+                        sb.append(comps[0]);
+                        for (int j = 1; j < comps.length; j++) {
+                            String name = comps[j];
+                            slashify(sb, rnd);
+                            sb.append(name);
+                        }
+                        slashify(sb, rnd);
+                        
+                        if (rnd.random(Byte.SIZE) < (Byte.SIZE / 2)) {
+                            sb.append('.');
+                        }
+
+                        String remotePath = sb.toString();
+                        String actual = sftp.canonicalPath(remotePath);
+                        assertEquals("Mismatched canonical value for " + remotePath, expected, actual);
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    private static int slashify(StringBuilder sb, Random rnd) {
+        int slashes = 1 /* at least one slash */ + rnd.random(Byte.SIZE);
+        for (int k=0; k < slashes; k++) {
+            sb.append('/');
+        }
+        
+        return slashes;
+    }
+
+    @Test
+    public void testOpen() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Path clientFolder = lclSftp.resolve("client");
+        Path testFile = clientFolder.resolve("file.txt");
+        String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
+
+        File javaFile = testFile.toFile();
+        assertHierarchyTargetFolderExists(javaFile.getParentFile());
+
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
 
-                File javaFile = testFile.toFile();
-                assertHierarchyTargetFolderExists(javaFile.getParentFile());
                 javaFile.createNewFile();
                 javaFile.setWritable(false, false);
                 javaFile.setReadable(false, false);
@@ -249,10 +341,9 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                 Path targetPath = detectTargetFolder().toPath();
                 Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
                 Utils.deleteRecursive(lclSftp);
-                Files.createDirectories(lclSftp);
 
                 Path parentPath = targetPath.getParent();
-                Path clientFolder = lclSftp.resolve("client");
+                Path clientFolder = assertHierarchyTargetFolderExists(lclSftp).resolve("client");
                 String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
         
                 try(SftpClient sftp = session.createSftpClient()) {
@@ -349,9 +440,8 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
-        Path target = lclSftp.resolve(getCurrentTestName() + ".txt");
+        Path target = assertHierarchyTargetFolderExists(lclSftp).resolve("file.txt");
         String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target);
 
         final int NUM_ITERATIONS=10;
@@ -376,9 +466,8 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
-        Path localPath = lclSftp.resolve(getCurrentTestName() + ".txt");
+        Path localPath = assertHierarchyTargetFolderExists(lclSftp).resolve("file.txt");
         String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath);
         String data = getCurrentTestName();
         String extraData = "@" + getClass().getSimpleName();
@@ -425,35 +514,10 @@ public class SftpTest extends AbstractSftpClientTestSupport {
     }
 
     @Test
-    public void testRealPath() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        c.connect();
-
-        try {
-            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
-            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
-            String path = new File(base.relativize(url).getPath()).getParent() + "/";
-            path = path.replace('\\', '/');
-            String real = c.realpath(path);
-            System.out.println(real);
-            try {
-                real = c.realpath(path + "/foobar");
-                System.out.println(real);
-                fail("Expected SftpException");
-            } catch (com.jcraft.jsch.SftpException e) {
-                // ok
-            }
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    @Test
     public void testRename() throws Exception {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
@@ -466,15 +530,15 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                 session.auth().verify(5L, TimeUnit.SECONDS);
         
                 try(SftpClient sftp = session.createSftpClient()) {
-                    Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
+                    Path file1 = clientFolder.resolve("file-1.txt");
                     String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
                     try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
                         os.write((getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
                     }
 
-                    Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt");
+                    Path file2 = clientFolder.resolve("file-2.txt");
                     String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2);
-                    Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt");
+                    Path file3 = clientFolder.resolve("file-3.txt");
                     String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3);
                     try {
                         sftp.rename(file2Path, file3Path);
@@ -507,10 +571,9 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
         byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
-        Path srcFile = lclSftp.resolve("src.txt");
+        Path srcFile = assertHierarchyTargetFolderExists(lclSftp).resolve("src.txt");
         Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
 
         Path parentPath = targetPath.getParent();
@@ -661,12 +724,11 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
         Path parentPath = targetPath.getParent();
-        Path clientFolder = lclSftp.resolve("client");
+        Path clientFolder = assertHierarchyTargetFolderExists(lclSftp).resolve("client");
         String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
-        String file = dir + "/" + getCurrentTestName() + ".txt";
+        String file = dir + "/" + "file.txt";
 
         sftp.mkdir(dir);
         
@@ -759,10 +821,9 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path targetPath = detectTargetFolder().toPath();
         Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
         Utils.deleteRecursive(lclSftp);
-        Files.createDirectories(lclSftp);
 
         Path parentPath = targetPath.getParent();
-        Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt");
+        Path sourcePath = assertHierarchyTargetFolderExists(lclSftp).resolve("src.txt");
         String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath);
         Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName());
         String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath);