diff --git a/src/ftp.lua b/src/ftp.lua index e0c3cae..11798ad 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -51,7 +51,7 @@ end function metat.__index:pasvconnect() self.data = self.try(socket.tcp()) self.try(self.data:settimeout(_M.TIMEOUT)) - self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) end function metat.__index:login(user, password) @@ -71,32 +71,65 @@ function metat.__index:pasv() local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) self.try(a and b and c and d and p1 and p2, reply) self.pasvt = { - ip = string.format("%d.%d.%d.%d", a, b, c, d), + address = string.format("%d.%d.%d.%d", a, b, c, d), port = p1*256 + p2 } if self.server then self.server:close() self.server = nil end - return self.pasvt.ip, self.pasvt.port + return self.pasvt.address, self.pasvt.port end -function metat.__index:port(ip, port) +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local code, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local d, prt, address, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) self.pasvt = nil - if not ip then - ip, port = self.try(self.tp:getcontrol():getsockname()) - self.server = self.try(socket.bind(ip, 0)) - ip, port = self.try(self.server:getsockname()) + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) self.try(self.server:settimeout(_M.TIMEOUT)) end local pl = math.mod(port, 256) local ph = (port - pl)/256 - local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") self.try(self.tp:command("port", arg)) self.try(self.tp:check("2..")) return 1 end +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + function metat.__index:send(sendt) self.try(self.pasvt or self.server, "need port or pasv first") -- if there is a pasvt table, we already sent a PASV command @@ -110,12 +143,12 @@ function metat.__index:send(sendt) -- send the transfer command and check the reply self.try(self.tp:command(command, argument)) local code, reply = self.try(self.tp:check{"2..", "1.."}) - -- if there is not a a pasvt table, then there is a server + -- if there is not a pasvt table, then there is a server -- and we already sent a PORT command if not self.pasvt then self:portconnect() end -- get the sink, source and step for the transfer local step = sendt.step or ltn12.pump.step - local readt = {self.tp.c} + local readt = { self.tp } local checkstep = function(src, snk) -- check status in control connection while downloading local readyt = socket.select(readt, nil, 0) @@ -207,7 +240,7 @@ local function tput(putt) f:greet() f:login(putt.user, putt.password) if putt.type then f:type(putt.type) end - f:pasv() + f:epsv() local sent = f:send(putt) f:quit() f:close() @@ -219,7 +252,7 @@ local default = { scheme = "ftp" } -local function parse(u) +local function genericform(u) local t = socket.try(url.parse(u, default)) socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") socket.try(t.host, "missing hostname") @@ -232,8 +265,10 @@ local function parse(u) return t end +_M.genericform = genericform + local function sput(u, body) - local putt = parse(u) + local putt = genericform(u) putt.source = ltn12.source.string(body) return tput(putt) end @@ -250,14 +285,14 @@ local function tget(gett) f:greet() f:login(gett.user, gett.password) if gett.type then f:type(gett.type) end - f:pasv() + f:epsv() f:receive(gett) f:quit() return f:close() end local function sget(u) - local gett = parse(u) + local gett = genericform(u) local t = {} gett.sink = ltn12.sink.table(t) tget(gett) diff --git a/src/http.lua b/src/http.lua index d6bcc91..f2fff01 100644 --- a/src/http.lua +++ b/src/http.lua @@ -346,11 +346,13 @@ end return 1, code, headers, status end -local function srequest(u, b) +-- turns an url and a body into a generic request +local function genericform(u, b) local t = {} local reqt = { url = u, - sink = ltn12.sink.table(t) + sink = ltn12.sink.table(t), + target = t } if b then reqt.source = ltn12.source.string(b) @@ -360,8 +362,15 @@ local function srequest(u, b) } reqt.method = "POST" end - local code, headers, status = socket.skip(1, trequest(reqt)) - return table.concat(t), code, headers, status + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status end _M.request = socket.protect(function(reqt, body) diff --git a/src/tp.lua b/src/tp.lua index 328cbab..b8ebc56 100644 --- a/src/tp.lua +++ b/src/tp.lua @@ -46,6 +46,14 @@ end -- metatable for sock object local metat = { __index = {} } +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + function metat.__index:check(ok) local code, reply = get_reply(self.c) if not code then return nil, reply end @@ -123,4 +131,4 @@ function _M.connect(host, port, timeout, create) return base.setmetatable({c = c}, metat) end -return _M \ No newline at end of file +return _M diff --git a/test/ftptest.lua b/test/ftptest.lua index fb13326..3ea0d39 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -3,19 +3,31 @@ local ftp = require("socket.ftp") local url = require("socket.url") local ltn12 = require("ltn12") +-- use dscl to create user "luasocket" with password "password" +-- with home in /Users/diego/luasocket/test/ftp +-- with group com.apple.access_ftp +-- with shell set to /sbin/nologin +-- set /etc/ftpchroot to chroot luasocket +-- must set group com.apple.access_ftp on user _ftp (for anonymous access) +-- copy index.html to /var/empty/pub (home of user ftp) +-- start ftp server with +-- sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist +-- copy index.html to /Users/diego/luasocket/test/ftp +-- stop with +-- sudo -s launchctl unload -w /System/Library/LaunchDaemons/ftp.plist + -- override protection to make sure we see all errors --socket.protect = function(s) return s end dofile("testsupport.lua") -local host, port, index_file, index, back, err, ret +local host = host or "localhost" +local port, index_file, index, back, err, ret local t = socket.gettime() -host = host or "localhost" index_file = "index.html" - -- a function that returns a directory listing local function nlst(u) local t = {} @@ -55,27 +67,27 @@ assert(not err and back == index, err) print("ok") io.write("erasing before upload: ") -ret, err = dele("ftp://luasocket:pedrovian@" .. host .. "/index.up.html") -if not ret then print(err) +ret, err = dele("ftp://luasocket:password@" .. host .. "/index.up.html") +if not ret then print(err) else print("ok") end io.write("testing upload: ") -ret, err = ftp.put("ftp://luasocket:pedrovian@" .. host .. "/index.up.html;type=i", index) +ret, err = ftp.put("ftp://luasocket:password@" .. host .. "/index.up.html;type=i", index) assert(ret and not err, err) print("ok") io.write("downloading uploaded file: ") -back, err = ftp.get("ftp://luasocket:pedrovian@" .. host .. "/index.up.html;type=i") +back, err = ftp.get("ftp://luasocket:password@" .. host .. "/index.up.html;type=i") assert(ret and not err and index == back, err) print("ok") io.write("erasing after upload/download: ") -ret, err = dele("ftp://luasocket:pedrovian@" .. host .. "/index.up.html") -assert(ret and not err, err) +ret, err = dele("ftp://luasocket:password@" .. host .. "/index.up.html") +assert(ret and not err, err) print("ok") io.write("testing weird-character translation: ") -back, err = ftp.get("ftp://luasocket:pedrovian@" .. host .. "/%23%3f;type=i") +back, err = ftp.get("ftp://luasocket:password@" .. host .. "/%23%3f;type=i") assert(not err and back == index, err) print("ok") @@ -84,7 +96,7 @@ local back = {} ret, err = ftp.get{ url = "//stupid:mistake@" .. host .. "/index.html", user = "luasocket", - password = "pedrovian", + password = "password", type = "i", sink = ltn12.sink.table(back) }