From 5414bd75337f205e664882ca4b2eaf823962cf63 Mon Sep 17 00:00:00 2001 From: Nabil Bendafi Date: Tue, 4 Apr 2017 19:53:12 +0200 Subject: [PATCH] Protocole::V06::Extended Handle SFTP V6 extensions commands - md5-hash-handle - check-file-handle - space-available - home-directory --- lib/net/sftp/protocol.rb | 5 +- lib/net/sftp/protocol/06/extended.rb | 107 +++++++++++++++++++++++++++ lib/net/sftp/protocol/base.rb | 13 ++-- test/protocol/06/test_extended.rb | 50 +++++++++++++ 4 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 lib/net/sftp/protocol/06/extended.rb create mode 100644 test/protocol/06/test_extended.rb diff --git a/lib/net/sftp/protocol.rb b/lib/net/sftp/protocol.rb index 382b25d..574eaf5 100644 --- a/lib/net/sftp/protocol.rb +++ b/lib/net/sftp/protocol.rb @@ -4,6 +4,7 @@ require 'net/sftp/protocol/04/base' require 'net/sftp/protocol/05/base' require 'net/sftp/protocol/06/base' +require 'net/sftp/protocol/06/extended' module Net; module SFTP @@ -22,11 +23,11 @@ def self.load(session, version) when 3 then V03::Base.new(session) when 4 then V04::Base.new(session) when 5 then V05::Base.new(session) - when 6 then V06::Base.new(session) + when 6 then V06::Extended.new(session) else raise NotImplementedError, "unsupported SFTP version #{version.inspect}" end end end -end; end \ No newline at end of file +end; end diff --git a/lib/net/sftp/protocol/06/extended.rb b/lib/net/sftp/protocol/06/extended.rb new file mode 100644 index 0000000..5f6a8ed --- /dev/null +++ b/lib/net/sftp/protocol/06/extended.rb @@ -0,0 +1,107 @@ +require 'net/sftp/protocol/06/base' + +module Net; module SFTP; module Protocol; module V06 + + # Wraps the low-level SFTP calls for version 6 of the SFTP protocol. + # + # None of these protocol methods block--all of them return immediately, + # requiring the SSH event loop to be run while the server response is + # pending. + # + # You will almost certainly never need to use this driver directly. Please + # see Net::SFTP::Session for the recommended interface. + class Extended < V06::Base + + # Parses the given "md5-hash" FXP_EXTENDED_REPL packet and returns a + # hash with one key, :md5, which references the computed hash. + def parse_md5_packet(data) + md5 = "" + + if !data.empty? + md5 = data.read_string + end + + { :md5 => md5 } + end + + # Parses the given "check-file" FXP_EXTENDED_REPL packet and returns a hash + # with two keys, :algo, which references the hash algorithm used and + # :hashes which references the computed hashes. + def parse_hash_packet(data) + hashes = [] + + algo = data.read_string + size = case algo + when "md5" then 128 + when "sha256" then 256 + when "sha384" then 284 + when "sha512" then 512 + else raise NotImplementedError, "unsupported algorithm: #{algo}" + end + + while !data.eof? do + hashes << data.read(size) + end + + { :algo => algo, :hashes => hashes } + end + + # Parses the given "home-directory" FXP_EXTENDED_REPL packet and returns a + # hash with one key, :home, which references the home directory returned by + # the server. + def parse_home_packet(data) + { :home => data.read_string } + end + + # Parses the given FXP_EXTENDED_REPL packet and returns a hash, with + # :extension key, which references SFTP extension and the associated keys + def parse_extented_reply_packet(packet) + packet.read_string do |extension| + data = packet.remainder_as_buffer + parsed_packet = case extension + when "md5-hash" then parse_md5_packet(data) + when "check-file" then parse_hash_packet(data) + when "home-directory" then parse_home_packet(data) + else raise NotImplementedError, "unknown packet type: #{extension}" + end + end + + { :extension => extension }.merge(parsed_packet) + end + + # Sends a FXP_EXTENDED packet to the server to request MD5 checksum + # computation for file (or portion of file) obtained on the given +handle+, + # for the given byte +offset+ and +length+. The +quick_hash+ parameter is + # the hash over the first 2048 bytes of the data. It allows the server to + # quickly check if it is worth the resources to hash a big file. + def md5(handle, offset, length, quick_hash) + send_request(FXP_EXTENDED, :string, "md5-hash-handle", :int64, offset, :int64, length, :string, quick_hash) + end + + # Sends a FXP_EXTENDED packet to the server to request checksum computation + # for file (or portion of file) obtained on the given +handle+, for the + # given byte +offset+ and +length+. The +block_size+ parameter is used to + # compute over every +block_size+ block in the file. If the +block_size+ is + # 0, then only one hash, over the entire range, is made. + def hash(handle, offset, length, block_size=0) + if block_size != 0 && block_size < 255 + block_size = 256 + end + send_request(FXP_EXTENDED, :string, "check-file-handle", :string, handle, :string, "md5,sha256,sha384,sha512", :int64, offset, :int64, length, :long, block_size) + end + + # Sends a FXP_EXTENDED packet to the server to request disk space availability + # for the given +path+ location. + def space_available(path) + send_request(FXP_EXTENDED, :string, "space-available", :string, path) + end + + # Sends a FXP_EXTENDED packet to the server to request home directory + # for the given +username+. + def home(username) + send_request(FXP_EXTENDED, :string, "home-directory", :string, username) + end + + end + +end; end; end; end diff --git a/lib/net/sftp/protocol/base.rb b/lib/net/sftp/protocol/base.rb index 0a5f149..4184982 100644 --- a/lib/net/sftp/protocol/base.rb +++ b/lib/net/sftp/protocol/base.rb @@ -26,11 +26,12 @@ def initialize(session) # (the keys in the hash are packet-type specific). def parse(packet) case packet.type - when FXP_STATUS then parse_status_packet(packet) - when FXP_HANDLE then parse_handle_packet(packet) - when FXP_DATA then parse_data_packet(packet) - when FXP_NAME then parse_name_packet(packet) - when FXP_ATTRS then parse_attrs_packet(packet) + when FXP_STATUS then parse_status_packet(packet) + when FXP_HANDLE then parse_handle_packet(packet) + when FXP_DATA then parse_data_packet(packet) + when FXP_NAME then parse_name_packet(packet) + when FXP_ATTRS then parse_attrs_packet(packet) + when FXP_EXTENDED_REPLY then parse_extented_reply_packet(packet) else raise NotImplementedError, "unknown packet type: #{packet.type}" end end @@ -47,4 +48,4 @@ def send_request(type, *args) end end -end; end; end \ No newline at end of file +end; end; end diff --git a/test/protocol/06/test_extended.rb b/test/protocol/06/test_extended.rb new file mode 100644 index 0000000..5d130a2 --- /dev/null +++ b/test/protocol/06/test_extended.rb @@ -0,0 +1,50 @@ +require 'common' +require 'protocol/06/test_base' + +class Protocol::V06::TestExtended < Protocol::V06::TestBase + + def setup + @session = stub('session', :logger => nil) + @base = driver.new(@session) + end + + def test_version + assert_equal 6, @base.version + end + + def test_md5_should_send_md5_hash_packet + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "md5-hash-handle", :int64, 112233, :int64, 445566, :string, "ABCDEF") + assert_equal 0, @base.md5("test", 112233, 445566, "ABCDEF") + end + + def test_hash_should_send_hash_packet + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "check-file-handle", :string, "test", :string, "md5,sha256,sha384,sha512", :int64, 112233, :int64, 445566, :long, 0) + assert_equal 0, @base.hash("test", 112233, 445566) + end + + def test_hash_should_send_hash_packet_with_block_size + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "check-file-handle", :string, "test", :string, "md5,sha256,sha384,sha512", :int64, 112233, :int64, 445566, :long, 256) + assert_equal 0, @base.hash("test", 112233, 445566, 123) + end + + def test_hash_should_send_hash_packet_with_valid_block_size + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "check-file-handle", :string, "test", :string, "md5,sha256,sha384,sha512", :int64, 112233, :int64, 445566, :long, 256) + assert_equal 0, @base.hash("test", 112233, 445566, -1) + end + + def test_space_available_should_send_space_available_packet + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "space-available", :string, "/var/log/Xorg.0.log") + assert_equal 0, @base.space_available("/var/log/Xorg.0.log") + end + + def test_home_should_send_home_directory_packet + @session.expects(:send_packet).with(FXP_EXTENDED, :long, 0, :string, "home-directory", :string, "test") + assert_equal 0, @base.home("test") + end + + private + + def driver + Net::SFTP::Protocol::V06::Extended + end +end