-- -- lovebird -- -- Copyright (c) 2014, rxi -- -- This library is free software; you can redistribute it and/or modify it -- under the terms of the MIT license. See LICENSE for details. -- local socket = require "socket" local lovebird = { _version = "0.0.1" } lovebird.inited = false lovebird.buffer = "" lovebird.lines = {} lovebird.host = "*" lovebird.wrapprint = true lovebird.timestamp = true lovebird.allowhtml = true lovebird.port = 8000 lovebird.whitelist = { "127.0.0.1", "localhost" } lovebird.maxlines = 200 lovebird.refreshrate = .5 lovebird.page = [[ lovebird
]] local loadstring = loadstring or load local map = function(t, fn) local res = {} for k, v in pairs(t) do res[k] = fn(v) end return res end local find = function(t, value) for k, v in pairs(t) do if v == value then return k end end end local trace = function(...) print("[lovebird] " .. table.concat(map({...}, tostring), " ")) end local unescape = function(str) local f = function(x) return string.char(tonumber("0x"..x)) end return (str:gsub("%+", " "):gsub("%%(..)", f)) end function lovebird.init() lovebird.server = assert(socket.bind(lovebird.host, lovebird.port)) lovebird.addr, lovebird.port = lovebird.server:getsockname() lovebird.server:settimeout(0) if lovebird.wrapprint then local oldprint = print print = function(...) oldprint(...) lovebird.print(...) end end lovebird.inited = true end function lovebird.template(str, env) env = env or {} local keys, vals = {}, {} for k, v in pairs(env) do table.insert(keys, k) table.insert(vals, v) end local f = function(x) return string.format(" echo(%q)", x) end str = ("?>"..str.."(.-)<%?lua", f) str = "local echo, " .. table.concat(keys, ",") .. " = ..." .. str local output = {} local echo = function(str) table.insert(output, str) end assert(loadstring(str))(echo, unpack(vals)) return table.concat(map(output, tostring)) end function lovebird.print(...) local str = table.concat(map({...}, tostring), " ") if not lovebird.allowhtml then str = str:gsub("<", "<") end if lovebird.timestamp then str = os.date('[%H:%M:%S] ') .. str end table.insert(lovebird.lines, str) if #lovebird.lines > lovebird.maxlines then table.remove(lovebird.lines, 1) end lovebird.buffer = table.concat(lovebird.lines, "
") end function lovebird.onError(err) trace("ERROR:", err) end function lovebird.onRequest(req, client) local head = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" -- Handle request for just the buffer if req.url:match("buffer") then return head .. lovebird.buffer end -- Handle input if req.body then local str = unescape(req.body:match(".-=(.*)")) xpcall(function() assert(loadstring(str))() end, lovebird.onError) end -- Generate page local t = {} table.insert(t, head) table.insert(t, lovebird.template(lovebird.page, { lovebird = lovebird })) return table.concat(t) end function lovebird.onConnect(client) -- Create request table local requestptn = "(%S*)%s*(%S*)%s*(%S*)" local req = {} req.socket = client req.addr, req.port = client:getsockname() req.request = client:receive() req.method, req.url, req.proto = req.request:match(requestptn) req.headers = {} while 1 do local line = client:receive() if not line or #line == 0 then break end local k, v = line:match("(.-):%s*(.*)$") req.headers[k] = v end if req.headers["Content-Length"] then req.body = client:receive(req.headers["Content-Length"]) end -- Handle request; get data to send local data, index = lovebird.onRequest(req), 0 -- Send data while index < #data do index = index + client:send(data, index) end -- Clear up client:close() end function lovebird.update() if not lovebird.inited then lovebird.init() end local client = lovebird.server:accept() if client then client:settimeout(2) local addr = client:getsockname() if not lovebird.whitelist or find(lovebird.whitelist, addr) then xpcall(function() lovebird.onConnect(client) end, lovebird.onError) else trace("got non-whitelisted connection attempt: ", addr) client:close() end end end return lovebird