net-sftp 4.0.0 → 4.1.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.
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  require 'net/ssh/loggable'
2
3
 
3
4
  module Net; module SFTP; module Operations
@@ -50,9 +51,20 @@ module Net; module SFTP; module Operations
50
51
  # higher might improve throughput. Reducing it will reduce throughput.
51
52
  # * <tt>:read_size</tt> - the maximum number of bytes to read at a time
52
53
  # from the source. Increasing this value might improve throughput. It
53
- # defaults to 32,000 bytes.
54
+ # defaults to 32,000 bytes. Each chunk is sent as one write request, so
55
+ # it must stay within the server's packet limit (OpenSSH drops the
56
+ # connection on packets over 256 KiB).
54
57
  # * <tt>:name</tt> - the filename to report to the progress monitor when
55
58
  # an IO object is given as +local+. This defaults to "<memory>".
59
+ # * <tt>:preserve</tt> - if true, the remote files and directories get the
60
+ # access and modification times of the local ones, and their permissions
61
+ # are set even on files that already exist remotely.
62
+ #
63
+ # Like OpenSSH's sftp client, new remote files and directories are created
64
+ # with the permissions of the local ones, without setuid, setgid and sticky
65
+ # bits and subject to the server's umask. Nothing is set when +local+ is an
66
+ # IO object. If the server refuses to set the attributes after the data has
67
+ # been written, a warning is logged and the upload still succeeds.
56
68
  #
57
69
  # == Progress Monitoring
58
70
  #
@@ -141,6 +153,9 @@ module Net; module SFTP; module Operations
141
153
  # This will return immediately, and requires that the SSH event loop be
142
154
  # run in order to effect the upload. (See #wait.)
143
155
  def initialize(sftp, local, remote, options={}, &progress) #:nodoc:
156
+ local = local.to_path if local.is_a?(Pathname)
157
+ remote = remote.to_path if remote.is_a?(Pathname)
158
+
144
159
  @sftp = sftp
145
160
  @local = local
146
161
  @remote = remote
@@ -148,11 +163,14 @@ module Net; module SFTP; module Operations
148
163
  @options = options
149
164
  @properties = options[:properties] || {}
150
165
  @active = 0
166
+ @directories = []
151
167
 
152
168
  self.logger = sftp.logger
153
169
 
154
170
  @uploads = []
155
171
  @recursive = local.respond_to?(:read) ? false : ::File.directory?(local)
172
+ requests
173
+ read_size
156
174
 
157
175
  if recursive?
158
176
  @stack = [entries_for(local)]
@@ -161,10 +179,10 @@ module Net; module SFTP; module Operations
161
179
 
162
180
  @active += 1
163
181
  if @options[:mkdir]
164
- sftp.mkdir(remote) do |response|
182
+ sftp.mkdir(remote, directory_attributes(local, remote)) do |response|
165
183
  @active -= 1
166
184
  raise StatusException.new(response, "mkdir `#{remote}'") unless response.ok?
167
- (options[:requests] || RECURSIVE_READERS).to_i.times do
185
+ requests.times do
168
186
  break unless process_next_entry
169
187
  end
170
188
  end
@@ -225,9 +243,28 @@ module Net; module SFTP; module Operations
225
243
  # The progress handler for this instance. Possibly nil.
226
244
  def progress; @progress; end
227
245
 
246
+ def requests
247
+ @requests ||= positive_option(:requests, recursive? ? RECURSIVE_READERS : SINGLE_FILE_READERS)
248
+ end
249
+
250
+ def read_size
251
+ @read_size ||= positive_option(:read_size, DEFAULT_READ_SIZE)
252
+ end
253
+
254
+ def positive_option(name, default)
255
+ value = (options[name] || default).to_i
256
+ raise ArgumentError, ":#{name} must be positive" unless value > 0
257
+
258
+ value
259
+ end
260
+
261
+ def preserve?
262
+ options[:preserve]
263
+ end
264
+
228
265
  # A simple struct for recording metadata about the file currently being
229
266
  # uploaded.
230
- LiveFile = Struct.new(:local, :remote, :io, :size, :handle)
267
+ LiveFile = Struct.new(:local, :remote, :io, :size, :handle, :attributes)
231
268
 
232
269
  # The default # of bytes to read from disk at a time.
233
270
  DEFAULT_READ_SIZE = 32_000
@@ -245,7 +282,11 @@ module Net; module SFTP; module Operations
245
282
  if @uploads.any?
246
283
  write_next_chunk(@uploads.first)
247
284
  elsif !active?
248
- update_progress(:finish)
285
+ if @directories.any?
286
+ set_directory_attributes
287
+ else
288
+ update_progress(:finish)
289
+ end
249
290
  end
250
291
  return false
251
292
  elsif @stack.last.empty?
@@ -265,7 +306,7 @@ module Net; module SFTP; module Operations
265
306
 
266
307
  @active += 1
267
308
  update_progress(:mkdir, rpath)
268
- request = sftp.mkdir(rpath, &method(:on_mkdir))
309
+ request = sftp.mkdir(rpath, directory_attributes(lpath, rpath), &method(:on_mkdir))
269
310
  request[:dir] = rpath
270
311
  else
271
312
  open_file(lpath, rpath)
@@ -294,10 +335,11 @@ module Net; module SFTP; module Operations
294
335
  size = file.size
295
336
  end
296
337
 
297
- metafile = LiveFile.new(name, remote, file, size)
338
+ attributes = local.respond_to?(:read) ? {} : attributes_for(file.stat)
339
+ metafile = LiveFile.new(name, remote, file, size, nil, attributes)
298
340
  update_progress(:open, metafile)
299
341
 
300
- request = sftp.open(remote, "w", &method(:on_open))
342
+ request = sftp.open(remote, "w", attributes, &method(:on_open))
301
343
  request[:file] = metafile
302
344
  end
303
345
 
@@ -326,7 +368,7 @@ module Net; module SFTP; module Operations
326
368
  write_next_chunk(file)
327
369
 
328
370
  if !recursive?
329
- (options[:requests] || SINGLE_FILE_READERS).to_i.times { write_next_chunk(file) }
371
+ (requests - 1).times { write_next_chunk(file) }
330
372
  end
331
373
  end
332
374
 
@@ -350,6 +392,49 @@ module Net; module SFTP; module Operations
350
392
  process_next_entry
351
393
  end
352
394
 
395
+ # setuid, setgid and sticky bits are never sent, matching OpenSSH.
396
+ def attributes_for(stat)
397
+ attributes = { :permissions => stat.mode & 0777 }
398
+ attributes.update(:atime => stat.atime.to_i, :mtime => stat.mtime.to_i) if preserve?
399
+ attributes
400
+ end
401
+
402
+ # The directory is created writable and searchable by its owner so its
403
+ # contents can be uploaded; its real attributes are set once everything
404
+ # else is done (see #set_directory_attributes).
405
+ def directory_attributes(local, remote)
406
+ stat = ::File.stat(local)
407
+ @directories << [remote, attributes_for(stat)]
408
+ { :permissions => (stat.mode & 0777) | 0300 }
409
+ end
410
+
411
+ # Deepest directories first; setting attributes on a directory doesn't
412
+ # change its parent's modification time.
413
+ def set_directory_attributes
414
+ @directories.reverse_each do |path, attributes|
415
+ @active += 1
416
+ sftp.setstat(path, attributes) do |response|
417
+ @active -= 1
418
+ warn_unless_ok(response, "setstat #{path}")
419
+ process_next_entry
420
+ end
421
+ end
422
+ @directories.clear
423
+ end
424
+
425
+ # Called when the +fsetstat+ for :preserve finishes.
426
+ def on_fsetstat(response)
427
+ @active -= 1
428
+ file = response.request[:file]
429
+ warn_unless_ok(response, "fsetstat #{file.remote}")
430
+ end
431
+
432
+ # Like OpenSSH's sftp client, failing to set attributes only warns; the
433
+ # data has already been transferred.
434
+ def warn_unless_ok(response, operation)
435
+ lwarn { "#{operation} failed: #{response}" } unless response.ok?
436
+ end
437
+
353
438
  # Attempts to send the next chunk from the given file (where +file+ is
354
439
  # a LiveFile instance).
355
440
  def write_next_chunk(file)
@@ -358,12 +443,18 @@ module Net; module SFTP; module Operations
358
443
  else
359
444
  @active += 1
360
445
  offset = file.io.pos
361
- data = file.io.read(options[:read_size] || DEFAULT_READ_SIZE)
446
+ data = file.io.read(read_size)
362
447
  if data.nil?
363
448
  update_progress(:close, file)
449
+ if preserve? && file.attributes.any?
450
+ @active += 1
451
+ request = sftp.fsetstat(file.handle, file.attributes, &method(:on_fsetstat))
452
+ request[:file] = file
453
+ end
364
454
  request = sftp.close(file.handle, &method(:on_close))
365
455
  request[:file] = file
366
- file.io.close
456
+ # An IO passed in by the caller is theirs to close.
457
+ file.io.close unless file.io.equal?(local)
367
458
  file.io = nil
368
459
  @uploads.delete(file)
369
460
  else
@@ -1,4 +1,5 @@
1
1
  require 'net/ssh/buffer'
2
+ require 'net/sftp/protocol/bounded_read'
2
3
 
3
4
  module Net; module SFTP; module Protocol; module V01
4
5
 
@@ -39,6 +40,8 @@ module Net; module SFTP; module Protocol; module V01
39
40
  T_BLOCK_DEVICE = 8
40
41
  T_FIFO = 9
41
42
 
43
+ extend Net::SFTP::Protocol::BoundedRead
44
+
42
45
  class <<self
43
46
  # Returns the array of attribute meta-data that defines the structure of
44
47
  # the attributes packet as described by this version of the protocol.
@@ -106,7 +109,8 @@ module Net; module SFTP; module Protocol; module V01
106
109
  # Parse the hash of extended data from the buffer.
107
110
  def parse_extended(buffer)
108
111
  extended = Hash.new
109
- buffer.read_long.times do
112
+ # Each pair is two strings, at least 8 bytes.
113
+ read_bounded_count!(buffer, 8, "extended attribute").times do
110
114
  extended[buffer.read_string] = buffer.read_string
111
115
  end
112
116
  extended
@@ -150,7 +154,7 @@ module Net; module SFTP; module Protocol; module V01
150
154
  # * :mtime:: the modification time of the file (integer, seconds since epoch)
151
155
  # * :extended:: a hash of name/value pairs identifying extended info
152
156
  def initialize(attributes={})
153
- @attributes = attributes
157
+ @attributes = attributes.dup
154
158
  end
155
159
 
156
160
  # Returns the user-id of the user that owns the file, or +nil+ if that
@@ -54,7 +54,8 @@ module Net; module SFTP; module Protocol; module V01
54
54
  def parse_name_packet(packet)
55
55
  names = []
56
56
 
57
- packet.read_long.times do
57
+ # Each entry is two strings and the attribute flags, at least 12 bytes.
58
+ read_bounded_count!(packet, 12, "name").times do
58
59
  filename = packet.read_string
59
60
  longname = packet.read_string
60
61
  attrs = attribute_factory.from_buffer(packet)
@@ -5,7 +5,7 @@ module Net; module SFTP; module Protocol; module V01
5
5
  # for use when displaying directory data, and has no specified format.
6
6
  class Name
7
7
  # The name of the item on the remote server.
8
- attr_reader :name
8
+ attr_accessor :name
9
9
 
10
10
  # The display-ready name of the item, possibly with other attributes.
11
11
  attr_reader :longname
@@ -18,6 +18,16 @@ module Net; module SFTP; module Protocol; module V03
18
18
  3
19
19
  end
20
20
 
21
+ # Version 3 adds a message and language tag to status responses.
22
+ def parse_status_packet(packet)
23
+ data = super
24
+ unless packet.eof?
25
+ data[:message] = packet.read_string
26
+ data[:language] = packet.read_string
27
+ end
28
+ data
29
+ end
30
+
21
31
  # Sends a FXP_READLINK packet to the server to request that the target of
22
32
  # the given symlink on the remote host (+path+) be returned.
23
33
  def readlink(path)
@@ -32,4 +42,4 @@ module Net; module SFTP; module Protocol; module V03
32
42
 
33
43
  end
34
44
 
35
- end; end; end; end
45
+ end; end; end; end
@@ -69,7 +69,8 @@ module Net; module SFTP; module Protocol; module V04
69
69
  def parse_acl(buffer)
70
70
  acl_buf = Net::SSH::Buffer.new(buffer.read_string)
71
71
  acl = []
72
- acl_buf.read_long.times do
72
+ # Each entry is three longs and a string, at least 16 bytes.
73
+ read_bounded_count!(acl_buf, 16, "ACL").times do
73
74
  acl << ACL.new(acl_buf.read_long, acl_buf.read_long, acl_buf.read_long, acl_buf.read_string)
74
75
  end
75
76
  acl
@@ -123,7 +124,7 @@ module Net; module SFTP; module Protocol; module V04
123
124
  # to T_REGULAR.
124
125
  def initialize(attributes={})
125
126
  super
126
- attributes[:type] ||= T_REGULAR
127
+ self.type ||= T_REGULAR
127
128
  end
128
129
 
129
130
  private
@@ -134,6 +135,15 @@ module Net; module SFTP; module Protocol; module V04
134
135
  # were given on instantiation
135
136
  owner
136
137
  group
138
+
139
+ # The subsecond flag applies to every timestamp present in the packet.
140
+ subseconds = self.class.elements.select { |_name, _type, flags| flags & F_SUBSECOND_TIMES != 0 }
141
+ if subseconds.any? { |name, _type, _flags| attributes[name] }
142
+ subseconds.each do |name, _type, _flags|
143
+ seconds = name.to_s.sub(/_nseconds\z/, "").to_sym
144
+ attributes[name] ||= 0 if attributes[seconds]
145
+ end
146
+ end
137
147
  end
138
148
 
139
149
  # Performs protocol-version-specific encoding of the access control
@@ -28,7 +28,8 @@ module Net; module SFTP; module Protocol; module V04
28
28
  def parse_name_packet(packet)
29
29
  names = []
30
30
 
31
- packet.read_long.times do
31
+ # Each entry is a string, the attribute flags and the type, at least 9 bytes.
32
+ read_bounded_count!(packet, 9, "name").times do
32
33
  filename = packet.read_string
33
34
  attrs = attribute_factory.from_buffer(packet)
34
35
  names << name_factory.new(filename, attrs)
@@ -9,7 +9,7 @@ module Net; module SFTP; module Protocol; module V04
9
9
  # a directory listing.
10
10
  class Name
11
11
  # The name of the item on the remote server.
12
- attr_reader :name
12
+ attr_accessor :name
13
13
 
14
14
  # Attributes instance describing this item.
15
15
  attr_reader :attributes
@@ -44,24 +44,33 @@ module Net; module SFTP; module Protocol; module V04
44
44
  "l"
45
45
  else
46
46
  "-"
47
- end
47
+ end.dup
48
48
 
49
- longname << (attributes.permissions & 0400 != 0 ? "r" : "-")
50
- longname << (attributes.permissions & 0200 != 0 ? "w" : "-")
51
- longname << (attributes.permissions & 0100 != 0 ? "x" : "-")
52
- longname << (attributes.permissions & 0040 != 0 ? "r" : "-")
53
- longname << (attributes.permissions & 0020 != 0 ? "w" : "-")
54
- longname << (attributes.permissions & 0010 != 0 ? "x" : "-")
55
- longname << (attributes.permissions & 0004 != 0 ? "r" : "-")
56
- longname << (attributes.permissions & 0002 != 0 ? "w" : "-")
57
- longname << (attributes.permissions & 0001 != 0 ? "x" : "-")
49
+ # Fields the server omitted are shown as "?", like GNU ls -l does for entries it cannot stat.
50
+ if (permissions = attributes.permissions)
51
+ longname << (permissions & 0400 != 0 ? "r" : "-")
52
+ longname << (permissions & 0200 != 0 ? "w" : "-")
53
+ longname << (permissions & 0100 != 0 ? "x" : "-")
54
+ longname << (permissions & 0040 != 0 ? "r" : "-")
55
+ longname << (permissions & 0020 != 0 ? "w" : "-")
56
+ longname << (permissions & 0010 != 0 ? "x" : "-")
57
+ longname << (permissions & 0004 != 0 ? "r" : "-")
58
+ longname << (permissions & 0002 != 0 ? "w" : "-")
59
+ longname << (permissions & 0001 != 0 ? "x" : "-")
60
+ else
61
+ longname << "?" * 9
62
+ end
58
63
 
59
- longname << (" %-8s %-8s %8d " % [attributes.owner, attributes.group, attributes.size])
64
+ longname << (" %-8s %-8s %8s " % [attributes.owner || "?", attributes.group || "?", attributes.size || "?"])
60
65
 
61
- longname << Time.at(attributes.mtime).strftime("%b %e %H:%M ")
66
+ longname << if attributes.mtime
67
+ Time.at(attributes.mtime).strftime("%b %e %H:%M ")
68
+ else
69
+ "%12s " % "?"
70
+ end
62
71
  longname << name
63
72
  end
64
73
  end
65
74
  end
66
75
 
67
- end; end; end; end
76
+ end; end; end; end
@@ -35,25 +35,28 @@ module Net; module SFTP; module Protocol; module V05
35
35
  def open(path, flags, options)
36
36
  flags = normalize_open_flags(flags)
37
37
 
38
- sftp_flags, desired_access = if flags & (IO::WRONLY | IO::RDWR) != 0
39
- open = if flags & (IO::CREAT | IO::EXCL) == (IO::CREAT | IO::EXCL)
38
+ sftp_flags = if flags & (IO::CREAT | IO::EXCL) == (IO::CREAT | IO::EXCL)
40
39
  FV5::CREATE_NEW
41
40
  elsif flags & (IO::CREAT | IO::TRUNC) == (IO::CREAT | IO::TRUNC)
42
41
  FV5::CREATE_TRUNCATE
43
42
  elsif flags & IO::CREAT == IO::CREAT
44
43
  FV5::OPEN_OR_CREATE
44
+ elsif flags & IO::TRUNC == IO::TRUNC
45
+ FV5::TRUNCATE_EXISTING
45
46
  else
46
47
  FV5::OPEN_EXISTING
47
48
  end
49
+
50
+ desired_access = if flags & (IO::WRONLY | IO::RDWR) != 0
48
51
  access = ACE::Mask::WRITE_DATA | ACE::Mask::WRITE_ATTRIBUTES
49
52
  access |= ACE::Mask::READ_DATA | ACE::Mask::READ_ATTRIBUTES if (flags & IO::RDWR) == IO::RDWR
50
53
  if flags & IO::APPEND == IO::APPEND
51
- open |= FV5::APPEND_DATA
54
+ sftp_flags |= FV5::APPEND_DATA
52
55
  access |= ACE::Mask::APPEND_DATA
53
56
  end
54
- [open, access]
57
+ access
55
58
  else
56
- [FV5::OPEN_EXISTING, ACE::Mask::READ_DATA | ACE::Mask::READ_ATTRIBUTES]
59
+ ACE::Mask::READ_DATA | ACE::Mask::READ_ATTRIBUTES
57
60
  end
58
61
 
59
62
  attributes = attribute_factory.new(options)
@@ -63,4 +66,4 @@ module Net; module SFTP; module Protocol; module V05
63
66
 
64
67
  end
65
68
 
66
- end; end; end; end
69
+ end; end; end; end
@@ -1,5 +1,6 @@
1
1
  require 'net/ssh/loggable'
2
2
  require 'net/sftp/constants'
3
+ require 'net/sftp/protocol/bounded_read'
3
4
 
4
5
  module Net; module SFTP; module Protocol
5
6
 
@@ -10,6 +11,7 @@ module Net; module SFTP; module Protocol
10
11
  include Net::SSH::Loggable
11
12
  include Net::SFTP::Constants
12
13
  include Net::SFTP::Constants::PacketTypes
14
+ include Net::SFTP::Protocol::BoundedRead
13
15
 
14
16
  # The SFTP session object that acts as client to this protocol instance
15
17
  attr_reader :session
@@ -19,6 +21,7 @@ module Net; module SFTP; module Protocol
19
21
  @session = session
20
22
  self.logger = session.logger
21
23
  @request_id_counter = -1
24
+ @request_ids_wrapped = false
22
25
  end
23
26
 
24
27
  # Attept to parse the given packet. If the packet is of an unsupported
@@ -41,10 +44,15 @@ module Net; module SFTP; module Protocol
41
44
  # A new request identifier will be allocated to this request, and will
42
45
  # be returned.
43
46
  def send_request(type, *args)
44
- @request_id_counter += 1
47
+ begin
48
+ previous_request_id = @request_id_counter
49
+ @request_id_counter = (@request_id_counter + 1) & 0xffffffff
50
+ @request_ids_wrapped = true if previous_request_id == 0xffffffff
51
+ end while @request_ids_wrapped && session.pending_requests.key?(@request_id_counter)
52
+
45
53
  session.send_packet(type, :long, @request_id_counter, *args)
46
54
  return @request_id_counter
47
55
  end
48
56
  end
49
57
 
50
- end; end; end
58
+ end; end; end
@@ -0,0 +1,25 @@
1
+ require 'net/sftp/errors'
2
+
3
+ module Net; module SFTP; module Protocol
4
+
5
+ # Several SFTP structures are a 32-bit item count followed by that many
6
+ # items. Net::SSH::Buffer returns nil instead of raising when a read runs
7
+ # past the end of the data, so a loop over an unchecked count from the
8
+ # server keeps allocating long after the packet is exhausted.
9
+ module BoundedRead
10
+ # Reads an item count from +buffer+ and raises Net::SFTP::Exception if it
11
+ # is missing, or larger than the remaining bytes could hold given that
12
+ # each item takes at least +min_item_size+ bytes.
13
+ def read_bounded_count!(buffer, min_item_size, what)
14
+ count = buffer.read_long
15
+ raise Net::SFTP::Exception, "truncated packet: missing #{what} count" if count.nil?
16
+
17
+ if count > buffer.available / min_item_size
18
+ raise Net::SFTP::Exception, "server sent #{count} #{what} entries in only #{buffer.available} bytes"
19
+ end
20
+
21
+ count
22
+ end
23
+ end
24
+
25
+ end; end; end
@@ -68,6 +68,10 @@ module Net; module SFTP
68
68
  # the server has not yet responded to will be represented here.
69
69
  attr_reader :pending_requests
70
70
 
71
+ # The extensions the server advertised in its FXP_VERSION packet, as a
72
+ # hash of name => data (e.g. "posix-rename@openssh.com" => "1").
73
+ attr_reader :extensions
74
+
71
75
  # Creates a new Net::SFTP instance atop the given Net::SSH connection.
72
76
  # This will return immediately, before the SFTP connection has been properly
73
77
  # initialized. Once the connection is ready, the given block will be called.
@@ -81,6 +85,7 @@ module Net; module SFTP
81
85
  @input = Net::SSH::Buffer.new
82
86
  self.logger = session.logger
83
87
  @state = :closed
88
+ @extensions = {}
84
89
  @pending_requests = {}
85
90
 
86
91
  connect(&block)
@@ -269,6 +274,10 @@ module Net; module SFTP
269
274
  # Request object. If a block is given, it will be invoked when the
270
275
  # server responds.
271
276
  #
277
+ # +data+ is sent as a single request, so it must stay within the
278
+ # server's packet limit (OpenSSH drops the connection on packets over
279
+ # 256 KiB). Net::SFTP::Operations::File#write splits large data for you.
280
+ #
272
281
  # request = sftp.write(handle, 0, "hello, world!\n")
273
282
  # request.wait
274
283
  def write(handle, offset, data, &callback)
@@ -772,6 +781,7 @@ module Net; module SFTP
772
781
  when :closed
773
782
  @state = :opening
774
783
  @channel = session.open_channel(&method(:when_channel_confirmed))
784
+ @input = Net::SSH::Buffer.new
775
785
  @packet_length = nil
776
786
  @protocol = nil
777
787
  @on_ready = Array(block)
@@ -919,19 +929,27 @@ module Net; module SFTP
919
929
  # version negotiation, instantiating the appropriate Protocol instance
920
930
  # and invoking the callback given to #connect, if any.
921
931
  def do_version(packet)
932
+ # Negotiation happens once. A later FXP_VERSION would swap the protocol
933
+ # driver and drop every pending request.
934
+ unless state == :init
935
+ raise Net::SFTP::Exception, "unexpected FXP_VERSION packet while session is #{state}"
936
+ end
937
+
922
938
  debug { "negotiating sftp protocol version, mine is #{HIGHEST_PROTOCOL_VERSION_SUPPORTED}" }
923
939
 
924
940
  server_version = packet.read_long
925
941
  debug { "server reports sftp version #{server_version}" }
926
942
 
927
- negotiated_version = [server_version, HIGHEST_PROTOCOL_VERSION_SUPPORTED].min
943
+ # Servers reply with their own version whatever we offered (OpenSSH
944
+ # always says 3), so cap at the version we offered as well.
945
+ negotiated_version = [server_version, @version, HIGHEST_PROTOCOL_VERSION_SUPPORTED].compact.min
928
946
  info { "negotiated version is #{negotiated_version}" }
929
947
 
930
- extensions = {}
948
+ @extensions = {}
931
949
  until packet.eof?
932
950
  name = packet.read_string
933
951
  data = packet.read_string
934
- extensions[name] = data
952
+ @extensions[name] = data
935
953
  end
936
954
 
937
955
  @protocol = Protocol.load(self, negotiated_version)
@@ -49,7 +49,7 @@ module Net
49
49
  MAJOR = 4
50
50
 
51
51
  # The minor component of this version of the Net::SFTP library
52
- MINOR = 0
52
+ MINOR = 1
53
53
 
54
54
  # The tiny component of this version of the Net::SFTP library
55
55
  TINY = 0
data/lib/net/sftp.rb CHANGED
@@ -34,18 +34,24 @@ module Net
34
34
  # set the SFTP protocol version to be used)
35
35
  def self.start(host, user, ssh_options={}, sftp_options={}, &block)
36
36
  session = Net::SSH.start(host, user, ssh_options)
37
- # We only use a single option here, but this leaves room for more later
38
- # without breaking the external API.
39
- version = sftp_options.fetch(:version, nil)
40
- sftp = Net::SFTP::Session.new(session, version, &block).connect!
37
+ begin
38
+ # We only use a single option here, but this leaves room for more later
39
+ # without breaking the external API.
40
+ version = sftp_options.fetch(:version, nil)
41
+ sftp = Net::SFTP::Session.new(session, version, &block).connect!
41
42
 
42
- if block_given?
43
- sftp.loop
44
- session.close
45
- return nil
46
- end
43
+ if block_given?
44
+ sftp.loop
45
+ return nil
46
+ end
47
47
 
48
- sftp
48
+ sftp
49
+ rescue Object
50
+ failed = true
51
+ raise
52
+ ensure
53
+ session.close if block_given? && !failed
54
+ end
49
55
  rescue Object => anything
50
56
  begin
51
57
  session.shutdown!
@@ -1,20 +1,21 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- MIIDQDCCAiigAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpuZXRz
3
- c2gvREM9c29sdXRpb3VzL0RDPWNvbTAeFw0yMjA5MjIxMTUwMDJaFw0yMzA5MjIx
4
- MTUwMDJaMCUxIzAhBgNVBAMMGm5ldHNzaC9EQz1zb2x1dGlvdXMvREM9Y29tMIIB
5
- IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxieE22fR/qmdPKUHyYTyUx2g
6
- wskLwrCkxay+Tvc97ZZUOwf85LDDDPqhQaTWLvRwnIOMgQE2nBPzwalVclK6a+pW
7
- x/18KDeZY15vm3Qn5p42b0wi9hUxOqPm3J2hdCLCcgtENgdX21nVzejn39WVqFJO
8
- lntgSDNW5+kCS8QaRsmIbzj17GKKkrsw39kiQw7FhWfJFeTjddzoZiWwc59KA/Bx
9
- fBbmDnsMLAtAtauMOxORrbx3EOY7sHku/kSrMg3FXFay7jc6BkbbUij+MjJ/k82l
10
- 4o8o0YO4BAnya90xgEmgOG0LCCxRhuXQFnMDuDjK2XnUe0h4/6NCn94C+z9GsQID
11
- AQABo3sweTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUBfKiwO2e
12
- M4NEiRrVG793qEPLYyMwHwYDVR0RBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20w
13
- HwYDVR0SBBgwFoEUbmV0c3NoQHNvbHV0aW91cy5jb20wDQYJKoZIhvcNAQELBQAD
14
- ggEBABI2ORK5kzUL7uOF0EHI4ECMWxQMiN+pURyGp9u7DU0H8eSdZN52jbUGHzSB
15
- j7bB6GpqElEWjOe0IbH3vR52IVXq2bOF4P4vFchGAb4OuzJD8aJmrC/SPLHbWBuV
16
- 2GpbRQRJyYPWN6Rt/4EHOxoFnhXOBEB6CGIy0dt7YezycVbzqtHoiI2Qf/bIFJQZ
17
- mpJAAUBkRiWksE7zrsE5DGK8kL2GVos7f8kdM71zT8p7VBwkMdY277T29TG2xD0D
18
- 66Oev0C3/x89NXqCHkl1JElSzEFbOoxan16z7xNEf2MKcBKGhsYfzWVNyEtJm785
19
- g+97rn/AuO6dcxJnW2qBGYQa7pQ=
2
+ MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMQ8wDQYDVQQDDAZuZXRz
3
+ c2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZFgNj
4
+ b20wHhcNMjYxMDIyMDk1NDI1WhcNMjcxMDIyMDk1NDI1WjBBMQ8wDQYDVQQDDAZu
5
+ ZXRzc2gxGTAXBgoJkiaJk/IsZAEZFglzb2x1dGlvdXMxEzARBgoJkiaJk/IsZAEZ
6
+ FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGJ4TbZ9H+qZ08
7
+ pQfJhPJTHaDCyQvCsKTFrL5O9z3tllQ7B/zksMMM+qFBpNYu9HCcg4yBATacE/PB
8
+ qVVyUrpr6lbH/XwoN5ljXm+bdCfmnjZvTCL2FTE6o+bcnaF0IsJyC0Q2B1fbWdXN
9
+ 6Off1ZWoUk6We2BIM1bn6QJLxBpGyYhvOPXsYoqSuzDf2SJDDsWFZ8kV5ON13Ohm
10
+ JbBzn0oD8HF8FuYOewwsC0C1q4w7E5GtvHcQ5juweS7+RKsyDcVcVrLuNzoGRttS
11
+ KP4yMn+TzaXijyjRg7gECfJr3TGASaA4bQsILFGG5dAWcwO4OMrZedR7SHj/o0Kf
12
+ 3gL7P0axAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
13
+ BBQF8qLA7Z4zg0SJGtUbv3eoQ8tjIzAfBgNVHREEGDAWgRRuZXRzc2hAc29sdXRp
14
+ b3VzLmNvbTAfBgNVHRIEGDAWgRRuZXRzc2hAc29sdXRpb3VzLmNvbTANBgkqhkiG
15
+ 9w0BAQsFAAOCAQEAbVrRpiCFmHDMLNU+SCjpg43snWYksQOtejPPTEMdOc9cCmmv
16
+ ctJbaPIySLUgzwFbQDZStDPbLJmIujh0fm5dHp/sDJpvPZ3Mm3FfzWQMJqzBx4uk
17
+ OMx00yJK8VeFrwUViE3zO5raWvw85dUybp25pgaKGW4AA5mJ/2UHqYLc4lefKFKK
18
+ 9xFsyYSciMEKyAsUNSNTJzPKUo2vn7TNF/pS0ZL+h8Qkr0A0F0ncfj7uM1kOWjO0
19
+ mMdUtmMoVbjbik3hqkY/18G03c3PoFNpL38VLKuyyROYz1DaCqOaRju3sbOoirD+
20
+ piQTZDCWxeKuX0Iuxq02uxkYHfoGB6In0y3OOQ==
20
21
  -----END CERTIFICATE-----