Skip to content

Commit

Permalink
Protocole::V06::Extended
Browse files Browse the repository at this point in the history
Handle SFTP V6 extensions commands
 - md5-hash-handle
 - check-file-handle
 - space-available
 - home-directory
  • Loading branch information
nabilbendafi committed Apr 4, 2017
1 parent 9d4e24a commit 9a25101
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 8 deletions.
5 changes: 3 additions & 2 deletions lib/net/sftp/protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
end; end
107 changes: 107 additions & 0 deletions lib/net/sftp/protocol/06/extended.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 7 additions & 6 deletions lib/net/sftp/protocol/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,4 +48,4 @@ def send_request(type, *args)
end
end

end; end; end
end; end; end
50 changes: 50 additions & 0 deletions test/protocol/06/test_extended.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 9a25101

Please sign in to comment.