rubysl-net-ftp 1.0.0 → 2.0.1
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 +4 -4
- data/.travis.yml +3 -2
- data/lib/rubysl/net/ftp/ftp.rb +541 -349
- data/lib/rubysl/net/ftp/version.rb +1 -1
- data/rubysl-net-ftp.gemspec +3 -1
- metadata +18 -19
- data/lib/net/ftptls.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 456965d440f1346a6441b73d14ad9fe6d8ed3c17
|
4
|
+
data.tar.gz: 60f559ec7a5f317c8cb42024694b897212dd34ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c05f1132989fd0faf92a8fcaf0fecc6dc36ba07a48d595230c6f4c693801f7e24fe12fd5696cca096f64a0d1bcecef7598841c2dbb4eb98ba0928d3645057d44
|
7
|
+
data.tar.gz: 1b9795aa38d39bc624b3b90eaca65c0302440490b9f5de8fade14b28797409f86543a0c11022f82813663c9c0ddb27ef9779bde99e195cf540832a554a7c8c1d
|
data/.travis.yml
CHANGED
data/lib/rubysl/net/ftp/ftp.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
#
|
1
|
+
#
|
2
2
|
# = net/ftp.rb - FTP Client Library
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# Written by Shugo Maeda <shugo@ruby-lang.org>.
|
5
5
|
#
|
6
6
|
# Documentation by Gavin Sinclair, sourced from "Programming Ruby" (Hunt/Thomas)
|
7
7
|
# and "Ruby In a Nutshell" (Matsumoto), used with permission.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# This library is distributed under the terms of the Ruby license.
|
10
10
|
# You can freely distribute/modify this library.
|
11
11
|
#
|
@@ -16,15 +16,17 @@
|
|
16
16
|
|
17
17
|
require "socket"
|
18
18
|
require "monitor"
|
19
|
+
require "net/protocol"
|
19
20
|
|
20
21
|
module Net
|
21
22
|
|
22
23
|
# :stopdoc:
|
23
24
|
class FTPError < StandardError; end
|
24
25
|
class FTPReplyError < FTPError; end
|
25
|
-
class FTPTempError < FTPError; end
|
26
|
-
class FTPPermError < FTPError; end
|
26
|
+
class FTPTempError < FTPError; end
|
27
|
+
class FTPPermError < FTPError; end
|
27
28
|
class FTPProtoError < FTPError; end
|
29
|
+
class FTPConnectionError < FTPError; end
|
28
30
|
# :startdoc:
|
29
31
|
|
30
32
|
#
|
@@ -34,12 +36,12 @@ module Net
|
|
34
36
|
# advantage of Ruby's style and strengths.
|
35
37
|
#
|
36
38
|
# == Example
|
37
|
-
#
|
39
|
+
#
|
38
40
|
# require 'net/ftp'
|
39
41
|
#
|
40
42
|
# === Example 1
|
41
|
-
#
|
42
|
-
# ftp = Net::FTP.new('
|
43
|
+
#
|
44
|
+
# ftp = Net::FTP.new('example.com')
|
43
45
|
# ftp.login
|
44
46
|
# files = ftp.chdir('pub/lang/ruby/contrib')
|
45
47
|
# files = ftp.list('n*')
|
@@ -48,7 +50,7 @@ module Net
|
|
48
50
|
#
|
49
51
|
# === Example 2
|
50
52
|
#
|
51
|
-
# Net::FTP.open('
|
53
|
+
# Net::FTP.open('example.com') do |ftp|
|
52
54
|
# ftp.login
|
53
55
|
# files = ftp.chdir('pub/lang/ruby/contrib')
|
54
56
|
# files = ftp.list('n*')
|
@@ -71,15 +73,15 @@ module Net
|
|
71
73
|
#
|
72
74
|
class FTP
|
73
75
|
include MonitorMixin
|
74
|
-
|
76
|
+
|
75
77
|
# :stopdoc:
|
76
78
|
FTP_PORT = 21
|
77
79
|
CRLF = "\r\n"
|
78
|
-
DEFAULT_BLOCKSIZE =
|
80
|
+
DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE
|
79
81
|
# :startdoc:
|
80
|
-
|
82
|
+
|
81
83
|
# When +true+, transfers are performed in binary mode. Default: +true+.
|
82
|
-
|
84
|
+
attr_reader :binary
|
83
85
|
|
84
86
|
# When +true+, the connection is in passive mode. Default: +false+.
|
85
87
|
attr_accessor :passive
|
@@ -92,6 +94,24 @@ module Net
|
|
92
94
|
# transfers are resumed or restarted. Default: +false+.
|
93
95
|
attr_accessor :resume
|
94
96
|
|
97
|
+
# Number of seconds to wait for the connection to open. Any number
|
98
|
+
# may be used, including Floats for fractional seconds. If the FTP
|
99
|
+
# object cannot open a connection in this many seconds, it raises a
|
100
|
+
# Net::OpenTimeout exception. The default value is +nil+.
|
101
|
+
attr_accessor :open_timeout
|
102
|
+
|
103
|
+
# Number of seconds to wait for one block to be read (via one read(2)
|
104
|
+
# call). Any number may be used, including Floats for fractional
|
105
|
+
# seconds. If the FTP object cannot read data in this many seconds,
|
106
|
+
# it raises a TimeoutError exception. The default value is 60 seconds.
|
107
|
+
attr_reader :read_timeout
|
108
|
+
|
109
|
+
# Setter for the read_timeout attribute.
|
110
|
+
def read_timeout=(sec)
|
111
|
+
@sock.read_timeout = sec
|
112
|
+
@read_timeout = sec
|
113
|
+
end
|
114
|
+
|
95
115
|
# The server's welcome message.
|
96
116
|
attr_reader :welcome
|
97
117
|
|
@@ -101,7 +121,7 @@ module Net
|
|
101
121
|
|
102
122
|
# The server's last response.
|
103
123
|
attr_reader :last_response
|
104
|
-
|
124
|
+
|
105
125
|
#
|
106
126
|
# A synonym for <tt>FTP.new</tt>, but with a mandatory host parameter.
|
107
127
|
#
|
@@ -120,7 +140,7 @@ module Net
|
|
120
140
|
new(host, user, passwd, acct)
|
121
141
|
end
|
122
142
|
end
|
123
|
-
|
143
|
+
|
124
144
|
#
|
125
145
|
# Creates and returns a new +FTP+ object. If a +host+ is given, a connection
|
126
146
|
# is made. Additionally, if the +user+ is given, the given user name,
|
@@ -132,35 +152,88 @@ module Net
|
|
132
152
|
@passive = false
|
133
153
|
@debug_mode = false
|
134
154
|
@resume = false
|
155
|
+
@sock = NullSocket.new
|
156
|
+
@logged_in = false
|
157
|
+
@open_timeout = nil
|
158
|
+
@read_timeout = 60
|
135
159
|
if host
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
160
|
+
connect(host)
|
161
|
+
if user
|
162
|
+
login(user, passwd, acct)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# A setter to toggle transfers in binary mode.
|
168
|
+
# +newmode+ is either +true+ or +false+
|
169
|
+
def binary=(newmode)
|
170
|
+
if newmode != @binary
|
171
|
+
@binary = newmode
|
172
|
+
send_type_command if @logged_in
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Sends a command to destination host, with the current binary sendmode
|
177
|
+
# type.
|
178
|
+
#
|
179
|
+
# If binary mode is +true+, then "TYPE I" (image) is sent, otherwise "TYPE
|
180
|
+
# A" (ascii) is sent.
|
181
|
+
def send_type_command # :nodoc:
|
182
|
+
if @binary
|
183
|
+
voidcmd("TYPE I")
|
184
|
+
else
|
185
|
+
voidcmd("TYPE A")
|
186
|
+
end
|
187
|
+
end
|
188
|
+
private :send_type_command
|
189
|
+
|
190
|
+
# Toggles transfers in binary mode and yields to a block.
|
191
|
+
# This preserves your current binary send mode, but allows a temporary
|
192
|
+
# transaction with binary sendmode of +newmode+.
|
193
|
+
#
|
194
|
+
# +newmode+ is either +true+ or +false+
|
195
|
+
def with_binary(newmode) # :nodoc:
|
196
|
+
oldmode = binary
|
197
|
+
self.binary = newmode
|
198
|
+
begin
|
199
|
+
yield
|
200
|
+
ensure
|
201
|
+
self.binary = oldmode
|
140
202
|
end
|
141
203
|
end
|
204
|
+
private :with_binary
|
142
205
|
|
143
206
|
# Obsolete
|
144
|
-
def return_code
|
207
|
+
def return_code # :nodoc:
|
145
208
|
$stderr.puts("warning: Net::FTP#return_code is obsolete and do nothing")
|
146
209
|
return "\n"
|
147
210
|
end
|
148
211
|
|
149
212
|
# Obsolete
|
150
|
-
def return_code=(s)
|
213
|
+
def return_code=(s) # :nodoc:
|
151
214
|
$stderr.puts("warning: Net::FTP#return_code= is obsolete and do nothing")
|
152
215
|
end
|
153
216
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
217
|
+
# Constructs a socket with +host+ and +port+.
|
218
|
+
#
|
219
|
+
# If SOCKSSocket is defined and the environment (ENV) defines
|
220
|
+
# SOCKS_SERVER, then a SOCKSSocket is returned, else a TCPSocket is
|
221
|
+
# returned.
|
222
|
+
def open_socket(host, port) # :nodoc:
|
223
|
+
return Timeout.timeout(@open_timeout, Net::OpenTimeout) {
|
224
|
+
if defined? SOCKSSocket and ENV["SOCKS_SERVER"]
|
225
|
+
@passive = true
|
226
|
+
sock = SOCKSSocket.open(host, port)
|
227
|
+
else
|
228
|
+
sock = TCPSocket.open(host, port)
|
229
|
+
end
|
230
|
+
io = BufferedSocket.new(sock)
|
231
|
+
io.read_timeout = @read_timeout
|
232
|
+
io
|
233
|
+
}
|
161
234
|
end
|
162
235
|
private :open_socket
|
163
|
-
|
236
|
+
|
164
237
|
#
|
165
238
|
# Establishes an FTP connection to host, optionally overriding the default
|
166
239
|
# port. If the environment variable +SOCKS_SERVER+ is set, sets up the
|
@@ -169,11 +242,11 @@ module Net
|
|
169
242
|
#
|
170
243
|
def connect(host, port = FTP_PORT)
|
171
244
|
if @debug_mode
|
172
|
-
|
245
|
+
print "connect: ", host, ", ", port, "\n"
|
173
246
|
end
|
174
247
|
synchronize do
|
175
|
-
|
176
|
-
|
248
|
+
@sock = open_socket(host, port)
|
249
|
+
voidresp
|
177
250
|
end
|
178
251
|
end
|
179
252
|
|
@@ -182,185 +255,192 @@ module Net
|
|
182
255
|
#
|
183
256
|
def set_socket(sock, get_greeting = true)
|
184
257
|
synchronize do
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
258
|
+
@sock = sock
|
259
|
+
if get_greeting
|
260
|
+
voidresp
|
261
|
+
end
|
189
262
|
end
|
190
263
|
end
|
191
264
|
|
192
|
-
|
265
|
+
# If string +s+ includes the PASS command (password), then the contents of
|
266
|
+
# the password are cleaned from the string using "*"
|
267
|
+
def sanitize(s) # :nodoc:
|
193
268
|
if s =~ /^PASS /i
|
194
|
-
|
269
|
+
return s[0, 5] + "*" * (s.length - 5)
|
195
270
|
else
|
196
|
-
|
271
|
+
return s
|
197
272
|
end
|
198
273
|
end
|
199
274
|
private :sanitize
|
200
|
-
|
201
|
-
|
275
|
+
|
276
|
+
# Ensures that +line+ has a control return / line feed (CRLF) and writes
|
277
|
+
# it to the socket.
|
278
|
+
def putline(line) # :nodoc:
|
202
279
|
if @debug_mode
|
203
|
-
|
280
|
+
print "put: ", sanitize(line), "\n"
|
204
281
|
end
|
205
282
|
line = line + CRLF
|
206
283
|
@sock.write(line)
|
207
284
|
end
|
208
285
|
private :putline
|
209
|
-
|
210
|
-
|
286
|
+
|
287
|
+
# Reads a line from the sock. If EOF, then it will raise EOFError
|
288
|
+
def getline # :nodoc:
|
211
289
|
line = @sock.readline # if get EOF, raise EOFError
|
212
290
|
line.sub!(/(\r\n|\n|\r)\z/n, "")
|
213
291
|
if @debug_mode
|
214
|
-
|
292
|
+
print "get: ", sanitize(line), "\n"
|
215
293
|
end
|
216
294
|
return line
|
217
295
|
end
|
218
296
|
private :getline
|
219
|
-
|
220
|
-
|
297
|
+
|
298
|
+
# Receive a section of lines until the response code's match.
|
299
|
+
def getmultiline # :nodoc:
|
221
300
|
line = getline
|
222
301
|
buff = line
|
223
302
|
if line[3] == ?-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
303
|
+
code = line[0, 3]
|
304
|
+
begin
|
305
|
+
line = getline
|
306
|
+
buff << "\n" << line
|
307
|
+
end until line[0, 3] == code and line[3] != ?-
|
229
308
|
end
|
230
309
|
return buff << "\n"
|
231
310
|
end
|
232
311
|
private :getmultiline
|
233
|
-
|
234
|
-
|
312
|
+
|
313
|
+
# Recieves a response from the destination host.
|
314
|
+
#
|
315
|
+
# Returns the response code or raises FTPTempError, FTPPermError, or
|
316
|
+
# FTPProtoError
|
317
|
+
def getresp # :nodoc:
|
235
318
|
@last_response = getmultiline
|
236
319
|
@last_response_code = @last_response[0, 3]
|
237
320
|
case @last_response_code
|
238
321
|
when /\A[123]/
|
239
|
-
|
322
|
+
return @last_response
|
240
323
|
when /\A4/
|
241
|
-
|
324
|
+
raise FTPTempError, @last_response
|
242
325
|
when /\A5/
|
243
|
-
|
326
|
+
raise FTPPermError, @last_response
|
244
327
|
else
|
245
|
-
|
328
|
+
raise FTPProtoError, @last_response
|
246
329
|
end
|
247
330
|
end
|
248
331
|
private :getresp
|
249
|
-
|
250
|
-
|
332
|
+
|
333
|
+
# Recieves a response.
|
334
|
+
#
|
335
|
+
# Raises FTPReplyError if the first position of the response code is not
|
336
|
+
# equal 2.
|
337
|
+
def voidresp # :nodoc:
|
251
338
|
resp = getresp
|
252
339
|
if resp[0] != ?2
|
253
|
-
|
340
|
+
raise FTPReplyError, resp
|
254
341
|
end
|
255
342
|
end
|
256
343
|
private :voidresp
|
257
|
-
|
344
|
+
|
258
345
|
#
|
259
346
|
# Sends a command and returns the response.
|
260
347
|
#
|
261
348
|
def sendcmd(cmd)
|
262
349
|
synchronize do
|
263
|
-
|
264
|
-
|
350
|
+
putline(cmd)
|
351
|
+
return getresp
|
265
352
|
end
|
266
353
|
end
|
267
|
-
|
354
|
+
|
268
355
|
#
|
269
356
|
# Sends a command and expect a response beginning with '2'.
|
270
357
|
#
|
271
358
|
def voidcmd(cmd)
|
272
359
|
synchronize do
|
273
|
-
|
274
|
-
|
360
|
+
putline(cmd)
|
361
|
+
voidresp
|
275
362
|
end
|
276
363
|
end
|
277
|
-
|
278
|
-
|
364
|
+
|
365
|
+
# Constructs and send the appropriate PORT (or EPRT) command
|
366
|
+
def sendport(host, port) # :nodoc:
|
279
367
|
af = (@sock.peeraddr)[0]
|
280
368
|
if af == "AF_INET"
|
281
|
-
|
369
|
+
cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",")
|
282
370
|
elsif af == "AF_INET6"
|
283
|
-
|
371
|
+
cmd = sprintf("EPRT |2|%s|%d|", host, port)
|
284
372
|
else
|
285
|
-
|
373
|
+
raise FTPProtoError, host
|
286
374
|
end
|
287
375
|
voidcmd(cmd)
|
288
376
|
end
|
289
377
|
private :sendport
|
290
|
-
|
291
|
-
|
378
|
+
|
379
|
+
# Constructs a TCPServer socket, and sends it the PORT command
|
380
|
+
#
|
381
|
+
# Returns the constructed TCPServer socket
|
382
|
+
def makeport # :nodoc:
|
292
383
|
sock = TCPServer.open(@sock.addr[3], 0)
|
293
384
|
port = sock.addr[1]
|
294
385
|
host = sock.addr[3]
|
295
|
-
|
386
|
+
sendport(host, port)
|
296
387
|
return sock
|
297
388
|
end
|
298
389
|
private :makeport
|
299
|
-
|
300
|
-
|
390
|
+
|
391
|
+
# sends the appropriate command to enable a passive connection
|
392
|
+
def makepasv # :nodoc:
|
301
393
|
if @sock.peeraddr[0] == "AF_INET"
|
302
|
-
|
394
|
+
host, port = parse227(sendcmd("PASV"))
|
303
395
|
else
|
304
|
-
|
305
|
-
|
396
|
+
host, port = parse229(sendcmd("EPSV"))
|
397
|
+
# host, port = parse228(sendcmd("LPSV"))
|
306
398
|
end
|
307
399
|
return host, port
|
308
400
|
end
|
309
401
|
private :makepasv
|
310
|
-
|
311
|
-
|
402
|
+
|
403
|
+
# Constructs a connection for transferring data
|
404
|
+
def transfercmd(cmd, rest_offset = nil) # :nodoc:
|
312
405
|
if @passive
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
406
|
+
host, port = makepasv
|
407
|
+
conn = open_socket(host, port)
|
408
|
+
if @resume and rest_offset
|
409
|
+
resp = sendcmd("REST " + rest_offset.to_s)
|
410
|
+
if resp[0] != ?3
|
411
|
+
raise FTPReplyError, resp
|
412
|
+
end
|
413
|
+
end
|
414
|
+
resp = sendcmd(cmd)
|
322
415
|
# skip 2XX for some ftp servers
|
323
416
|
resp = getresp if resp[0] == ?2
|
324
|
-
|
325
|
-
|
326
|
-
|
417
|
+
if resp[0] != ?1
|
418
|
+
raise FTPReplyError, resp
|
419
|
+
end
|
327
420
|
else
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
421
|
+
sock = makeport
|
422
|
+
if @resume and rest_offset
|
423
|
+
resp = sendcmd("REST " + rest_offset.to_s)
|
424
|
+
if resp[0] != ?3
|
425
|
+
raise FTPReplyError, resp
|
426
|
+
end
|
427
|
+
end
|
428
|
+
resp = sendcmd(cmd)
|
336
429
|
# skip 2XX for some ftp servers
|
337
430
|
resp = getresp if resp[0] == ?2
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
431
|
+
if resp[0] != ?1
|
432
|
+
raise FTPReplyError, resp
|
433
|
+
end
|
434
|
+
conn = BufferedSocket.new(sock.accept)
|
435
|
+
conn.read_timeout = @read_timeout
|
436
|
+
sock.shutdown(Socket::SHUT_WR) rescue nil
|
437
|
+
sock.read rescue nil
|
438
|
+
sock.close
|
343
439
|
end
|
344
440
|
return conn
|
345
441
|
end
|
346
442
|
private :transfercmd
|
347
|
-
|
348
|
-
def getaddress
|
349
|
-
thishost = Socket.gethostname rescue ""
|
350
|
-
if not thishost.index(".")
|
351
|
-
thishost = Socket.gethostbyname(thishost)[0] rescue ""
|
352
|
-
end
|
353
|
-
if ENV.has_key?("LOGNAME")
|
354
|
-
realuser = ENV["LOGNAME"]
|
355
|
-
elsif ENV.has_key?("USER")
|
356
|
-
realuser = ENV["USER"]
|
357
|
-
else
|
358
|
-
realuser = "anonymous"
|
359
|
-
end
|
360
|
-
return realuser + "@" + thishost
|
361
|
-
end
|
362
|
-
private :getaddress
|
363
|
-
|
443
|
+
|
364
444
|
#
|
365
445
|
# Logs in to the remote host. The session must have been previously
|
366
446
|
# connected. If +user+ is the string "anonymous" and the +password+ is
|
@@ -371,27 +451,29 @@ module Net
|
|
371
451
|
#
|
372
452
|
def login(user = "anonymous", passwd = nil, acct = nil)
|
373
453
|
if user == "anonymous" and passwd == nil
|
374
|
-
|
454
|
+
passwd = "anonymous@"
|
375
455
|
end
|
376
|
-
|
456
|
+
|
377
457
|
resp = ""
|
378
458
|
synchronize do
|
379
|
-
|
380
|
-
|
459
|
+
resp = sendcmd('USER ' + user)
|
460
|
+
if resp[0] == ?3
|
381
461
|
raise FTPReplyError, resp if passwd.nil?
|
382
|
-
|
383
|
-
|
384
|
-
|
462
|
+
resp = sendcmd('PASS ' + passwd)
|
463
|
+
end
|
464
|
+
if resp[0] == ?3
|
385
465
|
raise FTPReplyError, resp if acct.nil?
|
386
|
-
|
387
|
-
|
466
|
+
resp = sendcmd('ACCT ' + acct)
|
467
|
+
end
|
388
468
|
end
|
389
469
|
if resp[0] != ?2
|
390
|
-
|
470
|
+
raise FTPReplyError, resp
|
391
471
|
end
|
392
472
|
@welcome = resp
|
473
|
+
send_type_command
|
474
|
+
@logged_in = true
|
393
475
|
end
|
394
|
-
|
476
|
+
|
395
477
|
#
|
396
478
|
# Puts the connection into binary (image) mode, issues the given command,
|
397
479
|
# and fetches the data returned, passing it to the associated block in
|
@@ -400,18 +482,25 @@ module Net
|
|
400
482
|
#
|
401
483
|
def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
|
402
484
|
synchronize do
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
485
|
+
with_binary(true) do
|
486
|
+
begin
|
487
|
+
conn = transfercmd(cmd, rest_offset)
|
488
|
+
loop do
|
489
|
+
data = conn.read(blocksize)
|
490
|
+
break if data == nil
|
491
|
+
yield(data)
|
492
|
+
end
|
493
|
+
conn.shutdown(Socket::SHUT_WR)
|
494
|
+
conn.read_timeout = 1
|
495
|
+
conn.read
|
496
|
+
ensure
|
497
|
+
conn.close if conn
|
498
|
+
end
|
499
|
+
voidresp
|
500
|
+
end
|
412
501
|
end
|
413
502
|
end
|
414
|
-
|
503
|
+
|
415
504
|
#
|
416
505
|
# Puts the connection into ASCII (text) mode, issues the given command, and
|
417
506
|
# passes the resulting data, one line at a time, to the associated block. If
|
@@ -420,110 +509,146 @@ module Net
|
|
420
509
|
#
|
421
510
|
def retrlines(cmd) # :yield: line
|
422
511
|
synchronize do
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
512
|
+
with_binary(false) do
|
513
|
+
begin
|
514
|
+
conn = transfercmd(cmd)
|
515
|
+
loop do
|
516
|
+
line = conn.gets
|
517
|
+
break if line == nil
|
518
|
+
yield(line.sub(/\r?\n\z/, ""), !line.match(/\n\z/).nil?)
|
519
|
+
end
|
520
|
+
conn.shutdown(Socket::SHUT_WR)
|
521
|
+
conn.read_timeout = 1
|
522
|
+
conn.read
|
523
|
+
ensure
|
524
|
+
conn.close if conn
|
525
|
+
end
|
526
|
+
voidresp
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
440
531
|
#
|
441
532
|
# Puts the connection into binary (image) mode, issues the given server-side
|
442
533
|
# command (such as "STOR myfile"), and sends the contents of the file named
|
443
534
|
# +file+ to the server. If the optional block is given, it also passes it
|
444
535
|
# the data, in chunks of +blocksize+ characters.
|
445
536
|
#
|
446
|
-
def storbinary(cmd, file, blocksize, rest_offset = nil
|
537
|
+
def storbinary(cmd, file, blocksize, rest_offset = nil) # :yield: data
|
447
538
|
if rest_offset
|
448
539
|
file.seek(rest_offset, IO::SEEK_SET)
|
449
540
|
end
|
450
541
|
synchronize do
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
542
|
+
with_binary(true) do
|
543
|
+
conn = transfercmd(cmd)
|
544
|
+
loop do
|
545
|
+
buf = file.read(blocksize)
|
546
|
+
break if buf == nil
|
547
|
+
conn.write(buf)
|
548
|
+
yield(buf) if block_given?
|
549
|
+
end
|
550
|
+
conn.close
|
551
|
+
voidresp
|
552
|
+
end
|
461
553
|
end
|
554
|
+
rescue Errno::EPIPE
|
555
|
+
# EPIPE, in this case, means that the data connection was unexpectedly
|
556
|
+
# terminated. Rather than just raising EPIPE to the caller, check the
|
557
|
+
# response on the control connection. If getresp doesn't raise a more
|
558
|
+
# appropriate exception, re-raise the original exception.
|
559
|
+
getresp
|
560
|
+
raise
|
462
561
|
end
|
463
|
-
|
562
|
+
|
464
563
|
#
|
465
564
|
# Puts the connection into ASCII (text) mode, issues the given server-side
|
466
565
|
# command (such as "STOR myfile"), and sends the contents of the file
|
467
566
|
# named +file+ to the server, one line at a time. If the optional block is
|
468
567
|
# given, it also passes it the lines.
|
469
568
|
#
|
470
|
-
def storlines(cmd, file
|
569
|
+
def storlines(cmd, file) # :yield: line
|
471
570
|
synchronize do
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
571
|
+
with_binary(false) do
|
572
|
+
conn = transfercmd(cmd)
|
573
|
+
loop do
|
574
|
+
buf = file.gets
|
575
|
+
break if buf == nil
|
576
|
+
if buf[-2, 2] != CRLF
|
577
|
+
buf = buf.chomp + CRLF
|
578
|
+
end
|
579
|
+
conn.write(buf)
|
580
|
+
yield(buf) if block_given?
|
581
|
+
end
|
582
|
+
conn.close
|
583
|
+
voidresp
|
584
|
+
end
|
485
585
|
end
|
586
|
+
rescue Errno::EPIPE
|
587
|
+
# EPIPE, in this case, means that the data connection was unexpectedly
|
588
|
+
# terminated. Rather than just raising EPIPE to the caller, check the
|
589
|
+
# response on the control connection. If getresp doesn't raise a more
|
590
|
+
# appropriate exception, re-raise the original exception.
|
591
|
+
getresp
|
592
|
+
raise
|
486
593
|
end
|
487
594
|
|
488
595
|
#
|
489
596
|
# Retrieves +remotefile+ in binary mode, storing the result in +localfile+.
|
597
|
+
# If +localfile+ is nil, returns retrieved data.
|
490
598
|
# If a block is supplied, it is passed the retrieved data in +blocksize+
|
491
599
|
# chunks.
|
492
600
|
#
|
493
601
|
def getbinaryfile(remotefile, localfile = File.basename(remotefile),
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
602
|
+
blocksize = DEFAULT_BLOCKSIZE) # :yield: data
|
603
|
+
result = nil
|
604
|
+
if localfile
|
605
|
+
if @resume
|
606
|
+
rest_offset = File.size?(localfile)
|
607
|
+
f = open(localfile, "a")
|
608
|
+
else
|
609
|
+
rest_offset = nil
|
610
|
+
f = open(localfile, "w")
|
611
|
+
end
|
612
|
+
elsif !block_given?
|
613
|
+
result = ""
|
501
614
|
end
|
502
615
|
begin
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
616
|
+
f.binmode if localfile
|
617
|
+
retrbinary("RETR " + remotefile.to_s, blocksize, rest_offset) do |data|
|
618
|
+
f.write(data) if localfile
|
619
|
+
yield(data) if block_given?
|
620
|
+
result.concat(data) if result
|
621
|
+
end
|
622
|
+
return result
|
508
623
|
ensure
|
509
|
-
|
624
|
+
f.close if localfile
|
510
625
|
end
|
511
626
|
end
|
512
|
-
|
627
|
+
|
513
628
|
#
|
514
629
|
# Retrieves +remotefile+ in ASCII (text) mode, storing the result in
|
515
|
-
# +localfile+.
|
630
|
+
# +localfile+.
|
631
|
+
# If +localfile+ is nil, returns retrieved data.
|
632
|
+
# If a block is supplied, it is passed the retrieved data one
|
516
633
|
# line at a time.
|
517
634
|
#
|
518
|
-
def gettextfile(remotefile, localfile = File.basename(remotefile)
|
519
|
-
|
635
|
+
def gettextfile(remotefile, localfile = File.basename(remotefile)) # :yield: line
|
636
|
+
result = nil
|
637
|
+
if localfile
|
638
|
+
f = open(localfile, "w")
|
639
|
+
elsif !block_given?
|
640
|
+
result = ""
|
641
|
+
end
|
520
642
|
begin
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
643
|
+
retrlines("RETR " + remotefile) do |line, newline|
|
644
|
+
l = newline ? line + "\n" : line
|
645
|
+
f.print(l) if localfile
|
646
|
+
yield(line, newline) if block_given?
|
647
|
+
result.concat(l) if result
|
648
|
+
end
|
649
|
+
return result
|
525
650
|
ensure
|
526
|
-
|
651
|
+
f.close if localfile
|
527
652
|
end
|
528
653
|
end
|
529
654
|
|
@@ -532,21 +657,21 @@ module Net
|
|
532
657
|
# binary). See #gettextfile and #getbinaryfile.
|
533
658
|
#
|
534
659
|
def get(remotefile, localfile = File.basename(remotefile),
|
535
|
-
|
536
|
-
|
537
|
-
|
660
|
+
blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
|
661
|
+
if @binary
|
662
|
+
getbinaryfile(remotefile, localfile, blocksize, &block)
|
538
663
|
else
|
539
|
-
|
664
|
+
gettextfile(remotefile, localfile, &block)
|
540
665
|
end
|
541
666
|
end
|
542
|
-
|
667
|
+
|
543
668
|
#
|
544
669
|
# Transfers +localfile+ to the server in binary mode, storing the result in
|
545
670
|
# +remotefile+. If a block is supplied, calls it, passing in the transmitted
|
546
671
|
# data in +blocksize+ chunks.
|
547
672
|
#
|
548
673
|
def putbinaryfile(localfile, remotefile = File.basename(localfile),
|
549
|
-
|
674
|
+
blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
|
550
675
|
if @resume
|
551
676
|
begin
|
552
677
|
rest_offset = size(remotefile)
|
@@ -554,17 +679,21 @@ module Net
|
|
554
679
|
rest_offset = nil
|
555
680
|
end
|
556
681
|
else
|
557
|
-
|
682
|
+
rest_offset = nil
|
558
683
|
end
|
559
684
|
f = open(localfile)
|
560
685
|
begin
|
561
|
-
|
562
|
-
|
686
|
+
f.binmode
|
687
|
+
if rest_offset
|
688
|
+
storbinary("APPE " + remotefile, f, blocksize, rest_offset, &block)
|
689
|
+
else
|
690
|
+
storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
|
691
|
+
end
|
563
692
|
ensure
|
564
|
-
|
693
|
+
f.close
|
565
694
|
end
|
566
695
|
end
|
567
|
-
|
696
|
+
|
568
697
|
#
|
569
698
|
# Transfers +localfile+ to the server in ASCII (text) mode, storing the result
|
570
699
|
# in +remotefile+. If callback or an associated block is supplied, calls it,
|
@@ -573,9 +702,9 @@ module Net
|
|
573
702
|
def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line
|
574
703
|
f = open(localfile)
|
575
704
|
begin
|
576
|
-
|
705
|
+
storlines("STOR " + remotefile, f, &block)
|
577
706
|
ensure
|
578
|
-
|
707
|
+
f.close
|
579
708
|
end
|
580
709
|
end
|
581
710
|
|
@@ -584,37 +713,40 @@ module Net
|
|
584
713
|
# (text or binary). See #puttextfile and #putbinaryfile.
|
585
714
|
#
|
586
715
|
def put(localfile, remotefile = File.basename(localfile),
|
587
|
-
|
588
|
-
|
589
|
-
|
716
|
+
blocksize = DEFAULT_BLOCKSIZE, &block)
|
717
|
+
if @binary
|
718
|
+
putbinaryfile(localfile, remotefile, blocksize, &block)
|
590
719
|
else
|
591
|
-
|
720
|
+
puttextfile(localfile, remotefile, &block)
|
592
721
|
end
|
593
722
|
end
|
594
723
|
|
595
724
|
#
|
596
|
-
# Sends the ACCT command.
|
725
|
+
# Sends the ACCT command.
|
726
|
+
#
|
727
|
+
# This is a less common FTP command, to send account
|
728
|
+
# information if the destination host requires it.
|
597
729
|
#
|
598
730
|
def acct(account)
|
599
731
|
cmd = "ACCT " + account
|
600
732
|
voidcmd(cmd)
|
601
733
|
end
|
602
|
-
|
734
|
+
|
603
735
|
#
|
604
736
|
# Returns an array of filenames in the remote directory.
|
605
737
|
#
|
606
738
|
def nlst(dir = nil)
|
607
739
|
cmd = "NLST"
|
608
740
|
if dir
|
609
|
-
|
741
|
+
cmd = cmd + " " + dir
|
610
742
|
end
|
611
743
|
files = []
|
612
744
|
retrlines(cmd) do |line|
|
613
|
-
|
745
|
+
files.push(line)
|
614
746
|
end
|
615
747
|
return files
|
616
748
|
end
|
617
|
-
|
749
|
+
|
618
750
|
#
|
619
751
|
# Returns an array of file information in the directory (the output is like
|
620
752
|
# `ls -l`). If a block is given, it iterates through the listing.
|
@@ -622,78 +754,79 @@ module Net
|
|
622
754
|
def list(*args, &block) # :yield: line
|
623
755
|
cmd = "LIST"
|
624
756
|
args.each do |arg|
|
625
|
-
|
757
|
+
cmd = cmd + " " + arg.to_s
|
626
758
|
end
|
627
759
|
if block
|
628
|
-
|
760
|
+
retrlines(cmd, &block)
|
629
761
|
else
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
762
|
+
lines = []
|
763
|
+
retrlines(cmd) do |line|
|
764
|
+
lines << line
|
765
|
+
end
|
766
|
+
return lines
|
635
767
|
end
|
636
768
|
end
|
637
769
|
alias ls list
|
638
770
|
alias dir list
|
639
|
-
|
771
|
+
|
640
772
|
#
|
641
773
|
# Renames a file on the server.
|
642
774
|
#
|
643
775
|
def rename(fromname, toname)
|
644
776
|
resp = sendcmd("RNFR " + fromname)
|
645
777
|
if resp[0] != ?3
|
646
|
-
|
778
|
+
raise FTPReplyError, resp
|
647
779
|
end
|
648
780
|
voidcmd("RNTO " + toname)
|
649
781
|
end
|
650
|
-
|
782
|
+
|
651
783
|
#
|
652
784
|
# Deletes a file on the server.
|
653
785
|
#
|
654
786
|
def delete(filename)
|
655
787
|
resp = sendcmd("DELE " + filename)
|
656
788
|
if resp[0, 3] == "250"
|
657
|
-
|
789
|
+
return
|
658
790
|
elsif resp[0] == ?5
|
659
|
-
|
791
|
+
raise FTPPermError, resp
|
660
792
|
else
|
661
|
-
|
793
|
+
raise FTPReplyError, resp
|
662
794
|
end
|
663
795
|
end
|
664
|
-
|
796
|
+
|
665
797
|
#
|
666
798
|
# Changes the (remote) directory.
|
667
799
|
#
|
668
800
|
def chdir(dirname)
|
669
801
|
if dirname == ".."
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
802
|
+
begin
|
803
|
+
voidcmd("CDUP")
|
804
|
+
return
|
805
|
+
rescue FTPPermError => e
|
806
|
+
if e.message[0, 3] != "500"
|
807
|
+
raise e
|
808
|
+
end
|
809
|
+
end
|
678
810
|
end
|
679
811
|
cmd = "CWD " + dirname
|
680
812
|
voidcmd(cmd)
|
681
813
|
end
|
682
|
-
|
814
|
+
|
683
815
|
#
|
684
816
|
# Returns the size of the given (remote) filename.
|
685
817
|
#
|
686
818
|
def size(filename)
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
819
|
+
with_binary(true) do
|
820
|
+
resp = sendcmd("SIZE " + filename)
|
821
|
+
if resp[0, 3] != "213"
|
822
|
+
raise FTPReplyError, resp
|
823
|
+
end
|
824
|
+
return resp[3..-1].strip.to_i
|
691
825
|
end
|
692
|
-
return resp[3..-1].strip.to_i
|
693
826
|
end
|
694
|
-
|
827
|
+
|
695
828
|
MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ # :nodoc:
|
696
|
-
|
829
|
+
|
697
830
|
#
|
698
831
|
# Returns the last modification time of the (remote) file. If +local+ is
|
699
832
|
# +true+, it is returned as a local time, otherwise it's a UTC time.
|
@@ -703,7 +836,7 @@ module Net
|
|
703
836
|
ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
|
704
837
|
return local ? Time.local(*ary) : Time.gm(*ary)
|
705
838
|
end
|
706
|
-
|
839
|
+
|
707
840
|
#
|
708
841
|
# Creates a remote directory.
|
709
842
|
#
|
@@ -711,14 +844,14 @@ module Net
|
|
711
844
|
resp = sendcmd("MKD " + dirname)
|
712
845
|
return parse257(resp)
|
713
846
|
end
|
714
|
-
|
847
|
+
|
715
848
|
#
|
716
849
|
# Removes a remote directory.
|
717
850
|
#
|
718
851
|
def rmdir(dirname)
|
719
852
|
voidcmd("RMD " + dirname)
|
720
853
|
end
|
721
|
-
|
854
|
+
|
722
855
|
#
|
723
856
|
# Returns the current remote directory.
|
724
857
|
#
|
@@ -727,18 +860,18 @@ module Net
|
|
727
860
|
return parse257(resp)
|
728
861
|
end
|
729
862
|
alias getdir pwd
|
730
|
-
|
863
|
+
|
731
864
|
#
|
732
865
|
# Returns system information.
|
733
866
|
#
|
734
867
|
def system
|
735
868
|
resp = sendcmd("SYST")
|
736
869
|
if resp[0, 3] != "215"
|
737
|
-
|
870
|
+
raise FTPReplyError, resp
|
738
871
|
end
|
739
872
|
return resp[4 .. -1]
|
740
873
|
end
|
741
|
-
|
874
|
+
|
742
875
|
#
|
743
876
|
# Aborts the previous command (ABOR command).
|
744
877
|
#
|
@@ -748,11 +881,11 @@ module Net
|
|
748
881
|
@sock.send(line, Socket::MSG_OOB)
|
749
882
|
resp = getmultiline
|
750
883
|
unless ["426", "226", "225"].include?(resp[0, 3])
|
751
|
-
|
884
|
+
raise FTPProtoError, resp
|
752
885
|
end
|
753
886
|
return resp
|
754
887
|
end
|
755
|
-
|
888
|
+
|
756
889
|
#
|
757
890
|
# Returns the status (STAT command).
|
758
891
|
#
|
@@ -762,28 +895,28 @@ module Net
|
|
762
895
|
@sock.send(line, Socket::MSG_OOB)
|
763
896
|
return getresp
|
764
897
|
end
|
765
|
-
|
898
|
+
|
766
899
|
#
|
767
900
|
# Issues the MDTM command. TODO: more info.
|
768
901
|
#
|
769
902
|
def mdtm(filename)
|
770
903
|
resp = sendcmd("MDTM " + filename)
|
771
904
|
if resp[0, 3] == "213"
|
772
|
-
|
905
|
+
return resp[3 .. -1].strip
|
773
906
|
end
|
774
907
|
end
|
775
|
-
|
908
|
+
|
776
909
|
#
|
777
910
|
# Issues the HELP command.
|
778
911
|
#
|
779
912
|
def help(arg = nil)
|
780
913
|
cmd = "HELP"
|
781
914
|
if arg
|
782
|
-
|
915
|
+
cmd = cmd + " " + arg
|
783
916
|
end
|
784
917
|
sendcmd(cmd)
|
785
918
|
end
|
786
|
-
|
919
|
+
|
787
920
|
#
|
788
921
|
# Exits the FTP session.
|
789
922
|
#
|
@@ -794,6 +927,8 @@ module Net
|
|
794
927
|
#
|
795
928
|
# Issues a NOOP command.
|
796
929
|
#
|
930
|
+
# Does nothing except return a response.
|
931
|
+
#
|
797
932
|
def noop
|
798
933
|
voidcmd("NOOP")
|
799
934
|
end
|
@@ -805,118 +940,175 @@ module Net
|
|
805
940
|
cmd = "SITE " + arg
|
806
941
|
voidcmd(cmd)
|
807
942
|
end
|
808
|
-
|
943
|
+
|
809
944
|
#
|
810
945
|
# Closes the connection. Further operations are impossible until you open
|
811
946
|
# a new connection with #connect.
|
812
947
|
#
|
813
948
|
def close
|
814
|
-
|
949
|
+
if @sock and not @sock.closed?
|
950
|
+
begin
|
951
|
+
@sock.shutdown(Socket::SHUT_WR) rescue nil
|
952
|
+
orig, self.read_timeout = self.read_timeout, 3
|
953
|
+
@sock.read rescue nil
|
954
|
+
ensure
|
955
|
+
@sock.close
|
956
|
+
self.read_timeout = orig
|
957
|
+
end
|
958
|
+
end
|
815
959
|
end
|
816
|
-
|
960
|
+
|
817
961
|
#
|
818
962
|
# Returns +true+ iff the connection is closed.
|
819
963
|
#
|
820
964
|
def closed?
|
821
965
|
@sock == nil or @sock.closed?
|
822
966
|
end
|
823
|
-
|
824
|
-
|
967
|
+
|
968
|
+
# handler for response code 227
|
969
|
+
# (Entering Passive Mode (h1,h2,h3,h4,p1,p2))
|
970
|
+
#
|
971
|
+
# Returns host and port.
|
972
|
+
def parse227(resp) # :nodoc:
|
825
973
|
if resp[0, 3] != "227"
|
826
|
-
|
827
|
-
end
|
828
|
-
left = resp.index("(")
|
829
|
-
right = resp.index(")")
|
830
|
-
if left == nil or right == nil
|
831
|
-
raise FTPProtoError, resp
|
974
|
+
raise FTPReplyError, resp
|
832
975
|
end
|
833
|
-
|
834
|
-
|
835
|
-
|
976
|
+
if m = /\((?<host>\d+(,\d+){3}),(?<port>\d+,\d+)\)/.match(resp)
|
977
|
+
return parse_pasv_ipv4_host(m["host"]), parse_pasv_port(m["port"])
|
978
|
+
else
|
979
|
+
raise FTPProtoError, resp
|
836
980
|
end
|
837
|
-
host = numbers[0, 4].join(".")
|
838
|
-
port = (numbers[4].to_i << 8) + numbers[5].to_i
|
839
|
-
return host, port
|
840
981
|
end
|
841
982
|
private :parse227
|
842
|
-
|
843
|
-
|
983
|
+
|
984
|
+
# handler for response code 228
|
985
|
+
# (Entering Long Passive Mode)
|
986
|
+
#
|
987
|
+
# Returns host and port.
|
988
|
+
def parse228(resp) # :nodoc:
|
844
989
|
if resp[0, 3] != "228"
|
845
|
-
|
846
|
-
end
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
|
855
|
-
raise FTPProtoError, resp
|
856
|
-
end
|
857
|
-
host = numbers[2, 4].join(".")
|
858
|
-
port = (numbers[7].to_i << 8) + numbers[8].to_i
|
859
|
-
elsif numbers[0] == "6"
|
860
|
-
if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
|
861
|
-
raise FTPProtoError, resp
|
862
|
-
end
|
863
|
-
v6 = ["", "", "", "", "", "", "", ""]
|
864
|
-
for i in 0 .. 7
|
865
|
-
v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
|
866
|
-
numbers[(i * 2) + 3].to_i)
|
867
|
-
end
|
868
|
-
host = v6[0, 8].join(":")
|
869
|
-
port = (numbers[19].to_i << 8) + numbers[20].to_i
|
870
|
-
end
|
871
|
-
return host, port
|
990
|
+
raise FTPReplyError, resp
|
991
|
+
end
|
992
|
+
if m = /\(4,4,(?<host>\d+(,\d+){3}),2,(?<port>\d+,\d+)\)/.match(resp)
|
993
|
+
return parse_pasv_ipv4_host(m["host"]), parse_pasv_port(m["port"])
|
994
|
+
elsif m = /\(6,16,(?<host>\d+(,(\d+)){15}),2,(?<port>\d+,\d+)\)/.match(resp)
|
995
|
+
return parse_pasv_ipv6_host(m["host"]), parse_pasv_port(m["port"])
|
996
|
+
else
|
997
|
+
raise FTPProtoError, resp
|
998
|
+
end
|
872
999
|
end
|
873
1000
|
private :parse228
|
874
|
-
|
875
|
-
def
|
1001
|
+
|
1002
|
+
def parse_pasv_ipv4_host(s)
|
1003
|
+
return s.tr(",", ".")
|
1004
|
+
end
|
1005
|
+
private :parse_pasv_ipv4_host
|
1006
|
+
|
1007
|
+
def parse_pasv_ipv6_host(s)
|
1008
|
+
return s.split(/,/).map { |i|
|
1009
|
+
"%02x" % i.to_i
|
1010
|
+
}.each_slice(2).map(&:join).join(":")
|
1011
|
+
end
|
1012
|
+
private :parse_pasv_ipv6_host
|
1013
|
+
|
1014
|
+
def parse_pasv_port(s)
|
1015
|
+
return s.split(/,/).map(&:to_i).inject { |x, y|
|
1016
|
+
(x << 8) + y
|
1017
|
+
}
|
1018
|
+
end
|
1019
|
+
private :parse_pasv_port
|
1020
|
+
|
1021
|
+
# handler for response code 229
|
1022
|
+
# (Extended Passive Mode Entered)
|
1023
|
+
#
|
1024
|
+
# Returns host and port.
|
1025
|
+
def parse229(resp) # :nodoc:
|
876
1026
|
if resp[0, 3] != "229"
|
877
|
-
|
878
|
-
end
|
879
|
-
left = resp.index("(")
|
880
|
-
right = resp.index(")")
|
881
|
-
if left == nil or right == nil
|
882
|
-
raise FTPProtoError, resp
|
1027
|
+
raise FTPReplyError, resp
|
883
1028
|
end
|
884
|
-
|
885
|
-
|
886
|
-
|
1029
|
+
if m = /\((?<d>[!-~])\k<d>\k<d>(?<port>\d+)\k<d>\)/.match(resp)
|
1030
|
+
return @sock.peeraddr[3], m["port"].to_i
|
1031
|
+
else
|
1032
|
+
raise FTPProtoError, resp
|
887
1033
|
end
|
888
|
-
port = numbers[3].to_i
|
889
|
-
host = (@sock.peeraddr())[3]
|
890
|
-
return host, port
|
891
1034
|
end
|
892
1035
|
private :parse229
|
893
|
-
|
894
|
-
|
1036
|
+
|
1037
|
+
# handler for response code 257
|
1038
|
+
# ("PATHNAME" created)
|
1039
|
+
#
|
1040
|
+
# Returns host and port.
|
1041
|
+
def parse257(resp) # :nodoc:
|
895
1042
|
if resp[0, 3] != "257"
|
896
|
-
|
1043
|
+
raise FTPReplyError, resp
|
897
1044
|
end
|
898
1045
|
if resp[3, 2] != ' "'
|
899
|
-
|
1046
|
+
return ""
|
900
1047
|
end
|
901
1048
|
dirname = ""
|
902
1049
|
i = 5
|
903
1050
|
n = resp.length
|
904
1051
|
while i < n
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
1052
|
+
c = resp[i, 1]
|
1053
|
+
i = i + 1
|
1054
|
+
if c == '"'
|
1055
|
+
if i > n or resp[i, 1] != '"'
|
1056
|
+
break
|
1057
|
+
end
|
1058
|
+
i = i + 1
|
1059
|
+
end
|
1060
|
+
dirname = dirname + c
|
914
1061
|
end
|
915
1062
|
return dirname
|
916
1063
|
end
|
917
1064
|
private :parse257
|
918
|
-
end
|
919
1065
|
|
1066
|
+
# :stopdoc:
|
1067
|
+
class NullSocket
|
1068
|
+
def read_timeout=(sec)
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
def close
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def method_missing(mid, *args)
|
1075
|
+
raise FTPConnectionError, "not connected"
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
class BufferedSocket < BufferedIO
|
1080
|
+
[:addr, :peeraddr, :send, :shutdown].each do |method|
|
1081
|
+
define_method(method) { |*args|
|
1082
|
+
@io.__send__(method, *args)
|
1083
|
+
}
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def read(len = nil)
|
1087
|
+
if len
|
1088
|
+
s = super(len, "", true)
|
1089
|
+
return s.empty? ? nil : s
|
1090
|
+
else
|
1091
|
+
result = ""
|
1092
|
+
while s = super(DEFAULT_BLOCKSIZE, "", true)
|
1093
|
+
break if s.empty?
|
1094
|
+
result << s
|
1095
|
+
end
|
1096
|
+
return result
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
def gets
|
1101
|
+
return readuntil("\n")
|
1102
|
+
rescue EOFError
|
1103
|
+
return nil
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
def readline
|
1107
|
+
return readuntil("\n")
|
1108
|
+
end
|
1109
|
+
end
|
1110
|
+
# :startdoc:
|
1111
|
+
end
|
920
1112
|
end
|
921
1113
|
|
922
1114
|
|