Repository: mina-sshd
Updated Branches:
  refs/heads/master ba91a674d -> c5b163f2b


[SSHD-790] Allow user to specify charset to be used when decoding SFTP client 
file/folder name(s)


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

Branch: refs/heads/master
Commit: c5b163f2bf3b69c313018c18428cca32bebe5c2c
Parents: ba91a67
Author: Goldstein Lyor <[email protected]>
Authored: Wed Dec 27 13:12:20 2017 +0200
Committer: Goldstein Lyor <[email protected]>
Committed: Wed Dec 27 13:51:31 2017 +0200

----------------------------------------------------------------------
 README.md                                       | 51 ++++++++++++++++++++
 .../subsystem/sftp/AbstractSftpClient.java      | 16 ++++--
 .../subsystem/sftp/DefaultSftpClient.java       | 14 ++++++
 .../sshd/client/subsystem/sftp/SftpClient.java  | 25 ++++++++++
 .../client/subsystem/sftp/SftpFileSystem.java   | 11 +++++
 .../subsystem/sftp/SftpFileSystemProvider.java  | 19 ++++++--
 .../common/util/buffer/ByteArrayBuffer.java     |  3 ++
 7 files changed, 130 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index f5783e2..fb6560b 100644
--- a/README.md
+++ b/README.md
@@ -664,6 +664,57 @@ throughout the SFTP server subsystem code. The accessor is 
registered/overwritte
 
 ```
 
+### SFTP received names encoding
+
+By default, the SFTP client uses UTF-8 to decode any referenced file/folder 
name. However, some servers do not properly encode such names, and
+thus the "decoded" names by the client become corrupted, or even worse - cause 
an exception upon decoding attempt. The `SftpClient` exposes
+a `get/setNameDecodingCharset` method which enables the user to modify the 
charset - even while the SFTP session is in progress - e.g.:
+
+```java
+
+    try (SftpClient client = ...obtain an instance...) {
+        client.setNameDecodingCharset(Charset.forName("ISO-8859-8"));
+        for (DirEntry entry : client.readDir(...some path...)) {
+            ...handle entry assuming ISO-8859-8 encoded names...
+        }
+
+        client.setNameDecodingCharset(Charset.forName("ISO-8859-4"));
+        for (DirEntry entry : client.readDir(...some other path...)) {
+            ...handle entry assuming ISO-8859-4 encoded names...
+        }
+    }
+
+```
+
+The initial charset can be pre-configured on the client/session by using the 
`sftp-name-decoding-charset` property - if none specified then
+UTF-8 is used. **Note:** the value can be a charset name or a 
`java.nio.charset.Charset` instance - e.g.:
+
+```java
+
+    SshClient client = ... setup/obtain an instance...
+    // default for ALL SFTP clients obtained through this client
+    PropertyResolverUtils.updateProperty(client, 
SftpClient.NAME_DECODING_CHARSET, "ISO-8859-8");
+    
+    try (ClientSession session = client.connect(...)) {
+         // default for ALL SFTP clients obtained through the session - 
overrides client setting
+         PropertyResolverUtils.updateProperty(session, 
SftpClient.NAME_DECODING_CHARSET, "ISO-8859-4");
+         session.authenticate(...);
+         
+         try (SftpClient sftp = session.createSftpClient()) {
+             for (DirEntry entry : sftp.readDir(...some path...)) {
+                 ...handle entry assuming ISO-8859-4 (inherited from the 
session) encoded names...
+             }
+             
+             // override the inherited default from the session
+             sftp.setNameDecodingCharset(Charset.forName("ISO-8859-1"));
+             
+             for (DirEntry entry : sftp.readDir(...some other path...)) {
+                 ...handle entry assuming ISO-8859-1 encoded names...
+             }
+         }
+    }
+
+```
 
 ### Supported SFTP extensions
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
index fb1aed8..fd2875c 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.client.subsystem.sftp;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.file.attribute.FileTime;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -105,6 +106,11 @@ public abstract class AbstractSftpClient extends 
AbstractSubsystemClient impleme
         return parsed;
     }
 
+    protected String getReferencedName(Buffer buf) {
+        Charset cs = getNameDecodingCharset();
+        return buf.getString(cs);
+    }
+
     /**
      * Sends the specified command, waits for the response and then invokes 
{@link #checkResponseStatus(int, Buffer)}
      * @param cmd The command to send
@@ -285,11 +291,11 @@ public abstract class AbstractSftpClient extends 
AbstractSubsystemClient impleme
             if (len != 1) {
                 throw new SshException("SFTP error: received " + len + " names 
instead of 1");
             }
-            String name = buffer.getString();
+            String name = getReferencedName(buffer);
             String longName = null;
             int version = getVersion();
             if (version == SftpConstants.SFTP_V3) {
-                longName = buffer.getString();
+                longName = getReferencedName(buffer);
             }
 
             Attributes attrs = readAttributes(buffer);
@@ -417,7 +423,7 @@ public abstract class AbstractSftpClient extends 
AbstractSubsystemClient impleme
                 }
                 if ((flags & 
SftpConstants.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
                     @SuppressWarnings("unused")
-                    String untranslated = buffer.getString(); // TODO: handle 
untranslated-name
+                    String untranslated = getReferencedName(buffer); // TODO: 
handle untranslated-name
                 }
             }
         } else {
@@ -893,8 +899,8 @@ public abstract class AbstractSftpClient extends 
AbstractSubsystemClient impleme
 
             List<DirEntry> entries = new ArrayList<>(len);
             for (int i = 0; i < len; i++) {
-                String name = buffer.getString();
-                String longName = (version == SftpConstants.SFTP_V3) ? 
buffer.getString() : null;
+                String name = getReferencedName(buffer);
+                String longName = (version == SftpConstants.SFTP_V3) ? 
getReferencedName(buffer) : null;
                 Attributes attrs = readAttributes(buffer);
                 if (log.isTraceEnabled()) {
                     log.trace("checkDirResponse({})[id={}][{}] ({})[{}]: {}",

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
index b72502c..0788b67 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -26,6 +26,7 @@ import java.io.InterruptedIOException;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
 import java.net.SocketTimeoutException;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -43,6 +44,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.sshd.client.channel.ChannelSubsystem;
 import org.apache.sshd.client.channel.ClientChannel;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
@@ -67,8 +69,10 @@ public class DefaultSftpClient extends AbstractSftpClient {
     private final AtomicBoolean closing = new AtomicBoolean(false);
     private final NavigableMap<String, byte[]> extensions = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
     private final NavigableMap<String, byte[]> exposedExtensions = 
Collections.unmodifiableNavigableMap(extensions);
+    private Charset nameDecodingCharset = DEFAULT_NAME_DECODING_CHARSET;
 
     public DefaultSftpClient(ClientSession clientSession) throws IOException {
+        this.nameDecodingCharset = 
PropertyResolverUtils.getCharset(clientSession, NAME_DECODING_CHARSET, 
DEFAULT_NAME_DECODING_CHARSET);
         this.clientSession = Objects.requireNonNull(clientSession, "No client 
session");
         this.channel = 
clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
         this.channel.setOut(new OutputStream() {
@@ -130,6 +134,16 @@ public class DefaultSftpClient extends AbstractSftpClient {
     }
 
     @Override
+    public Charset getNameDecodingCharset() {
+        return nameDecodingCharset;
+    }
+
+    @Override
+    public void setNameDecodingCharset(Charset nameDecodingCharset) {
+        this.nameDecodingCharset = Objects.requireNonNull(nameDecodingCharset, 
"No charset provided");
+    }
+
+    @Override
     public boolean isClosing() {
         return closing.get();
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index 410fa22..c835e35 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -23,6 +23,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.channels.Channel;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.AclEntry;
@@ -53,6 +55,21 @@ import org.apache.sshd.common.util.buffer.BufferUtils;
  * @author <a href="http://mina.apache.org";>Apache MINA Project</a>
  */
 public interface SftpClient extends SubsystemClient {
+    /**
+     * Used to indicate the {@link Charset} (or its name) for decoding
+     * referenced files/folders names - extracted from the client session
+     * when 1st initialized.
+     * @see #DEFAULT_NAME_DECODING_CHARSET
+     * @see #getNameDecodingCharset()
+     * @see #setNameDecodingCharset(Charset)
+     */
+    String NAME_DECODING_CHARSET = "sftp-name-decoding-charset";
+
+    /**
+     * Default value of {@value #NAME_DECODING_CHARSET}
+     */
+    Charset DEFAULT_NAME_DECODING_CHARSET = StandardCharsets.UTF_8;
+
     enum OpenMode {
         Read,
         Write,
@@ -540,6 +557,14 @@ public interface SftpClient extends SubsystemClient {
     }
 
     /**
+     * @return The (never {@code null}) {@link Charset} used to decode 
referenced files/folders names
+     * @see #NAME_DECODING_CHARSET
+     */
+    Charset getNameDecodingCharset();
+
+    void setNameDecodingCharset(Charset cs);
+
+    /**
      * @return An (unmodifiable) {@link NavigableMap} of the reported server 
extensions.
      * where key=extension name (case <U>insensitive</U>)
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
index cd41691..f028a5b 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
+import java.nio.charset.Charset;
 import java.nio.file.FileStore;
 import java.nio.file.FileSystemException;
 import java.nio.file.attribute.GroupPrincipal;
@@ -241,6 +242,16 @@ public class SftpFileSystem extends 
BaseFileSystem<SftpPath> implements ClientSe
         }
 
         @Override
+        public Charset getNameDecodingCharset() {
+            return delegate.getNameDecodingCharset();
+        }
+
+        @Override
+        public void setNameDecodingCharset(Charset cs) {
+            delegate.setNameDecodingCharset(cs);
+        }
+
+        @Override
         public boolean isClosing() {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index f8af4f5..5b2f49d 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -25,6 +25,7 @@ import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.URI;
 import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
 import java.nio.file.AccessDeniedException;
 import java.nio.file.AccessMode;
 import java.nio.file.CopyOption;
@@ -59,9 +60,9 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
@@ -100,6 +101,9 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
     public static final long DEFAULT_CONNECT_TIME = 
SftpClient.DEFAULT_WAIT_TIMEOUT;
     public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
     public static final long DEFAULT_AUTH_TIME = 
SftpClient.DEFAULT_WAIT_TIMEOUT;
+    public static final String NAME_DECORDER_CHARSET_PROP_NAME = 
"sftp-fs-name-decoder-charset";
+    public static final Charset DEFAULT_NAME_DECODER_CHARSET = 
SftpClient.DEFAULT_NAME_DECODING_CHARSET;
+
     /**
      * <P>
      * URI parameter that can be used to specify a special version selection. 
Options are:
@@ -125,7 +129,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
 
     private final SshClient client;
     private final SftpVersionSelector selector;
-    private final Map<String, SftpFileSystem> fileSystems = new HashMap<>();
+    private final NavigableMap<String, SftpFileSystem> fileSystems = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
 
     public SftpFileSystemProvider() {
         this((SshClient) null);
@@ -183,6 +187,10 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         Map<String, Object> params = resolveFileSystemParameters(env, 
parseURIParameters(uri));
         PropertyResolver resolver = 
PropertyResolverUtils.toPropertyResolver(params);
         SftpVersionSelector selector = resolveSftpVersionSelector(uri, 
getSftpVersionSelector(), resolver);
+        Charset decodingCharset =
+            PropertyResolverUtils.getCharset(resolver, 
NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
+        long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, 
DEFAULT_CONNECT_TIME);
+        long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, 
DEFAULT_AUTH_TIME);
 
         SftpFileSystem fileSystem;
         synchronized (fileSystems) {
@@ -194,7 +202,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
             ClientSession session = null;
             try {
                 session = client.connect(username, host, port)
-                        
.verify(resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME))
+                        .verify(maxConnectTime)
                         .getSession();
                 if (GenericUtils.size(params) > 0) {
                     // Cannot use forEach because the session is not 
effectively final
@@ -207,9 +215,12 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
 
                         PropertyResolverUtils.updateProperty(session, key, 
value);
                     }
+
+                    PropertyResolverUtils.updateProperty(session, 
SftpClient.NAME_DECODING_CHARSET, decodingCharset);
                 }
+
                 session.addPasswordIdentity(password);
-                
session.auth().verify(resolver.getLongProperty(AUTH_TIME_PROP_NAME, 
DEFAULT_AUTH_TIME));
+                session.auth().verify(maxAuthTime);
 
                 fileSystem = new SftpFileSystem(this, id, session, selector);
                 fileSystems.put(id, fileSystem);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c5b163f2/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
index 91e6498..6655ec1 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.util.buffer;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.function.IntUnaryOperator;
 
 import org.apache.sshd.common.util.GenericUtils;
@@ -175,6 +176,8 @@ public class ByteArrayBuffer extends Buffer {
             throw new BufferException("Bad item length: " + len);
         }
         ensureAvailable(len);
+
+        Objects.requireNonNull(charset, "No charset specified");
         String s = new String(data, rpos, len, charset);
         rpos += len;
         return s;

Reply via email to