net-sftp-backports 4.0.0.backports
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +35 -0
- data/.gitignore +6 -0
- data/CHANGES.txt +67 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +19 -0
- data/Manifest +55 -0
- data/README.rdoc +118 -0
- data/Rakefile +53 -0
- data/lib/net/sftp/constants.rb +187 -0
- data/lib/net/sftp/errors.rb +39 -0
- data/lib/net/sftp/operations/dir.rb +93 -0
- data/lib/net/sftp/operations/download.rb +365 -0
- data/lib/net/sftp/operations/file.rb +198 -0
- data/lib/net/sftp/operations/file_factory.rb +60 -0
- data/lib/net/sftp/operations/upload.rb +395 -0
- data/lib/net/sftp/packet.rb +21 -0
- data/lib/net/sftp/protocol/01/attributes.rb +315 -0
- data/lib/net/sftp/protocol/01/base.rb +268 -0
- data/lib/net/sftp/protocol/01/name.rb +43 -0
- data/lib/net/sftp/protocol/02/base.rb +31 -0
- data/lib/net/sftp/protocol/03/base.rb +35 -0
- data/lib/net/sftp/protocol/04/attributes.rb +152 -0
- data/lib/net/sftp/protocol/04/base.rb +94 -0
- data/lib/net/sftp/protocol/04/name.rb +67 -0
- data/lib/net/sftp/protocol/05/base.rb +66 -0
- data/lib/net/sftp/protocol/06/attributes.rb +107 -0
- data/lib/net/sftp/protocol/06/base.rb +63 -0
- data/lib/net/sftp/protocol/base.rb +50 -0
- data/lib/net/sftp/protocol.rb +32 -0
- data/lib/net/sftp/request.rb +91 -0
- data/lib/net/sftp/response.rb +76 -0
- data/lib/net/sftp/session.rb +954 -0
- data/lib/net/sftp/version.rb +68 -0
- data/lib/net/sftp.rb +78 -0
- data/net-sftp-public_cert.pem +20 -0
- data/net-sftp.gemspec +48 -0
- data/setup.rb +1331 -0
- metadata +132 -0
@@ -0,0 +1,954 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/sftp/constants'
|
3
|
+
require 'net/sftp/errors'
|
4
|
+
require 'net/sftp/protocol'
|
5
|
+
require 'net/sftp/request'
|
6
|
+
require 'net/sftp/operations/dir'
|
7
|
+
require 'net/sftp/operations/upload'
|
8
|
+
require 'net/sftp/operations/download'
|
9
|
+
require 'net/sftp/operations/file_factory'
|
10
|
+
|
11
|
+
module Net; module SFTP
|
12
|
+
|
13
|
+
# The Session class encapsulates a single SFTP channel on a Net::SSH
|
14
|
+
# connection. Instances of this class are what most applications will
|
15
|
+
# interact with most, as it provides access to both low-level (mkdir,
|
16
|
+
# rename, remove, symlink, etc.) and high-level (upload, download, etc.)
|
17
|
+
# SFTP operations.
|
18
|
+
#
|
19
|
+
# Although Session makes it easy to do SFTP operations serially, you can
|
20
|
+
# also set up multiple operations to be done in parallel, too, without
|
21
|
+
# needing to resort to threading. You merely need to fire off the requests,
|
22
|
+
# and then run the event loop until all of the requests have completed:
|
23
|
+
#
|
24
|
+
# handle1 = sftp.open!("/path/to/file1")
|
25
|
+
# handle2 = sftp.open!("/path/to/file2")
|
26
|
+
#
|
27
|
+
# r1 = sftp.read(handle1, 0, 1024)
|
28
|
+
# r2 = sftp.read(handle2, 0, 1024)
|
29
|
+
# sftp.loop { [r1, r2].any? { |r| r.pending? } }
|
30
|
+
#
|
31
|
+
# puts "chunk #1: #{r1.response[:data]}"
|
32
|
+
# puts "chunk #2: #{r2.response[:data]}"
|
33
|
+
#
|
34
|
+
# By passing blocks to the operations, you can set up powerful state
|
35
|
+
# machines, to fire off subsequent operations. In fact, the Net::SFTP::Operations::Upload
|
36
|
+
# and Net::SFTP::Operations::Download classes set up such state machines, so that
|
37
|
+
# multiple uploads and/or downloads can be running simultaneously.
|
38
|
+
#
|
39
|
+
# The convention with the names of the operations is as follows: if the method
|
40
|
+
# name ends with an exclamation mark, like #read!, it will be synchronous
|
41
|
+
# (e.g., it will block until the server responds). Methods without an
|
42
|
+
# exclamation mark (e.g. #read) are asynchronous, and return before the
|
43
|
+
# server has responded. You will need to make sure the SSH event loop is
|
44
|
+
# run in order to process these requests. (See #loop.)
|
45
|
+
class Session
|
46
|
+
include Net::SSH::Loggable
|
47
|
+
include Net::SFTP::Constants::PacketTypes
|
48
|
+
|
49
|
+
# The highest protocol version supported by the Net::SFTP library.
|
50
|
+
HIGHEST_PROTOCOL_VERSION_SUPPORTED = 6
|
51
|
+
|
52
|
+
# A reference to the Net::SSH session object that powers this SFTP session.
|
53
|
+
attr_reader :session
|
54
|
+
|
55
|
+
# The Net::SSH::Connection::Channel object that the SFTP session is being
|
56
|
+
# processed by.
|
57
|
+
attr_reader :channel
|
58
|
+
|
59
|
+
# The state of the SFTP connection. It will be :opening, :subsystem, :init,
|
60
|
+
# :open, or :closed.
|
61
|
+
attr_reader :state
|
62
|
+
|
63
|
+
# The protocol instance being used by this SFTP session. Useful for
|
64
|
+
# querying the protocol version in effect.
|
65
|
+
attr_reader :protocol
|
66
|
+
|
67
|
+
# The hash of pending requests. Any requests that have been sent and which
|
68
|
+
# the server has not yet responded to will be represented here.
|
69
|
+
attr_reader :pending_requests
|
70
|
+
|
71
|
+
# Creates a new Net::SFTP instance atop the given Net::SSH connection.
|
72
|
+
# This will return immediately, before the SFTP connection has been properly
|
73
|
+
# initialized. Once the connection is ready, the given block will be called.
|
74
|
+
# If you want to block until the connection has been initialized, try this:
|
75
|
+
#
|
76
|
+
# sftp = Net::SFTP::Session.new(ssh)
|
77
|
+
# sftp.loop { sftp.opening? }
|
78
|
+
def initialize(session, version = nil, &block)
|
79
|
+
@session = session
|
80
|
+
@version = version
|
81
|
+
@input = Net::SSH::Buffer.new
|
82
|
+
self.logger = session.logger
|
83
|
+
@state = :closed
|
84
|
+
@pending_requests = {}
|
85
|
+
|
86
|
+
connect(&block)
|
87
|
+
end
|
88
|
+
|
89
|
+
public # high-level SFTP operations
|
90
|
+
|
91
|
+
# Initiates an upload from +local+ to +remote+, asynchronously. This
|
92
|
+
# method will return a new Net::SFTP::Operations::Upload instance, and requires
|
93
|
+
# the event loop to be run in order for the upload to progress. See
|
94
|
+
# Net::SFTP::Operations::Upload for a full discussion of how this method can be
|
95
|
+
# used.
|
96
|
+
#
|
97
|
+
# uploader = sftp.upload("/local/path", "/remote/path")
|
98
|
+
# uploader.wait
|
99
|
+
def upload(local, remote = File.basename(local), options={}, &block)
|
100
|
+
Operations::Upload.new(self, local, remote, options, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Identical to #upload, but blocks until the upload is complete.
|
104
|
+
def upload!(local, remote = File.basename(local), options={}, &block)
|
105
|
+
upload(local, remote, options, &block).wait
|
106
|
+
end
|
107
|
+
|
108
|
+
# Initiates a download from +remote+ to +local+, asynchronously. This
|
109
|
+
# method will return a new Net::SFTP::Operations::Download instance, and requires
|
110
|
+
# that the event loop be run in order for the download to progress. See
|
111
|
+
# Net::SFTP::Operations::Download for a full discussion of how this method can be
|
112
|
+
# used.
|
113
|
+
#
|
114
|
+
# download = sftp.download("/remote/path", "/local/path")
|
115
|
+
# download.wait
|
116
|
+
def download(remote, local, options={}, &block)
|
117
|
+
Operations::Download.new(self, local, remote, options, &block)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Identical to #download, but blocks until the download is complete.
|
121
|
+
# If +local+ is omitted, downloads the file to an in-memory buffer
|
122
|
+
# and returns the result as a string; otherwise, returns the
|
123
|
+
# Net::SFTP::Operations::Download instance.
|
124
|
+
def download!(remote, local=nil, options={}, &block)
|
125
|
+
require 'stringio' unless defined?(StringIO)
|
126
|
+
destination = local || StringIO.new
|
127
|
+
result = download(remote, destination, options, &block).wait
|
128
|
+
local ? result : destination.string
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns an Net::SFTP::Operations::FileFactory instance, which can be used to
|
132
|
+
# mimic synchronous, IO-like file operations on a remote file via
|
133
|
+
# SFTP.
|
134
|
+
#
|
135
|
+
# sftp.file.open("/path/to/file") do |file|
|
136
|
+
# while line = file.gets
|
137
|
+
# puts line
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# See Net::SFTP::Operations::FileFactory and Net::SFTP::Operations::File for more details.
|
142
|
+
def file
|
143
|
+
@file ||= Operations::FileFactory.new(self)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a Net::SFTP::Operations::Dir instance, which can be used to
|
147
|
+
# conveniently iterate over and search directories on the remote server.
|
148
|
+
#
|
149
|
+
# sftp.dir.glob("/base/path", "*/**/*.rb") do |entry|
|
150
|
+
# p entry.name
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# See Net::SFTP::Operations::Dir for a more detailed discussion of how
|
154
|
+
# to use this.
|
155
|
+
def dir
|
156
|
+
@dir ||= Operations::Dir.new(self)
|
157
|
+
end
|
158
|
+
|
159
|
+
public # low-level SFTP operations
|
160
|
+
|
161
|
+
# :call-seq:
|
162
|
+
# open(path, flags="r", options={}) -> request
|
163
|
+
# open(path, flags="r", options={}) { |response| ... } -> request
|
164
|
+
#
|
165
|
+
# Opens a file on the remote server. The +flags+ parameter determines
|
166
|
+
# how the flag is open, and accepts the same format as IO#open (e.g.,
|
167
|
+
# either a string like "r" or "w", or a combination of the IO constants).
|
168
|
+
# The +options+ parameter is a hash of attributes to be associated
|
169
|
+
# with the file, and varies greatly depending on the SFTP protocol
|
170
|
+
# version in use, but some (like :permissions) are always available.
|
171
|
+
#
|
172
|
+
# Returns immediately with a Request object. If a block is given, it will
|
173
|
+
# be invoked when the server responds, with a Response object as the only
|
174
|
+
# parameter. The :handle property of the response is the handle of the
|
175
|
+
# opened file, and may be passed to other methods (like #close, #read,
|
176
|
+
# #write, and so forth).
|
177
|
+
#
|
178
|
+
# sftp.open("/path/to/file") do |response|
|
179
|
+
# raise "fail!" unless response.ok?
|
180
|
+
# sftp.close(response[:handle])
|
181
|
+
# end
|
182
|
+
# sftp.loop
|
183
|
+
def open(path, flags="r", options={}, &callback)
|
184
|
+
request :open, path, flags, options, &callback
|
185
|
+
end
|
186
|
+
|
187
|
+
# Identical to #open, but blocks until the server responds. It will raise
|
188
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
189
|
+
# return the handle of the newly opened file.
|
190
|
+
#
|
191
|
+
# handle = sftp.open!("/path/to/file")
|
192
|
+
def open!(path, flags="r", options={}, &callback)
|
193
|
+
wait_for(open(path, flags, options, &callback), :handle)
|
194
|
+
end
|
195
|
+
|
196
|
+
# :call-seq:
|
197
|
+
# close(handle) -> request
|
198
|
+
# close(handle) { |response| ... } -> request
|
199
|
+
#
|
200
|
+
# Closes an open handle, whether obtained via #open, or #opendir. Returns
|
201
|
+
# immediately with a Request object. If a block is given, it will be
|
202
|
+
# invoked when the server responds.
|
203
|
+
#
|
204
|
+
# sftp.open("/path/to/file") do |response|
|
205
|
+
# raise "fail!" unless response.ok?
|
206
|
+
# sftp.close(response[:handle])
|
207
|
+
# end
|
208
|
+
# sftp.loop
|
209
|
+
def close(handle, &callback)
|
210
|
+
request :close, handle, &callback
|
211
|
+
end
|
212
|
+
|
213
|
+
# Identical to #close, but blocks until the server responds. It will
|
214
|
+
# raise a StatusException if the request was unsuccessful. Otherwise,
|
215
|
+
# it returns the Response object for this request.
|
216
|
+
#
|
217
|
+
# sftp.close!(handle)
|
218
|
+
def close!(handle, &callback)
|
219
|
+
wait_for(close(handle, &callback))
|
220
|
+
end
|
221
|
+
|
222
|
+
# :call-seq:
|
223
|
+
# read(handle, offset, length) -> request
|
224
|
+
# read(handle, offset, length) { |response| ... } -> request
|
225
|
+
#
|
226
|
+
# Requests that +length+ bytes, starting at +offset+ bytes from the
|
227
|
+
# beginning of the file, be read from the file identified by
|
228
|
+
# +handle+. (The +handle+ should be a value obtained via the #open
|
229
|
+
# method.) Returns immediately with a Request object. If a block is
|
230
|
+
# given, it will be invoked when the server responds.
|
231
|
+
#
|
232
|
+
# The :data property of the response will contain the requested data,
|
233
|
+
# assuming the call was successful.
|
234
|
+
#
|
235
|
+
# request = sftp.read(handle, 0, 1024) do |response|
|
236
|
+
# if response.eof?
|
237
|
+
# puts "end of file reached before reading any data"
|
238
|
+
# elsif !response.ok?
|
239
|
+
# puts "error (#{response})"
|
240
|
+
# else
|
241
|
+
# print(response[:data])
|
242
|
+
# end
|
243
|
+
# end
|
244
|
+
# request.wait
|
245
|
+
#
|
246
|
+
# To read an entire file will usually require multiple calls to #read,
|
247
|
+
# unless you know in advance how large the file is.
|
248
|
+
def read(handle, offset, length, &callback)
|
249
|
+
request :read, handle, offset, length, &callback
|
250
|
+
end
|
251
|
+
|
252
|
+
# Identical to #read, but blocks until the server responds. It will raise
|
253
|
+
# a StatusException if the request was unsuccessful. If the end of the file
|
254
|
+
# was reached, +nil+ will be returned. Otherwise, it returns the data that
|
255
|
+
# was read, as a String.
|
256
|
+
#
|
257
|
+
# data = sftp.read!(handle, 0, 1024)
|
258
|
+
def read!(handle, offset, length, &callback)
|
259
|
+
wait_for(read(handle, offset, length, &callback), :data)
|
260
|
+
end
|
261
|
+
|
262
|
+
# :call-seq:
|
263
|
+
# write(handle, offset, data) -> request
|
264
|
+
# write(handle, offset, data) { |response| ... } -> request
|
265
|
+
#
|
266
|
+
# Requests that +data+ be written to the file identified by +handle+,
|
267
|
+
# starting at +offset+ bytes from the start of the file. The file must
|
268
|
+
# have been opened for writing via #open. Returns immediately with a
|
269
|
+
# Request object. If a block is given, it will be invoked when the
|
270
|
+
# server responds.
|
271
|
+
#
|
272
|
+
# request = sftp.write(handle, 0, "hello, world!\n")
|
273
|
+
# request.wait
|
274
|
+
def write(handle, offset, data, &callback)
|
275
|
+
request :write, handle, offset, data, &callback
|
276
|
+
end
|
277
|
+
|
278
|
+
# Identical to #write, but blocks until the server responds. It will raise
|
279
|
+
# a StatusException if the request was unsuccessful, or the end of the file
|
280
|
+
# was reached. Otherwise, it returns the Response object for this request.
|
281
|
+
#
|
282
|
+
# sftp.write!(handle, 0, "hello, world!\n")
|
283
|
+
def write!(handle, offset, data, &callback)
|
284
|
+
wait_for(write(handle, offset, data, &callback))
|
285
|
+
end
|
286
|
+
|
287
|
+
# :call-seq:
|
288
|
+
# lstat(path, flags=nil) -> request
|
289
|
+
# lstat(path, flags=nil) { |response| ... } -> request
|
290
|
+
#
|
291
|
+
# This method is identical to the #stat method, with the exception that
|
292
|
+
# it will not follow symbolic links (thus allowing you to stat the
|
293
|
+
# link itself, rather than what it refers to). The +flags+ parameter
|
294
|
+
# is not used in SFTP protocol versions prior to 4, and will be ignored
|
295
|
+
# in those versions of the protocol that do not use it. For those that
|
296
|
+
# do, however, you may provide hints as to which file proprties you wish
|
297
|
+
# to query (e.g., if all you want is permissions, you could pass the
|
298
|
+
# Net::SFTP::Protocol::V04::Attributes::F_PERMISSIONS flag as the value
|
299
|
+
# for the +flags+ parameter).
|
300
|
+
#
|
301
|
+
# The method returns immediately with a Request object. If a block is given,
|
302
|
+
# it will be invoked when the server responds. The :attrs property of
|
303
|
+
# the response will contain an Attributes instance appropriate for the
|
304
|
+
# the protocol version (see Protocol::V01::Attributes, Protocol::V04::Attributes,
|
305
|
+
# and Protocol::V06::Attributes).
|
306
|
+
#
|
307
|
+
# request = sftp.lstat("/path/to/file") do |response|
|
308
|
+
# raise "fail!" unless response.ok?
|
309
|
+
# puts "permissions: %04o" % response[:attrs].permissions
|
310
|
+
# end
|
311
|
+
# request.wait
|
312
|
+
def lstat(path, flags=nil, &callback)
|
313
|
+
request :lstat, path, flags, &callback
|
314
|
+
end
|
315
|
+
|
316
|
+
# Identical to the #lstat method, but blocks until the server responds.
|
317
|
+
# It will raise a StatusException if the request was unsuccessful.
|
318
|
+
# Otherwise, it will return the attribute object describing the path.
|
319
|
+
#
|
320
|
+
# puts sftp.lstat!("/path/to/file").permissions
|
321
|
+
def lstat!(path, flags=nil, &callback)
|
322
|
+
wait_for(lstat(path, flags, &callback), :attrs)
|
323
|
+
end
|
324
|
+
|
325
|
+
# The fstat method is identical to the #stat and #lstat methods, with
|
326
|
+
# the exception that it takes a +handle+ as the first parameter, such
|
327
|
+
# as would be obtained via the #open or #opendir methods. (See the #lstat
|
328
|
+
# method for full documentation).
|
329
|
+
def fstat(handle, flags=nil, &callback)
|
330
|
+
request :fstat, handle, flags, &callback
|
331
|
+
end
|
332
|
+
|
333
|
+
# Identical to the #fstat method, but blocks until the server responds.
|
334
|
+
# It will raise a StatusException if the request was unsuccessful.
|
335
|
+
# Otherwise, it will return the attribute object describing the path.
|
336
|
+
#
|
337
|
+
# puts sftp.fstat!(handle).permissions
|
338
|
+
def fstat!(handle, flags=nil, &callback)
|
339
|
+
wait_for(fstat(handle, flags, &callback), :attrs)
|
340
|
+
end
|
341
|
+
|
342
|
+
# :call-seq:
|
343
|
+
# setstat(path, attrs) -> request
|
344
|
+
# setstat(path, attrs) { |response| ... } -> request
|
345
|
+
#
|
346
|
+
# This method may be used to set file metadata (such as permissions, or
|
347
|
+
# user/group information) on a remote file. The exact metadata that may
|
348
|
+
# be tweaked is dependent on the SFTP protocol version in use, but in
|
349
|
+
# general you may set at least the permissions, user, and group. (See
|
350
|
+
# Protocol::V01::Attributes, Protocol::V04::Attributes, and Protocol::V06::Attributes
|
351
|
+
# for the full lists of attributes that may be set for the different
|
352
|
+
# protocols.)
|
353
|
+
#
|
354
|
+
# The +attrs+ parameter is a hash, where the keys are symbols identifying
|
355
|
+
# the attributes to set.
|
356
|
+
#
|
357
|
+
# The method returns immediately with a Request object. If a block is given,
|
358
|
+
# it will be invoked when the server responds.
|
359
|
+
#
|
360
|
+
# request = sftp.setstat("/path/to/file", :permissions => 0644)
|
361
|
+
# request.wait
|
362
|
+
# puts "success: #{request.response.ok?}"
|
363
|
+
def setstat(path, attrs, &callback)
|
364
|
+
request :setstat, path, attrs, &callback
|
365
|
+
end
|
366
|
+
|
367
|
+
# Identical to the #setstat method, but blocks until the server responds.
|
368
|
+
# It will raise a StatusException if the request was unsuccessful.
|
369
|
+
# Otherwise, it will return the Response object for the request.
|
370
|
+
#
|
371
|
+
# sftp.setstat!("/path/to/file", :permissions => 0644)
|
372
|
+
def setstat!(path, attrs, &callback)
|
373
|
+
wait_for(setstat(path, attrs, &callback))
|
374
|
+
end
|
375
|
+
|
376
|
+
# The fsetstat method is identical to the #setstat method, with the
|
377
|
+
# exception that it takes a +handle+ as the first parameter, such as
|
378
|
+
# would be obtained via the #open or #opendir methods. (See the
|
379
|
+
# #setstat method for full documentation.)
|
380
|
+
def fsetstat(handle, attrs, &callback)
|
381
|
+
request :fsetstat, handle, attrs, &callback
|
382
|
+
end
|
383
|
+
|
384
|
+
# Identical to the #fsetstat method, but blocks until the server responds.
|
385
|
+
# It will raise a StatusException if the request was unsuccessful.
|
386
|
+
# Otherwise, it will return the Response object for the request.
|
387
|
+
#
|
388
|
+
# sftp.fsetstat!(handle, :permissions => 0644)
|
389
|
+
def fsetstat!(handle, attrs, &callback)
|
390
|
+
wait_for(fsetstat(handle, attrs, &callback))
|
391
|
+
end
|
392
|
+
|
393
|
+
# :call-seq:
|
394
|
+
# opendir(path) -> request
|
395
|
+
# opendir(path) { |response| ... } -> request
|
396
|
+
#
|
397
|
+
# Attempts to open a directory on the remote host for reading. Once the
|
398
|
+
# handle is obtained, directory entries may be retrieved using the
|
399
|
+
# #readdir method. The method returns immediately with a Request object.
|
400
|
+
# If a block is given, it will be invoked when the server responds.
|
401
|
+
#
|
402
|
+
# sftp.opendir("/path/to/directory") do |response|
|
403
|
+
# raise "fail!" unless response.ok?
|
404
|
+
# sftp.close(response[:handle])
|
405
|
+
# end
|
406
|
+
# sftp.loop
|
407
|
+
def opendir(path, &callback)
|
408
|
+
request :opendir, path, &callback
|
409
|
+
end
|
410
|
+
|
411
|
+
# Identical to #opendir, but blocks until the server responds. It will raise
|
412
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
413
|
+
# return a handle to the given path.
|
414
|
+
#
|
415
|
+
# handle = sftp.opendir!("/path/to/directory")
|
416
|
+
def opendir!(path, &callback)
|
417
|
+
wait_for(opendir(path, &callback), :handle)
|
418
|
+
end
|
419
|
+
|
420
|
+
# :call-seq:
|
421
|
+
# readdir(handle) -> request
|
422
|
+
# readdir(handle) { |response| ... } -> request
|
423
|
+
#
|
424
|
+
# Reads a set of entries from the given directory handle (which must
|
425
|
+
# have been obtained via #opendir). If the response is EOF, then there
|
426
|
+
# are no more entries in the directory. Otherwise, the entries will be
|
427
|
+
# in the :names property of the response:
|
428
|
+
#
|
429
|
+
# loop do
|
430
|
+
# request = sftp.readdir(handle).wait
|
431
|
+
# break if request.response.eof?
|
432
|
+
# raise "fail!" unless request.response.ok?
|
433
|
+
# request.response[:names].each do |entry|
|
434
|
+
# puts entry.name
|
435
|
+
# end
|
436
|
+
# end
|
437
|
+
#
|
438
|
+
# See also Protocol::V01::Name and Protocol::V04::Name for the specific
|
439
|
+
# properties of each individual entry (which vary based on the SFTP
|
440
|
+
# protocol version in use).
|
441
|
+
def readdir(handle, &callback)
|
442
|
+
request :readdir, handle, &callback
|
443
|
+
end
|
444
|
+
|
445
|
+
# Identical to #readdir, but blocks until the server responds. It will raise
|
446
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
447
|
+
# return nil if there were no more names to read, or an array of name
|
448
|
+
# entries.
|
449
|
+
#
|
450
|
+
# while (entries = sftp.readdir!(handle)) do
|
451
|
+
# entries.each { |entry| puts(entry.name) }
|
452
|
+
# end
|
453
|
+
def readdir!(handle, &callback)
|
454
|
+
wait_for(readdir(handle, &callback), :names)
|
455
|
+
end
|
456
|
+
|
457
|
+
# :call-seq:
|
458
|
+
# remove(filename) -> request
|
459
|
+
# remove(filename) { |response| ... } -> request
|
460
|
+
#
|
461
|
+
# Attempts to remove the given file from the remote file system. Returns
|
462
|
+
# immediately with a Request object. If a block is given, the block will
|
463
|
+
# be invoked when the server responds, and will be passed a Response
|
464
|
+
# object.
|
465
|
+
#
|
466
|
+
# sftp.remove("/path/to/file").wait
|
467
|
+
def remove(filename, &callback)
|
468
|
+
request :remove, filename, &callback
|
469
|
+
end
|
470
|
+
|
471
|
+
# Identical to #remove, but blocks until the server responds. It will raise
|
472
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
473
|
+
# return the Response object for the request.
|
474
|
+
#
|
475
|
+
# sftp.remove!("/path/to/file")
|
476
|
+
def remove!(filename, &callback)
|
477
|
+
wait_for(remove(filename, &callback))
|
478
|
+
end
|
479
|
+
|
480
|
+
# :call-seq:
|
481
|
+
# mkdir(path, attrs={}) -> request
|
482
|
+
# mkdir(path, attrs={}) { |response| ... } -> request
|
483
|
+
#
|
484
|
+
# Creates the named directory on the remote server. If an attribute hash
|
485
|
+
# is given, it must map to the set of attributes supported by the version
|
486
|
+
# of the SFTP protocol in use. (See Protocol::V01::Attributes,
|
487
|
+
# Protocol::V04::Attributes, and Protocol::V06::Attributes.)
|
488
|
+
#
|
489
|
+
# sftp.mkdir("/path/to/directory", :permissions => 0550).wait
|
490
|
+
def mkdir(path, attrs={}, &callback)
|
491
|
+
request :mkdir, path, attrs, &callback
|
492
|
+
end
|
493
|
+
|
494
|
+
# Identical to #mkdir, but blocks until the server responds. It will raise
|
495
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
496
|
+
# return the Response object for the request.
|
497
|
+
#
|
498
|
+
# sftp.mkdir!("/path/to/directory", :permissions => 0550)
|
499
|
+
def mkdir!(path, attrs={}, &callback)
|
500
|
+
wait_for(mkdir(path, attrs, &callback))
|
501
|
+
end
|
502
|
+
|
503
|
+
# :call-seq:
|
504
|
+
# rmdir(path) -> request
|
505
|
+
# rmdir(path) { |response| ... } -> request
|
506
|
+
#
|
507
|
+
# Removes the named directory on the remote server. The directory must
|
508
|
+
# be empty before it can be removed.
|
509
|
+
#
|
510
|
+
# sftp.rmdir("/path/to/directory").wait
|
511
|
+
def rmdir(path, &callback)
|
512
|
+
request :rmdir, path, &callback
|
513
|
+
end
|
514
|
+
|
515
|
+
# Identical to #rmdir, but blocks until the server responds. It will raise
|
516
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
517
|
+
# return the Response object for the request.
|
518
|
+
#
|
519
|
+
# sftp.rmdir!("/path/to/directory")
|
520
|
+
def rmdir!(path, &callback)
|
521
|
+
wait_for(rmdir(path, &callback))
|
522
|
+
end
|
523
|
+
|
524
|
+
# :call-seq:
|
525
|
+
# realpath(path) -> request
|
526
|
+
# realpath(path) { |response| ... } -> request
|
527
|
+
#
|
528
|
+
# Tries to canonicalize the given path, turning any given path into an
|
529
|
+
# absolute path. This is primarily useful for converting a path with
|
530
|
+
# ".." or "." segments into an identical path without those segments.
|
531
|
+
# The answer will be in the response's :names attribute, as a
|
532
|
+
# one-element array.
|
533
|
+
#
|
534
|
+
# request = sftp.realpath("/path/../to/../directory").wait
|
535
|
+
# puts request[:names].first.name
|
536
|
+
def realpath(path, &callback)
|
537
|
+
request :realpath, path, &callback
|
538
|
+
end
|
539
|
+
|
540
|
+
# Identical to #realpath, but blocks until the server responds. It will raise
|
541
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
542
|
+
# return a name object identifying the path.
|
543
|
+
#
|
544
|
+
# puts(sftp.realpath!("/path/../to/../directory"))
|
545
|
+
def realpath!(path, &callback)
|
546
|
+
wait_for(realpath(path, &callback), :names).first
|
547
|
+
end
|
548
|
+
|
549
|
+
# Identical to the #lstat method, except that it follows symlinks
|
550
|
+
# (e.g., if you give it the path to a symlink, it will stat the target
|
551
|
+
# of the symlink rather than the symlink itself). See the #lstat method
|
552
|
+
# for full documentation.
|
553
|
+
def stat(path, flags=nil, &callback)
|
554
|
+
request :stat, path, flags, &callback
|
555
|
+
end
|
556
|
+
|
557
|
+
# Identical to #stat, but blocks until the server responds. It will raise
|
558
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
559
|
+
# return an attribute object for the named path.
|
560
|
+
#
|
561
|
+
# attrs = sftp.stat!("/path/to/file")
|
562
|
+
def stat!(path, flags=nil, &callback)
|
563
|
+
wait_for(stat(path, flags, &callback), :attrs)
|
564
|
+
end
|
565
|
+
|
566
|
+
# :call-seq:
|
567
|
+
# rename(name, new_name, flags=nil) -> request
|
568
|
+
# rename(name, new_name, flags=nil) { |response| ... } -> request
|
569
|
+
#
|
570
|
+
# Renames the given file. This operation is only available in SFTP
|
571
|
+
# protocol versions two and higher. The +flags+ parameter is ignored
|
572
|
+
# in versions prior to 5. In versions 5 and higher, the +flags+
|
573
|
+
# parameter can be used to specify how the rename should be performed
|
574
|
+
# (atomically, etc.).
|
575
|
+
#
|
576
|
+
# The following flags are defined in protocol version 5:
|
577
|
+
#
|
578
|
+
# * 0x0001 - overwrite an existing file if the new name specifies a file
|
579
|
+
# that already exists.
|
580
|
+
# * 0x0002 - perform the rewrite atomically.
|
581
|
+
# * 0x0004 - allow the server to perform the rename as it prefers.
|
582
|
+
def rename(name, new_name, flags=nil, &callback)
|
583
|
+
request :rename, name, new_name, flags, &callback
|
584
|
+
end
|
585
|
+
|
586
|
+
# Identical to #rename, but blocks until the server responds. It will raise
|
587
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
588
|
+
# return the Response object for the request.
|
589
|
+
#
|
590
|
+
# sftp.rename!("/path/to/old", "/path/to/new")
|
591
|
+
def rename!(name, new_name, flags=nil, &callback)
|
592
|
+
wait_for(rename(name, new_name, flags, &callback))
|
593
|
+
end
|
594
|
+
|
595
|
+
# :call-seq:
|
596
|
+
# readlink(path) -> request
|
597
|
+
# readlink(path) { |response| ... } -> request
|
598
|
+
#
|
599
|
+
# Queries the server for the target of the specified symbolic link.
|
600
|
+
# This operation is only available in protocol versions 3 and higher.
|
601
|
+
# The response to this request will include a names property, a one-element
|
602
|
+
# array naming the target of the symlink.
|
603
|
+
#
|
604
|
+
# request = sftp.readlink("/path/to/symlink").wait
|
605
|
+
# puts request.response[:names].first.name
|
606
|
+
def readlink(path, &callback)
|
607
|
+
request :readlink, path, &callback
|
608
|
+
end
|
609
|
+
|
610
|
+
# Identical to #readlink, but blocks until the server responds. It will raise
|
611
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
612
|
+
# return the Name object for the path that the symlink targets.
|
613
|
+
#
|
614
|
+
# item = sftp.readlink!("/path/to/symlink")
|
615
|
+
def readlink!(path, &callback)
|
616
|
+
wait_for(readlink(path, &callback), :names).first
|
617
|
+
end
|
618
|
+
|
619
|
+
# :call-seq:
|
620
|
+
# symlink(path, target) -> request
|
621
|
+
# symlink(path, target) { |response| ... } -> request
|
622
|
+
#
|
623
|
+
# Attempts to create a symlink to +path+ at +target+. This operation
|
624
|
+
# is only available in protocol versions 3, 4, and 5, but the Net::SFTP
|
625
|
+
# library mimics the symlink behavior in protocol version 6 using the
|
626
|
+
# #link method, so it is safe to use this method in protocol version 6.
|
627
|
+
#
|
628
|
+
# sftp.symlink("/path/to/file", "/path/to/symlink").wait
|
629
|
+
def symlink(path, target, &callback)
|
630
|
+
request :symlink, path, target, &callback
|
631
|
+
end
|
632
|
+
|
633
|
+
# Identical to #symlink, but blocks until the server responds. It will raise
|
634
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
635
|
+
# return the Response object for the request.
|
636
|
+
#
|
637
|
+
# sftp.symlink!("/path/to/file", "/path/to/symlink")
|
638
|
+
def symlink!(path, target, &callback)
|
639
|
+
wait_for(symlink(path, target, &callback))
|
640
|
+
end
|
641
|
+
|
642
|
+
# :call-seq:
|
643
|
+
# link(new_link_path, existing_path, symlink=true) -> request
|
644
|
+
# link(new_link_path, existing_path, symlink=true) { |response| ... } -> request
|
645
|
+
#
|
646
|
+
# Attempts to create a link, either hard or symbolic. This operation is
|
647
|
+
# only available in SFTP protocol versions 6 and higher. If the +symlink+
|
648
|
+
# paramter is true, a symbolic link will be created, otherwise a hard
|
649
|
+
# link will be created. The link will be named +new_link_path+, and will
|
650
|
+
# point to the path +existing_path+.
|
651
|
+
#
|
652
|
+
# sftp.link("/path/to/symlink", "/path/to/file", true).wait
|
653
|
+
#
|
654
|
+
# Note that #link is only available for SFTP protocol 6 and higher. You
|
655
|
+
# can use #symlink for protocols 3 and higher.
|
656
|
+
def link(new_link_path, existing_path, symlink=true, &callback)
|
657
|
+
request :link, new_link_path, existing_path, symlink, &callback
|
658
|
+
end
|
659
|
+
|
660
|
+
# Identical to #link, but blocks until the server responds. It will raise
|
661
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
662
|
+
# return the Response object for the request.
|
663
|
+
#
|
664
|
+
# sftp.link!("/path/to/symlink", "/path/to/file", true)
|
665
|
+
def link!(new_link_path, existing_path, symlink=true, &callback)
|
666
|
+
wait_for(link(new_link_path, existing_path, symlink, &callback))
|
667
|
+
end
|
668
|
+
|
669
|
+
# :call-seq:
|
670
|
+
# block(handle, offset, length, mask) -> request
|
671
|
+
# block(handle, offset, length, mask) { |response| ... } -> request
|
672
|
+
#
|
673
|
+
# Creates a byte-range lock on the file specified by the given +handle+.
|
674
|
+
# This operation is only available in SFTP protocol versions 6 and
|
675
|
+
# higher. The lock may be either mandatory or advisory.
|
676
|
+
#
|
677
|
+
# The +handle+ parameter is a file handle, as obtained by the #open method.
|
678
|
+
#
|
679
|
+
# The +offset+ and +length+ parameters describe the location and size of
|
680
|
+
# the byte range.
|
681
|
+
#
|
682
|
+
# The +mask+ describes how the lock should be defined, and consists of
|
683
|
+
# some combination of the following bit masks:
|
684
|
+
#
|
685
|
+
# * 0x0040 - Read lock. The byte range may not be accessed for reading
|
686
|
+
# by via any other handle, though it may be written to.
|
687
|
+
# * 0x0080 - Write lock. The byte range may not be written to via any
|
688
|
+
# other handle, though it may be read from.
|
689
|
+
# * 0x0100 - Delete lock. No other handle may delete this file.
|
690
|
+
# * 0x0200 - Advisory lock. The server need not honor the lock instruction.
|
691
|
+
#
|
692
|
+
# Once created, the lock may be removed via the #unblock method.
|
693
|
+
def block(handle, offset, length, mask, &callback)
|
694
|
+
request :block, handle, offset, length, mask, &callback
|
695
|
+
end
|
696
|
+
|
697
|
+
# Identical to #block, but blocks until the server responds. It will raise
|
698
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
699
|
+
# return the Response object for the request.
|
700
|
+
def block!(handle, offset, length, mask, &callback)
|
701
|
+
wait_for(block(handle, offset, length, mask, &callback))
|
702
|
+
end
|
703
|
+
|
704
|
+
# :call-seq:
|
705
|
+
# unblock(handle, offset, length) -> request
|
706
|
+
# unblock(handle, offset, length) { |response| ... } -> request
|
707
|
+
#
|
708
|
+
# Removes a previously created byte-range lock. This operation is only
|
709
|
+
# available in protocol versions 6 and higher. The +offset+ and +length+
|
710
|
+
# parameters must exactly match those that were given to #block when the
|
711
|
+
# lock was acquired.
|
712
|
+
def unblock(handle, offset, length, &callback)
|
713
|
+
request :unblock, handle, offset, length, &callback
|
714
|
+
end
|
715
|
+
|
716
|
+
# Identical to #unblock, but blocks until the server responds. It will raise
|
717
|
+
# a StatusException if the request was unsuccessful. Otherwise, it will
|
718
|
+
# return the Response object for the request.
|
719
|
+
def unblock!(handle, offset, length, &callback)
|
720
|
+
wait_for(unblock(handle, offset, length, &callback))
|
721
|
+
end
|
722
|
+
|
723
|
+
public # miscellaneous methods
|
724
|
+
|
725
|
+
# Closes the SFTP connection, but not the SSH connection. Blocks until the
|
726
|
+
# session has terminated. Once the session has terminated, further operations
|
727
|
+
# on this object will result in errors. You can reopen the SFTP session
|
728
|
+
# via the #connect method.
|
729
|
+
def close_channel
|
730
|
+
return unless open?
|
731
|
+
channel.close
|
732
|
+
loop { !closed? }
|
733
|
+
end
|
734
|
+
|
735
|
+
# Returns true if the connection has been initialized.
|
736
|
+
def open?
|
737
|
+
state == :open
|
738
|
+
end
|
739
|
+
|
740
|
+
# Returns true if the connection has been closed.
|
741
|
+
def closed?
|
742
|
+
state == :closed
|
743
|
+
end
|
744
|
+
|
745
|
+
# Returns true if the connection is in the process of being initialized
|
746
|
+
# (e.g., it is not closed, but is not yet fully open).
|
747
|
+
def opening?
|
748
|
+
!(open? || closed?)
|
749
|
+
end
|
750
|
+
|
751
|
+
# Attempts to establish an SFTP connection over the SSH session given when
|
752
|
+
# this object was instantiated. If the object is already open, this will
|
753
|
+
# simply execute the given block (if any), passing the SFTP session itself
|
754
|
+
# as argument. If the session is currently being opened, this will add
|
755
|
+
# the given block to the list of callbacks, to be executed when the session
|
756
|
+
# is fully open.
|
757
|
+
#
|
758
|
+
# This method does not block, and will return immediately. If you pass a
|
759
|
+
# block to it, that block will be invoked when the connection has been
|
760
|
+
# fully established. Thus, you can do something like this:
|
761
|
+
#
|
762
|
+
# sftp.connect do
|
763
|
+
# puts "open!"
|
764
|
+
# end
|
765
|
+
#
|
766
|
+
# If you just want to block until the connection is ready, see the #connect!
|
767
|
+
# method.
|
768
|
+
def connect(&block)
|
769
|
+
case state
|
770
|
+
when :open
|
771
|
+
block.call(self) if block
|
772
|
+
when :closed
|
773
|
+
@state = :opening
|
774
|
+
@channel = session.open_channel(&method(:when_channel_confirmed))
|
775
|
+
@packet_length = nil
|
776
|
+
@protocol = nil
|
777
|
+
@on_ready = Array(block)
|
778
|
+
else # opening
|
779
|
+
@on_ready << block if block
|
780
|
+
end
|
781
|
+
|
782
|
+
self
|
783
|
+
end
|
784
|
+
|
785
|
+
# Same as the #connect method, but blocks until the SFTP connection has
|
786
|
+
# been fully initialized.
|
787
|
+
def connect!(&block)
|
788
|
+
connect(&block)
|
789
|
+
loop { opening? }
|
790
|
+
self
|
791
|
+
end
|
792
|
+
|
793
|
+
alias :loop_forever :loop
|
794
|
+
|
795
|
+
# Runs the SSH event loop while the given block returns true. This lets
|
796
|
+
# you set up a state machine and then "fire it off". If you do not specify
|
797
|
+
# a block, the event loop will run for as long as there are any pending
|
798
|
+
# SFTP requests. This makes it easy to do thing like this:
|
799
|
+
#
|
800
|
+
# sftp.remove("/path/to/file")
|
801
|
+
# sftp.loop
|
802
|
+
def loop(&block)
|
803
|
+
block ||= Proc.new { pending_requests.any? }
|
804
|
+
session.loop(&block)
|
805
|
+
end
|
806
|
+
|
807
|
+
# Formats, constructs, and sends an SFTP packet of the given type and with
|
808
|
+
# the given data. This does not block, but merely enqueues the packet for
|
809
|
+
# sending and returns.
|
810
|
+
#
|
811
|
+
# You should probably use the operation methods, rather than building and
|
812
|
+
# sending the packet directly. (See #open, #close, etc.)
|
813
|
+
def send_packet(type, *args)
|
814
|
+
data = Net::SSH::Buffer.from(*args)
|
815
|
+
msg = Net::SSH::Buffer.from(:long, data.length+1, :byte, type, :raw, data)
|
816
|
+
channel.send_data(msg.to_s)
|
817
|
+
end
|
818
|
+
|
819
|
+
private
|
820
|
+
|
821
|
+
#--
|
822
|
+
# "ruby -w" hates private attributes, so we have to do this longhand
|
823
|
+
#++
|
824
|
+
|
825
|
+
# The input buffer used to accumulate packet data
|
826
|
+
def input; @input; end
|
827
|
+
|
828
|
+
# Create and enqueue a new SFTP request of the given type, with the
|
829
|
+
# given arguments. Returns a new Request instance that encapsulates the
|
830
|
+
# request.
|
831
|
+
def request(type, *args, &callback)
|
832
|
+
request = Request.new(self, type, protocol.send(type, *args), &callback)
|
833
|
+
info { "sending #{type} packet (#{request.id})" }
|
834
|
+
pending_requests[request.id] = request
|
835
|
+
end
|
836
|
+
|
837
|
+
# Waits for the given request to complete. If the response is
|
838
|
+
# EOF, nil is returned. If the response was not successful
|
839
|
+
# (e.g., !response.ok?), a StatusException will be raised.
|
840
|
+
# If +property+ is given, the corresponding property from the response
|
841
|
+
# will be returned; otherwise, the response object itself will be
|
842
|
+
# returned.
|
843
|
+
def wait_for(request, property=nil)
|
844
|
+
request.wait
|
845
|
+
if request.response.eof?
|
846
|
+
nil
|
847
|
+
elsif !request.response.ok?
|
848
|
+
raise StatusException.new(request.response)
|
849
|
+
elsif property
|
850
|
+
request.response[property.to_sym]
|
851
|
+
else
|
852
|
+
request.response
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
# Called when the SSH channel is confirmed as "open" by the server.
|
857
|
+
# This is one of the states of the SFTP state machine, and is followed
|
858
|
+
# by the #when_subsystem_started state.
|
859
|
+
def when_channel_confirmed(channel)
|
860
|
+
debug { "requesting sftp subsystem" }
|
861
|
+
@state = :subsystem
|
862
|
+
channel.subsystem("sftp", &method(:when_subsystem_started))
|
863
|
+
end
|
864
|
+
|
865
|
+
# Called when the SSH server confirms that the SFTP subsystem was
|
866
|
+
# successfully started. This sets up the appropriate callbacks on the
|
867
|
+
# SSH channel and then starts the SFTP protocol version negotiation
|
868
|
+
# process.
|
869
|
+
def when_subsystem_started(channel, success)
|
870
|
+
raise Net::SFTP::Exception, "could not start SFTP subsystem" unless success
|
871
|
+
|
872
|
+
debug { "sftp subsystem successfully started" }
|
873
|
+
@state = :init
|
874
|
+
|
875
|
+
channel.on_data { |c,data| input.append(data) }
|
876
|
+
channel.on_extended_data { |c,t,data| debug { data } }
|
877
|
+
|
878
|
+
channel.on_close(&method(:when_channel_closed))
|
879
|
+
channel.on_process(&method(:when_channel_polled))
|
880
|
+
|
881
|
+
send_packet(FXP_INIT, :long, @version || HIGHEST_PROTOCOL_VERSION_SUPPORTED)
|
882
|
+
end
|
883
|
+
|
884
|
+
# Called when the SSH server closes the underlying channel.
|
885
|
+
def when_channel_closed(channel)
|
886
|
+
debug { "sftp channel closed" }
|
887
|
+
@channel = nil
|
888
|
+
@state = :closed
|
889
|
+
end
|
890
|
+
|
891
|
+
# Called whenever Net::SSH polls the SFTP channel for pending activity.
|
892
|
+
# This basically checks the input buffer to see if enough input has been
|
893
|
+
# accumulated to handle. If there has, the packet is parsed and
|
894
|
+
# dispatched, according to its type (see #do_version and #dispatch_request).
|
895
|
+
def when_channel_polled(channel)
|
896
|
+
while input.length > 0
|
897
|
+
if @packet_length.nil?
|
898
|
+
# make sure we've read enough data to tell how long the packet is
|
899
|
+
return unless input.length >= 4
|
900
|
+
@packet_length = input.read_long
|
901
|
+
end
|
902
|
+
|
903
|
+
return unless input.length >= @packet_length + 4
|
904
|
+
packet = Net::SFTP::Packet.new(input.read(@packet_length))
|
905
|
+
input.consume!
|
906
|
+
@packet_length = nil
|
907
|
+
|
908
|
+
debug { "received sftp packet #{packet.type} len #{packet.length}" }
|
909
|
+
|
910
|
+
if packet.type == FXP_VERSION
|
911
|
+
do_version(packet)
|
912
|
+
else
|
913
|
+
dispatch_request(packet)
|
914
|
+
end
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
918
|
+
# Called to handle FXP_VERSION packets. This performs the SFTP protocol
|
919
|
+
# version negotiation, instantiating the appropriate Protocol instance
|
920
|
+
# and invoking the callback given to #connect, if any.
|
921
|
+
def do_version(packet)
|
922
|
+
debug { "negotiating sftp protocol version, mine is #{HIGHEST_PROTOCOL_VERSION_SUPPORTED}" }
|
923
|
+
|
924
|
+
server_version = packet.read_long
|
925
|
+
debug { "server reports sftp version #{server_version}" }
|
926
|
+
|
927
|
+
negotiated_version = [server_version, HIGHEST_PROTOCOL_VERSION_SUPPORTED].min
|
928
|
+
info { "negotiated version is #{negotiated_version}" }
|
929
|
+
|
930
|
+
extensions = {}
|
931
|
+
until packet.eof?
|
932
|
+
name = packet.read_string
|
933
|
+
data = packet.read_string
|
934
|
+
extensions[name] = data
|
935
|
+
end
|
936
|
+
|
937
|
+
@protocol = Protocol.load(self, negotiated_version)
|
938
|
+
@pending_requests = {}
|
939
|
+
|
940
|
+
@state = :open
|
941
|
+
@on_ready.each { |callback| callback.call(self) }
|
942
|
+
@on_ready = nil
|
943
|
+
end
|
944
|
+
|
945
|
+
# Parses the packet, finds the associated Request instance, and tells
|
946
|
+
# the Request instance to respond to the packet (see Request#respond_to).
|
947
|
+
def dispatch_request(packet)
|
948
|
+
id = packet.read_long
|
949
|
+
request = pending_requests.delete(id) or raise Net::SFTP::Exception, "no such request `#{id}'"
|
950
|
+
request.respond_to(packet)
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
end; end
|