Skip to content

Commit 5ed6181

Browse files
authored
Merge pull request #71 from robertcheramy/fix_remote_closes_without_eof
Handle remote server closes channel without exit-status
2 parents 8943911 + cce3605 commit 5ed6181

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

lib/net/scp.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,21 @@ def start_command(mode, local, remote, options={}, &callback)
362362
channel[:stack ] = []
363363
channel[:error_string] = ''
364364

365-
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 }
365+
channel.on_close do
366+
# If we got an exit-status and it is not 0, something went wrong
367+
if !channel[:exit].nil? && channel[:exit] != 0
368+
raise Net::SCP::Error, 'SCP did not finish successfully ' \
369+
"(#{channel[:exit]}): #{channel[:error_string]}"
370+
end
371+
# We may get no exit-status at all as returning a status is only RECOMENDED
372+
# in RFC4254. But if our state is not :finish, something went wrong
373+
if channel[:exit].nil? && channel[:state] != :finish
374+
raise Net::SCP::Error, 'SCP did not finish successfully ' \
375+
'(channel closed before end of transmission)'
376+
end
377+
# At this point, :state can be :finish or :next_item
378+
send("#{channel[:state]}_state", channel)
379+
end
366380
channel.on_data { |ch2, data| channel[:buffer].append(data) }
367381
channel.on_extended_data { |ch2, type, data| debug { data.chomp } }
368382
channel.on_request("exit-status") { |ch2, data| channel[:exit] = data.read_long }

test/test_download.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,52 @@ def test_download_directory_should_create_directory_and_files_locally
225225
assert_equal "a" * 1234, file.io.string
226226
end
227227

228+
def test_download_should_work_when_remote_closes_channel_without_exit_status
229+
file = prepare_file('/path/to/local.txt', 'a' * 1234)
230+
231+
story do |session|
232+
channel = session.opens_channel
233+
channel.sends_exec 'scp -f /path/to/remote.txt'
234+
simple_download(channel)
235+
# Remote closes without sending an exit-status
236+
channel.gets_close
237+
# We just send eof and close the channel
238+
channel.sends_eof
239+
channel.sends_close
240+
end
241+
242+
assert_scripted { scp.download!('/path/to/remote.txt', '/path/to/local.txt') }
243+
assert_equal 'a' * 1234, file.io.string
244+
end
245+
246+
def test_download_should_raise_error_when_remote_closes_channel_before_end
247+
prepare_file('/path/to/local.txt', 'a' * 1234)
248+
249+
story do |session|
250+
channel = session.opens_channel
251+
channel.sends_exec 'scp -f /path/to/remote.txt'
252+
channel.sends_ok
253+
channel.gets_data "C0666 1234 remote.txt\n"
254+
channel.sends_ok
255+
channel.gets_data 'a' * 500
256+
# We should have received 1234 bytes and \0 but remote closed before the end
257+
channel.gets_close
258+
# We just send eof and close the channel
259+
channel.sends_eof
260+
channel.sends_close
261+
end
262+
263+
error = nil
264+
begin
265+
assert_scripted { scp.download!('/path/to/remote.txt', '/path/to/local.txt') }
266+
rescue => e
267+
error = e
268+
end
269+
270+
assert_equal Net::SCP::Error, error.class
271+
assert_equal 'SCP did not finish successfully (channel closed before end of transmission)', error.message
272+
end
273+
228274
private
229275

230276
def simple_download(channel, mode=0666)

0 commit comments

Comments
 (0)