ftp.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. --- 模块功能:FTP客户端
  2. -- @module ftp
  3. -- @author Dozingfiretruck
  4. -- @license MIT
  5. -- @copyright OpenLuat.com
  6. -- @release 2020.12.08
  7. require "socket"
  8. require "utils"
  9. module(..., package.seeall)
  10. local ftp_client -- ftp命令连接socket对象
  11. local ftp_data_client -- ftp数据连接socket对象
  12. local data_client_ip, data_client_port -- ftp数据连接地址
  13. --- FTP客户端关闭
  14. function close()
  15. ftp_client:send("QUIT\r\n")
  16. ftp_client:close()
  17. log.info("ftp", "ftp close")
  18. end
  19. --- FTP客户端命令
  20. -- @string command 命令,例如"PWD","HELP","SYST"
  21. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  22. -- @return string,string,返回 response_code, response_message
  23. function command(command, timeout)
  24. if not ftp_client:send(command .. "\r\n") then
  25. close()
  26. return '426', 'SOCKET_SEND_ERROR'
  27. end
  28. local r, n = ftp_client:recv(timeout)
  29. if r == true and n:sub(1, 3) == "230" then
  30. r, n = ftp_client:recv(timeout)
  31. end
  32. log.info("ftp_command", n)
  33. if not r then
  34. close()
  35. return '503', 'SOCKET_RECV_TIMOUT'
  36. else
  37. return n:sub(1, 3), n:sub(4, #n)
  38. end
  39. end
  40. --- 连接到PASV接口
  41. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  42. -- @return string,string,返回 response_code, response_message
  43. local function pasv_connect(timeout)
  44. ---被动模式
  45. if not ftp_client:send("PASV\r\n") then
  46. close()
  47. return '426', 'SOCKET_SEND_ERROR'
  48. end
  49. local r, n = ftp_client:recv(timeout)
  50. if r == true and n:sub(1, 3) == "230" then
  51. r, n = ftp_client:recv(timeout)
  52. end
  53. if not r then
  54. close()
  55. return '503', 'SOCKET_RECV_TIMOUT'
  56. end
  57. local h1, h2, h3, h4, p1, p2 =
  58. n:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")
  59. data_client_ip, data_client_port =
  60. h1 .. '.' .. h2 .. '.' .. h3 .. '.' .. h4,
  61. string.format("%d", (p1 * 256 + p2))
  62. log.info("ftp ip", data_client_ip, "port", data_client_port)
  63. if not r then
  64. close()
  65. return '503', 'SOCKET_RECV_TIMOUT'
  66. end
  67. ---创建ftp数据连接
  68. ftp_data_client = socket.tcp()
  69. log.info("ftp ip", data_client_ip, "port", data_client_port)
  70. if not ftp_data_client:connect(data_client_ip, data_client_port) then
  71. close()
  72. ftp_data_client:close()
  73. return '502', 'SOCKET_CONN_ERROR'
  74. end
  75. return '200', 'ftp pasv success'
  76. end
  77. --- FTP客户端登录
  78. -- @string ftp_mode FTP模式"PASV"or"PORT",默认PASV:被动模式,PORT:主动模式(暂时仅支持被动模式)
  79. -- @string host ip地址
  80. -- @string port 端口,默认21
  81. -- @string username 用户名
  82. -- @string password 密码
  83. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  84. -- @bool ssl 可选参数,默认为nil,ssl,是否为ssl连接,true表示是,其余表示否
  85. -- @table cert 可选参数,默认为nil,cert,ssl连接需要的证书配置,只有ssl参数为true时,才参数才有意义,cert格式如下:
  86. -- {
  87. -- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
  88. -- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
  89. -- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
  90. -- clientPassword = "123456", --客户端证书文件密码[可选]
  91. -- }
  92. -- @return string,string,返回 response_code, response_message
  93. function login(ftp_mode, host, port, username, password, timeout, ssl, cert)
  94. if ftp_mode ~= "PASV" then
  95. log.error("暂不支持主动模式 ")
  96. return '-1', 'ftp ftp_mode error'
  97. end
  98. while not socket.isReady() do sys.wait(1000) end
  99. ftp_client = socket.tcp(ssl, cert)
  100. if not ftp_client:connect(host, port or 21) then
  101. close()
  102. return '502', 'SOCKET_CONN_ERROR'
  103. end
  104. local r, n = ftp_client:recv(timeout)
  105. if not r then
  106. close()
  107. return '503', 'SOCKET_RECV_TIMOUT'
  108. end
  109. if not ftp_client:send("USER " .. username .. "\r\n") then
  110. close()
  111. return '426', 'SOCKET_SEND_ERROR'
  112. end
  113. r = ftp_client:recv(timeout)
  114. if not r then
  115. close()
  116. return '503', 'SOCKET_RECV_TIMOUT'
  117. end
  118. -- 密码
  119. if not ftp_client:send("PASS " .. password .. "\r\n") then
  120. close()
  121. return '426', 'SOCKET_SEND_ERROR'
  122. end
  123. r, n = ftp_client:recv(timeout)
  124. if not r then
  125. close()
  126. return '503', 'SOCKET_RECV_TIMOUT'
  127. end
  128. if n:sub(1, 3) == '230' then
  129. log.info("ftp", n)
  130. elseif n:sub(1, 3) == '530' then
  131. log.error("ftp Password error ", n)
  132. close()
  133. return '530', n
  134. end
  135. log.info("ftp login success")
  136. return '200', 'ftp login success'
  137. end
  138. --- FTP客户端文件上传
  139. -- @string remote_file 远程文件名
  140. -- @string local_file 本地文件名
  141. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  142. -- @return string,string,返回 response_code, response_message
  143. function upload(remote_file, local_file, timeout)
  144. local com, msg = pasv_connect()
  145. if com ~= "200" then return com, msg end
  146. -- 文件上传
  147. if not ftp_client:send("APPE " .. remote_file .. "\r\n") then
  148. close()
  149. ftp_data_client:close()
  150. return '426', 'SOCKET_SEND_ERROR'
  151. end
  152. local r, n = ftp_client:recv(timeout)
  153. log.info("ftp upload", n)
  154. if not r then
  155. close()
  156. ftp_data_client:close()
  157. return '503', 'SOCKET_RECV_TIMOUT'
  158. end
  159. if n:sub(1, 3) == '553' then
  160. log.error("ftp STOR error ", n)
  161. close()
  162. ftp_data_client:close()
  163. return '553', n
  164. end
  165. local file = io.open(local_file, "r")
  166. if file then
  167. local file_size = io.fileSize(local_file)
  168. local file_cnt = 0
  169. log.info("ftp", "local_file_size", file_size)
  170. repeat
  171. log.info("ftp", "file_cnt", file_cnt, "file_size - file_cnt",
  172. file_size - file_cnt)
  173. local temp_data = io.readStream(local_file, file_cnt, 11200)
  174. if not ftp_data_client:send(temp_data) then
  175. close()
  176. ftp_data_client:close()
  177. return '426', 'SOCKET_SEND_ERROR'
  178. end
  179. file_cnt = file_cnt + 11200
  180. until (file_size - file_cnt < 11200)
  181. local temp_data = io.readStream(local_file, file_cnt,
  182. file_size - file_cnt)
  183. if not ftp_data_client:send(temp_data) then
  184. close()
  185. ftp_data_client:close()
  186. return '426', 'SOCKET_SEND_ERROR'
  187. end
  188. file:close()
  189. ftp_data_client:close()
  190. return '200', 'ftp success'
  191. else
  192. log.info("文件打开失败")
  193. ftp_data_client:close()
  194. return '503', 'file open error'
  195. end
  196. end
  197. --- FTP客户端文件下载
  198. -- @string remote_file 远程文件名
  199. -- @string local_file 本地文件名
  200. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  201. -- @return string,string,返回 response_code, response_message
  202. function download(remote_file, local_file, timeout)
  203. local com, msg = pasv_connect()
  204. if com ~= "200" then return com, msg end
  205. -- 文件大小
  206. if not ftp_client:send("SIZE " .. remote_file .. "\r\n") then
  207. close()
  208. ftp_data_client:close()
  209. return '426', 'SOCKET_SEND_ERROR'
  210. end
  211. local r, n = ftp_client:recv(timeout)
  212. if not r then
  213. close()
  214. ftp_data_client:close()
  215. return '503', 'SOCKET_RECV_TIMOUT'
  216. end
  217. if n:sub(1, 3) == '213' then
  218. log.info("ftp filename size", n:sub(5, #n))
  219. filename_size = tonumber(n:sub(5, #n))
  220. elseif n:sub(1, 3) == '550' then
  221. log.error("ftp filename error ", n)
  222. return
  223. end
  224. -- 文件下载
  225. if not ftp_client:send("RETR " .. remote_file .. "\r\n") then
  226. close()
  227. ftp_data_client:close()
  228. return '426', 'SOCKET_SEND_ERROR'
  229. end
  230. local r, n = ftp_client:recv(timeout)
  231. log.info("ftp download", n)
  232. if not r then
  233. close()
  234. ftp_data_client:close()
  235. return '503', 'SOCKET_RECV_TIMOUT'
  236. end
  237. if io.exists(local_file) then
  238. os.remove(local_file)
  239. log.info("删除文件", local_file)
  240. end
  241. local file = io.open(local_file, "ab")
  242. if file then
  243. while true do
  244. local r, file_data = ftp_data_client:recv(timeout)
  245. if r then
  246. if file_data:find("226 Successfully transferred ") == nil and
  247. file_data:find("226 Transfer OK") == nil then
  248. file:write(file_data)
  249. end
  250. else
  251. break
  252. end
  253. end
  254. file:close()
  255. ftp_data_client:close()
  256. return '200', 'ftp success'
  257. else
  258. log.info("文件打开失败")
  259. ftp_data_client:close()
  260. return '503', 'file open error'
  261. end
  262. end
  263. --- 设置FTP传输类型 A:ascii I:Binary
  264. -- @string mode A:ascii I:Binary
  265. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  266. -- @return string,string,返回 response_code, response_message
  267. function checktype(mode, timeout) return command("TYPE " .. mode, timeout) end
  268. --- 显示当前工作目录
  269. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  270. -- @return string,string,返回 response_code, response_message
  271. function pwd(timeout) return command("PWD ", timeout) end
  272. --- 更改工作目录
  273. -- @string path 工作目录
  274. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  275. -- @return string,string,返回 response_code, response_message
  276. function cwd(path, timeout) return command("CWD " .. path, timeout) end
  277. --- 回到上级目录
  278. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  279. -- @return string,string,返回 response_code, response_message
  280. function cdup(timeout) return command("CDUP", timeout) end
  281. --- 创建目录
  282. -- @string path 目录
  283. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  284. -- @return string,string,返回 response_code, response_message
  285. function mkd(path, timeout) return command("MKD " .. path, timeout) end
  286. --- 列出目录列表或文件信息
  287. -- @string file_irectory 目录或文件
  288. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  289. -- @return string,string,返回 response_code, response_message
  290. function list(file_irectory, timeout)
  291. local com, msg = pasv_connect()
  292. if com ~= "200" then return com, msg end
  293. local r, n = command("LIST " .. file_irectory, timeout)
  294. if r == "503" then return r, n end
  295. r, n = ftp_client:recv(timeout)
  296. log.info("ftp", r, n)
  297. if not r then
  298. close()
  299. ftp_data_client:close()
  300. return '503', 'SOCKET_RECV_TIMOUT'
  301. end
  302. local data = ""
  303. while true do
  304. local r, n = ftp_data_client:recv(timeout)
  305. if r then
  306. data = data .. n
  307. else
  308. break
  309. end
  310. end
  311. ftp_data_client:close()
  312. return r, data
  313. end
  314. --- 删除目录
  315. -- @string file_irectory 路径目录
  316. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  317. -- @return string,string,返回 response_code, response_message
  318. function deletefolder(file_irectory, timeout)
  319. return command("RMD " .. file_irectory, timeout)
  320. end
  321. --- 删除文件
  322. -- @string file_irectory 路径文件(相对/绝对)
  323. -- @number[opt=0] timeout 接收超时时间,单位毫秒
  324. -- @return string,string,返回 response_code, response_message
  325. function deletefile(file_irectory, timeout)
  326. return command("DELE " .. file_irectory, timeout)
  327. end