PR #25014 opened by timrae
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25014
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25014.patch

The path from av_url_split() was passed to the SFTP server as-is, so
any percent-encoded character in the URL was looked up literally. This
breaks URLs produced by applications that encode paths per RFC 3986,
e.g. sftp://host/My%20Movie.mkv fails with "SFTP server: No such file",
while sftp://host/My Movie.mkv works.

Decode the path in libssh_connect(), and the destination path in
libssh_move(), so both refer to the same file the URL names. Paths
without escapes are unchanged. A file whose name contains a literal
'%' followed by two hex digits must now be written with the '%'
escaped as %25.



>From 6cb33a3fe6ab10151372b470462d8ef2f3c6b6c6 Mon Sep 17 00:00:00 2001
From: Tim Rae <[email protected]>
Date: Sun, 11 Oct 2026 17:48:21 +0200
Subject: [PATCH] avformat/libssh: percent-decode the URL path

The path from av_url_split() was passed to the SFTP server as-is, so
any percent-encoded character in the URL was looked up literally. This
breaks URLs produced by applications that encode paths per RFC 3986,
e.g. sftp://host/My%20Movie.mkv fails with "SFTP server: No such file",
while sftp://host/My Movie.mkv works.

Decode the path in libssh_connect(), and the destination path in
libssh_move(), so both refer to the same file the URL names. Paths
without escapes are unchanged. A file whose name contains a literal
'%' followed by two hex digits must now be written with the '%'
escaped as %25.

Signed-off-by: Tim Rae <[email protected]>
---
 libavformat/libssh.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/libavformat/libssh.c b/libavformat/libssh.c
index 470175924d..eb30d0e281 100644
--- a/libavformat/libssh.c
+++ b/libavformat/libssh.c
@@ -29,6 +29,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "url.h"
+#include "urldecode.h"
 
 typedef struct {
     const AVClass *class;
@@ -230,6 +231,16 @@ static av_cold int libssh_close(URLContext *h)
     return 0;
 }
 
+static int libssh_decode_path(char *path, size_t path_size)
+{
+    char *decoded = ff_urldecode(path, 0);
+    if (!decoded)
+        return AVERROR(ENOMEM);
+    av_strlcpy(path, decoded, path_size);
+    av_free(decoded);
+    return 0;
+}
+
 static av_cold int libssh_connect(URLContext *h, const char *url, char *path, 
size_t path_size)
 {
     LIBSSHContext *libssh = h->priv_data;
@@ -245,6 +256,9 @@ static av_cold int libssh_connect(URLContext *h, const char 
*url, char *path, si
                  path, path_size,
                  url);
 
+    if ((ret = libssh_decode_path(path, path_size)) < 0)
+        return ret;
+
     if (!(*path))
         av_strlcpy(path, "/", path_size);
 
@@ -498,6 +512,9 @@ static int libssh_move(URLContext *h_src, URLContext *h_dst)
         return AVERROR(EINVAL);
     }
 
+    if ((ret = libssh_decode_path(path_dst, sizeof(path_dst))) < 0)
+        return ret;
+
     if ((ret = libssh_connect(h_src, h_src->filename, path_src, 
sizeof(path_src))) < 0)
         goto cleanup;
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to