| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- --- 模块功能: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
|