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,315 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
|
3
|
+
module Net; module SFTP; module Protocol; module V01
|
4
|
+
|
5
|
+
# A class representing the attributes of a file or directory on the server.
|
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).
|
24
|
+
class Attributes
|
25
|
+
|
26
|
+
F_SIZE = 0x00000001
|
27
|
+
F_UIDGID = 0x00000002
|
28
|
+
F_PERMISSIONS = 0x00000004
|
29
|
+
F_ACMODTIME = 0x00000008
|
30
|
+
F_EXTENDED = 0x80000000
|
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.
|
120
|
+
attr_accessor :size
|
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
|
129
|
+
attr_accessor :permissions
|
130
|
+
|
131
|
+
# The last access time of the file
|
132
|
+
attr_accessor :atime
|
133
|
+
|
134
|
+
# The modification time of the file
|
135
|
+
attr_accessor :mtime
|
136
|
+
|
137
|
+
# The hash of name/value pairs identifying extended information about the file
|
138
|
+
attr_accessor :extended
|
139
|
+
|
140
|
+
# Create a new Attributes instance with the given attributes. The
|
141
|
+
# following keys are supported:
|
142
|
+
#
|
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
|
154
|
+
end
|
155
|
+
|
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]
|
166
|
+
end
|
167
|
+
|
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]
|
178
|
+
end
|
179
|
+
|
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]
|
190
|
+
end
|
191
|
+
|
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
|
200
|
+
end
|
201
|
+
attributes[:group]
|
202
|
+
end
|
203
|
+
|
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
|
224
|
+
end
|
225
|
+
|
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?)"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
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
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
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
|
259
|
+
end
|
260
|
+
|
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
|
268
|
+
end
|
269
|
+
|
270
|
+
# Convert the object to a string suitable for passing in an SFTP
|
271
|
+
# packet. This is the raw representation of the attribute packet payload,
|
272
|
+
# and is not intended to be human readable.
|
273
|
+
def to_s
|
274
|
+
prepare_serialization!
|
275
|
+
|
276
|
+
flags = 0
|
277
|
+
|
278
|
+
self.class.elements.each do |name, type, condition|
|
279
|
+
flags |= condition if attributes[name]
|
280
|
+
end
|
281
|
+
|
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
|
291
|
+
end
|
292
|
+
|
293
|
+
buffer.to_s
|
294
|
+
end
|
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
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
end ; end ; end ; end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'net/ssh/loggable'
|
2
|
+
require 'net/sftp/constants'
|
3
|
+
require 'net/sftp/packet'
|
4
|
+
require 'net/sftp/protocol/base'
|
5
|
+
require 'net/sftp/protocol/01/attributes'
|
6
|
+
require 'net/sftp/protocol/01/name'
|
7
|
+
|
8
|
+
module Net; module SFTP; module Protocol; module V01
|
9
|
+
|
10
|
+
# Wraps the low-level SFTP calls for version 1 of the SFTP protocol. Also
|
11
|
+
# implements the packet parsing as defined by version 1 of the protocol.
|
12
|
+
#
|
13
|
+
# None of these protocol methods block--all of them return immediately,
|
14
|
+
# requiring the SSH event loop to be run while the server response is
|
15
|
+
# pending.
|
16
|
+
#
|
17
|
+
# You will almost certainly never need to use this driver directly. Please
|
18
|
+
# see Net::SFTP::Session for the recommended interface.
|
19
|
+
class Base < Protocol::Base
|
20
|
+
include Net::SFTP::Constants::OpenFlags
|
21
|
+
|
22
|
+
# Returns the protocol version implemented by this driver. (1, in this
|
23
|
+
# case)
|
24
|
+
def version
|
25
|
+
1
|
26
|
+
end
|
27
|
+
|
28
|
+
# Parses the given FXP_HANDLE packet and returns a hash with one key,
|
29
|
+
# :handle, which references the handle.
|
30
|
+
def parse_handle_packet(packet)
|
31
|
+
{ :handle => packet.read_string }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parses the given FXP_STATUS packet and returns a hash with one key,
|
35
|
+
# :code, which references the status code returned by the server.
|
36
|
+
def parse_status_packet(packet)
|
37
|
+
{ :code => packet.read_long }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Parses the given FXP_DATA packet and returns a hash with one key,
|
41
|
+
# :data, which references the data returned in the packet.
|
42
|
+
def parse_data_packet(packet)
|
43
|
+
{ :data => packet.read_string }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parses the given FXP_ATTRS packet and returns a hash with one key,
|
47
|
+
# :attrs, which references an Attributes object.
|
48
|
+
def parse_attrs_packet(packet)
|
49
|
+
{ :attrs => attribute_factory.from_buffer(packet) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Parses the given FXP_NAME packet and returns a hash with one key, :names,
|
53
|
+
# which references an array of Name objects.
|
54
|
+
def parse_name_packet(packet)
|
55
|
+
names = []
|
56
|
+
|
57
|
+
packet.read_long.times do
|
58
|
+
filename = packet.read_string
|
59
|
+
longname = packet.read_string
|
60
|
+
attrs = attribute_factory.from_buffer(packet)
|
61
|
+
names << name_factory.new(filename, longname, attrs)
|
62
|
+
end
|
63
|
+
|
64
|
+
{ :names => names }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sends a FXP_OPEN packet to the server and returns the packet identifier.
|
68
|
+
# The +flags+ parameter is either an integer (in which case it must be
|
69
|
+
# a combination of the IO constants) or a string (in which case it must
|
70
|
+
# be one of the mode strings that IO::open accepts). The +options+
|
71
|
+
# parameter is a hash that is used to construct a new Attribute object,
|
72
|
+
# to pass as part of the FXP_OPEN request.
|
73
|
+
def open(path, flags, options)
|
74
|
+
flags = normalize_open_flags(flags)
|
75
|
+
|
76
|
+
if flags & (IO::WRONLY | IO::RDWR) != 0
|
77
|
+
sftp_flags = FV1::WRITE
|
78
|
+
sftp_flags |= FV1::READ if flags & IO::RDWR != 0
|
79
|
+
sftp_flags |= FV1::APPEND if flags & IO::APPEND != 0
|
80
|
+
else
|
81
|
+
sftp_flags = FV1::READ
|
82
|
+
end
|
83
|
+
|
84
|
+
sftp_flags |= FV1::CREAT if flags & IO::CREAT != 0
|
85
|
+
sftp_flags |= FV1::TRUNC if flags & IO::TRUNC != 0
|
86
|
+
sftp_flags |= FV1::EXCL if flags & IO::EXCL != 0
|
87
|
+
|
88
|
+
attributes = attribute_factory.new(options)
|
89
|
+
|
90
|
+
send_request(FXP_OPEN, :string, path, :long, sftp_flags, :raw, attributes.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sends a FXP_CLOSE packet to the server for the given +handle+ (such as
|
94
|
+
# would be returned via a FXP_HANDLE packet). Returns the new packet id.
|
95
|
+
def close(handle)
|
96
|
+
send_request(FXP_CLOSE, :string, handle)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Sends a FXP_READ packet to the server, requesting that +length+ bytes
|
100
|
+
# be read from the file identified by +handle+, starting at +offset+ bytes
|
101
|
+
# within the file. The handle must be one that was returned via a
|
102
|
+
# FXP_HANDLE packet. Returns the new packet id.
|
103
|
+
def read(handle, offset, length)
|
104
|
+
send_request(FXP_READ, :string, handle, :int64, offset, :long, length)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sends a FXP_WRITE packet to the server, requesting that +data+ (a string),
|
108
|
+
# be written to the file identified by +handle+, starting at +offset+ bytes
|
109
|
+
# from the beginning of the file. The handle must be one that was returned
|
110
|
+
# via a FXP_HANDLE packet. Returns the new packet id.
|
111
|
+
def write(handle, offset, data)
|
112
|
+
send_request(FXP_WRITE, :string, handle, :int64, offset, :string, data)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Sends a FXP_LSTAT packet to the server, requesting a FXP_ATTR response
|
116
|
+
# for the file at the given remote +path+ (a string). The +flags+ parameter
|
117
|
+
# is ignored in this version of the protocol. #lstat will not follow
|
118
|
+
# symbolic links; see #stat for a version that will.
|
119
|
+
def lstat(path, flags=nil)
|
120
|
+
send_request(FXP_LSTAT, :string, path)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Sends a FXP_FSTAT packet to the server, requesting a FXP_ATTR response
|
124
|
+
# for the file represented by the given +handle+ (which must have been
|
125
|
+
# obtained from a FXP_HANDLE packet). The +flags+ parameter is ignored in
|
126
|
+
# this version of the protocol.
|
127
|
+
def fstat(handle, flags=nil)
|
128
|
+
send_request(FXP_FSTAT, :string, handle)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sends a FXP_SETSTAT packet to the server, to update the attributes for
|
132
|
+
# the file at the given remote +path+ (a string). The +attrs+ parameter is
|
133
|
+
# a hash that defines the attributes to set.
|
134
|
+
def setstat(path, attrs)
|
135
|
+
send_request(FXP_SETSTAT, :string, path, :raw, attribute_factory.new(attrs).to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Sends a FXP_FSETSTAT packet to the server, to update the attributes for
|
139
|
+
# the file represented by the given +handle+ (which must have been obtained
|
140
|
+
# from a FXP_HANDLE packet). The +attrs+ parameter is a hash that defines
|
141
|
+
# the attributes to set.
|
142
|
+
def fsetstat(handle, attrs)
|
143
|
+
send_request(FXP_FSETSTAT, :string, handle, :raw, attribute_factory.new(attrs).to_s)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Sends a FXP_OPENDIR packet to the server, to request a handle for
|
147
|
+
# manipulating the directory at the given remote +path+.
|
148
|
+
def opendir(path)
|
149
|
+
send_request(FXP_OPENDIR, :string, path)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Sends a FXP_READDIR packet to the server, to request a batch of
|
153
|
+
# directory name entries in the directory identified by +handle+ (which
|
154
|
+
# must have been obtained via a FXP_OPENDIR request).
|
155
|
+
def readdir(handle)
|
156
|
+
send_request(FXP_READDIR, :string, handle)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Sends a FXP_REMOTE packet to the server, to request that the given
|
160
|
+
# file be deleted from the remote server.
|
161
|
+
def remove(filename)
|
162
|
+
send_request(FXP_REMOVE, :string, filename)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Sends a FXP_MKDIR packet to the server, to request that a new directory
|
166
|
+
# at +path+ on the remote server be created, and with +attrs+ (a hash)
|
167
|
+
# describing the attributes of the new directory.
|
168
|
+
def mkdir(path, attrs)
|
169
|
+
send_request(FXP_MKDIR, :string, path, :raw, attribute_factory.new(attrs).to_s)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Sends a FXP_RMDIR packet to the server, to request that the directory
|
173
|
+
# at +path+ on the remote server be deleted.
|
174
|
+
def rmdir(path)
|
175
|
+
send_request(FXP_RMDIR, :string, path)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Sends a FXP_REALPATH packet to the server, to request that the given
|
179
|
+
# +path+ be canonicalized, taking into account path segments like "..".
|
180
|
+
def realpath(path)
|
181
|
+
send_request(FXP_REALPATH, :string, path)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Sends a FXP_STAT packet to the server, requesting a FXP_ATTR response
|
185
|
+
# for the file at the given remote +path+ (a string). The +flags+ parameter
|
186
|
+
# is ignored in this version of the protocol. #stat will follow
|
187
|
+
# symbolic links; see #lstat for a version that will not.
|
188
|
+
def stat(path, flags=nil)
|
189
|
+
send_request(FXP_STAT, :string, path)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
193
|
+
# NotImplementedError if called.
|
194
|
+
def rename(name, new_name, flags=nil)
|
195
|
+
not_implemented! :rename
|
196
|
+
end
|
197
|
+
|
198
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
199
|
+
# NotImplementedError if called.
|
200
|
+
def readlink(path)
|
201
|
+
not_implemented! :readlink
|
202
|
+
end
|
203
|
+
|
204
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
205
|
+
# NotImplementedError if called.
|
206
|
+
def symlink(path, target)
|
207
|
+
not_implemented! :symlink
|
208
|
+
end
|
209
|
+
|
210
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
211
|
+
# NotImplementedError if called.
|
212
|
+
def link(*args)
|
213
|
+
not_implemented! :link
|
214
|
+
end
|
215
|
+
|
216
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
217
|
+
# NotImplementedError if called.
|
218
|
+
def block(handle, offset, length, mask)
|
219
|
+
not_implemented! :block
|
220
|
+
end
|
221
|
+
|
222
|
+
# Not implemented in version 1 of the SFTP protocol. Raises a
|
223
|
+
# NotImplementedError if called.
|
224
|
+
def unblock(handle, offset, length)
|
225
|
+
not_implemented! :unblock
|
226
|
+
end
|
227
|
+
|
228
|
+
protected
|
229
|
+
|
230
|
+
# A helper method for implementing wrappers for operations that are
|
231
|
+
# not implemented by the current SFTP protocol version. Simply raises
|
232
|
+
# NotImplementedError with a message based on the given operation name.
|
233
|
+
def not_implemented!(operation)
|
234
|
+
raise NotImplementedError, "the #{operation} operation is not available in the version of the SFTP protocol supported by your server"
|
235
|
+
end
|
236
|
+
|
237
|
+
# Normalizes the given flags parameter, converting it into a combination
|
238
|
+
# of IO constants.
|
239
|
+
def normalize_open_flags(flags)
|
240
|
+
if String === flags
|
241
|
+
case flags.tr("b", "")
|
242
|
+
when "r" then IO::RDONLY
|
243
|
+
when "r+" then IO::RDWR
|
244
|
+
when "w" then IO::WRONLY | IO::TRUNC | IO::CREAT
|
245
|
+
when "w+" then IO::RDWR | IO::TRUNC | IO::CREAT
|
246
|
+
when "a" then IO::APPEND | IO::CREAT | IO::WRONLY
|
247
|
+
when "a+" then IO::APPEND | IO::CREAT | IO::RDWR
|
248
|
+
else raise ArgumentError, "unsupported flags: #{flags.inspect}"
|
249
|
+
end
|
250
|
+
else
|
251
|
+
flags.to_i
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the Attributes class used by this version of the protocol
|
256
|
+
# (Net::SFTP::Protocol::V01::Attributes, in this case)
|
257
|
+
def attribute_factory
|
258
|
+
V01::Attributes
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns the Name class used by this version of the protocol
|
262
|
+
# (Net::SFTP::Protocol::V01::Name, in this case)
|
263
|
+
def name_factory
|
264
|
+
V01::Name
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end; end; end; end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Net; module SFTP; module Protocol; module V01
|
2
|
+
|
3
|
+
# Represents a single named item on the remote server. This includes the
|
4
|
+
# name, attributes about the item, and the "longname", which is intended
|
5
|
+
# for use when displaying directory data, and has no specified format.
|
6
|
+
class Name
|
7
|
+
# The name of the item on the remote server.
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# The display-ready name of the item, possibly with other attributes.
|
11
|
+
attr_reader :longname
|
12
|
+
|
13
|
+
# The Attributes object describing this item.
|
14
|
+
attr_reader :attributes
|
15
|
+
|
16
|
+
# Create a new Name object with the given name, longname, and attributes.
|
17
|
+
def initialize(name, longname, attributes)
|
18
|
+
@name, @longname, @attributes = name, longname, attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns +true+ if the item appears to be a directory. It does this by
|
22
|
+
# examining the attributes. If there is insufficient information in the
|
23
|
+
# attributes, this will return nil, rather than a boolean.
|
24
|
+
def directory?
|
25
|
+
attributes.directory?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns +true+ if the item appears to be a symlink. It does this by
|
29
|
+
# examining the attributes. If there is insufficient information in the
|
30
|
+
# attributes, this will return nil, rather than a boolean.
|
31
|
+
def symlink?
|
32
|
+
attributes.symlink?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns +true+ if the item appears to be a regular file. It does this by
|
36
|
+
# examining the attributes. If there is insufficient information in the
|
37
|
+
# attributes, this will return nil, rather than a boolean.
|
38
|
+
def file?
|
39
|
+
attributes.file?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end; end; end; end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'net/sftp/protocol/01/base'
|
2
|
+
|
3
|
+
module Net; module SFTP; module Protocol; module V02
|
4
|
+
|
5
|
+
# Wraps the low-level SFTP calls for version 2 of the SFTP protocol.
|
6
|
+
#
|
7
|
+
# None of these protocol methods block--all of them return immediately,
|
8
|
+
# requiring the SSH event loop to be run while the server response is
|
9
|
+
# pending.
|
10
|
+
#
|
11
|
+
# You will almost certainly never need to use this driver directly. Please
|
12
|
+
# see Net::SFTP::Session for the recommended interface.
|
13
|
+
class Base < V01::Base
|
14
|
+
|
15
|
+
# Returns the protocol version implemented by this driver. (2, in this
|
16
|
+
# case)
|
17
|
+
def version
|
18
|
+
2
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sends a FXP_RENAME packet to the server to request that the file or
|
22
|
+
# directory with the given +name+ (must be a full path) be changed to
|
23
|
+
# +new_name+ (which must also be a path). The +flags+ parameter is
|
24
|
+
# ignored in this version of the protocol.
|
25
|
+
def rename(name, new_name, flags=nil)
|
26
|
+
send_request(FXP_RENAME, :string, name, :string, new_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end; end; end; end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'net/sftp/protocol/02/base'
|
2
|
+
|
3
|
+
module Net; module SFTP; module Protocol; module V03
|
4
|
+
|
5
|
+
# Wraps the low-level SFTP calls for version 3 of the SFTP protocol.
|
6
|
+
#
|
7
|
+
# None of these protocol methods block--all of them return immediately,
|
8
|
+
# requiring the SSH event loop to be run while the server response is
|
9
|
+
# pending.
|
10
|
+
#
|
11
|
+
# You will almost certainly never need to use this driver directly. Please
|
12
|
+
# see Net::SFTP::Session for the recommended interface.
|
13
|
+
class Base < V02::Base
|
14
|
+
|
15
|
+
# Returns the protocol version implemented by this driver. (3, in this
|
16
|
+
# case)
|
17
|
+
def version
|
18
|
+
3
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sends a FXP_READLINK packet to the server to request that the target of
|
22
|
+
# the given symlink on the remote host (+path+) be returned.
|
23
|
+
def readlink(path)
|
24
|
+
send_request(FXP_READLINK, :string, path)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sends a FXP_SYMLINK packet to the server to request that a symlink at the
|
28
|
+
# given +path+ be created, pointing at +target+..
|
29
|
+
def symlink(path, target)
|
30
|
+
send_request(FXP_SYMLINK, :string, path, :string, target)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end; end; end; end
|