Family agostic FTP and expose HTTP/FTP url parsing
parent
5b4b915879
commit
916b548240
67
src/ftp.lua
67
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)
|
||||
|
|
17
src/http.lua
17
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)
|
||||
|
|
10
src/tp.lua
10
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
|
||||
return _M
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue