net-sftp 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. data/CHANGELOG.rdoc +23 -0
  2. data/Manifest +55 -0
  3. data/README.rdoc +96 -0
  4. data/Rakefile +30 -0
  5. data/lib/net/sftp.rb +53 -38
  6. data/lib/net/sftp/constants.rb +187 -0
  7. data/lib/net/sftp/errors.rb +34 -20
  8. data/lib/net/sftp/operations/dir.rb +93 -0
  9. data/lib/net/sftp/operations/download.rb +364 -0
  10. data/lib/net/sftp/operations/file.rb +176 -0
  11. data/lib/net/sftp/operations/file_factory.rb +60 -0
  12. data/lib/net/sftp/operations/upload.rb +387 -0
  13. data/lib/net/sftp/packet.rb +21 -0
  14. data/lib/net/sftp/protocol.rb +32 -0
  15. data/lib/net/sftp/protocol/01/attributes.rb +265 -96
  16. data/lib/net/sftp/protocol/01/base.rb +268 -0
  17. data/lib/net/sftp/protocol/01/name.rb +43 -0
  18. data/lib/net/sftp/protocol/02/base.rb +31 -0
  19. data/lib/net/sftp/protocol/03/base.rb +35 -0
  20. data/lib/net/sftp/protocol/04/attributes.rb +120 -195
  21. data/lib/net/sftp/protocol/04/base.rb +94 -0
  22. data/lib/net/sftp/protocol/04/name.rb +67 -0
  23. data/lib/net/sftp/protocol/05/base.rb +66 -0
  24. data/lib/net/sftp/protocol/06/attributes.rb +107 -0
  25. data/lib/net/sftp/protocol/06/base.rb +63 -0
  26. data/lib/net/sftp/protocol/base.rb +50 -0
  27. data/lib/net/sftp/request.rb +91 -0
  28. data/lib/net/sftp/response.rb +76 -0
  29. data/lib/net/sftp/session.rb +914 -238
  30. data/lib/net/sftp/version.rb +14 -21
  31. data/net-sftp.gemspec +60 -0
  32. data/setup.rb +1331 -0
  33. data/test/common.rb +173 -0
  34. data/test/protocol/01/test_attributes.rb +97 -0
  35. data/test/protocol/01/test_base.rb +210 -0
  36. data/test/protocol/01/test_name.rb +27 -0
  37. data/test/protocol/02/test_base.rb +26 -0
  38. data/test/protocol/03/test_base.rb +27 -0
  39. data/test/protocol/04/test_attributes.rb +148 -0
  40. data/test/protocol/04/test_base.rb +74 -0
  41. data/test/protocol/04/test_name.rb +49 -0
  42. data/test/protocol/05/test_base.rb +62 -0
  43. data/test/protocol/06/test_attributes.rb +124 -0
  44. data/test/protocol/06/test_base.rb +51 -0
  45. data/test/protocol/test_base.rb +42 -0
  46. data/test/test_all.rb +3 -0
  47. data/test/test_dir.rb +47 -0
  48. data/test/test_download.rb +252 -0
  49. data/test/test_file.rb +159 -0
  50. data/test/test_file_factory.rb +48 -0
  51. data/test/test_packet.rb +9 -0
  52. data/test/test_protocol.rb +17 -0
  53. data/test/test_request.rb +71 -0
  54. data/test/test_response.rb +53 -0
  55. data/test/test_session.rb +741 -0
  56. data/test/test_upload.rb +219 -0
  57. metadata +59 -111
  58. data/doc/LICENSE-BSD +0 -27
  59. data/doc/LICENSE-GPL +0 -280
  60. data/doc/LICENSE-RUBY +0 -56
  61. data/doc/faq/faq.html +0 -298
  62. data/doc/faq/faq.rb +0 -154
  63. data/doc/faq/faq.yml +0 -183
  64. data/examples/asynchronous.rb +0 -57
  65. data/examples/get-put.rb +0 -45
  66. data/examples/sftp-open-uri.rb +0 -30
  67. data/examples/ssh-service.rb +0 -30
  68. data/examples/synchronous.rb +0 -131
  69. data/lib/net/sftp/operations/abstract.rb +0 -108
  70. data/lib/net/sftp/operations/close.rb +0 -31
  71. data/lib/net/sftp/operations/errors.rb +0 -76
  72. data/lib/net/sftp/operations/fsetstat.rb +0 -36
  73. data/lib/net/sftp/operations/fstat.rb +0 -32
  74. data/lib/net/sftp/operations/lstat.rb +0 -31
  75. data/lib/net/sftp/operations/mkdir.rb +0 -33
  76. data/lib/net/sftp/operations/open.rb +0 -32
  77. data/lib/net/sftp/operations/opendir.rb +0 -32
  78. data/lib/net/sftp/operations/read.rb +0 -88
  79. data/lib/net/sftp/operations/readdir.rb +0 -55
  80. data/lib/net/sftp/operations/realpath.rb +0 -37
  81. data/lib/net/sftp/operations/remove.rb +0 -31
  82. data/lib/net/sftp/operations/rename.rb +0 -32
  83. data/lib/net/sftp/operations/rmdir.rb +0 -31
  84. data/lib/net/sftp/operations/services.rb +0 -42
  85. data/lib/net/sftp/operations/setstat.rb +0 -33
  86. data/lib/net/sftp/operations/stat.rb +0 -31
  87. data/lib/net/sftp/operations/write.rb +0 -63
  88. data/lib/net/sftp/protocol/01/impl.rb +0 -251
  89. data/lib/net/sftp/protocol/01/packet-assistant.rb +0 -82
  90. data/lib/net/sftp/protocol/01/services.rb +0 -47
  91. data/lib/net/sftp/protocol/02/impl.rb +0 -39
  92. data/lib/net/sftp/protocol/02/packet-assistant.rb +0 -32
  93. data/lib/net/sftp/protocol/02/services.rb +0 -44
  94. data/lib/net/sftp/protocol/03/impl.rb +0 -42
  95. data/lib/net/sftp/protocol/03/packet-assistant.rb +0 -35
  96. data/lib/net/sftp/protocol/03/services.rb +0 -44
  97. data/lib/net/sftp/protocol/04/impl.rb +0 -86
  98. data/lib/net/sftp/protocol/04/packet-assistant.rb +0 -45
  99. data/lib/net/sftp/protocol/04/services.rb +0 -44
  100. data/lib/net/sftp/protocol/05/impl.rb +0 -90
  101. data/lib/net/sftp/protocol/05/packet-assistant.rb +0 -34
  102. data/lib/net/sftp/protocol/05/services.rb +0 -44
  103. data/lib/net/sftp/protocol/constants.rb +0 -60
  104. data/lib/net/sftp/protocol/driver.rb +0 -235
  105. data/lib/net/sftp/protocol/packet-assistant.rb +0 -84
  106. data/lib/net/sftp/protocol/services.rb +0 -55
  107. data/lib/uri/open-sftp.rb +0 -54
  108. data/lib/uri/sftp.rb +0 -42
  109. data/test/ALL-TESTS.rb +0 -23
  110. data/test/operations/tc_abstract.rb +0 -124
  111. data/test/operations/tc_close.rb +0 -40
  112. data/test/operations/tc_fsetstat.rb +0 -48
  113. data/test/operations/tc_fstat.rb +0 -40
  114. data/test/operations/tc_lstat.rb +0 -40
  115. data/test/operations/tc_mkdir.rb +0 -48
  116. data/test/operations/tc_open.rb +0 -42
  117. data/test/operations/tc_opendir.rb +0 -40
  118. data/test/operations/tc_read.rb +0 -103
  119. data/test/operations/tc_readdir.rb +0 -88
  120. data/test/operations/tc_realpath.rb +0 -54
  121. data/test/operations/tc_remove.rb +0 -40
  122. data/test/operations/tc_rmdir.rb +0 -40
  123. data/test/operations/tc_setstat.rb +0 -48
  124. data/test/operations/tc_stat.rb +0 -40
  125. data/test/operations/tc_write.rb +0 -91
  126. data/test/protocol/01/tc_attributes.rb +0 -138
  127. data/test/protocol/01/tc_impl.rb +0 -294
  128. data/test/protocol/01/tc_packet_assistant.rb +0 -81
  129. data/test/protocol/02/tc_impl.rb +0 -41
  130. data/test/protocol/02/tc_packet_assistant.rb +0 -31
  131. data/test/protocol/03/tc_impl.rb +0 -48
  132. data/test/protocol/03/tc_packet_assistant.rb +0 -34
  133. data/test/protocol/04/tc_attributes.rb +0 -174
  134. data/test/protocol/04/tc_impl.rb +0 -91
  135. data/test/protocol/04/tc_packet_assistant.rb +0 -38
  136. data/test/protocol/05/tc_impl.rb +0 -61
  137. data/test/protocol/05/tc_packet_assistant.rb +0 -32
  138. data/test/protocol/tc_driver.rb +0 -219
@@ -0,0 +1,21 @@
1
+ require 'net/ssh/buffer'
2
+
3
+ module Net; module SFTP
4
+
5
+ # A specialization of the Net::SSH::Buffer class, which simply auto-reads
6
+ # the type byte from the front of every packet it represents.
7
+ class Packet < Net::SSH::Buffer
8
+ # The (intger) type of this packet. See Net::SFTP::Constants for all
9
+ # possible packet types.
10
+ attr_reader :type
11
+
12
+ # Create a new Packet object that wraps the given +data+ (which should be
13
+ # a String). The first byte of the data will be consumed automatically and
14
+ # interpreted as the #type of this packet.
15
+ def initialize(data)
16
+ super
17
+ @type = read_byte
18
+ end
19
+ end
20
+
21
+ end; end
@@ -0,0 +1,32 @@
1
+ require 'net/sftp/protocol/01/base'
2
+ require 'net/sftp/protocol/02/base'
3
+ require 'net/sftp/protocol/03/base'
4
+ require 'net/sftp/protocol/04/base'
5
+ require 'net/sftp/protocol/05/base'
6
+ require 'net/sftp/protocol/06/base'
7
+
8
+ module Net; module SFTP
9
+
10
+ # The Protocol module contains the definitions for all supported SFTP
11
+ # protocol versions.
12
+ module Protocol
13
+
14
+ # Instantiates and returns a new protocol driver instance for the given
15
+ # protocol version. +session+ must be a valid SFTP session object, and
16
+ # +version+ must be an integer. If an unsupported version is given,
17
+ # an exception will be raised.
18
+ def self.load(session, version)
19
+ case version
20
+ when 1 then V01::Base.new(session)
21
+ when 2 then V02::Base.new(session)
22
+ when 3 then V03::Base.new(session)
23
+ when 4 then V04::Base.new(session)
24
+ when 5 then V05::Base.new(session)
25
+ when 6 then V06::Base.new(session)
26
+ else raise NotImplementedError, "unsupported SFTP version #{version.inspect}"
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end; end
@@ -1,23 +1,26 @@
1
- #--
2
- # =============================================================================
3
- # Copyright (c) 2004, Jamis Buck (jamis@37signals.com)
4
- # All rights reserved.
5
- #
6
- # This source file is distributed as part of the Net::SFTP Secure FTP Client
7
- # library for Ruby. This file (and the library as a whole) may be used only as
8
- # allowed by either the BSD license, or the Ruby license (or, by association
9
- # with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SFTP
10
- # distribution for the texts of these licenses.
11
- # -----------------------------------------------------------------------------
12
- # net-sftp website: http://net-ssh.rubyforge.org/sftp
13
- # project website : http://rubyforge.org/projects/net-ssh
14
- # =============================================================================
15
- #++
16
-
17
- module Net ; module SFTP ; module Protocol ; module V_01
1
+ require 'net/ssh/buffer'
2
+
3
+ module Net; module SFTP; module Protocol; module V01
18
4
 
19
5
  # A class representing the attributes of a file or directory on the server.
20
6
  # It may be used to specify new attributes, or to query existing attributes.
7
+ #
8
+ # To specify new attributes, just pass a hash as the argument to the
9
+ # constructor. The following keys are supported:
10
+ #
11
+ # * :size:: the size of the file
12
+ # * :uid:: the user-id that owns the file (integer)
13
+ # * :gid:: the group-id that owns the file (integer)
14
+ # * :owner:: the name of the user that owns the file (string)
15
+ # * :group:: the name of the group that owns the file (string)
16
+ # * :permissions:: the permissions on the file (integer, e.g. 0755)
17
+ # * :atime:: the access time of the file (integer, seconds since epoch)
18
+ # * :mtime:: the modification time of the file (integer, seconds since epoch)
19
+ # * :extended:: a hash of name/value pairs identifying extended info
20
+ #
21
+ # Likewise, when the server sends an Attributes object, all of the
22
+ # above attributes are exposed as methods (though not all will be set with
23
+ # non-nil values from the server).
21
24
  class Attributes
22
25
 
23
26
  F_SIZE = 0x00000001
@@ -25,122 +28,288 @@ module Net ; module SFTP ; module Protocol ; module V_01
25
28
  F_PERMISSIONS = 0x00000004
26
29
  F_ACMODTIME = 0x00000008
27
30
  F_EXTENDED = 0x80000000
28
-
31
+
32
+ T_REGULAR = 1
33
+ T_DIRECTORY = 2
34
+ T_SYMLINK = 3
35
+ T_SPECIAL = 4
36
+ T_UNKNOWN = 5
37
+ T_SOCKET = 6
38
+ T_CHAR_DEVICE = 7
39
+ T_BLOCK_DEVICE = 8
40
+ T_FIFO = 9
41
+
42
+ class <<self
43
+ # Returns the array of attribute meta-data that defines the structure of
44
+ # the attributes packet as described by this version of the protocol.
45
+ def elements #:nodoc:
46
+ @elements ||= [
47
+ [:size, :int64, F_SIZE],
48
+ [:uid, :long, F_UIDGID],
49
+ [:gid, :long, F_UIDGID],
50
+ [:permissions, :long, F_PERMISSIONS],
51
+ [:atime, :long, F_ACMODTIME],
52
+ [:mtime, :long, F_ACMODTIME],
53
+ [:extended, :special, F_EXTENDED]
54
+ ]
55
+ end
56
+
57
+ # Parses the given buffer and returns an Attributes object compsed from
58
+ # the data extracted from it.
59
+ def from_buffer(buffer)
60
+ flags = buffer.read_long
61
+ data = {}
62
+
63
+ elements.each do |name, type, condition|
64
+ if flags & condition == condition
65
+ if type == :special
66
+ data[name] = send("parse_#{name}", buffer)
67
+ else
68
+ data[name] = buffer.send("read_#{type}")
69
+ end
70
+ end
71
+ end
72
+
73
+ new(data)
74
+ end
75
+
76
+ # A convenience method for defining methods that expose specific
77
+ # attributes. This redefines the standard attr_accessor (an admittedly
78
+ # bad practice) because (1) I don't need any "regular" accessors, and
79
+ # (2) because rdoc will automatically pick up and note methods defined
80
+ # via attr_accessor.
81
+ def attr_accessor(name) #:nodoc:
82
+ class_eval <<-CODE
83
+ def #{name}
84
+ attributes[:#{name}]
85
+ end
86
+ CODE
87
+
88
+ attr_writer(name)
89
+ end
90
+
91
+ # A convenience method for defining methods that expose specific
92
+ # attributes. This redefines the standard attr_writer (an admittedly
93
+ # bad practice) because (1) I don't need any "regular" accessors, and
94
+ # (2) because rdoc will automatically pick up and note methods defined
95
+ # via attr_writer.
96
+ def attr_writer(name) #:nodoc:
97
+ class_eval <<-CODE
98
+ def #{name}=(value)
99
+ attributes[:#{name}] = value
100
+ end
101
+ CODE
102
+ end
103
+
104
+ private
105
+
106
+ # Parse the hash of extended data from the buffer.
107
+ def parse_extended(buffer)
108
+ extended = Hash.new
109
+ buffer.read_long.times do
110
+ extended[buffer.read_string] = buffer.read_string
111
+ end
112
+ extended
113
+ end
114
+ end
115
+
116
+ # The hash of name/value pairs that backs this Attributes instance
117
+ attr_reader :attributes
118
+
119
+ # The size of the file.
29
120
  attr_accessor :size
30
- attr_accessor :uid
31
- attr_accessor :gid
121
+
122
+ # The user-id of the user that owns the file
123
+ attr_writer :uid
124
+
125
+ # The group-id of the user that owns the file
126
+ attr_writer :gid
127
+
128
+ # The permissions on the file
32
129
  attr_accessor :permissions
130
+
131
+ # The last access time of the file
33
132
  attr_accessor :atime
133
+
134
+ # The modification time of the file
34
135
  attr_accessor :mtime
136
+
137
+ # The hash of name/value pairs identifying extended information about the file
35
138
  attr_accessor :extended
36
139
 
37
- # An initialization routine, to grant the class (factory) access to a
38
- # buffer factory. The buffer factory is used by the class' #to_s
39
- # method to encode the object's attributes.
140
+ # Create a new Attributes instance with the given attributes. The
141
+ # following keys are supported:
40
142
  #
41
- # This returns +self+, making it suitable for chaining.
42
- def self.init( buffers )
43
- @buffers = buffers
44
- self
143
+ # * :size:: the size of the file
144
+ # * :uid:: the user-id that owns the file (integer)
145
+ # * :gid:: the group-id that owns the file (integer)
146
+ # * :owner:: the name of the user that owns the file (string)
147
+ # * :group:: the name of the group that owns the file (string)
148
+ # * :permissions:: the permissions on the file (integer, e.g. 0755)
149
+ # * :atime:: the access time of the file (integer, seconds since epoch)
150
+ # * :mtime:: the modification time of the file (integer, seconds since epoch)
151
+ # * :extended:: a hash of name/value pairs identifying extended info
152
+ def initialize(attributes={})
153
+ @attributes = attributes
45
154
  end
46
155
 
47
- # Returns the buffer factory for this class.
48
- def self.buffers
49
- @buffers
156
+ # Returns the user-id of the user that owns the file, or +nil+ if that
157
+ # information is not available. If an :owner key exists, but not a :uid
158
+ # key, the Etc module will be used to reverse lookup the id from the name.
159
+ # This might fail on some systems (e.g., Windows).
160
+ def uid
161
+ if attributes[:owner] && !attributes.key?(:uid)
162
+ require 'etc'
163
+ attributes[:uid] = Etc.getpwnam(attributes[:owner]).uid
164
+ end
165
+ attributes[:uid]
50
166
  end
51
167
 
52
- # Returns the buffer factory for the object's class.
53
- def buffers
54
- self.class.buffers
168
+ # Returns the group-id of the group that owns the file, or +nil+ if that
169
+ # information is not available. If a :group key exists, but not a :gid
170
+ # key, the Etc module will be used to reverse lookup the id from the name.
171
+ # This might fail on some systems (e.g., Windows).
172
+ def gid
173
+ if attributes[:group] && !attributes.key?(:gid)
174
+ require 'etc'
175
+ attributes[:gid] = Etc.getgrnam(attributes[:group]).gid
176
+ end
177
+ attributes[:gid]
55
178
  end
56
179
 
57
- # Create a new, empty Attributes object.
58
- def self.empty
59
- new
180
+ # Returns the username of the user that owns the file, or +nil+ if that
181
+ # information is not available. If the :uid is given, but not the :owner,
182
+ # the Etc module will be used to lookup the name from the id. This might
183
+ # fail on some systems (e.g. Windows).
184
+ def owner
185
+ if attributes[:uid] && !attributes[:owner]
186
+ require 'etc'
187
+ attributes[:owner] = Etc.getpwuid(attributes[:uid].to_i).name
188
+ end
189
+ attributes[:owner]
60
190
  end
61
191
 
62
- # Create a new Attributes object, initialized from the given buffer.
63
- def self.from_buffer( buffer )
64
- flags = buffer.read_long
65
-
66
- size = buffer.read_int64 if ( flags & F_SIZE ) != 0
67
- uid = buffer.read_long if ( flags & F_UIDGID ) != 0
68
- gid = buffer.read_long if ( flags & F_UIDGID ) != 0
69
- permissions = buffer.read_long if ( flags & F_PERMISSIONS ) != 0
70
- atime = buffer.read_long if ( flags & F_ACMODTIME ) != 0
71
- mtime = buffer.read_long if ( flags & F_ACMODTIME ) != 0
72
-
73
- if ( flags & F_EXTENDED ) != 0
74
- extended = Hash.new
75
- buffer.read_long.times do
76
- extended[ buffer.read_string ] = buffer.read_string
77
- end
192
+ # Returns the group name of the group that owns the file, or +nil+ if that
193
+ # information is not available. If the :gid is given, but not the :group,
194
+ # the Etc module will be used to lookup the name from the id. This might
195
+ # fail on some systems (e.g. Windows).
196
+ def group
197
+ if attributes[:gid] && !attributes[:group]
198
+ require 'etc'
199
+ attributes[:group] = Etc.getgrgid(attributes[:gid].to_i).name
78
200
  end
201
+ attributes[:group]
202
+ end
79
203
 
80
- new( size, uid, gid, permissions, atime, mtime, extended )
204
+ # Inspects the permissions bits to determine what type of entity this
205
+ # attributes object represents. If will return one of the T_ constants.
206
+ def type
207
+ if permissions & 0140000 == 0140000 then
208
+ T_SOCKET
209
+ elsif permissions & 0120000 == 0120000 then
210
+ T_SYMLINK
211
+ elsif permissions & 0100000 == 0100000 then
212
+ T_REGULAR
213
+ elsif permissions & 060000 == 060000 then
214
+ T_BLOCK_DEVICE
215
+ elsif permissions & 040000 == 040000 then
216
+ T_DIRECTORY
217
+ elsif permissions & 020000 == 020000 then
218
+ T_CHAR_DEVICE
219
+ elsif permissions & 010000 == 010000 then
220
+ T_FIFO
221
+ else
222
+ T_UNKNOWN
223
+ end
81
224
  end
82
225
 
83
- # Create a new attributes object, initialized from the given hash. The
84
- # :owner and :group attributes are treated specially; they are not actually
85
- # supported by this version of the protocol, but are instead converted
86
- # by this method to their corresponding id numbers, and assigned
87
- # (respectively) to :uid and :gid.
88
- def self.from_hash( hash )
89
- if hash[:owner]
90
- require 'etc'
91
- hash[:uid] = Etc.getpwnam( hash[:owner] ).uid
226
+ # Returns the type as a symbol, rather than an integer, for easier use in
227
+ # Ruby programs.
228
+ def symbolic_type
229
+ case type
230
+ when T_SOCKET then :socket
231
+ when T_SYMLINK then :symlink
232
+ when T_REGULAR then :regular
233
+ when T_BLOCK_DEVICE then :block_device
234
+ when T_DIRECTORY then :directory
235
+ when T_CHAR_DEVICE then :char_device
236
+ when T_FIFO then :fifo
237
+ when T_SPECIAL then :special
238
+ when T_UNKNOWN then :unknown
239
+ else raise NotImplementedError, "unknown file type #{type} (bug?)"
92
240
  end
241
+ end
93
242
 
94
- if hash[:group]
95
- require 'etc'
96
- hash[:gid] = Etc.getgrnam( hash[:group] ).gid
243
+ # Returns true if these attributes appear to describe a directory.
244
+ def directory?
245
+ case type
246
+ when T_DIRECTORY then true
247
+ when T_UNKNOWN then nil
248
+ else false
97
249
  end
250
+ end
98
251
 
99
- new hash[:size], hash[:uid], hash[:gid], hash[:permissions],
100
- hash[:atime], hash[:mtime], hash[:extended]
252
+ # Returns true if these attributes appear to describe a symlink.
253
+ def symlink?
254
+ case type
255
+ when T_SYMLINK then true
256
+ when T_UNKNOWN then nil
257
+ else false
258
+ end
101
259
  end
102
260
 
103
- private_class_method :new
104
-
105
- # Create a new Attributes with the given attributes.
106
- def initialize( size=nil, uid=nil, gid=nil, permissions=nil,
107
- atime=nil, mtime=nil, extended=nil )
108
- # begin
109
- @size = size
110
- @uid = uid
111
- @gid = gid
112
- @permissions = permissions
113
- @atime = atime
114
- @mtime = mtime
115
- @extended = extended
261
+ # Returns true if these attributes appear to describe a regular file.
262
+ def file?
263
+ case type
264
+ when T_REGULAR then true
265
+ when T_UNKNOWN then nil
266
+ else false
267
+ end
116
268
  end
117
269
 
118
270
  # Convert the object to a string suitable for passing in an SFTP
119
- # packet.
271
+ # packet. This is the raw representation of the attribute packet payload,
272
+ # and is not intended to be human readable.
120
273
  def to_s
121
- flags = 0
274
+ prepare_serialization!
122
275
 
123
- flags |= F_SIZE if @size
124
- flags |= F_UIDGID if @uid && @gid
125
- flags |= F_PERMISSIONS if @permissions
126
- flags |= F_ACMODTIME if @atime && @mtime
127
- flags |= F_EXTENDED if @extended
276
+ flags = 0
128
277
 
129
- buffer = buffers.writer
130
- buffer.write_long flags
131
- buffer.write_int64 @size if @size
132
- buffer.write_long @uid, @gid if @uid && @gid
133
- buffer.write_long @permissions if @permissions
134
- buffer.write_long @atime, @mtime if @atime && @mtime
278
+ self.class.elements.each do |name, type, condition|
279
+ flags |= condition if attributes[name]
280
+ end
135
281
 
136
- if @extended
137
- buffer.write_long @extended.size
138
- @extended.each { |k,v| buffer.write_string k, v }
282
+ buffer = Net::SSH::Buffer.from(:long, flags)
283
+ self.class.elements.each do |name, type, condition|
284
+ if flags & condition == condition
285
+ if type == :special
286
+ send("encode_#{name}", buffer)
287
+ else
288
+ buffer.send("write_#{type}", attributes[name])
289
+ end
290
+ end
139
291
  end
140
292
 
141
293
  buffer.to_s
142
294
  end
143
295
 
296
+ private
297
+
298
+ # Perform protocol-version-specific preparations for serialization.
299
+ def prepare_serialization!
300
+ # force the uid/gid to be translated from owner/group, if those keys
301
+ # were given on instantiation
302
+ uid
303
+ gid
304
+ end
305
+
306
+ # Encodes information about the extended info onto the end of the given
307
+ # buffer.
308
+ def encode_extended(buffer)
309
+ buffer.write_long extended.size
310
+ extended.each { |k,v| buffer.write_string k, v }
311
+ end
312
+
144
313
  end
145
314
 
146
315
  end ; end ; end ; end