Files
BR_YKC/4G/源代码/lib/ftp.lua
2026-05-21 13:24:05 +08:00

345 lines
11 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--- 模块功能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 可选参数默认为nilssl是否为ssl连接true表示是其余表示否
-- @table cert 可选参数默认为nilcertssl连接需要的证书配置只有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