tiny-gopherd/gopherd.lua

145 lines
3.3 KiB
Lua

local socket = require "socket"
local fs = require "lfs"
local tArgs = {...}
local pcount = 0
local threads = {}
-- initial configuration
local config = {}
config.path = "/var/www/gopher"
config.hostname = "shadowkat.net"
config.port = 70
config.bindport = 7000
config.dirinfo = true
config.timer = 0.1
config.cgi = true
--config.cgipattern = ".*" -- todo
-- load the config as a lua script
if tArgs[1] then
local f = io.open(tArgs[1],"rb")
local fn = load(f:read("*a"))
f:close()
if fn then
for k,v in pairs(fn()) do
config[k] = v
end
end
end
local function cleanPath(p) -- canonicalizes the path in theory
local t,o = {},""
for s in p:gmatch("[^/]+") do
if s == ".." then
t[#t] = nil
else
t[#t+1] = s
end
end
for k,v in pairs(t) do
o=o.."/"..v
end
return o
end
local function logerr(msg)
-- todo: proper error logging logic
print("error: "..msg)
end
local function detectft(path) -- tries to detect the file type
local attr = fs.attributes(path)
if attr.mode:sub(1,3) == "dir" then
return "1"
end
if path:sub(-4) == ".png" or path:sub(-4) == ".jpg" or path:sub(-5) == ".jpeg" or path:sub(-4) == ".bmp" or path:sub(-4) == "gif" then
return "I"
elseif path:sub(-5) == ".html" or path:sub(-4) == ".htm" then
return "h"
elseif path:sub(-10) == ".gopherdir" then
return "1"
end
return "0"
end
local function handleConnect(client)
client:settimeout(0)
threads[pcount+1] = coroutine.create(function() local w,err = pcall(function()
local host,port = client:getsockname()
repeat
coroutine.yield()
line=client:receive()
until line
print(string.format("%s:%d: %s",host,port,line))
local path,args = config.path .. cleanPath(line)
local attr = fs.attributes(path)
if attr then
if attr.mode:sub(1,3) == "dir" then
if lfs.attributes(path.."/.gopherdir.cgi") and config.cgi then
local f = io.popen(path.."/.gopherdir.cgi")
coroutine.yield()
client:send(f:read("*a"))
f:close()
elseif lfs.attributes(path.."/.gopherdir") then
local f = io.open(path.."/.gopherdir")
client:send(f:read("*a"))
f:close()
else
if config.dirinfo then
client:send(string.format("i%s\ni%s\n",config.hostname,cleanPath(line)))
end
for file in lfs.dir(path) do
if file:sub(1,1) ~= "." then
client:send(string.format("%s%s\t%s\t%s\t%d\n",detectft(path.."/"..file),file,string.format("%s/%s",cleanPath(line),file),config.hostname,config.port))
end
end
end
else
if path:sub(-4) == ".cgi" and config.cgi then
local f = io.popen(path)
coroutine.yield()
client:send(f:read("*a"))
f:close()
else
local f = io.open(path)
client:send(f:read("*a"))
f:close()
end
end
else
client:send("Not found.")
end
client:close()
end)
if not w then
logerr(err)
client:close()
end
end)
pcount=pcount+1
end
local server = socket.bind("*",config.bindport)
while true do -- totally not a scheduler
client = server:accept()
if client then
server:settimeout(config.timer)
handleConnect(client)
end
local c = 0
for k,v in pairs(threads) do
if coroutine.status(v) == "dead" then
threads[k] = nil
else
coroutine.resume(v)
c = c + 1
end
end
if c == 0 then
-- if there are no clients connected, make server:accept() block forever
server:settimeout(inf)
end
end