| Class | Net::FTP |
| In: |
lib/net/ftp.rb
|
| Parent: | Object |
This class implements the File Transfer Protocol. If you have used a command-line FTP program, and are familiar with the commands, you will be able to use this class easily. Some extra features are included to take advantage of Ruby‘s style and strengths.
require 'net/ftp'
ftp = Net::FTP.new('ftp.netlab.co.jp')
ftp.login
files = ftp.chdir('pub/lang/ruby/contrib')
files = ftp.list('n*')
ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
ftp.close
Net::FTP.open('ftp.netlab.co.jp') do |ftp|
ftp.login
files = ftp.chdir('pub/lang/ruby/contrib')
files = ftp.list('n*')
ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
end
The following are the methods most likely to be useful to users:
| last_response_code | -> | lastresp |
| binary | [RW] | When true, transfers are performed in binary mode. Default: true. |
| debug_mode | [RW] | When true, all traffic to and from the server is written to +$stdout+. Default: false. |
| last_response | [R] | The server‘s last response. |
| last_response_code | [R] | The server‘s last response code. |
| passive | [RW] | When true, the connection is in passive mode. Default: false. |
| resume | [RW] | Sets or retrieves the resume status, which decides whether incomplete transfers are resumed or restarted. Default: false. |
| welcome | [R] | The server‘s welcome message. |
Creates and returns a new FTP object. If a host is given, a connection is made. Additionally, if the user is given, the given user name, password, and (optionally) account are used to log in. See login.
# File lib/net/ftp.rb, line 129
129: def initialize(host = nil, user = nil, passwd = nil, acct = nil)
130: super()
131: @binary = true
132: @passive = false
133: @debug_mode = false
134: @resume = false
135: if host
136: connect(host)
137: if user
138: login(user, passwd, acct)
139: end
140: end
141: end
A synonym for FTP.new, but with a mandatory host parameter.
If a block is given, it is passed the FTP object, which will be closed when the block finishes, or when an exception is raised.
# File lib/net/ftp.rb, line 111
111: def FTP.open(host, user = nil, passwd = nil, acct = nil)
112: if block_given?
113: ftp = new(host, user, passwd, acct)
114: begin
115: yield ftp
116: ensure
117: ftp.close
118: end
119: else
120: new(host, user, passwd, acct)
121: end
122: end
Aborts the previous command (ABOR command).
# File lib/net/ftp.rb, line 742
742: def abort
743: line = "ABOR" + CRLF
744: print "put: ABOR\n" if @debug_mode
745: @sock.send(line, Socket::MSG_OOB)
746: resp = getmultiline
747: unless ["426", "226", "225"].include?(resp[0, 3])
748: raise FTPProtoError, resp
749: end
750: return resp
751: end
Sends the ACCT command. TODO: more info.
# File lib/net/ftp.rb, line 595
595: def acct(account)
596: cmd = "ACCT " + account
597: voidcmd(cmd)
598: end
Changes the (remote) directory.
# File lib/net/ftp.rb, line 665
665: def chdir(dirname)
666: if dirname == ".."
667: begin
668: voidcmd("CDUP")
669: return
670: rescue FTPPermError
671: if $![0, 3] != "500"
672: raise FTPPermError, $!
673: end
674: end
675: end
676: cmd = "CWD " + dirname
677: voidcmd(cmd)
678: end
Returns true iff the connection is closed.
# File lib/net/ftp.rb, line 817
817: def closed?
818: @sock == nil or @sock.closed?
819: end
Establishes an FTP connection to host, optionally overriding the default port. If the environment variable SOCKS_SERVER is set, sets up the connection through a SOCKS proxy. Raises an exception (typically Errno::ECONNREFUSED) if the connection cannot be established.
# File lib/net/ftp.rb, line 170
170: def connect(host, port = FTP_PORT)
171: if @debug_mode
172: print "connect: ", host, ", ", port, "\n"
173: end
174: synchronize do
175: @sock = open_socket(host, port)
176: voidresp
177: end
178: end
Deletes a file on the server.
# File lib/net/ftp.rb, line 651
651: def delete(filename)
652: resp = sendcmd("DELE " + filename)
653: if resp[0, 3] == "250"
654: return
655: elsif resp[0] == ?5
656: raise FTPPermError, resp
657: else
658: raise FTPReplyError, resp
659: end
660: end
Retrieves remotefile in whatever mode the session is set (text or binary). See gettextfile and getbinaryfile.
# File lib/net/ftp.rb, line 531
531: def get(remotefile, localfile = File.basename(remotefile),
532: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
533: unless @binary
534: gettextfile(remotefile, localfile, &block)
535: else
536: getbinaryfile(remotefile, localfile, blocksize, &block)
537: end
538: end
Retrieves remotefile in binary mode, storing the result in localfile. If a block is supplied, it is passed the retrieved data in blocksize chunks.
# File lib/net/ftp.rb, line 490
490: def getbinaryfile(remotefile, localfile = File.basename(remotefile),
491: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
492: if @resume
493: rest_offset = File.size?(localfile)
494: f = open(localfile, "a")
495: else
496: rest_offset = nil
497: f = open(localfile, "w")
498: end
499: begin
500: f.binmode
501: retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
502: f.write(data)
503: yield(data) if block
504: end
505: ensure
506: f.close
507: end
508: end
Retrieves remotefile in ASCII (text) mode, storing the result in localfile. If a block is supplied, it is passed the retrieved data one line at a time.
# File lib/net/ftp.rb, line 515
515: def gettextfile(remotefile, localfile = File.basename(remotefile), &block) # :yield: line
516: f = open(localfile, "w")
517: begin
518: retrlines("RETR " + remotefile) do |line|
519: f.puts(line)
520: yield(line) if block
521: end
522: ensure
523: f.close
524: end
525: end
Issues the HELP command.
# File lib/net/ftp.rb, line 776
776: def help(arg = nil)
777: cmd = "HELP"
778: if arg
779: cmd = cmd + " " + arg
780: end
781: sendcmd(cmd)
782: end
Returns an array of file information in the directory (the output is like `ls -l`). If a block is given, it iterates through the listing.
# File lib/net/ftp.rb, line 619
619: def list(*args, &block) # :yield: line
620: cmd = "LIST"
621: args.each do |arg|
622: cmd = cmd + " " + arg
623: end
624: if block
625: retrlines(cmd, &block)
626: else
627: lines = []
628: retrlines(cmd) do |line|
629: lines << line
630: end
631: return lines
632: end
633: end
Logs in to the remote host. The session must have been previously connected. If user is the string "anonymous" and the password is nil, a password of user@host is synthesized. If the acct parameter is not nil, an FTP ACCT command is sent following the successful login. Raises an exception on error (typically Net::FTPPermError).
# File lib/net/ftp.rb, line 371
371: def login(user = "anonymous", passwd = nil, acct = nil)
372: if user == "anonymous" and passwd == nil
373: passwd = getaddress
374: end
375:
376: resp = ""
377: synchronize do
378: resp = sendcmd('USER ' + user)
379: if resp[0] == ?3
380: resp = sendcmd('PASS ' + passwd)
381: end
382: if resp[0] == ?3
383: resp = sendcmd('ACCT ' + acct)
384: end
385: end
386: if resp[0] != ?2
387: raise FTPReplyError, resp
388: end
389: @welcome = resp
390: end
Issues the MDTM command. TODO: more info.
# File lib/net/ftp.rb, line 766
766: def mdtm(filename)
767: resp = sendcmd("MDTM " + filename)
768: if resp[0, 3] == "213"
769: return resp[3 .. -1].strip
770: end
771: end
Creates a remote directory.
# File lib/net/ftp.rb, line 707
707: def mkdir(dirname)
708: resp = sendcmd("MKD " + dirname)
709: return parse257(resp)
710: end
Returns the last modification time of the (remote) file. If local is true, it is returned as a local time, otherwise it‘s a UTC time.
# File lib/net/ftp.rb, line 698
698: def mtime(filename, local = false)
699: str = mdtm(filename)
700: ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
701: return local ? Time.local(*ary) : Time.gm(*ary)
702: end
Returns an array of filenames in the remote directory.
# File lib/net/ftp.rb, line 603
603: def nlst(dir = nil)
604: cmd = "NLST"
605: if dir
606: cmd = cmd + " " + dir
607: end
608: files = []
609: retrlines(cmd) do |line|
610: files.push(line)
611: end
612: return files
613: end
Transfers localfile to the server in whatever mode the session is set (text or binary). See puttextfile and putbinaryfile.
# File lib/net/ftp.rb, line 583
583: def put(localfile, remotefile = File.basename(localfile),
584: blocksize = DEFAULT_BLOCKSIZE, &block)
585: unless @binary
586: puttextfile(localfile, remotefile, &block)
587: else
588: putbinaryfile(localfile, remotefile, blocksize, &block)
589: end
590: end
Transfers localfile to the server in binary mode, storing the result in remotefile. If a block is supplied, calls it, passing in the transmitted data in blocksize chunks.
# File lib/net/ftp.rb, line 545
545: def putbinaryfile(localfile, remotefile = File.basename(localfile),
546: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
547: if @resume
548: begin
549: rest_offset = size(remotefile)
550: rescue Net::FTPPermError
551: rest_offset = nil
552: end
553: else
554: rest_offset = nil
555: end
556: f = open(localfile)
557: begin
558: f.binmode
559: storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
560: ensure
561: f.close
562: end
563: end
Transfers localfile to the server in ASCII (text) mode, storing the result in remotefile. If callback or an associated block is supplied, calls it, passing in the transmitted data one line at a time.
# File lib/net/ftp.rb, line 570
570: def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line
571: f = open(localfile)
572: begin
573: storlines("STOR " + remotefile, f, &block)
574: ensure
575: f.close
576: end
577: end
Returns the current remote directory.
# File lib/net/ftp.rb, line 722
722: def pwd
723: resp = sendcmd("PWD")
724: return parse257(resp)
725: end
Renames a file on the server.
# File lib/net/ftp.rb, line 640
640: def rename(fromname, toname)
641: resp = sendcmd("RNFR " + fromname)
642: if resp[0] != ?3
643: raise FTPReplyError, resp
644: end
645: voidcmd("RNTO " + toname)
646: end
Puts the connection into binary (image) mode, issues the given command, and fetches the data returned, passing it to the associated block in chunks of blocksize characters. Note that cmd is a server command (such as "RETR myfile").
# File lib/net/ftp.rb, line 398
398: def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
399: synchronize do
400: voidcmd("TYPE I")
401: conn = transfercmd(cmd, rest_offset)
402: loop do
403: data = conn.read(blocksize)
404: break if data == nil
405: yield(data)
406: end
407: conn.close
408: voidresp
409: end
410: end
Puts the connection into ASCII (text) mode, issues the given command, and passes the resulting data, one line at a time, to the associated block. If no block is given, prints the lines. Note that cmd is a server command (such as "RETR myfile").
# File lib/net/ftp.rb, line 418
418: def retrlines(cmd) # :yield: line
419: synchronize do
420: voidcmd("TYPE A")
421: conn = transfercmd(cmd)
422: loop do
423: line = conn.gets
424: break if line == nil
425: if line[-2, 2] == CRLF
426: line = line[0 .. -3]
427: elsif line[-1] == ?\n
428: line = line[0 .. -2]
429: end
430: yield(line)
431: end
432: conn.close
433: voidresp
434: end
435: end
Obsolete
# File lib/net/ftp.rb, line 144
144: def return_code
145: $stderr.puts("warning: Net::FTP#return_code is obsolete and do nothing")
146: return "\n"
147: end
Obsolete
# File lib/net/ftp.rb, line 150
150: def return_code=(s)
151: $stderr.puts("warning: Net::FTP#return_code= is obsolete and do nothing")
152: end
Removes a remote directory.
# File lib/net/ftp.rb, line 715
715: def rmdir(dirname)
716: voidcmd("RMD " + dirname)
717: end
Sends a command and returns the response.
# File lib/net/ftp.rb, line 261
261: def sendcmd(cmd)
262: synchronize do
263: putline(cmd)
264: return getresp
265: end
266: end
WRITEME or make private
# File lib/net/ftp.rb, line 183
183: def set_socket(sock, get_greeting = true)
184: synchronize do
185: @sock = sock
186: if get_greeting
187: voidresp
188: end
189: end
190: end
Issues a SITE command.
# File lib/net/ftp.rb, line 801
801: def site(arg)
802: cmd = "SITE " + arg
803: voidcmd(cmd)
804: end
Puts the connection into binary (image) mode, issues the given server-side command (such as "STOR myfile"), and sends the contents of the file named file to the server. If the optional block is given, it also passes it the data, in chunks of blocksize characters.
# File lib/net/ftp.rb, line 443
443: def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
444: if rest_offset
445: file.seek(rest_offset, IO::SEEK_SET)
446: end
447: synchronize do
448: voidcmd("TYPE I")
449: conn = transfercmd(cmd, rest_offset)
450: loop do
451: buf = file.read(blocksize)
452: break if buf == nil
453: conn.write(buf)
454: yield(buf) if block
455: end
456: conn.close
457: voidresp
458: end
459: end
Puts the connection into ASCII (text) mode, issues the given server-side command (such as "STOR myfile"), and sends the contents of the file named file to the server, one line at a time. If the optional block is given, it also passes it the lines.
# File lib/net/ftp.rb, line 467
467: def storlines(cmd, file, &block) # :yield: line
468: synchronize do
469: voidcmd("TYPE A")
470: conn = transfercmd(cmd)
471: loop do
472: buf = file.gets
473: break if buf == nil
474: if buf[-2, 2] != CRLF
475: buf = buf.chomp + CRLF
476: end
477: conn.write(buf)
478: yield(buf) if block
479: end
480: conn.close
481: voidresp
482: end
483: end
Sends a command and expect a response beginning with ‘2’.
# File lib/net/ftp.rb, line 271
271: def voidcmd(cmd)
272: synchronize do
273: putline(cmd)
274: voidresp
275: end
276: end
# File lib/net/ftp.rb, line 347
347: def getaddress
348: thishost = Socket.gethostname
349: if not thishost.index(".")
350: thishost = Socket.gethostbyname(thishost)[0]
351: end
352: if ENV.has_key?("LOGNAME")
353: realuser = ENV["LOGNAME"]
354: elsif ENV.has_key?("USER")
355: realuser = ENV["USER"]
356: else
357: realuser = "anonymous"
358: end
359: return realuser + "@" + thishost
360: end
# File lib/net/ftp.rb, line 210
210: def getline
211: line = @sock.readline # if get EOF, raise EOFError
212: line.sub!(/(\r\n|\n|\r)\z/n, "")
213: if @debug_mode
214: print "get: ", sanitize(line), "\n"
215: end
216: return line
217: end
# File lib/net/ftp.rb, line 220
220: def getmultiline
221: line = getline
222: buff = line
223: 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] != ?-
229: end
230: return buff << "\n"
231: end
# File lib/net/ftp.rb, line 234
234: def getresp
235: @last_response = getmultiline
236: @last_response_code = @last_response[0, 3]
237: case @last_response_code
238: when /\A[123]/
239: return @last_response
240: when /\A4/
241: raise FTPTempError, @last_response
242: when /\A5/
243: raise FTPPermError, @last_response
244: else
245: raise FTPProtoError, @last_response
246: end
247: end
# File lib/net/ftp.rb, line 303
303: def makepasv
304: if @sock.peeraddr[0] == "AF_INET"
305: host, port = parse227(sendcmd("PASV"))
306: else
307: host, port = parse229(sendcmd("EPSV"))
308: # host, port = parse228(sendcmd("LPSV"))
309: end
310: return host, port
311: end
# File lib/net/ftp.rb, line 294
294: def makeport
295: sock = TCPServer.open(@sock.addr[3], 0)
296: port = sock.addr[1]
297: host = sock.addr[3]
298: resp = sendport(host, port)
299: return sock
300: end
# File lib/net/ftp.rb, line 154
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
161: end
# File lib/net/ftp.rb, line 821
821: def parse227(resp)
822: if resp[0, 3] != "227"
823: raise FTPReplyError, resp
824: end
825: left = resp.index("(")
826: right = resp.index(")")
827: if left == nil or right == nil
828: raise FTPProtoError, resp
829: end
830: numbers = resp[left + 1 .. right - 1].split(",")
831: if numbers.length != 6
832: raise FTPProtoError, resp
833: end
834: host = numbers[0, 4].join(".")
835: port = (numbers[4].to_i << 8) + numbers[5].to_i
836: return host, port
837: end
# File lib/net/ftp.rb, line 840
840: def parse228(resp)
841: if resp[0, 3] != "228"
842: raise FTPReplyError, resp
843: end
844: left = resp.index("(")
845: right = resp.index(")")
846: if left == nil or right == nil
847: raise FTPProtoError, resp
848: end
849: numbers = resp[left + 1 .. right - 1].split(",")
850: if numbers[0] == "4"
851: if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
852: raise FTPProtoError, resp
853: end
854: host = numbers[2, 4].join(".")
855: port = (numbers[7].to_i << 8) + numbers[8].to_i
856: elsif numbers[0] == "6"
857: if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
858: raise FTPProtoError, resp
859: end
860: v6 = ["", "", "", "", "", "", "", ""]
861: for i in 0 .. 7
862: v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
863: numbers[(i * 2) + 3].to_i)
864: end
865: host = v6[0, 8].join(":")
866: port = (numbers[19].to_i << 8) + numbers[20].to_i
867: end
868: return host, port
869: end
# File lib/net/ftp.rb, line 872
872: def parse229(resp)
873: if resp[0, 3] != "229"
874: raise FTPReplyError, resp
875: end
876: left = resp.index("(")
877: right = resp.index(")")
878: if left == nil or right == nil
879: raise FTPProtoError, resp
880: end
881: numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1])
882: if numbers.length != 4
883: raise FTPProtoError, resp
884: end
885: port = numbers[3].to_i
886: host = (@sock.peeraddr())[3]
887: return host, port
888: end
# File lib/net/ftp.rb, line 891
891: def parse257(resp)
892: if resp[0, 3] != "257"
893: raise FTPReplyError, resp
894: end
895: if resp[3, 2] != ' "'
896: return ""
897: end
898: dirname = ""
899: i = 5
900: n = resp.length
901: while i < n
902: c = resp[i, 1]
903: i = i + 1
904: if c == '"'
905: if i > n or resp[i, 1] != '"'
906: break
907: end
908: i = i + 1
909: end
910: dirname = dirname + c
911: end
912: return dirname
913: end
# File lib/net/ftp.rb, line 201
201: def putline(line)
202: if @debug_mode
203: print "put: ", sanitize(line), "\n"
204: end
205: line = line + CRLF
206: @sock.write(line)
207: end
# File lib/net/ftp.rb, line 192
192: def sanitize(s)
193: if s =~ /^PASS /i
194: return s[0, 5] + "*" * (s.length - 5)
195: else
196: return s
197: end
198: end
# File lib/net/ftp.rb, line 278
278: def sendport(host, port)
279: af = (@sock.peeraddr)[0]
280: if af == "AF_INET"
281: hbytes = host.split(".")
282: pbytes = [port / 256, port % 256]
283: bytes = hbytes + pbytes
284: cmd = "PORT " + bytes.join(",")
285: elsif af == "AF_INET6"
286: cmd = "EPRT |2|" + host + "|" + sprintf("%d", port) + "|"
287: else
288: raise FTPProtoError, host
289: end
290: voidcmd(cmd)
291: end
# File lib/net/ftp.rb, line 314
314: def transfercmd(cmd, rest_offset = nil)
315: if @passive
316: host, port = makepasv
317: conn = open_socket(host, port)
318: if @resume and rest_offset
319: resp = sendcmd("REST " + rest_offset.to_s)
320: if resp[0] != ?3
321: raise FTPReplyError, resp
322: end
323: end
324: resp = sendcmd(cmd)
325: if resp[0] != ?1
326: raise FTPReplyError, resp
327: end
328: else
329: sock = makeport
330: if @resume and rest_offset
331: resp = sendcmd("REST " + rest_offset.to_s)
332: if resp[0] != ?3
333: raise FTPReplyError, resp
334: end
335: end
336: resp = sendcmd(cmd)
337: if resp[0] != ?1
338: raise FTPReplyError, resp
339: end
340: conn = sock.accept
341: sock.close
342: end
343: return conn
344: end