mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
03b60c7068
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@2649 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
675 lines
14 KiB
Ruby
675 lines
14 KiB
Ruby
=begin
|
|
|
|
= net/ftp.rb
|
|
|
|
written by Shugo Maeda <shugo@ruby-lang.org>
|
|
|
|
This library is distributed under the terms of the Ruby license.
|
|
You can freely distribute/modify this library.
|
|
|
|
=end
|
|
|
|
require "socket"
|
|
require "monitor"
|
|
|
|
module Net
|
|
|
|
class FTPError < StandardError; end
|
|
class FTPReplyError < FTPError; end
|
|
class FTPTempError < FTPError; end
|
|
class FTPPermError < FTPError; end
|
|
class FTPProtoError < FTPError; end
|
|
|
|
class FTP
|
|
include MonitorMixin
|
|
|
|
FTP_PORT = 21
|
|
CRLF = "\r\n"
|
|
|
|
DEFAULT_BLOCKSIZE = 4096
|
|
|
|
attr_accessor :binary, :passive, :return_code, :debug_mode, :resume
|
|
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()
|
|
@binary = true
|
|
@passive = false
|
|
@return_code = "\n"
|
|
@debug_mode = false
|
|
@resume = 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 set_socket(sock, get_greeting = true)
|
|
synchronize do
|
|
@sock = sock
|
|
if get_greeting
|
|
voidresp
|
|
end
|
|
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)
|
|
af = (@sock.peeraddr)[0]
|
|
if af == "AF_INET"
|
|
hbytes = host.split(".")
|
|
pbytes = [port / 256, port % 256]
|
|
bytes = hbytes + pbytes
|
|
cmd = "PORT " + bytes.join(",")
|
|
elsif af == "AF_INET6"
|
|
cmd = "EPRT |2|" + host + "|" + sprintf("%d", port) + "|"
|
|
else
|
|
raise FTPProtoError, host
|
|
end
|
|
voidcmd(cmd)
|
|
end
|
|
private :sendport
|
|
|
|
def makeport
|
|
sock = TCPServer.open(@sock.addr[3], 0)
|
|
port = sock.addr[1]
|
|
host = sock.addr[3]
|
|
resp = sendport(host, port)
|
|
return sock
|
|
end
|
|
private :makeport
|
|
|
|
def makepasv
|
|
if @sock.peeraddr[0] == "AF_INET"
|
|
host, port = parse227(sendcmd("PASV"))
|
|
else
|
|
host, port = parse229(sendcmd("EPSV"))
|
|
# host, port = parse228(sendcmd("LPSV"))
|
|
end
|
|
return host, port
|
|
end
|
|
private :makepasv
|
|
|
|
def transfercmd(cmd, rest_offset = nil)
|
|
if @passive
|
|
host, port = makepasv
|
|
conn = open_socket(host, port)
|
|
if @resume and rest_offset
|
|
resp = sendcmd("REST " + rest_offset.to_s)
|
|
if resp[0] != ?3
|
|
raise FTPReplyError, resp
|
|
end
|
|
end
|
|
resp = sendcmd(cmd)
|
|
if resp[0] != ?1
|
|
raise FTPReplyError, resp
|
|
end
|
|
else
|
|
sock = makeport
|
|
if @resume and rest_offset
|
|
resp = sendcmd("REST " + rest_offset.to_s)
|
|
if resp[0] != ?3
|
|
raise FTPReplyError, resp
|
|
end
|
|
end
|
|
resp = sendcmd(cmd)
|
|
if resp[0] != ?1
|
|
raise FTPReplyError, resp
|
|
end
|
|
conn = sock.accept
|
|
sock.close
|
|
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, rest_offset = nil)
|
|
synchronize do
|
|
voidcmd("TYPE I")
|
|
conn = transfercmd(cmd, rest_offset)
|
|
loop do
|
|
data = conn.read(blocksize)
|
|
break if data == nil
|
|
yield(data)
|
|
end
|
|
conn.close
|
|
voidresp
|
|
end
|
|
end
|
|
|
|
def retrlines(cmd)
|
|
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
|
|
yield(line)
|
|
end
|
|
conn.close
|
|
voidresp
|
|
end
|
|
end
|
|
|
|
def storbinary(cmd, file, blocksize, rest_offset = nil, &block)
|
|
synchronize do
|
|
voidcmd("TYPE I")
|
|
conn = transfercmd(cmd, rest_offset)
|
|
loop do
|
|
buf = file.read(blocksize)
|
|
break if buf == nil
|
|
conn.write(buf)
|
|
yield(buf) if block
|
|
end
|
|
conn.close
|
|
voidresp
|
|
end
|
|
end
|
|
|
|
def storlines(cmd, file, &block)
|
|
synchronize do
|
|
voidcmd("TYPE A")
|
|
conn = transfercmd(cmd)
|
|
loop do
|
|
buf = file.gets
|
|
break if buf == nil
|
|
if buf[-2, 2] != CRLF
|
|
buf = buf.chomp + CRLF
|
|
end
|
|
conn.write(buf)
|
|
yield(buf) if block
|
|
end
|
|
conn.close
|
|
voidresp
|
|
end
|
|
end
|
|
|
|
def getbinaryfile(remotefile, localfile = File.basename(remotefile),
|
|
blocksize = DEFAULT_BLOCKSIZE, &block)
|
|
if @resume
|
|
rest_offset = File.size?(localfile)
|
|
f = open(localfile, "a")
|
|
else
|
|
rest_offset = nil
|
|
f = open(localfile, "w")
|
|
end
|
|
begin
|
|
f.binmode
|
|
retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
|
|
f.write(data)
|
|
yield(data) if block
|
|
end
|
|
ensure
|
|
f.close
|
|
end
|
|
end
|
|
|
|
def gettextfile(remotefile, localfile = File.basename(remotefile), &block)
|
|
f = open(localfile, "w")
|
|
begin
|
|
retrlines("RETR " + remotefile) do |line|
|
|
line = line + @return_code
|
|
f.write(line)
|
|
yield(line) if block
|
|
end
|
|
ensure
|
|
f.close
|
|
end
|
|
end
|
|
|
|
def get(localfile, remotefile = File.basename(localfile),
|
|
blocksize = DEFAULT_BLOCKSIZE, &block)
|
|
unless @binary
|
|
gettextfile(localfile, remotefile, &block)
|
|
else
|
|
getbinaryfile(localfile, remotefile, blocksize, &block)
|
|
end
|
|
end
|
|
|
|
def putbinaryfile(localfile, remotefile = File.basename(localfile),
|
|
blocksize = DEFAULT_BLOCKSIZE, &block)
|
|
if @resume
|
|
rest_offset = size(remotefile)
|
|
else
|
|
rest_offset = nil
|
|
end
|
|
f = open(localfile)
|
|
begin
|
|
f.binmode
|
|
storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
|
|
ensure
|
|
f.close
|
|
end
|
|
end
|
|
|
|
def puttextfile(localfile, remotefile = File.basename(localfile), &block)
|
|
f = open(localfile)
|
|
begin
|
|
storlines("STOR " + remotefile, f, &block)
|
|
ensure
|
|
f.close
|
|
end
|
|
end
|
|
|
|
def put(localfile, remotefile = File.basename(localfile),
|
|
blocksize = DEFAULT_BLOCKSIZE, &block)
|
|
unless @binary
|
|
puttextfile(localfile, remotefile, &block)
|
|
else
|
|
putbinaryfile(localfile, remotefile, blocksize, &block)
|
|
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.to_i
|
|
end
|
|
|
|
MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/
|
|
|
|
def mtime(filename, local = false)
|
|
str = mdtm(filename)
|
|
ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
|
|
return local ? Time.local(*ary) : 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 noop
|
|
voidcmd("NOOP")
|
|
end
|
|
|
|
def site(arg)
|
|
cmd = "SITE " + arg
|
|
voidcmd(cmd)
|
|
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 parse228(resp)
|
|
if resp[0, 3] != "228"
|
|
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[0] == "4"
|
|
if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
|
|
raise FTPProtoError, resp
|
|
end
|
|
host = numbers[2, 4].join(".")
|
|
port = (numbers[7].to_i << 8) + numbers[8].to_i
|
|
elsif numbers[0] == "6"
|
|
if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
|
|
raise FTPProtoError, resp
|
|
end
|
|
v6 = ["", "", "", "", "", "", "", ""]
|
|
for i in 0 .. 7
|
|
v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
|
|
numbers[(i * 2) + 3].to_i)
|
|
end
|
|
host = v6[0, 8].join(":")
|
|
port = (numbers[19].to_i << 8) + numbers[20].to_i
|
|
end
|
|
return host, port
|
|
end
|
|
private :parse228
|
|
|
|
def parse229(resp)
|
|
if resp[0, 3] != "229"
|
|
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(resp[left + 1, 1])
|
|
if numbers.length != 4
|
|
raise FTPProtoError, resp
|
|
end
|
|
port = numbers[3].to_i
|
|
host = (@sock.peeraddr())[3]
|
|
return host, port
|
|
end
|
|
private :parse229
|
|
|
|
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
|
|
|
|
end
|