From 89a08bf514abdd166b4d984fcfdc6a0d0ed537ae Mon Sep 17 00:00:00 2001 From: Robert Cheramy Date: Mon, 26 Feb 2024 09:50:02 +0100 Subject: [PATCH] Handle remotes closes without eof Some ssh-server (APC Network Management Card AP9640) close the channel without sending an eof. This is correctly handled by openssh, so NET:SCP should also handle it. Added a test for this case. The changed/added code passes Rubocop with default settings, with the exception of the length of the test name, wich ich intended to be long. --- lib/net/scp.rb | 13 ++++++++++++- test/test_download.rb | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/net/scp.rb b/lib/net/scp.rb index fb7f53b..9a9e695 100644 --- a/lib/net/scp.rb +++ b/lib/net/scp.rb @@ -362,7 +362,18 @@ def start_command(mode, local, remote, options={}, &callback) channel[:stack ] = [] channel[:error_string] = '' - channel.on_close { |ch2| send("#{channel[:state]}_state", channel); raise Net::SCP::Error, "SCP did not finish successfully (#{channel[:exit]}): #{channel[:error_string]}" if channel[:exit] != 0 } + channel.on_close do + if channel[:exit].nil? && channel[:state] == :finish + # The remote closed the channel without sending an eof, but + # the transfer was successful, so whe set channel[:exit] to 0 + channel[:exit] = 0 + end + send("#{channel[:state]}_state", channel) + if channel[:exit] != 0 + raise Net::SCP::Error, 'SCP did not finish successfully ' \ + "(#{channel[:exit]}): #{channel[:error_string]}" + end + end channel.on_data { |ch2, data| channel[:buffer].append(data) } channel.on_extended_data { |ch2, type, data| debug { data.chomp } } channel.on_request("exit-status") { |ch2, data| channel[:exit] = data.read_long } diff --git a/test/test_download.rb b/test/test_download.rb index 5e97b1e..92f4136 100644 --- a/test/test_download.rb +++ b/test/test_download.rb @@ -225,6 +225,24 @@ def test_download_directory_should_create_directory_and_files_locally assert_equal "a" * 1234, file.io.string end + def test_download_should_work_when_remote_closes_channel_without_eof + file = prepare_file('/path/to/local.txt', 'a' * 1234) + + story do |session| + channel = session.opens_channel + channel.sends_exec 'scp -f /path/to/remote.txt' + simple_download(channel) + # Remote closes without sending an eof + channel.gets_close + # We are polite and send an eof & close the channel + channel.sends_eof + channel.sends_close + end + + assert_scripted { scp.download!('/path/to/remote.txt', '/path/to/local.txt') } + assert_equal 'a' * 1234, file.io.string + end + private def simple_download(channel, mode=0666)