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

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

commit a792e08922482894db080dd791c112623ed42601
Author: Thomas Wolf <[email protected]>
AuthorDate: Thu Jun 18 18:26:51 2026 +0200

    SCP: escape newlines in file names on sending
    
    Unix filenames may contain literal newlines. Escape them as ^J like
    OpenSSH does to avoid breaking the line-oriented SCP protocol.
    
    Note that OpenSSH actually produces \^J for a newline, but backslashes
    might be problematic for receivers on Windows. Most likely that extra
    backslash is a bug in OpenSSH?
    
    Also handle \^J in filenames on reception by removing the backslash
    on Windows lest it be taken as a file separator.
---
 CHANGES.md                                               |  1 +
 .../java/org/apache/sshd/scp/common/ScpFileOpener.java   | 16 +++++++++++-----
 .../main/java/org/apache/sshd/scp/common/ScpHelper.java  | 12 +++++++++---
 3 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 23bdfc4a3..6c19fa96d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -38,6 +38,7 @@
 * Improve checking SSH user certificates in public-key authentication
 * Improve handling of repository paths in `sshd-git` on Windows.
 * Validate file names in SCP
+* Escape newlines in filenames in the SCP protocol
 
 ## New Features
 
diff --git 
a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java 
b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
index 82cb32465..4f245b7de 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
@@ -71,14 +71,20 @@ public interface ScpFileOpener {
                 || fileName.indexOf('/') >= 0) {
             throw new InvalidPathException(fileName, "Not a valid SCP 
fileName");
         }
-        if (OsUtils.isWin32() && (fileName.indexOf('\\') >= 0 || 
fileName.indexOf(':') >= 0)) {
-            throw new InvalidPathException(fileName, "Not a valid SCP 
fileName");
+        String name = fileName;
+        if (OsUtils.isWin32()) {
+            // OpenSSH replaces literal newlines in file names with "\^J" in 
the SCP protocol. The backslash is likely
+            // a bug in OpenSSH and causes trouble on Windows.
+            name = name.replace("\\^J", "^J");
+            if (name.indexOf('\\') >= 0 || name.indexOf(':') >= 0) {
+                throw new InvalidPathException(fileName, "Not a valid SCP 
fileName");
+            }
         }
-        Path p = fs.getPath(fileName);
-        if (p.isAbsolute() || !fileName.equals(p.getFileName().toString())) {
+        Path p = fs.getPath(name);
+        if (p.isAbsolute() || !name.equals(p.getFileName().toString())) {
             throw new InvalidPathException(fileName, "Not a valid SCP 
fileName");
         }
-        return fileName;
+        return name;
     }
 
     /**
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java 
b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
index bff83ff78..38f265ddc 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
@@ -561,7 +561,10 @@ public class ScpHelper extends AbstractLoggingBean 
implements SessionHolder<Sess
         String octalPerms = ((!preserve) || GenericUtils.isEmpty(perms))
                 ? ScpReceiveFileCommandDetails.DEFAULT_FILE_OCTAL_PERMISSIONS
                 : ScpPathCommandDetailsSupport.getOctalPermissions(perms);
-        String fileName = resolver.getFileName();
+        // Replace literal \n in the fileName (may occur on Linux) like 
OpenSSH does. A newline in the file name would
+        // break the line-oriented SCP protocol. Note that OpenSSH actually 
produces \^J, but I believe this is a bug?
+        // In any case adding a backslash might cause trouble for Windows 
remotes.
+        String fileName = resolver.getFileName().replace("\n", "^J");
         String cmd = ScpReceiveFileCommandDetails.COMMAND_NAME + octalPerms + 
" " + fileSize + " " + fileName;
         if (debugEnabled) {
             log.debug("sendStream({})[{}] send 'C' command: {}", this, 
resolver, cmd);
@@ -675,8 +678,11 @@ public class ScpHelper extends AbstractLoggingBean 
implements SessionHolder<Sess
         String octalPerms = ((!preserve) || GenericUtils.isEmpty(perms))
                 ? ScpReceiveDirCommandDetails.DEFAULT_DIR_OCTAL_PERMISSIONS
                 : ScpPathCommandDetailsSupport.getOctalPermissions(perms);
-        String cmd = ScpReceiveDirCommandDetails.COMMAND_NAME + octalPerms + " 
" + "0" + " "
-                     + Objects.toString(path.getFileName(), null);
+        // Replace literal \n in the fileName (may occur on Linux) like 
OpenSSH does. A newline in the file name would
+        // break the line-oriented SCP protocol. Note that OpenSSH actually 
produces \^J, but I believe this is a bug?
+        // In any case adding a backslash might cause trouble for Windows 
remotes.
+        String fileName = Objects.toString(path.getFileName(), 
"").replace("\n", "^J");
+        String cmd = ScpReceiveDirCommandDetails.COMMAND_NAME + octalPerms + " 
0 " + fileName;
         if (debugEnabled) {
             log.debug("sendDir({})[{}] send 'D' command: {}", this, path, cmd);
         }

Reply via email to