net-sftp 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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