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