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 2021/03/25 19:47:40 UTC

[mina-sshd] 04/05: [SSHD-1132] Added SFTP servder-side support for non-UTF8 encoding of returned file names

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 4a1c58d101019df2ece89a932412186a39728245
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Tue Mar 23 17:41:33 2021 +0200

    [SSHD-1132] Added SFTP servder-side support for non-UTF8 encoding of returned file names
---
 CHANGES.md                                         |  1 +
 README.md                                          |  1 +
 docs/sftp.md                                       |  5 +-
 .../sftp/server/AbstractSftpSubsystemHelper.java   | 54 ++++++++++++++--------
 .../sshd/sftp/server/SftpFileSystemAccessor.java   | 20 ++++++++
 5 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index f10009f..09cdcc2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -50,6 +50,7 @@
 * [SSHD-1127](https://issues.apache.org/jira/browse/SSHD-1127) Added capability to register a custom receiver for SFTP STDERR channel raw or stream data
 * [SSHD-1132](https://issues.apache.org/jira/browse/SSHD-1132) Added SFTP client-side support for 'filename-charset' extension
 * [SSHD-1132](https://issues.apache.org/jira/browse/SSHD-1132) Added SFTP client-side support for 'filename-translation-control' extension
+* [SSHD-1132](https://issues.apache.org/jira/browse/SSHD-1132) Added SFTP servder-side support for non-UTF8 encoding of returned file names
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for parsing incoming commands to the `ScpShell`
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for returning environment variables related data from the `ScpShell`
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for handling the SCP protocol textual commands and responses
diff --git a/README.md b/README.md
index 9fab4f7..0d4297c 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ based applications requiring SSH support.
     * `check-file-handle`, `check-file-name` - [DRAFT 09 - section 9.1.2](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt)
     * `copy-file`, `copy-data` - [DRAFT 00 - sections 6, 7](http://tools.ietf.org/id/draft-ietf-secsh-filexfer-extensions-00.txt)
     * `space-available` - [DRAFT 09 - section 9.3](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt)
+    * `filename-charset`, `filename-translation-control` - [DRAFT 13 - section 6](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6) - only client side
     * Several [OpenSSH SFTP extensions](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL)
 * [Endless tarpit](https://nullprogram.com/blog/2019/03/22/) - see [HOWTO(s)](./docs/howto.md) section.
 
diff --git a/docs/sftp.md b/docs/sftp.md
index 08707d5..3a5c083 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -426,6 +426,8 @@ try (ClientSession session = client.connect(...)) {
 
 ```
 
+On the server side, one can use the `SftpFileSystemAccessor#putRemoteFileName` to encode the returned file name/path using non-UTF8 encoding. However, this might break clients that expect UTF-8 - i.e., as long as both the client and server are somehow "aligned" on the encoding being used it will work. In this context, one might also need to consider implementing the `filename-charset` , `filename-translation-control` extensions as described in [DRAFT 13 - section 6](https://tools.ietf.or [...]
+
 ### SFTP aware directory scanners
 
 The framework provides special SFTP aware directory scanners that look for files/folders matching specific patterns. The
@@ -484,13 +486,14 @@ Both client and server support several of the SFTP extensions specified in vario
 * `check-file-handle`, `check-file-name` - [DRAFT 09 - section 9.1.2](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt)
 * `copy-file`, `copy-data` - [DRAFT 00 - sections 6, 7](http://tools.ietf.org/id/draft-ietf-secsh-filexfer-extensions-00.txt)
 * `space-available` - [DRAFT 09 - section 9.3](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt)
+* `filename-charset`, `filename-translation-control` - [DRAFT 13 - section 6](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6) - only client side
 
 Furthermore several [OpenSSH SFTP extensions](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL) are also supported:
 
 * `fsync@openssh.com`
 * `fstatvfs@openssh.com`
 * `hardlink@openssh.com`
-* `posix-rename@openssh.com`
+* `posix-rename@openssh.com` - only client side
 * `statvfs@openssh.com`
 * `lsetstat@openssh.com`
 
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
index 926fb2c..a7f0c75 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
@@ -1134,30 +1134,38 @@ public abstract class AbstractSftpSubsystemHelper
 
     protected void doReadLink(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
-        String l;
+        Map.Entry<Path, String> link;
         try {
             if (log.isDebugEnabled()) {
                 log.debug("doReadLink({})[id={}] SSH_FXP_READLINK path={}",
                         getServerSession(), id, path);
             }
-            l = doReadLink(id, path);
+            link = doReadLink(id, path);
         } catch (IOException | RuntimeException e) {
             sendStatus(prepareReply(buffer), id, e, SftpConstants.SSH_FXP_READLINK, path);
             return;
         }
 
-        sendLink(prepareReply(buffer), id, l);
+        sendLink(prepareReply(buffer), id, link.getKey(), link.getValue());
     }
 
-    protected String doReadLink(int id, String path) throws IOException {
+    /**
+     *
+     * @param  id          Request identifier
+     * @param  path        Referenced path
+     * @return             A &quot;pair&quot; containing the local link {@link Path} and its referenced symbolic link
+     * @throws IOException If failed to resolve the requested data
+     */
+    protected SimpleImmutableEntry<Path, String> doReadLink(int id, String path) throws IOException {
         Path link = resolveFile(path);
         SftpFileSystemAccessor accessor = getFileSystemAccessor();
-        String target = accessor.resolveLinkTarget(getServerSession(), this, link);
+        ServerSession session = getServerSession();
+        String target = accessor.resolveLinkTarget(session, this, link);
         if (log.isDebugEnabled()) {
             log.debug("doReadLink({})[id={}] path={}[{}]: {}",
-                    getServerSession(), id, path, link, target);
+                    session, id, path, link, target);
         }
-        return target;
+        return new SimpleImmutableEntry<>(link, target);
     }
 
     protected void doRename(Buffer buffer, int id) throws IOException {
@@ -2071,14 +2079,16 @@ public abstract class AbstractSftpSubsystemHelper
         send(buffer);
     }
 
-    protected void sendLink(Buffer buffer, int id, String link) throws IOException {
+    protected void sendLink(Buffer buffer, int id, Path file, String link) throws IOException {
         // in case we are running on Windows
         String unixPath = link.replace(File.separatorChar, '/');
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        ServerSession session = getServerSession();
 
         buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
         buffer.putInt(id);
         buffer.putInt(1); // one response
-        buffer.putString(unixPath);
+        accessor.putRemoteFileName(session, this, file, buffer, unixPath, true);
 
         /*
          * As per the spec (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.10):
@@ -2088,11 +2098,12 @@ public abstract class AbstractSftpSubsystemHelper
         Map<String, Object> attrs = Collections.emptyMap();
         int version = getVersion();
         if (version == SftpConstants.SFTP_V3) {
-            buffer.putString(SftpHelper.getLongName(unixPath, attrs));
+            String longName = SftpHelper.getLongName(unixPath, attrs);
+            accessor.putRemoteFileName(session, this, file, buffer, longName, false);
         }
 
         writeAttrs(buffer, attrs);
-        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession());
+        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), session);
         send(buffer);
     }
 
@@ -2106,15 +2117,18 @@ public abstract class AbstractSftpSubsystemHelper
         String originalPath = f.toString();
         // in case we are running on Windows
         String unixPath = originalPath.replace(File.separatorChar, '/');
-        buffer.putString(unixPath);
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        ServerSession session = getServerSession();
+        accessor.putRemoteFileName(session, this, f, buffer, unixPath, true);
 
         int version = getVersion();
         if (version == SftpConstants.SFTP_V3) {
-            buffer.putString(getLongName(f, getShortName(f), attrs));
+            String longName = getLongName(f, getShortName(f), attrs);
+            accessor.putRemoteFileName(session, this, f, buffer, longName, false);
         }
 
         writeAttrs(buffer, attrs);
-        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession());
+        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), session);
         send(buffer);
     }
 
@@ -2174,18 +2188,22 @@ public abstract class AbstractSftpSubsystemHelper
                 f, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
         entries.put(shortName, f);
 
-        buffer.putString(shortName);
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        ServerSession session = getServerSession();
+        accessor.putRemoteFileName(session, this, f, buffer, shortName, true);
+
         int version = getVersion();
         if (version == SftpConstants.SFTP_V3) {
             String longName = getLongName(f, shortName, options);
-            buffer.putString(longName);
+            accessor.putRemoteFileName(session, this, f, buffer, longName, false);
+
             if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(" + getServerSession() + ") id=" + id + ")[" + index + "] - "
+                log.trace("writeDirEntry(" + session + ") id=" + id + ")[" + index + "] - "
                           + shortName + " [" + longName + "]: " + attrs);
             }
         } else {
             if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(" + getServerSession() + "(id=" + id + ")[" + index + "] - "
+                log.trace("writeDirEntry(" + session + "(id=" + id + ")[" + index + "] - "
                           + shortName + ": " + attrs);
             }
         }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpFileSystemAccessor.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpFileSystemAccessor.java
index 39f5137..0d2d3ce 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpFileSystemAccessor.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpFileSystemAccessor.java
@@ -56,6 +56,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.io.FileInfoExtractor;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
@@ -117,6 +118,25 @@ public interface SftpFileSystemAccessor {
     }
 
     /**
+     * Invoked in order to encode the outgoing referenced file name/path
+     *
+     * @param  session     The {@link ServerSession} through which the request was received
+     * @param  subsystem   The SFTP subsystem instance that manages the session
+     * @param  path        The associated file {@link Path} - <B>Note:</B> might be a symbolic link container
+     * @param  buf         The target {@link Buffer} for the encoded string
+     * @param  name        The string to send
+     * @param  shortName   If {@code true} then this is the &quot;pure&quot; file name/path, otherwise it also contains
+     *                     user/group/size/last-modified-time/etc.
+     * @throws IOException If failed to resolve the remote name
+     * @see                <A HREF="https://issues.apache.org/jira/browse/SSHD-1132">SSHD-1132</A>
+     */
+    default void putRemoteFileName(
+            ServerSession session, SftpSubsystemProxy subsystem, Path path, Buffer buf, String name, boolean shortName)
+            throws IOException {
+        buf.putString(name);
+    }
+
+    /**
      * Called whenever a new file is opened
      *
      * @param  session     The {@link ServerSession} through which the request was received