Version 0.1.4
This commit is contained in:
parent
0552ea7cae
commit
3190891ad2
2 changed files with 28 additions and 23 deletions
|
|
@ -1,3 +1,8 @@
|
||||||
|
vrel 0.1.4:
|
||||||
|
- Now stores the sender's IP
|
||||||
|
- Pastes are detroyed with "burn on read" only if the request comes from a different IP
|
||||||
|
- When sending a paste from the web interface, automatically redirects to the syntax colored paste page
|
||||||
|
- POST /p/ lifetime arg is now in seconds instead of hours
|
||||||
vrel 0.1.3:
|
vrel 0.1.3:
|
||||||
- Added basic support for "Expect: 100-continue" header
|
- Added basic support for "Expect: 100-continue" header
|
||||||
- Added POST data size limitation
|
- Added POST data size limitation
|
||||||
|
|
|
||||||
46
vrel.lua
46
vrel.lua
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/lua
|
#!/bin/lua
|
||||||
--- vrel v0.1.3: online paste service, in 256 lines of Lua (max line lenght = 256 but we shouldn't go this far if not needed).
|
--- vrel v0.1.4: online paste service, in 256 lines of Lua (max line lenght = 256 but we shouldn't go this far if not needed).
|
||||||
-- This module requires LuaSocket 2.0.2, and debug mode requires LuaFileSystem 1.6.3. Install pygmentize for the optional syntax highlighting.
|
-- This module requires LuaSocket 2.0.2, and debug mode requires LuaFileSystem 1.6.3. Install pygmentize for the optional syntax highlighting.
|
||||||
-- If you want persistance for paste storage, install lsqlite3. vrel should work with Lua 5.1 to 5.3.
|
-- If you want persistance for paste storage, install lsqlite3. vrel should work with Lua 5.1 to 5.3.
|
||||||
-- Basic HTTP server --
|
-- Basic HTTP server --
|
||||||
|
|
@ -67,18 +67,15 @@ httpd = {
|
||||||
start = function(address, port, pages, errorPages, options)
|
start = function(address, port, pages, errorPages, options)
|
||||||
options = options or { debug = false, timeout = 1 }
|
options = options or { debug = false, timeout = 1 }
|
||||||
-- Start server
|
-- Start server
|
||||||
local socket = require("socket")
|
local socket, url = require("socket"), require("socket.url")
|
||||||
local url = require("socket.url")
|
local server, running = socket.bind(address, port), true
|
||||||
local server = socket.bind(address, port)
|
|
||||||
httpd.log("HTTP server started on %s", ("%s:%s"):format(server:getsockname()))
|
httpd.log("HTTP server started on %s", ("%s:%s"):format(server:getsockname()))
|
||||||
local running = true
|
|
||||||
-- Debug mode
|
-- Debug mode
|
||||||
if options.debug then
|
if options.debug then
|
||||||
httpd.log("Debug mode enabled")
|
httpd.log("Debug mode enabled")
|
||||||
server:settimeout(1) -- Enable timeout (don't block forever so we can run debug code)
|
server:settimeout(1) -- Enable timeout (don't block forever so we can run debug code)
|
||||||
-- Warp the server object so we can rewrite its functions
|
-- Warp the server object so we can rewrite its functions
|
||||||
local realServer = server
|
local realServer, server = server, setmetatable({}, {__index = function(t, k) return function(_, ...) return realServer[k](realServer, ...) end end})
|
||||||
server = setmetatable({}, {__index = function(t, k) return function(_, ...) return realServer[k](realServer, ...) end end})
|
|
||||||
-- Reload file on change
|
-- Reload file on change
|
||||||
local lfs = require("lfs")
|
local lfs = require("lfs")
|
||||||
local lastModification = lfs.attributes(arg[0]).modification -- current last modification time
|
local lastModification = lfs.attributes(arg[0]).modification -- current last modification time
|
||||||
|
|
@ -134,20 +131,20 @@ httpd = {
|
||||||
}
|
}
|
||||||
-- Vrel --
|
-- Vrel --
|
||||||
-- Load data
|
-- Load data
|
||||||
local data = {} -- { ["name"] = { expire = os.time()+lifetime, burnOnRead = false, data = "Hello\nWorld" } }
|
local data = {} -- { ["name"] = { expire = os.time()+lifetime, burnOnRead = false, senderIp = "0.0.0.0", data = "Hello\nWorld" } }
|
||||||
local sqliteAvailable, sqlite3 = pcall(require, "lsqlite3")
|
local sqliteAvailable, sqlite3 = pcall(require, "lsqlite3")
|
||||||
if sqliteAvailable then httpd.log("Using SQlite3 storage backend") -- SQlite backend
|
if sqliteAvailable then httpd.log("Using SQlite3 storage backend") -- SQlite backend
|
||||||
local db = sqlite3.open("database.sqlite3")
|
local db = sqlite3.open("database.sqlite3")
|
||||||
db:exec("CREATE TABLE IF NOT EXISTS data (name STRING PRIMARY KEY NOT NULL, expire INTEGER NOT NULL, burnOnRead INTEGER NOT NULL, data STRING NOT NULL)")
|
db:exec("CREATE TABLE IF NOT EXISTS data (name STRING PRIMARY KEY NOT NULL, expire INTEGER NOT NULL, burnOnRead INTEGER NOT NULL, senderIp STRING NOT NULL, data STRING NOT NULL)")
|
||||||
setmetatable(data, {
|
setmetatable(data, {
|
||||||
__index = function(self, key) -- data[name]: get paste { expire = integer, burnOnRead = boolean, data = string }
|
__index = function(self, key) -- data[name]: get paste { expire = integer, burnOnRead = boolean, data = string }
|
||||||
local stmt = db:prepare("SELECT expire, burnOnRead, data FROM data WHERE name = ?") stmt:bind_values(key)
|
local stmt = db:prepare("SELECT expire, burnOnRead, senderIp, data FROM data WHERE name = ?") stmt:bind_values(key)
|
||||||
local r for row in stmt:nrows() do r = row r.burnOnRead = r.burnOnRead == 1 break end stmt:finalize()
|
local r for row in stmt:nrows() do r = row r.burnOnRead = r.burnOnRead == 1 break end stmt:finalize()
|
||||||
return r
|
return r
|
||||||
end,
|
end,
|
||||||
__newindex = function(self, key, value)
|
__newindex = function(self, key, value)
|
||||||
if value ~= nil then -- data[name] = { expire = integer, burnOnRead = boolean, data = string }: add paste
|
if value ~= nil then -- data[name] = { expire = integer, burnOnRead = boolean, data = string }: add paste
|
||||||
local stmt = db:prepare("INSERT INTO data VALUES (?, ?, ?, ?)") stmt:bind_values(key, value.expire, value.burnOnRead, value.data)
|
local stmt = db:prepare("INSERT INTO data VALUES (?, ?, ?, ?, ?)") stmt:bind_values(key, value.expire, value.burnOnRead, value.senderIp, value.data)
|
||||||
stmt:step() stmt:finalize()
|
stmt:step() stmt:finalize()
|
||||||
else -- data[name] = nil: delete paste
|
else -- data[name] = nil: delete paste
|
||||||
local stmt = db:prepare("DELETE FROM data WHERE name = ?") stmt:bind_values(key)
|
local stmt = db:prepare("DELETE FROM data WHERE name = ?") stmt:bind_values(key)
|
||||||
|
|
@ -185,19 +182,20 @@ local function clean() -- clean the database each cleanInterval
|
||||||
lastClean = time
|
lastClean = time
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function get(name) clean() -- get a paste (returns nil if non-existent) (returned data is expected to be safe)
|
local function get(name, request) clean() -- get a paste (returns nil if non-existent) (returned data is expected to be safe)
|
||||||
if data[name] then
|
if data[name] then
|
||||||
local d = data[name]
|
local d = data[name]
|
||||||
if d.expire < os.time() then data[name] = nil return end
|
if d.expire < os.time() then data[name] = nil return end
|
||||||
if d.burnOnRead then data[name] = nil end
|
if request.client:getpeername() ~= d.senderIp and d.burnOnRead then data[name] = nil end
|
||||||
return d
|
return d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function post(paste) clean() -- add a paste, will check data and auto-fill defaults; returns name, data table
|
local function post(paste, request) clean() -- add a paste, will check data and auto-fill defaults; returns name, data table
|
||||||
local name = generateName()
|
local name = generateName()
|
||||||
if paste.lifetime then paste.expire = os.time() + (tonumber(paste.lifetime) or defaultLifetime) end
|
if paste.lifetime then paste.expire = os.time() + (tonumber(paste.lifetime) or defaultLifetime) end
|
||||||
paste.expire = math.min(tonumber(paste.expire) or os.time()+defaultLifetime, os.time()+maxLifetime)
|
paste.expire = math.min(tonumber(paste.expire) or os.time()+defaultLifetime, os.time()+maxLifetime)
|
||||||
paste.burnOnRead = paste.burnOnRead == true
|
paste.burnOnRead = paste.burnOnRead == true
|
||||||
|
paste.senderIp = paste.senderIp or request.client:getpeername() or "0.0.0.0"
|
||||||
paste.data = tostring(paste.data)
|
paste.data = tostring(paste.data)
|
||||||
data[name] = paste
|
data[name] = paste
|
||||||
return name, data[name]
|
return name, data[name]
|
||||||
|
|
@ -208,7 +206,7 @@ local function highlight(code, lexer) -- Syntax highlighting; should returns the
|
||||||
source:write(code) source:close()
|
source:write(code) source:close()
|
||||||
local pygments = assert(io.popen("pygmentize -f html -O linenos=table,style="..pygmentsStyle.." -l "..lexer.." pygmentize.tmp", "r"))
|
local pygments = assert(io.popen("pygmentize -f html -O linenos=table,style="..pygmentsStyle.." -l "..lexer.." pygmentize.tmp", "r"))
|
||||||
local out = assert(pygments:read("*a")) pygments:close()
|
local out = assert(pygments:read("*a")) pygments:close()
|
||||||
if #out > 0 then -- if pygments available (returned something)
|
if #out > 0 then -- if pygments available and available lexer (returned something)
|
||||||
local style = assert(io.popen("pygmentize -f html -S "..pygmentsStyle, "r")) -- get style data
|
local style = assert(io.popen("pygmentize -f html -S "..pygmentsStyle, "r")) -- get style data
|
||||||
out = out.."<style>"..extraStyle..assert(style:read("*a")).."</style>" style:close()
|
out = out.."<style>"..extraStyle..assert(style:read("*a")).."</style>" style:close()
|
||||||
return out
|
return out
|
||||||
|
|
@ -219,8 +217,11 @@ end
|
||||||
httpd.start("*", 8155, { -- Pages
|
httpd.start("*", 8155, { -- Pages
|
||||||
["/([^/]*)"] = function(request, name)
|
["/([^/]*)"] = function(request, name)
|
||||||
if forbiddenName[name] then return end
|
if forbiddenName[name] then return end
|
||||||
return { "200 OK", {["Content-Type"] = "text/html"},
|
if request.method == "POST" and request.post.data then
|
||||||
[[<!DOCTYPE html>
|
local name = post({ lifetime = (tonumber(request.post.lifetime) or defaultLifetime/3600)*3600, burnOnRead = request.post.burnOnRead == "on", data = request.post.data }, request)
|
||||||
|
return { "303 See Other", {["Location"] = "/"..name}, "" }
|
||||||
|
end
|
||||||
|
return { "200 OK", {["Content-Type"] = "text/html"}, [[<!DOCTYPE html>
|
||||||
<html><head><meta charset="utf-8"/><title>vrel</title></head>
|
<html><head><meta charset="utf-8"/><title>vrel</title></head>
|
||||||
<body>]]..(#name == 0 and [[
|
<body>]]..(#name == 0 and [[
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -235,18 +236,17 @@ httpd.start("*", 8155, { -- Pages
|
||||||
#topbar input[type=submit] { cursor: pointer; width: 10em; }
|
#topbar input[type=submit] { cursor: pointer; width: 10em; }
|
||||||
#topbar #vrel { font-size: 1.5em; float: right; }
|
#topbar #vrel { font-size: 1.5em; float: right; }
|
||||||
</style>
|
</style>
|
||||||
<form method="POST" action="/p">
|
<form method="POST" action="/">
|
||||||
<div id="topbar"><span id="controls">expires in <input name="lifetime" type="number" min="1" max="]]..math.floor(maxLifetime/3600)..[[" value="]]..math.floor(defaultLifetime/3600)..
|
<div id="topbar"><span id="controls">expires in <input name="lifetime" type="number" min="1" max="]]..math.floor(maxLifetime/3600)..[[" value="]]..math.floor(defaultLifetime/3600)..
|
||||||
[["/> hours (<input name="burnOnRead" type="checkbox"/>burn on read) <input type="submit" value="post"/></span><a id="vrel" href="/">vrel</a></div>
|
[["/> hours (<input name="burnOnRead" type="checkbox"/>burn on read) <input type="submit" value="post"/></span><a id="vrel" href="/">vrel</a></div>
|
||||||
<textarea name="data" required=true></textarea>
|
<textarea name="data" required=true></textarea>
|
||||||
</form>]] or highlight((get(name) or {data="paste not found"}).data, "lua"))..[[
|
</form>]] or highlight((get(name:match("^[^.]+"), request) or {data="paste not found"}).data, name:match("%.(.+)$") or "lua"))..[[
|
||||||
</body></html>]]
|
</body></html>]] }
|
||||||
}
|
|
||||||
end,
|
end,
|
||||||
["/g/(.+)"] = function(request, name) local d = get(name) return d and { "200 OK", {["Content-Type"] = "text"}, d.data } or nil end,
|
["/g/(.+)"] = function(request, name) local d = get(name, request) return d and { "200 OK", {["Content-Type"] = "text"}, d.data } or nil end,
|
||||||
["/p"] = function(request)
|
["/p"] = function(request)
|
||||||
if request.method == "POST" and request.post.data then
|
if request.method == "POST" and request.post.data then
|
||||||
local name, data = post({ lifetime = (tonumber(request.post.lifetime) or defaultLifetime/3600)*3600, burnOnRead = request.post.burnOnRead == "on", data = request.post.data })
|
local name, data = post({ lifetime = tonumber(request.post.lifetime) or defaultLifetime, burnOnRead = request.post.burnOnRead == "on", data = request.post.data }, request)
|
||||||
return { "200 OK", {["Content-Type"] = "text/json"}, "{\"name\":\""..name.."\",\"lifetime\":"..data.expire-os.time()..",\"burnOnRead\":"..tostring(data.burnOnRead).."}\n" }
|
return { "200 OK", {["Content-Type"] = "text/json"}, "{\"name\":\""..name.."\",\"lifetime\":"..data.expire-os.time()..",\"burnOnRead\":"..tostring(data.burnOnRead).."}\n" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Reference in a new issue