diff --git a/app/Models/Server.php b/app/Models/Server.php index cae9d8c18a..1ed2e15eaa 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -552,6 +552,13 @@ public function getSftpUrl(?string $username = null, ?string $directory = null): $directory = explode('/', trim($directory, '/')); $directory = array_map(fn (string $part) => rawurlencode($part), $directory); $directory = '/' . implode('/', $directory); + + // SFTP clients only treat a URL path as a directory when it ends with a + // slash, otherwise they prompt to download the path as a file instead of + // opening it. See #2577. + if ($directory !== '/') { + $directory .= '/'; + } } if ($directory === '/') { diff --git a/tests/Unit/Models/ServerTest.php b/tests/Unit/Models/ServerTest.php new file mode 100644 index 0000000000..b7c272293e --- /dev/null +++ b/tests/Unit/Models/ServerTest.php @@ -0,0 +1,60 @@ +serverWithNode(); + + $this->assertSame("sftp://user.abcdefgh@node.example.com:2022{$expectedPath}", $server->getSftpUrl('user', $directory)); + } + + public static function sftpUrlDataProvider(): array + { + return [ + 'no directory' => [null, ''], + 'root' => ['/', ''], + 'single directory' => ['/data', '/data/'], + 'directory without a leading slash' => ['data', '/data/'], + 'directory with a trailing slash' => ['data/', '/data/'], + 'nested directory' => ['/data/world', '/data/world/'], + 'directory with encoded parts' => ['/data/my world', '/data/my%20world/'], + ]; + } + + public function test_sftp_url_uses_the_node_sftp_alias_when_set(): void + { + $server = $this->serverWithNode(sftpAlias: 'sftp.example.com'); + + $this->assertSame('sftp://user.abcdefgh@sftp.example.com:2022/data/', $server->getSftpUrl('user', '/data')); + } + + public function test_sftp_url_encodes_the_username(): void + { + $server = $this->serverWithNode(); + + $this->assertSame('sftp://user%20name.abcdefgh@node.example.com:2022/data/', $server->getSftpUrl('user name', '/data')); + } + + private function serverWithNode(string $fqdn = 'node.example.com', ?string $sftpAlias = null): Server + { + $node = new Node(); + $node->fqdn = $fqdn; + $node->daemon_sftp_alias = $sftpAlias; + $node->daemon_sftp = 2022; + + $server = new Server(); + $server->uuid_short = 'abcdefgh'; + $server->setRelation('node', $node); + + return $server; + } +}