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 <lgoldst...@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: * `fs...@openssh.com` * `fstat...@openssh.com` * `hardl...@openssh.com` -* `posix-ren...@openssh.com` +* `posix-ren...@openssh.com` - only client side * `stat...@openssh.com` * `lsets...@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 "pair" 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 "pure" 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