## ftplib.rb # Author: Shugo Maeda # Version: $Revision: 1.7 $ ## Code: require "socket" require "monitor" class FTPError < Exception; end class FTPReplyError < FTPError; end class FTPTempError < FTPError; end class FTPPermError < FTPError; end class FTPProtoError < FTPError; end class FTP RCS_ID = %q$Id: ftplib.rb,v 1.7 1998/04/13 12:34:24 shugo Exp shugo $ include MonitorMixin FTP_PORT = 21 CRLF = "\r\n" attr_accessor :passive, :return_code, :debug_mode attr_reader :welcome, :lastresp def FTP.open(host, user = nil, passwd = nil, acct = nil) new(host, user, passwd, acct) end def initialize(host = nil, user = nil, passwd = nil, acct = nil) super @passive = false @return_code = "\n" @debug_mode = false if host connect(host) if user login(user, passwd, acct) end end end def open_socket(host, port) if defined? SOCKSsocket and ENV["SOCKS_SERVER"] @passive = true return SOCKSsocket.open(host, port) else return TCPsocket.open(host, port) end end private :open_socket def connect(host, port = FTP_PORT) if @debug_mode print "connect: ", host, ", ", port, "\n" end synchronize do @sock = open_socket(host, port) voidresp end end def sanitize(s) if s =~ /^PASS /i return s[0, 5] + "*" * (s.length - 5) else return s end end private :sanitize def putline(line) if @debug_mode print "put: ", sanitize(line), "\n" end line = line + CRLF @sock.write(line) end private :putline def getline line = @sock.readline # if get EOF, raise EOFError if line[-2, 2] == CRLF line = line[0 .. -3] elsif line[-1] == ?\r or line[-1] == ?\n line = line[0 .. -2] end if @debug_mode print "get: ", sanitize(line), "\n" end return line end private :getline def getmultiline line = getline buff = line if line[3] == ?- code = line[0, 3] begin line = getline buff << "\n" << line end until line[0, 3] == code and line[3] != ?- end return buff << "\n" end private :getmultiline def getresp resp = getmultiline @lastresp = resp[0, 3] c = resp[0] case c when ?1, ?2, ?3 return resp when ?4 raise FTPTempError, resp when ?5 raise FTPPermError, resp else raise FTPProtoError, resp end end private :getresp def voidresp resp = getresp if resp[0] != ?2 raise FTPReplyError, resp end end private :voidresp def sendcmd(cmd) synchronize do putline(cmd) return getresp end end def voidcmd(cmd) synchronize do putline(cmd) voidresp end end def sendport(host, port) hbytes = host.split(".") pbytes = [port / 256, port % 256] bytes = hbytes + pbytes cmd = "PORT " + bytes.join(",") voidcmd(cmd) end private :sendport def makeport sock = TCPserver.open(0) port = sock.addr[1] host = TCPsocket.getaddress(@sock.addr[2]) resp = sendport(host, port) return sock end private :makeport def transfercmd(cmd) if @passive host, port = parse227(sendcmd("PASV")) conn = open_socket(host, port) resp = sendcmd(cmd) if resp[0] != ?1 raise FTPReplyError, resp end else sock = makeport resp = sendcmd(cmd) if resp[0] != ?1 raise FTPReplyError, resp end conn = sock.accept end return conn end private :transfercmd def getaddress thishost = Socket.gethostname if not thishost.index(".") thishost = Socket.gethostbyname(thishost)[0] end if ENV.has_key?("LOGNAME") realuser = ENV["LOGNAME"] elsif ENV.has_key?("USER") realuser = ENV["USER"] else realuser = "anonymous" end return realuser + "@" + thishost end private :getaddress def login(user = "anonymous", passwd = nil, acct = nil) if user == "anonymous" and passwd == nil passwd = getaddress end resp = "" synchronize do resp = sendcmd('USER ' + user) if resp[0] == ?3 resp = sendcmd('PASS ' + passwd) end if resp[0] == ?3 resp = sendcmd('ACCT ' + acct) end end if resp[0] != ?2 raise FTPReplyError, resp end @welcome = resp end def retrbinary(cmd, blocksize, callback = Proc.new) synchronize do voidcmd("TYPE I") conn = transfercmd(cmd) loop do data = conn.read(blocksize) break if data == nil callback.call(data) end conn.close voidresp end end def retrlines(cmd, callback = nil) if iterator? callback = Proc.new elsif not callback.is_a?(Proc) callback = Proc.new {|line| print line, "\n"} end synchronize do voidcmd("TYPE A") conn = transfercmd(cmd) loop do line = conn.gets break if line == nil if line[-2, 2] == CRLF line = line[0 .. -3] elsif line[-1] == ?\n line = line[0 .. -2] end callback.call(line) end conn.close voidresp end end def storbinary(cmd, file, blocksize, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) synchronize do voidcmd("TYPE I") conn = transfercmd(cmd) loop do buf = file.read(blocksize) break if buf == nil conn.write(buf) callback.call(buf) if use_callback end conn.close voidresp end end def storlines(cmd, file, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) synchronize do voidcmd("TYPE A") conn = transfercmd(cmd) loop do buf = file.gets break if buf == nil if buf[-2, 2] != CRLF if buf[-1] == ?\r or buf[-1] == ?\n buf = buf[0 .. -2] end buf = buf + CRLF end conn.write(buf) callback.call(buf) if use_callback end conn.close voidresp end end def getbinaryfile(remotefile, localfile, blocksize, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) f = open(localfile, "w") begin f.binmode retrbinary("RETR " + remotefile, blocksize) do |data| f.write(data) callback.call(data) if use_callback end ensure f.close end end def gettextfile(remotefile, localfile, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) f = open(localfile, "w") begin retrlines("RETR " + remotefile) do |line| line = line + @return_code f.write(line) callback.call(line) if use_callback end ensure f.close end end def putbinaryfile(localfile, remotefile, blocksize, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) f = open(localfile) begin f.binmode storbinary("STOR " + remotefile, f, blocksize) do |data| callback.call(data) if use_callback end ensure f.close end end def puttextfile(localfile, remotefile, callback = nil) if iterator? callback = Proc.new end use_callback = callback.is_a?(Proc) f = open(localfile) begin storlines("STOR " + remotefile, f) do |line| callback.call(line) if use_callback end ensure f.close end end def acct(account) cmd = "ACCT " + account voidcmd(cmd) end def nlst(dir = nil) cmd = "NLST" if dir cmd = cmd + " " + dir end files = [] retrlines(cmd) do |line| files.push(line) end return files end def list(*args, &block) cmd = "LIST" args.each do |arg| cmd = cmd + " " + arg end if block retrlines(cmd, &block) else lines = [] retrlines(cmd) do |line| lines << line end return lines end end alias ls list alias dir list def rename(fromname, toname) resp = sendcmd("RNFR " + fromname) if resp[0] != ?3 raise FTPReplyError, resp end voidcmd("RNTO " + toname) end def delete(filename) resp = sendcmd("DELE " + filename) if resp[0, 3] == "250" return elsif resp[0] == ?5 raise FTPPermError, resp else raise FTPReplyError, resp end end def chdir(dirname) if dirname == ".." begin voidcmd("CDUP") return rescue FTPPermError if $![0, 3] != "500" raise FTPPermError, $! end end end cmd = "CWD " + dirname voidcmd(cmd) end def size(filename) voidcmd("TYPE I") resp = sendcmd("SIZE " + filename) if resp[0, 3] != "213" raise FTPReplyError, resp end return resp[3..-1].strip end MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ def mtime(filename) str = mdtm(filename) ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i} return Time.gm(*ary) end def mkdir(dirname) resp = sendcmd("MKD " + dirname) return parse257(resp) end def rmdir(dirname) voidcmd("RMD " + dirname) end def pwd resp = sendcmd("PWD") return parse257(resp) end alias getdir pwd def system resp = sendcmd("SYST") if resp[0, 3] != "215" raise FTPReplyError, resp end return resp[4 .. -1] end def abort line = "ABOR" + CRLF print "put: ABOR\n" if @debug_mode @sock.send(line, Socket::MSG_OOB) resp = getmultiline unless ["426", "226", "225"].include?(resp[0, 3]) raise FTPProtoError, resp end return resp end def status line = "STAT" + CRLF print "put: STAT\n" if @debug_mode @sock.send(line, Socket::MSG_OOB) return getresp end def mdtm(filename) resp = sendcmd("MDTM " + filename) if resp[0, 3] == "213" return resp[3 .. -1].strip end end def help(arg = nil) cmd = "HELP" if arg cmd = cmd + " " + arg end sendcmd(cmd) end def quit voidcmd("QUIT") end def close @sock.close if @sock and not @sock.closed? end def closed? @sock == nil or @sock.closed? end def parse227(resp) if resp[0, 3] != "227" raise FTPReplyError, resp end left = resp.index("(") right = resp.index(")") if left == nil or right == nil raise FTPProtoError, resp end numbers = resp[left + 1 .. right - 1].split(",") if numbers.length != 6 raise FTPProtoError, resp end host = numbers[0, 4].join(".") port = (numbers[4].to_i << 8) + numbers[5].to_i return host, port end private :parse227 def parse257(resp) if resp[0, 3] != "257" raise FTPReplyError, resp end if resp[3, 2] != ' "' return "" end dirname = "" i = 5 n = resp.length while i < n c = resp[i, 1] i = i + 1 if c == '"' if i > n or resp[i, 1] != '"' break end i = i + 1 end dirname = dirname + c end return dirname end private :parse257 end ## ftplib.rb ends here