You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
9.0 KiB

  1. -----------------------------------------------------------------------------
  2. -- FTP support for the Lua language
  3. -- LuaSocket toolkit.
  4. -- Author: Diego Nehab
  5. -- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $
  6. -----------------------------------------------------------------------------
  7. -----------------------------------------------------------------------------
  8. -- Declare module and import dependencies
  9. -----------------------------------------------------------------------------
  10. local base = _G
  11. local table = require("table")
  12. local string = require("string")
  13. local math = require("math")
  14. local socket = require("preload.tools.socket")
  15. local url = require("preload.tools.url")
  16. local tp = require("preload.tools.tp")
  17. local ltn12 = require("preload.tools.ltn12")
  18. module("socket.ftp")
  19. -----------------------------------------------------------------------------
  20. -- Program constants
  21. -----------------------------------------------------------------------------
  22. -- timeout in seconds before the program gives up on a connection
  23. TIMEOUT = 60
  24. -- default port for ftp service
  25. PORT = 21
  26. -- this is the default anonymous password. used when no password is
  27. -- provided in url. should be changed to your e-mail.
  28. USER = "ftp"
  29. PASSWORD = "anonymous@anonymous.org"
  30. -----------------------------------------------------------------------------
  31. -- Low level FTP API
  32. -----------------------------------------------------------------------------
  33. local metat = { __index = {} }
  34. function open(server, port, create)
  35. local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create))
  36. local f = base.setmetatable({ tp = tp }, metat)
  37. -- make sure everything gets closed in an exception
  38. f.try = socket.newtry(function() f:close() end)
  39. return f
  40. end
  41. function metat.__index:portconnect()
  42. self.try(self.server:settimeout(TIMEOUT))
  43. self.data = self.try(self.server:accept())
  44. self.try(self.data:settimeout(TIMEOUT))
  45. end
  46. function metat.__index:pasvconnect()
  47. self.data = self.try(socket.tcp())
  48. self.try(self.data:settimeout(TIMEOUT))
  49. self.try(self.data:connect(self.pasvt.ip, self.pasvt.port))
  50. end
  51. function metat.__index:login(user, password)
  52. self.try(self.tp:command("user", user or USER))
  53. local code, reply = self.try(self.tp:check{"2..", 331})
  54. if code == 331 then
  55. self.try(self.tp:command("pass", password or PASSWORD))
  56. self.try(self.tp:check("2.."))
  57. end
  58. return 1
  59. end
  60. function metat.__index:pasv()
  61. self.try(self.tp:command("pasv"))
  62. local code, reply = self.try(self.tp:check("2.."))
  63. local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)"
  64. local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern))
  65. self.try(a and b and c and d and p1 and p2, reply)
  66. self.pasvt = {
  67. ip = string.format("%d.%d.%d.%d", a, b, c, d),
  68. port = p1*256 + p2
  69. }
  70. if self.server then
  71. self.server:close()
  72. self.server = nil
  73. end
  74. return self.pasvt.ip, self.pasvt.port
  75. end
  76. function metat.__index:port(ip, port)
  77. self.pasvt = nil
  78. if not ip then
  79. ip, port = self.try(self.tp:getcontrol():getsockname())
  80. self.server = self.try(socket.bind(ip, 0))
  81. ip, port = self.try(self.server:getsockname())
  82. self.try(self.server:settimeout(TIMEOUT))
  83. end
  84. local pl = math.mod(port, 256)
  85. local ph = (port - pl)/256
  86. local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
  87. self.try(self.tp:command("port", arg))
  88. self.try(self.tp:check("2.."))
  89. return 1
  90. end
  91. function metat.__index:send(sendt)
  92. self.try(self.pasvt or self.server, "need port or pasv first")
  93. -- if there is a pasvt table, we already sent a PASV command
  94. -- we just get the data connection into self.data
  95. if self.pasvt then self:pasvconnect() end
  96. -- get the transfer argument and command
  97. local argument = sendt.argument or
  98. url.unescape(string.gsub(sendt.path or "", "^[/\\]", ""))
  99. if argument == "" then argument = nil end
  100. local command = sendt.command or "stor"
  101. -- send the transfer command and check the reply
  102. self.try(self.tp:command(command, argument))
  103. local code, reply = self.try(self.tp:check{"2..", "1.."})
  104. -- if there is not a a pasvt table, then there is a server
  105. -- and we already sent a PORT command
  106. if not self.pasvt then self:portconnect() end
  107. -- get the sink, source and step for the transfer
  108. local step = sendt.step or ltn12.pump.step
  109. local readt = {self.tp.c}
  110. local checkstep = function(src, snk)
  111. -- check status in control connection while downloading
  112. local readyt = socket.select(readt, nil, 0)
  113. if readyt[tp] then code = self.try(self.tp:check("2..")) end
  114. return step(src, snk)
  115. end
  116. local sink = socket.sink("close-when-done", self.data)
  117. -- transfer all data and check error
  118. self.try(ltn12.pump.all(sendt.source, sink, checkstep))
  119. if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  120. -- done with data connection
  121. self.data:close()
  122. -- find out how many bytes were sent
  123. local sent = socket.skip(1, self.data:getstats())
  124. self.data = nil
  125. return sent
  126. end
  127. function metat.__index:receive(recvt)
  128. self.try(self.pasvt or self.server, "need port or pasv first")
  129. if self.pasvt then self:pasvconnect() end
  130. local argument = recvt.argument or
  131. url.unescape(string.gsub(recvt.path or "", "^[/\\]", ""))
  132. if argument == "" then argument = nil end
  133. local command = recvt.command or "retr"
  134. self.try(self.tp:command(command, argument))
  135. local code = self.try(self.tp:check{"1..", "2.."})
  136. if not self.pasvt then self:portconnect() end
  137. local source = socket.source("until-closed", self.data)
  138. local step = recvt.step or ltn12.pump.step
  139. self.try(ltn12.pump.all(source, recvt.sink, step))
  140. if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  141. self.data:close()
  142. self.data = nil
  143. return 1
  144. end
  145. function metat.__index:cwd(dir)
  146. self.try(self.tp:command("cwd", dir))
  147. self.try(self.tp:check(250))
  148. return 1
  149. end
  150. function metat.__index:type(type)
  151. self.try(self.tp:command("type", type))
  152. self.try(self.tp:check(200))
  153. return 1
  154. end
  155. function metat.__index:greet()
  156. local code = self.try(self.tp:check{"1..", "2.."})
  157. if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  158. return 1
  159. end
  160. function metat.__index:quit()
  161. self.try(self.tp:command("quit"))
  162. self.try(self.tp:check("2.."))
  163. return 1
  164. end
  165. function metat.__index:close()
  166. if self.data then self.data:close() end
  167. if self.server then self.server:close() end
  168. return self.tp:close()
  169. end
  170. -----------------------------------------------------------------------------
  171. -- High level FTP API
  172. -----------------------------------------------------------------------------
  173. local function override(t)
  174. if t.url then
  175. local u = url.parse(t.url)
  176. for i,v in base.pairs(t) do
  177. u[i] = v
  178. end
  179. return u
  180. else return t end
  181. end
  182. local function tput(putt)
  183. putt = override(putt)
  184. socket.try(putt.host, "missing hostname")
  185. local f = open(putt.host, putt.port, putt.create)
  186. f:greet()
  187. f:login(putt.user, putt.password)
  188. if putt.type then f:type(putt.type) end
  189. f:pasv()
  190. local sent = f:send(putt)
  191. f:quit()
  192. f:close()
  193. return sent
  194. end
  195. local default = {
  196. path = "/",
  197. scheme = "ftp"
  198. }
  199. local function parse(u)
  200. local t = socket.try(url.parse(u, default))
  201. socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'")
  202. socket.try(t.host, "missing hostname")
  203. local pat = "^type=(.)$"
  204. if t.params then
  205. t.type = socket.skip(2, string.find(t.params, pat))
  206. socket.try(t.type == "a" or t.type == "i",
  207. "invalid type '" .. t.type .. "'")
  208. end
  209. return t
  210. end
  211. local function sput(u, body)
  212. local putt = parse(u)
  213. putt.source = ltn12.source.string(body)
  214. return tput(putt)
  215. end
  216. put = socket.protect(function(putt, body)
  217. if base.type(putt) == "string" then return sput(putt, body)
  218. else return tput(putt) end
  219. end)
  220. local function tget(gett)
  221. gett = override(gett)
  222. socket.try(gett.host, "missing hostname")
  223. local f = open(gett.host, gett.port, gett.create)
  224. f:greet()
  225. f:login(gett.user, gett.password)
  226. if gett.type then f:type(gett.type) end
  227. f:pasv()
  228. f:receive(gett)
  229. f:quit()
  230. return f:close()
  231. end
  232. local function sget(u)
  233. local gett = parse(u)
  234. local t = {}
  235. gett.sink = ltn12.sink.table(t)
  236. tget(gett)
  237. return table.concat(t)
  238. end
  239. command = socket.protect(function(cmdt)
  240. cmdt = override(cmdt)
  241. socket.try(cmdt.host, "missing hostname")
  242. socket.try(cmdt.command, "missing command")
  243. local f = open(cmdt.host, cmdt.port, cmdt.create)
  244. f:greet()
  245. f:login(cmdt.user, cmdt.password)
  246. f.try(f.tp:command(cmdt.command, cmdt.argument))
  247. if cmdt.check then f.try(f.tp:check(cmdt.check)) end
  248. f:quit()
  249. return f:close()
  250. end)
  251. get = socket.protect(function(gett)
  252. if base.type(gett) == "string" then return sget(gett)
  253. else return tget(gett) end
  254. end)