rubysl-net-ftp 1.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 056ab8cdeb2260ec5fc8c32aa3e859384b0f1293
4
- data.tar.gz: edec86bfc6933c0580c237a73bd6c8dda32b78a9
3
+ metadata.gz: 456965d440f1346a6441b73d14ad9fe6d8ed3c17
4
+ data.tar.gz: 60f559ec7a5f317c8cb42024694b897212dd34ab
5
5
  SHA512:
6
- metadata.gz: 4c78e9ed1e31a894ebd2949aa4d957c3c800b29e341437f7e1b22592d23e16a9cebb16ca275e4de11d09063ebbc059e566af1de9d9680bc7899a6b359e14e122
7
- data.tar.gz: f97d6244aa78a2a07f894f2c71e4d8a9fbe85466e8bc4a493ee201c2c7ea60e997e0c8d8a1dc05d19c6fc28f74464271317e2261e1b5d25f32165a2bcb234aa1
6
+ metadata.gz: c05f1132989fd0faf92a8fcaf0fecc6dc36ba07a48d595230c6f4c693801f7e24fe12fd5696cca096f64a0d1bcecef7598841c2dbb4eb98ba0928d3645057d44
7
+ data.tar.gz: 1b9795aa38d39bc624b3b90eaca65c0302440490b9f5de8fade14b28797409f86543a0c11022f82813663c9c0ddb27ef9779bde99e195cf540832a554a7c8c1d
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
2
  before_install:
3
+ - rvm use $RVM --install --binary --fuzzy
3
4
  - gem update --system
4
5
  - gem --version
5
6
  - gem install rubysl-bundler
7
+ env:
8
+ - RVM=rbx-nightly-d21 RUBYLIB=lib
6
9
  script: bundle exec mspec spec
7
- rvm:
8
- - rbx-nightly-18mode
@@ -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('ftp.netlab.co.jp')
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('ftp.netlab.co.jp') do |ftp|
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 = 4096
80
+ DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE
79
81
  # :startdoc:
80
-
82
+
81
83
  # When +true+, transfers are performed in binary mode. Default: +true+.
82
- attr_accessor :binary
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
- connect(host)
137
- if user
138
- login(user, passwd, acct)
139
- end
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
- def open_socket(host, port)
155
- if defined? SOCKSSocket and ENV["SOCKS_SERVER"]
156
- @passive = true
157
- return SOCKSSocket.open(host, port)
158
- else
159
- return TCPSocket.open(host, port)
160
- end
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
- print "connect: ", host, ", ", port, "\n"
245
+ print "connect: ", host, ", ", port, "\n"
173
246
  end
174
247
  synchronize do
175
- @sock = open_socket(host, port)
176
- voidresp
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
- @sock = sock
186
- if get_greeting
187
- voidresp
188
- end
258
+ @sock = sock
259
+ if get_greeting
260
+ voidresp
261
+ end
189
262
  end
190
263
  end
191
264
 
192
- def sanitize(s)
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
- return s[0, 5] + "*" * (s.length - 5)
269
+ return s[0, 5] + "*" * (s.length - 5)
195
270
  else
196
- return s
271
+ return s
197
272
  end
198
273
  end
199
274
  private :sanitize
200
-
201
- def putline(line)
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
- print "put: ", sanitize(line), "\n"
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
- def getline
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
- print "get: ", sanitize(line), "\n"
292
+ print "get: ", sanitize(line), "\n"
215
293
  end
216
294
  return line
217
295
  end
218
296
  private :getline
219
-
220
- def getmultiline
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
- code = line[0, 3]
225
- begin
226
- line = getline
227
- buff << "\n" << line
228
- end until line[0, 3] == code and line[3] != ?-
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
- def getresp
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
- return @last_response
322
+ return @last_response
240
323
  when /\A4/
241
- raise FTPTempError, @last_response
324
+ raise FTPTempError, @last_response
242
325
  when /\A5/
243
- raise FTPPermError, @last_response
326
+ raise FTPPermError, @last_response
244
327
  else
245
- raise FTPProtoError, @last_response
328
+ raise FTPProtoError, @last_response
246
329
  end
247
330
  end
248
331
  private :getresp
249
-
250
- def voidresp
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
- raise FTPReplyError, resp
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
- putline(cmd)
264
- return getresp
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
- putline(cmd)
274
- voidresp
360
+ putline(cmd)
361
+ voidresp
275
362
  end
276
363
  end
277
-
278
- def sendport(host, port)
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
- cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",")
369
+ cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",")
282
370
  elsif af == "AF_INET6"
283
- cmd = sprintf("EPRT |2|%s|%d|", host, port)
371
+ cmd = sprintf("EPRT |2|%s|%d|", host, port)
284
372
  else
285
- raise FTPProtoError, host
373
+ raise FTPProtoError, host
286
374
  end
287
375
  voidcmd(cmd)
288
376
  end
289
377
  private :sendport
290
-
291
- def makeport
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
- resp = sendport(host, port)
386
+ sendport(host, port)
296
387
  return sock
297
388
  end
298
389
  private :makeport
299
-
300
- def makepasv
390
+
391
+ # sends the appropriate command to enable a passive connection
392
+ def makepasv # :nodoc:
301
393
  if @sock.peeraddr[0] == "AF_INET"
302
- host, port = parse227(sendcmd("PASV"))
394
+ host, port = parse227(sendcmd("PASV"))
303
395
  else
304
- host, port = parse229(sendcmd("EPSV"))
305
- # host, port = parse228(sendcmd("LPSV"))
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
- def transfercmd(cmd, rest_offset = nil)
402
+
403
+ # Constructs a connection for transferring data
404
+ def transfercmd(cmd, rest_offset = nil) # :nodoc:
312
405
  if @passive
313
- host, port = makepasv
314
- conn = open_socket(host, port)
315
- if @resume and rest_offset
316
- resp = sendcmd("REST " + rest_offset.to_s)
317
- if resp[0] != ?3
318
- raise FTPReplyError, resp
319
- end
320
- end
321
- resp = sendcmd(cmd)
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
- if resp[0] != ?1
325
- raise FTPReplyError, resp
326
- end
417
+ if resp[0] != ?1
418
+ raise FTPReplyError, resp
419
+ end
327
420
  else
328
- sock = makeport
329
- if @resume and rest_offset
330
- resp = sendcmd("REST " + rest_offset.to_s)
331
- if resp[0] != ?3
332
- raise FTPReplyError, resp
333
- end
334
- end
335
- resp = sendcmd(cmd)
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
- if resp[0] != ?1
339
- raise FTPReplyError, resp
340
- end
341
- conn = sock.accept
342
- sock.close
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
- passwd = getaddress
454
+ passwd = "anonymous@"
375
455
  end
376
-
456
+
377
457
  resp = ""
378
458
  synchronize do
379
- resp = sendcmd('USER ' + user)
380
- if resp[0] == ?3
459
+ resp = sendcmd('USER ' + user)
460
+ if resp[0] == ?3
381
461
  raise FTPReplyError, resp if passwd.nil?
382
- resp = sendcmd('PASS ' + passwd)
383
- end
384
- if resp[0] == ?3
462
+ resp = sendcmd('PASS ' + passwd)
463
+ end
464
+ if resp[0] == ?3
385
465
  raise FTPReplyError, resp if acct.nil?
386
- resp = sendcmd('ACCT ' + acct)
387
- end
466
+ resp = sendcmd('ACCT ' + acct)
467
+ end
388
468
  end
389
469
  if resp[0] != ?2
390
- raise FTPReplyError, resp
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
- voidcmd("TYPE I")
404
- conn = transfercmd(cmd, rest_offset)
405
- loop do
406
- data = conn.read(blocksize)
407
- break if data == nil
408
- yield(data)
409
- end
410
- conn.close
411
- voidresp
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
- voidcmd("TYPE A")
424
- conn = transfercmd(cmd)
425
- loop do
426
- line = conn.gets
427
- break if line == nil
428
- if line[-2, 2] == CRLF
429
- line = line[0 .. -3]
430
- elsif line[-1] == ?\n
431
- line = line[0 .. -2]
432
- end
433
- yield(line)
434
- end
435
- conn.close
436
- voidresp
437
- end
438
- end
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, &block) # :yield: data
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
- voidcmd("TYPE I")
452
- conn = transfercmd(cmd, rest_offset)
453
- loop do
454
- buf = file.read(blocksize)
455
- break if buf == nil
456
- conn.write(buf)
457
- yield(buf) if block
458
- end
459
- conn.close
460
- voidresp
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, &block) # :yield: line
569
+ def storlines(cmd, file) # :yield: line
471
570
  synchronize do
472
- voidcmd("TYPE A")
473
- conn = transfercmd(cmd)
474
- loop do
475
- buf = file.gets
476
- break if buf == nil
477
- if buf[-2, 2] != CRLF
478
- buf = buf.chomp + CRLF
479
- end
480
- conn.write(buf)
481
- yield(buf) if block
482
- end
483
- conn.close
484
- voidresp
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
- blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
495
- if @resume
496
- rest_offset = File.size?(localfile)
497
- f = open(localfile, "a")
498
- else
499
- rest_offset = nil
500
- f = open(localfile, "w")
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
- f.binmode
504
- retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
505
- f.write(data)
506
- yield(data) if block
507
- end
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
- f.close
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+. If a block is supplied, it is passed the retrieved data one
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), &block) # :yield: line
519
- f = open(localfile, "w")
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
- retrlines("RETR " + remotefile) do |line|
522
- f.puts(line)
523
- yield(line) if block
524
- end
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
- f.close
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
- blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
536
- unless @binary
537
- gettextfile(remotefile, localfile, &block)
660
+ blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
661
+ if @binary
662
+ getbinaryfile(remotefile, localfile, blocksize, &block)
538
663
  else
539
- getbinaryfile(remotefile, localfile, blocksize, &block)
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
- blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
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
- rest_offset = nil
682
+ rest_offset = nil
558
683
  end
559
684
  f = open(localfile)
560
685
  begin
561
- f.binmode
562
- storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
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
- f.close
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
- storlines("STOR " + remotefile, f, &block)
705
+ storlines("STOR " + remotefile, f, &block)
577
706
  ensure
578
- f.close
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
- blocksize = DEFAULT_BLOCKSIZE, &block)
588
- unless @binary
589
- puttextfile(localfile, remotefile, &block)
716
+ blocksize = DEFAULT_BLOCKSIZE, &block)
717
+ if @binary
718
+ putbinaryfile(localfile, remotefile, blocksize, &block)
590
719
  else
591
- putbinaryfile(localfile, remotefile, blocksize, &block)
720
+ puttextfile(localfile, remotefile, &block)
592
721
  end
593
722
  end
594
723
 
595
724
  #
596
- # Sends the ACCT command. TODO: more info.
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
- cmd = cmd + " " + dir
741
+ cmd = cmd + " " + dir
610
742
  end
611
743
  files = []
612
744
  retrlines(cmd) do |line|
613
- files.push(line)
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
- cmd = cmd + " " + arg
757
+ cmd = cmd + " " + arg.to_s
626
758
  end
627
759
  if block
628
- retrlines(cmd, &block)
760
+ retrlines(cmd, &block)
629
761
  else
630
- lines = []
631
- retrlines(cmd) do |line|
632
- lines << line
633
- end
634
- return lines
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
- raise FTPReplyError, resp
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
- return
789
+ return
658
790
  elsif resp[0] == ?5
659
- raise FTPPermError, resp
791
+ raise FTPPermError, resp
660
792
  else
661
- raise FTPReplyError, resp
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
- begin
671
- voidcmd("CDUP")
672
- return
673
- rescue FTPPermError => e
674
- if e.message[0, 3] != "500"
675
- raise e
676
- end
677
- end
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
- voidcmd("TYPE I")
688
- resp = sendcmd("SIZE " + filename)
689
- if resp[0, 3] != "213"
690
- raise FTPReplyError, resp
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
- raise FTPReplyError, resp
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
- raise FTPProtoError, resp
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
- return resp[3 .. -1].strip
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
- cmd = cmd + " " + arg
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
- @sock.close if @sock and not @sock.closed?
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
- def parse227(resp)
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
- raise FTPReplyError, resp
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
- numbers = resp[left + 1 .. right - 1].split(",")
834
- if numbers.length != 6
835
- raise FTPProtoError, resp
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
- def parse228(resp)
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
- raise FTPReplyError, resp
846
- end
847
- left = resp.index("(")
848
- right = resp.index(")")
849
- if left == nil or right == nil
850
- raise FTPProtoError, resp
851
- end
852
- numbers = resp[left + 1 .. right - 1].split(",")
853
- if numbers[0] == "4"
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 parse229(resp)
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
- raise FTPReplyError, resp
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
- numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1])
885
- if numbers.length != 4
886
- raise FTPProtoError, resp
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
- def parse257(resp)
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
- raise FTPReplyError, resp
1043
+ raise FTPReplyError, resp
897
1044
  end
898
1045
  if resp[3, 2] != ' "'
899
- return ""
1046
+ return ""
900
1047
  end
901
1048
  dirname = ""
902
1049
  i = 5
903
1050
  n = resp.length
904
1051
  while i < n
905
- c = resp[i, 1]
906
- i = i + 1
907
- if c == '"'
908
- if i > n or resp[i, 1] != '"'
909
- break
910
- end
911
- i = i + 1
912
- end
913
- dirname = dirname + c
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