345 lines
11 KiB
Lua
345 lines
11 KiB
Lua
|
|
--- 模块功能:FTP客户端
|
|||
|
|
-- @module ftp
|
|||
|
|
-- @author Dozingfiretruck
|
|||
|
|
-- @license MIT
|
|||
|
|
-- @copyright OpenLuat.com
|
|||
|
|
-- @release 2020.12.08
|
|||
|
|
require "socket"
|
|||
|
|
require "utils"
|
|||
|
|
module(..., package.seeall)
|
|||
|
|
|
|||
|
|
local ftp_client -- ftp命令连接socket对象
|
|||
|
|
local ftp_data_client -- ftp数据连接socket对象
|
|||
|
|
local data_client_ip, data_client_port -- ftp数据连接地址
|
|||
|
|
|
|||
|
|
--- FTP客户端关闭
|
|||
|
|
function close()
|
|||
|
|
ftp_client:send("QUIT\r\n")
|
|||
|
|
ftp_client:close()
|
|||
|
|
log.info("ftp", "ftp close")
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- FTP客户端命令
|
|||
|
|
-- @string command 命令,例如"PWD","HELP","SYST"
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function command(command, timeout)
|
|||
|
|
if not ftp_client:send(command .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
if r == true and n:sub(1, 3) == "230" then
|
|||
|
|
r, n = ftp_client:recv(timeout)
|
|||
|
|
end
|
|||
|
|
log.info("ftp_command", n)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
else
|
|||
|
|
return n:sub(1, 3), n:sub(4, #n)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- 连接到PASV接口
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
local function pasv_connect(timeout)
|
|||
|
|
---被动模式
|
|||
|
|
if not ftp_client:send("PASV\r\n") then
|
|||
|
|
close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
if r == true and n:sub(1, 3) == "230" then
|
|||
|
|
r, n = ftp_client:recv(timeout)
|
|||
|
|
end
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
local h1, h2, h3, h4, p1, p2 =
|
|||
|
|
n:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")
|
|||
|
|
data_client_ip, data_client_port =
|
|||
|
|
h1 .. '.' .. h2 .. '.' .. h3 .. '.' .. h4,
|
|||
|
|
string.format("%d", (p1 * 256 + p2))
|
|||
|
|
log.info("ftp ip", data_client_ip, "port", data_client_port)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
---创建ftp数据连接
|
|||
|
|
ftp_data_client = socket.tcp()
|
|||
|
|
log.info("ftp ip", data_client_ip, "port", data_client_port)
|
|||
|
|
if not ftp_data_client:connect(data_client_ip, data_client_port) then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '502', 'SOCKET_CONN_ERROR'
|
|||
|
|
end
|
|||
|
|
return '200', 'ftp pasv success'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- FTP客户端登录
|
|||
|
|
-- @string ftp_mode FTP模式"PASV"or"PORT",默认PASV:被动模式,PORT:主动模式(暂时仅支持被动模式)
|
|||
|
|
-- @string host ip地址
|
|||
|
|
-- @string port 端口,默认21
|
|||
|
|
-- @string username 用户名
|
|||
|
|
-- @string password 密码
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @bool ssl 可选参数,默认为nil,ssl,是否为ssl连接,true表示是,其余表示否
|
|||
|
|
-- @table cert 可选参数,默认为nil,cert,ssl连接需要的证书配置,只有ssl参数为true时,才参数才有意义,cert格式如下:
|
|||
|
|
-- {
|
|||
|
|
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
|
|||
|
|
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
|
|||
|
|
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
|
|||
|
|
-- clientPassword = "123456", --客户端证书文件密码[可选]
|
|||
|
|
-- }
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function login(ftp_mode, host, port, username, password, timeout, ssl, cert)
|
|||
|
|
if ftp_mode ~= "PASV" then
|
|||
|
|
log.error("暂不支持主动模式 ")
|
|||
|
|
return '-1', 'ftp ftp_mode error'
|
|||
|
|
end
|
|||
|
|
while not socket.isReady() do sys.wait(1000) end
|
|||
|
|
ftp_client = socket.tcp(ssl, cert)
|
|||
|
|
if not ftp_client:connect(host, port or 21) then
|
|||
|
|
close()
|
|||
|
|
return '502', 'SOCKET_CONN_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
if not ftp_client:send("USER " .. username .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
r = ftp_client:recv(timeout)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
-- 密码
|
|||
|
|
if not ftp_client:send("PASS " .. password .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
r, n = ftp_client:recv(timeout)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
if n:sub(1, 3) == '230' then
|
|||
|
|
log.info("ftp", n)
|
|||
|
|
elseif n:sub(1, 3) == '530' then
|
|||
|
|
log.error("ftp Password error ", n)
|
|||
|
|
close()
|
|||
|
|
return '530', n
|
|||
|
|
end
|
|||
|
|
log.info("ftp login success")
|
|||
|
|
return '200', 'ftp login success'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- FTP客户端文件上传
|
|||
|
|
-- @string remote_file 远程文件名
|
|||
|
|
-- @string local_file 本地文件名
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function upload(remote_file, local_file, timeout)
|
|||
|
|
local com, msg = pasv_connect()
|
|||
|
|
if com ~= "200" then return com, msg end
|
|||
|
|
-- 文件上传
|
|||
|
|
if not ftp_client:send("APPE " .. remote_file .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
log.info("ftp upload", n)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
if n:sub(1, 3) == '553' then
|
|||
|
|
log.error("ftp STOR error ", n)
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '553', n
|
|||
|
|
end
|
|||
|
|
local file = io.open(local_file, "r")
|
|||
|
|
if file then
|
|||
|
|
local file_size = io.fileSize(local_file)
|
|||
|
|
local file_cnt = 0
|
|||
|
|
log.info("ftp", "local_file_size", file_size)
|
|||
|
|
repeat
|
|||
|
|
log.info("ftp", "file_cnt", file_cnt, "file_size - file_cnt",
|
|||
|
|
file_size - file_cnt)
|
|||
|
|
local temp_data = io.readStream(local_file, file_cnt, 11200)
|
|||
|
|
if not ftp_data_client:send(temp_data) then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
file_cnt = file_cnt + 11200
|
|||
|
|
until (file_size - file_cnt < 11200)
|
|||
|
|
local temp_data = io.readStream(local_file, file_cnt,
|
|||
|
|
file_size - file_cnt)
|
|||
|
|
if not ftp_data_client:send(temp_data) then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
file:close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '200', 'ftp success'
|
|||
|
|
else
|
|||
|
|
log.info("文件打开失败")
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'file open error'
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- FTP客户端文件下载
|
|||
|
|
-- @string remote_file 远程文件名
|
|||
|
|
-- @string local_file 本地文件名
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function download(remote_file, local_file, timeout)
|
|||
|
|
local com, msg = pasv_connect()
|
|||
|
|
if com ~= "200" then return com, msg end
|
|||
|
|
-- 文件大小
|
|||
|
|
if not ftp_client:send("SIZE " .. remote_file .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
if n:sub(1, 3) == '213' then
|
|||
|
|
log.info("ftp filename size", n:sub(5, #n))
|
|||
|
|
filename_size = tonumber(n:sub(5, #n))
|
|||
|
|
elseif n:sub(1, 3) == '550' then
|
|||
|
|
log.error("ftp filename error ", n)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
-- 文件下载
|
|||
|
|
if not ftp_client:send("RETR " .. remote_file .. "\r\n") then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '426', 'SOCKET_SEND_ERROR'
|
|||
|
|
end
|
|||
|
|
local r, n = ftp_client:recv(timeout)
|
|||
|
|
log.info("ftp download", n)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
if io.exists(local_file) then
|
|||
|
|
os.remove(local_file)
|
|||
|
|
log.info("删除文件", local_file)
|
|||
|
|
end
|
|||
|
|
local file = io.open(local_file, "ab")
|
|||
|
|
if file then
|
|||
|
|
while true do
|
|||
|
|
local r, file_data = ftp_data_client:recv(timeout)
|
|||
|
|
if r then
|
|||
|
|
if file_data:find("226 Successfully transferred ") == nil and
|
|||
|
|
file_data:find("226 Transfer OK") == nil then
|
|||
|
|
file:write(file_data)
|
|||
|
|
end
|
|||
|
|
else
|
|||
|
|
break
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
file:close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '200', 'ftp success'
|
|||
|
|
else
|
|||
|
|
log.info("文件打开失败")
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'file open error'
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- 设置FTP传输类型 A:ascii I:Binary
|
|||
|
|
-- @string mode A:ascii I:Binary
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function checktype(mode, timeout) return command("TYPE " .. mode, timeout) end
|
|||
|
|
|
|||
|
|
--- 显示当前工作目录
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function pwd(timeout) return command("PWD ", timeout) end
|
|||
|
|
|
|||
|
|
--- 更改工作目录
|
|||
|
|
-- @string path 工作目录
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function cwd(path, timeout) return command("CWD " .. path, timeout) end
|
|||
|
|
|
|||
|
|
--- 回到上级目录
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function cdup(timeout) return command("CDUP", timeout) end
|
|||
|
|
|
|||
|
|
--- 创建目录
|
|||
|
|
-- @string path 目录
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function mkd(path, timeout) return command("MKD " .. path, timeout) end
|
|||
|
|
|
|||
|
|
--- 列出目录列表或文件信息
|
|||
|
|
-- @string file_irectory 目录或文件
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function list(file_irectory, timeout)
|
|||
|
|
local com, msg = pasv_connect()
|
|||
|
|
if com ~= "200" then return com, msg end
|
|||
|
|
local r, n = command("LIST " .. file_irectory, timeout)
|
|||
|
|
if r == "503" then return r, n end
|
|||
|
|
|
|||
|
|
r, n = ftp_client:recv(timeout)
|
|||
|
|
log.info("ftp", r, n)
|
|||
|
|
if not r then
|
|||
|
|
close()
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return '503', 'SOCKET_RECV_TIMOUT'
|
|||
|
|
end
|
|||
|
|
local data = ""
|
|||
|
|
while true do
|
|||
|
|
local r, n = ftp_data_client:recv(timeout)
|
|||
|
|
if r then
|
|||
|
|
data = data .. n
|
|||
|
|
else
|
|||
|
|
break
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
ftp_data_client:close()
|
|||
|
|
return r, data
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- 删除目录
|
|||
|
|
-- @string file_irectory 路径目录
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function deletefolder(file_irectory, timeout)
|
|||
|
|
return command("RMD " .. file_irectory, timeout)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--- 删除文件
|
|||
|
|
-- @string file_irectory 路径文件(相对/绝对)
|
|||
|
|
-- @number[opt=0] timeout 接收超时时间,单位毫秒
|
|||
|
|
-- @return string,string,返回 response_code, response_message
|
|||
|
|
function deletefile(file_irectory, timeout)
|
|||
|
|
return command("DELE " .. file_irectory, timeout)
|
|||
|
|
end
|
|||
|
|
|