| 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 741
741: def abort
742: line = "ABOR" + CRLF
743: print "put: ABOR\n" if @debug_mode
744: @sock.send(line, Socket::MSG_OOB)
745: resp = getmultiline
746: unless ["426", "226", "225"].include?(resp[0, 3])
747: raise FTPProtoError, resp
748: end
749: return resp
750: end
Sends the ACCT command. TODO: more info.
# File lib/net/ftp.rb, line 594
594: def acct(account)
595: cmd = "ACCT " + account
596: voidcmd(cmd)
597: end
Changes the (remote) directory.
# File lib/net/ftp.rb, line 664
664: def chdir(dirname)
665: if dirname == ".."
666: begin
667: voidcmd("CDUP")
668: return
669: rescue FTPPermError => e
670: if e.message[0, 3] != "500"
671: raise e
672: end
673: end
674: end
675: cmd = "CWD " + dirname
676: voidcmd(cmd)
677: end
Returns true iff the connection is closed.
# File lib/net/ftp.rb, line 816
816: def closed?
817: @sock == nil or @sock.closed?
818: 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 650
650: def delete(filename)
651: resp = sendcmd("DELE " + filename)
652: if resp[0, 3] == "250"
653: return
654: elsif resp[0] == ?5
655: raise FTPPermError, resp
656: else
657: raise FTPReplyError, resp
658: end
659: end
Retrieves remotefile in whatever mode the session is set (text or binary). See gettextfile and getbinaryfile.
# File lib/net/ftp.rb, line 530
530: def get(remotefile, localfile = File.basename(remotefile),
531: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
532: unless @binary
533: gettextfile(remotefile, localfile, &block)
534: else
535: getbinaryfile(remotefile, localfile, blocksize, &block)
536: end
537: 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 489
489: def getbinaryfile(remotefile, localfile = File.basename(remotefile),
490: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
491: if @resume
492: rest_offset = File.size?(localfile)
493: f = open(localfile, "a")
494: else
495: rest_offset = nil
496: f = open(localfile, "w")
497: end
498: begin
499: f.binmode
500: retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
501: f.write(data)
502: yield(data) if block
503: end
504: ensure
505: f.close
506: end
507: 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 514
514: def gettextfile(remotefile, localfile = File.basename(remotefile), &block) # :yield: line
515: f = open(localfile, "w")
516: begin
517: retrlines("RETR " + remotefile) do |line|
518: f.puts(line)
519: yield(line) if block
520: end
521: ensure
522: f.close
523: end
524: end
Issues the HELP command.
# File lib/net/ftp.rb, line 775
775: def help(arg = nil)
776: cmd = "HELP"
777: if arg
778: cmd = cmd + " " + arg
779: end
780: sendcmd(cmd)
781: 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 618
618: def list(*args, &block) # :yield: line
619: cmd = "LIST"
620: args.each do |arg|
621: cmd = cmd + " " + arg
622: end
623: if block
624: retrlines(cmd, &block)
625: else
626: lines = []
627: retrlines(cmd) do |line|
628: lines << line
629: end
630: return lines
631: end
632: 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 368
368: def login(user = "anonymous", passwd = nil, acct = nil)
369: if user == "anonymous" and passwd == nil
370: passwd = getaddress
371: end
372:
373: resp = ""
374: synchronize do
375: resp = sendcmd('USER ' + user)
376: if resp[0] == ?3
377: raise FTPReplyError, resp if passwd.nil?
378: resp = sendcmd('PASS ' + passwd)
379: end
380: if resp[0] == ?3
381: raise FTPReplyError, resp if acct.nil?
382: resp = sendcmd('ACCT ' + acct)
383: end
384: end
385: if resp[0] != ?2
386: raise FTPReplyError, resp
387: end
388: @welcome = resp
389: end
Issues the MDTM command. TODO: more info.
# File lib/net/ftp.rb, line 765
765: def mdtm(filename)
766: resp = sendcmd("MDTM " + filename)
767: if resp[0, 3] == "213"
768: return resp[3 .. -1].strip
769: end
770: end
Creates a remote directory.
# File lib/net/ftp.rb, line 706
706: def mkdir(dirname)
707: resp = sendcmd("MKD " + dirname)
708: return parse257(resp)
709: 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 697
697: def mtime(filename, local = false)
698: str = mdtm(filename)
699: ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
700: return local ? Time.local(*ary) : Time.gm(*ary)
701: end
Returns an array of filenames in the remote directory.
# File lib/net/ftp.rb, line 602
602: def nlst(dir = nil)
603: cmd = "NLST"
604: if dir
605: cmd = cmd + " " + dir
606: end
607: files = []
608: retrlines(cmd) do |line|
609: files.push(line)
610: end
611: return files
612: 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 582
582: def put(localfile, remotefile = File.basename(localfile),
583: blocksize = DEFAULT_BLOCKSIZE, &block)
584: unless @binary
585: puttextfile(localfile, remotefile, &block)
586: else
587: putbinaryfile(localfile, remotefile, blocksize, &block)
588: end
589: 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 544
544: def putbinaryfile(localfile, remotefile = File.basename(localfile),
545: blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
546: if @resume
547: begin
548: rest_offset = size(remotefile)
549: rescue Net::FTPPermError
550: rest_offset = nil
551: end
552: else
553: rest_offset = nil
554: end
555: f = open(localfile)
556: begin
557: f.binmode
558: storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
559: ensure
560: f.close
561: end
562: 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 569
569: def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line
570: f = open(localfile)
571: begin
572: storlines("STOR " + remotefile, f, &block)
573: ensure
574: f.close
575: end
576: end
Returns the current remote directory.
# File lib/net/ftp.rb, line 721
721: def pwd
722: resp = sendcmd("PWD")
723: return parse257(resp)
724: end
Renames a file on the server.
# File lib/net/ftp.rb, line 639
639: def rename(fromname, toname)
640: resp = sendcmd("RNFR " + fromname)
641: if resp[0] != ?3
642: raise FTPReplyError, resp
643: end
644: voidcmd("RNTO " + toname)
645: 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 397
397: def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
398: synchronize do
399: voidcmd("TYPE I")
400: conn = transfercmd(cmd, rest_offset)
401: loop do
402: data = conn.read(blocksize)
403: break if data == nil
404: yield(data)
405: end
406: conn.close
407: voidresp
408: end
409: 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 417
417: def retrlines(cmd) # :yield: line
418: synchronize do
419: voidcmd("TYPE A")
420: conn = transfercmd(cmd)
421: loop do
422: line = conn.gets
423: break if line == nil
424: if line[-2, 2] == CRLF
425: line = line[0 .. -3]
426: elsif line[-1] == ?\n
427: line = line[0 .. -2]
428: end
429: yield(line)
430: end
431: conn.close
432: voidresp
433: end
434: 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 714
714: def rmdir(dirname)
715: voidcmd("RMD " + dirname)
716: 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 800
800: def site(arg)
801: cmd = "SITE " + arg
802: voidcmd(cmd)
803: 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 442
442: def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
443: if rest_offset
444: file.seek(rest_offset, IO::SEEK_SET)
445: end
446: synchronize do
447: voidcmd("TYPE I")
448: conn = transfercmd(cmd, rest_offset)
449: loop do
450: buf = file.read(blocksize)
451: break if buf == nil
452: conn.write(buf)
453: yield(buf) if block
454: end
455: conn.close
456: voidresp
457: end
458: 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 466
466: def storlines(cmd, file, &block) # :yield: line
467: synchronize do
468: voidcmd("TYPE A")
469: conn = transfercmd(cmd)
470: loop do
471: buf = file.gets
472: break if buf == nil
473: if buf[-2, 2] != CRLF
474: buf = buf.chomp + CRLF
475: end
476: conn.write(buf)
477: yield(buf) if block
478: end
479: conn.close
480: voidresp
481: end
482: 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 344
344: def getaddress
345: thishost = Socket.gethostname
346: if not thishost.index(".")
347: thishost = Socket.gethostbyname(thishost)[0]
348: end
349: if ENV.has_key?("LOGNAME")
350: realuser = ENV["LOGNAME"]
351: elsif ENV.has_key?("USER")
352: realuser = ENV["USER"]
353: else
354: realuser = "anonymous"
355: end
356: return realuser + "@" + thishost
357: 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 300
300: def makepasv
301: if @sock.peeraddr[0] == "AF_INET"
302: host, port = parse227(sendcmd("PASV"))
303: else
304: host, port = parse229(sendcmd("EPSV"))
305: # host, port = parse228(sendcmd("LPSV"))
306: end
307: return host, port
308: end
# File lib/net/ftp.rb, line 291
291: def makeport
292: sock = TCPServer.open(@sock.addr[3], 0)
293: port = sock.addr[1]
294: host = sock.addr[3]
295: resp = sendport(host, port)
296: return sock
297: 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 820
820: def parse227(resp)
821: if resp[0, 3] != "227"
822: raise FTPReplyError, resp
823: end
824: left = resp.index("(")
825: right = resp.index(")")
826: if left == nil or right == nil
827: raise FTPProtoError, resp
828: end
829: numbers = resp[left + 1 .. right - 1].split(",")
830: if numbers.length != 6
831: raise FTPProtoError, resp
832: end
833: host = numbers[0, 4].join(".")
834: port = (numbers[4].to_i << 8) + numbers[5].to_i
835: return host, port
836: end
# File lib/net/ftp.rb, line 839
839: def parse228(resp)
840: if resp[0, 3] != "228"
841: raise FTPReplyError, resp
842: end
843: left = resp.index("(")
844: right = resp.index(")")
845: if left == nil or right == nil
846: raise FTPProtoError, resp
847: end
848: numbers = resp[left + 1 .. right - 1].split(",")
849: if numbers[0] == "4"
850: if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
851: raise FTPProtoError, resp
852: end
853: host = numbers[2, 4].join(".")
854: port = (numbers[7].to_i << 8) + numbers[8].to_i
855: elsif numbers[0] == "6"
856: if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
857: raise FTPProtoError, resp
858: end
859: v6 = ["", "", "", "", "", "", "", ""]
860: for i in 0 .. 7
861: v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
862: numbers[(i * 2) + 3].to_i)
863: end
864: host = v6[0, 8].join(":")
865: port = (numbers[19].to_i << 8) + numbers[20].to_i
866: end
867: return host, port
868: end
# File lib/net/ftp.rb, line 871
871: def parse229(resp)
872: if resp[0, 3] != "229"
873: raise FTPReplyError, resp
874: end
875: left = resp.index("(")
876: right = resp.index(")")
877: if left == nil or right == nil
878: raise FTPProtoError, resp
879: end
880: numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1])
881: if numbers.length != 4
882: raise FTPProtoError, resp
883: end
884: port = numbers[3].to_i
885: host = (@sock.peeraddr())[3]
886: return host, port
887: end
# File lib/net/ftp.rb, line 890
890: def parse257(resp)
891: if resp[0, 3] != "257"
892: raise FTPReplyError, resp
893: end
894: if resp[3, 2] != ' "'
895: return ""
896: end
897: dirname = ""
898: i = 5
899: n = resp.length
900: while i < n
901: c = resp[i, 1]
902: i = i + 1
903: if c == '"'
904: if i > n or resp[i, 1] != '"'
905: break
906: end
907: i = i + 1
908: end
909: dirname = dirname + c
910: end
911: return dirname
912: 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: cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",")
282: elsif af == "AF_INET6"
283: cmd = sprintf("EPRT |2|%s|%d|", host, port)
284: else
285: raise FTPProtoError, host
286: end
287: voidcmd(cmd)
288: end
# File lib/net/ftp.rb, line 311
311: def transfercmd(cmd, rest_offset = nil)
312: 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)
322: if resp[0] != ?1
323: raise FTPReplyError, resp
324: end
325: else
326: sock = makeport
327: if @resume and rest_offset
328: resp = sendcmd("REST " + rest_offset.to_s)
329: if resp[0] != ?3
330: raise FTPReplyError, resp
331: end
332: end
333: resp = sendcmd(cmd)
334: if resp[0] != ?1
335: raise FTPReplyError, resp
336: end
337: conn = sock.accept
338: sock.close
339: end
340: return conn
341: end