-- -- 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.1.0" } lovebird.inited = false lovebird.host = "*" lovebird.buffer = "" lovebird.lines = {} lovebird.pages = {} lovebird.wrapprint = true lovebird.timestamp = true lovebird.allowhtml = true lovebird.port = 8000 lovebird.whitelist = { "127.0.0.1", "192.168.*.*" } lovebird.maxlines = 200 lovebird.updateinterval = .5 lovebird.pages["index"] = [[ lovebird
]] lovebird.pages["buffer"] = [[ ]] lovebird.pages["env.json"] = [[ { "valid": true, "path": "", "vars": [ { "key": "", "value": , "type": "", }, ] } ]] 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 trace = function(...) local str = "[lovebird] " .. table.concat(map({...}, tostring), " ") print(str) if not lovebird.wrapprint then lovebird.print(str) end 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.parseurl(url) local res = {} res.path, res.search = url:match("/([^%?]*)%??(.*)") res.query = {} for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do res.query[k] = unescape(v) end return res end function lovebird.htmlescape(str) return str:gsub("<", "<") end function lovebird.truncate(str, len) if #str <= len then return str end return str:sub(1, len - 3) .. "..." end function lovebird.checkwhitelist(addr) if lovebird.whitelist == nil then return true end for _, a in pairs(lovebird.whitelist) do local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$" if addr:match(ptn) then return true end end return false end function lovebird.clear() lovebird.lines = {} lovebird.buffer = "" end function lovebird.print(...) local str = table.concat(map({...}, tostring), " ") if not lovebird.allowhtml then str = lovebird.htmlescape(str) 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 page = req.parsedurl.path page = page ~= "" and page or "index" -- Handle "page not found" if not lovebird.pages[page] then return "HTTP/1.1 404\r\nContent-Type: text/html\r\n\r\nBad page" end -- Handle page local str xpcall(function() str = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" .. lovebird.template(lovebird.pages[page], { lovebird = lovebird, req = req }) end, lovebird.onerror) return str 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 -- Parse body req.parsedbody = {} if req.body then for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do req.parsedbody[k] = unescape(v) end end -- Parse request line's url req.parsedurl = lovebird.parseurl(req.url) -- 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 lovebird.checkwhitelist(addr) then xpcall(function() lovebird.onconnect(client) end, function() end) else trace("got non-whitelisted connection attempt: ", addr) client:close() end end end return lovebird