From eec0ae6fb3f034a2e5c8205be9405bb9db1fb041 Mon Sep 17 00:00:00 2001 From: Ben Schmeckpeper Date: Tue, 5 Jul 2022 11:19:53 -0500 Subject: [PATCH 1/3] Parse the attrib-bits attribute field for V05 file attributes The attrib-bits field was introduced in V05 of the SFTP spec and needs to be parsed from the attributes struct when parsing file data. Without handling attrib-bits, we wind up off by 4 bytes for each entry in the filename data and quickly wind up parsing gibberish. --- lib/net/sftp/protocol/05/attributes.rb | 64 ++++++++++++++++++++++++++ lib/net/sftp/protocol/05/base.rb | 11 ++++- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 lib/net/sftp/protocol/05/attributes.rb diff --git a/lib/net/sftp/protocol/05/attributes.rb b/lib/net/sftp/protocol/05/attributes.rb new file mode 100644 index 0000000..efb727b --- /dev/null +++ b/lib/net/sftp/protocol/05/attributes.rb @@ -0,0 +1,64 @@ +require 'net/sftp/protocol/01/attributes' + +module Net; module SFTP; module Protocol; module V05 + + # A class representing the attributes of a file or directory on the server. + # It may be used to specify new attributes, or to query existing attributes. + # This particular class is specific to versions 4 and 5 of the SFTP + # protocol. + # + # To specify new attributes, just pass a hash as the argument to the + # constructor. The following keys are supported: + # + # * :type:: the type of the item (integer, one of the T_ constants) + # * :size:: the size of the item (integer) + # * :uid:: the user-id that owns the file (integer) + # * :gid:: the group-id that owns the file (integer) + # * :owner:: the name of the user that owns the file (string) + # * :group:: the name of the group that owns the file (string) + # * :permissions:: the permissions on the file (integer, e.g. 0755) + # * :atime:: the access time of the file (integer, seconds since epoch) + # * :atime_nseconds:: the nanosecond component of atime (integer) + # * :createtime:: the time at which the file was created (integer, seconds since epoch) + # * :createtime_nseconds:: the nanosecond component of createtime (integer) + # * :mtime:: the modification time of the file (integer, seconds since epoch) + # * :mtime_nseconds:: the nanosecond component of mtime (integer) + # * :acl:: an array of ACL entries for the item + # * :extended:: a hash of name/value pairs identifying extended info + # + # Likewise, when the server sends an Attributes object, all of the + # above attributes are exposed as methods (though not all will be set with + # non-nil values from the server). + class Attributes < V04::Attributes + + F_ATTRIB_BITS = 0x00000200 + + class < Date: Tue, 9 Aug 2022 11:53:21 -0500 Subject: [PATCH 2/3] V06 attributes can inherit from V05 and reuse the F_BITS flag --- lib/net/sftp/protocol/05/attributes.rb | 47 ++++++++++++-------------- lib/net/sftp/protocol/06/attributes.rb | 5 ++- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/net/sftp/protocol/05/attributes.rb b/lib/net/sftp/protocol/05/attributes.rb index efb727b..d28579c 100644 --- a/lib/net/sftp/protocol/05/attributes.rb +++ b/lib/net/sftp/protocol/05/attributes.rb @@ -1,4 +1,4 @@ -require 'net/sftp/protocol/01/attributes' +require 'net/sftp/protocol/04/attributes' module Net; module SFTP; module Protocol; module V05 @@ -24,41 +24,38 @@ module Net; module SFTP; module Protocol; module V05 # * :mtime:: the modification time of the file (integer, seconds since epoch) # * :mtime_nseconds:: the nanosecond component of mtime (integer) # * :acl:: an array of ACL entries for the item + # * :attrib_bits:: other attributes of the file or directory (as a bit field) (integer) # * :extended:: a hash of name/value pairs identifying extended info # # Likewise, when the server sends an Attributes object, all of the # above attributes are exposed as methods (though not all will be set with # non-nil values from the server). class Attributes < V04::Attributes + F_BITS = 0x00000200 - F_ATTRIB_BITS = 0x00000200 - - class < Date: Tue, 9 Aug 2022 12:15:10 -0500 Subject: [PATCH 3/3] Add tests around the new V05 attributes --- test/protocol/05/test_attributes.rb | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/protocol/05/test_attributes.rb diff --git a/test/protocol/05/test_attributes.rb b/test/protocol/05/test_attributes.rb new file mode 100644 index 0000000..fb47b9c --- /dev/null +++ b/test/protocol/05/test_attributes.rb @@ -0,0 +1,80 @@ +require 'common' + +module Etc; end + +class Protocol::V05::TestAttributes < Net::SFTP::TestCase + def test_from_buffer_should_correctly_parse_buffer_and_return_attribute_object + attributes = attributes_factory.from_buffer(full_buffer) + + assert_equal 9, attributes.type + assert_equal 1234567890, attributes.size + assert_equal "jamis", attributes.owner + assert_equal "users", attributes.group + assert_equal 0755, attributes.permissions + assert_equal 1234567890, attributes.atime + assert_equal 12345, attributes.atime_nseconds + assert_equal 2345678901, attributes.createtime + assert_equal 23456, attributes.createtime_nseconds + assert_equal 3456789012, attributes.mtime + assert_equal 34567, attributes.mtime_nseconds + + assert_equal 2, attributes.acl.length + + assert_equal 1, attributes.acl.first.type + assert_equal 2, attributes.acl.first.flag + assert_equal 3, attributes.acl.first.mask + assert_equal "foo", attributes.acl.first.who + + assert_equal 4, attributes.acl.last.type + assert_equal 5, attributes.acl.last.flag + assert_equal 6, attributes.acl.last.mask + assert_equal "bar", attributes.acl.last.who + + assert_equal 0x12341234, attributes.attrib_bits + + assert_equal "second", attributes.extended["first"] + end + + def test_attributes_to_s_should_build_binary_representation + attributes = attributes_factory.new( + :type => 9, + :size => 1234567890, + :owner => "jamis", :group => "users", + :permissions => 0755, + :atime => 1234567890, :atime_nseconds => 12345, + :createtime => 2345678901, :createtime_nseconds => 23456, + :mtime => 3456789012, :mtime_nseconds => 34567, + :acl => [attributes_factory::ACL.new(1,2,3,"foo"), + attributes_factory::ACL.new(4,5,6,"bar")], + :attrib_bits => 0x12341234, + :extended => { "first" => "second" }) + + assert_equal full_buffer.to_s, attributes.to_s + end + + def test_attributes_to_s_should_build_binary_representation_when_subset_is_present + attributes = attributes_factory.new(:permissions => 0755) + assert_equal Net::SSH::Buffer.from(:long, 0x4, :byte, 1, :long, 0755).to_s, attributes.to_s + end + + private + + def full_buffer + Net::SSH::Buffer.from(:long, 0x800003fd, + :byte, 9, :int64, 1234567890, + :string, "jamis", :string, "users", + :long, 0755, + :int64, 1234567890, :long, 12345, + :int64, 2345678901, :long, 23456, + :int64, 3456789012, :long, 34567, + :string, raw(:long, 2, + :long, 1, :long, 2, :long, 3, :string, "foo", + :long, 4, :long, 5, :long, 6, :string, "bar"), + :long, 0x12341234, + :long, 1, :string, "first", :string, "second") + end + + def attributes_factory + Net::SFTP::Protocol::V05::Attributes + end +end