Wow such commit
This commit is contained in:
parent
3190891ad2
commit
2ce4676d6a
4 changed files with 53 additions and 22 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
||||||
pygmentize.tmp
|
pygmentize.tmp
|
||||||
|
database.sqlite3
|
||||||
|
config.lua
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
vrel 0.1.5 (wip):
|
||||||
|
- Doubled the default maximum lifetime (3 months -> 6 months).
|
||||||
|
- Added an optional configuration file.
|
||||||
|
- Various luacheck cleaning (a few potential bugs are fixed). It's still complaining about legit things tho.
|
||||||
vrel 0.1.4:
|
vrel 0.1.4:
|
||||||
- Now stores the sender's IP
|
- Now stores the sender's IP
|
||||||
- Pastes are detroyed with "burn on read" only if the request comes from a different IP
|
- Pastes are detroyed with "burn on read" only if the request comes from a different IP
|
||||||
|
|
|
||||||
24
config.default.lua
Normal file
24
config.default.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
--- Configuration files for vrel.
|
||||||
|
-- No line count limitation ; vrel will work fine without this, so I guess it's not cheating.
|
||||||
|
-- In fact, this file will be read by vrel only if named config.lua (and not config.default.lua).
|
||||||
|
-- If you want to live the full experience, you can easliy find where theses variables are used in vrel.lua and change the default value here
|
||||||
|
-- instead of using a config.lua file.
|
||||||
|
-- Also, the comments describing each variable in this file are here solely for practicality purposes ; the comments and code in vrel.lua should
|
||||||
|
-- be enough to descibe or imply their utility.
|
||||||
|
|
||||||
|
return {
|
||||||
|
-- Server address to bind
|
||||||
|
address = "*",
|
||||||
|
-- TCP port to bind
|
||||||
|
port = 8155,
|
||||||
|
-- Maximal lifetime of a paste
|
||||||
|
maxLifetime = 15552000, -- 6 months
|
||||||
|
-- Default lifetime of a paste in the web interface
|
||||||
|
defaultLifetime = 86400, -- 1 day
|
||||||
|
-- Maximal size of a request/paste
|
||||||
|
requestMaxDataSize = 15728640, -- 15MB
|
||||||
|
-- Request timeout
|
||||||
|
timeout = 1, -- 1 second
|
||||||
|
-- Debug mode
|
||||||
|
debug = false
|
||||||
|
}
|
||||||
45
vrel.lua
45
vrel.lua
|
|
@ -1,9 +1,10 @@
|
||||||
#!/bin/lua
|
#!/bin/lua
|
||||||
--- 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).
|
--- vrel v0.1.5: 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.
|
||||||
|
local config = pcall(dofile, "config.lua") or {}
|
||||||
-- Basic HTTP server --
|
-- Basic HTTP server --
|
||||||
local httpd, requestMaxDataSize = nil, 15728640 -- max post data size (bytes) (15MB)
|
local httpd, requestMaxDataSize = nil, config.requestMaxDataSize or 15728640 -- max post/paste data size (bytes) (15MB)
|
||||||
httpd = {
|
httpd = {
|
||||||
log = function(str, ...) print("["..os.date().."] "..str:format(...)) end, -- log a message (str:format(...))
|
log = function(str, ...) print("["..os.date().."] "..str:format(...)) end, -- log a message (str:format(...))
|
||||||
peername = function(client) return ("%s:%s"):format(client:getpeername()) end, -- returns a nice display name for the client (address:port)
|
peername = function(client) return ("%s:%s"):format(client:getpeername()) end, -- returns a nice display name for the client (address:port)
|
||||||
|
|
@ -74,8 +75,8 @@ httpd = {
|
||||||
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
|
local realServer = server
|
||||||
local realServer, server = server, setmetatable({}, {__index = function(t, k) return function(_, ...) return realServer[k](realServer, ...) end end})
|
server = setmetatable({}, {__index = function(_, k) return function(_, ...) return realServer[k](realServer, ...) end end}) -- Warp the server object so we can rewrite its functions
|
||||||
-- 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
|
||||||
|
|
@ -111,7 +112,7 @@ httpd = {
|
||||||
end
|
end
|
||||||
if not responded then
|
if not responded then
|
||||||
local page = errorPages["404"] or {"404", {}, "Page not found"} -- simple default 404 page
|
local page = errorPages["404"] or {"404", {}, "Page not found"} -- simple default 404 page
|
||||||
httpd.sendResponse(client, unpack(type(page) == "table" and page or page(request)))
|
httpd.sendResponse(client, unpack(type(page) == "table" and page or page(req)))
|
||||||
end
|
end
|
||||||
else httpd.log("%s - Invalid request: %s", httpd.peername(client), err) end
|
else httpd.log("%s - Invalid request: %s", httpd.peername(client), err) end
|
||||||
end, function(error) return error..debug.traceback("", 2) end) -- add traceback to the error message
|
end, function(error) return error..debug.traceback("", 2) end) -- add traceback to the error message
|
||||||
|
|
@ -119,7 +120,7 @@ httpd = {
|
||||||
httpd.log("Internal server error: %s", err)
|
httpd.log("Internal server error: %s", err)
|
||||||
pcall(function()
|
pcall(function()
|
||||||
local page = errorPages["500"] or {"500", {}, "Internal server error"} -- simple default 500 page
|
local page = errorPages["500"] or {"500", {}, "Internal server error"} -- simple default 500 page
|
||||||
httpd.sendResponse(client, unpack(type(page) == "table" and page or page(request)))
|
httpd.sendResponse(client, unpack(type(page) == "table" and page or page()))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
client:close()
|
client:close()
|
||||||
|
|
@ -131,20 +132,20 @@ httpd = {
|
||||||
}
|
}
|
||||||
-- Vrel --
|
-- Vrel --
|
||||||
-- Load data
|
-- Load data
|
||||||
local data = {} -- { ["name"] = { expire = os.time()+lifetime, burnOnRead = false, senderIp = "0.0.0.0", data = "Hello\nWorld" } }
|
local data = {} -- { ["name"] = { expire = os.time()+lifetime, burnOnRead = false, senderId = "someuniqueidentifier", 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, senderIp STRING 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, senderId 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, senderIp, data FROM data WHERE name = ?") stmt:bind_values(key)
|
local stmt = db:prepare("SELECT expire, burnOnRead, senderId, 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.senderIp, value.data)
|
local stmt = db:prepare("INSERT INTO data VALUES (?, ?, ?, ?, ?)") stmt:bind_values(key, value.expire, value.burnOnRead, value.senderId, 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)
|
||||||
|
|
@ -162,7 +163,7 @@ else httpd.log("Using in-memory storage backend") -- In-memory (table) backend
|
||||||
end
|
end
|
||||||
-- Helpers functions
|
-- Helpers functions
|
||||||
local forbiddenName = { ["g"] = true, ["p"] = true }
|
local forbiddenName = { ["g"] = true, ["p"] = true }
|
||||||
local function generateName() -- generate a paste name
|
local function generateName(size) -- generate a paste name. If size ~= nil, will generate a random ID of this lenght.
|
||||||
local name = ""
|
local name = ""
|
||||||
repeat
|
repeat
|
||||||
local charType, char = math.random()
|
local charType, char = math.random()
|
||||||
|
|
@ -170,11 +171,11 @@ local function generateName() -- generate a paste name
|
||||||
elseif charType < 36/62 then char = math.random(65, 90) -- upper letters (26 possibilities out of 62)
|
elseif charType < 36/62 then char = math.random(65, 90) -- upper letters (26 possibilities out of 62)
|
||||||
else char = math.random(97, 122) end -- lower letters (26 possibilities out of 62)
|
else char = math.random(97, 122) end -- lower letters (26 possibilities out of 62)
|
||||||
name = name..string.char(char)
|
name = name..string.char(char)
|
||||||
until not (data[name] or forbiddenName[name])
|
until (not size and not (data[name] or forbiddenName[name])) or (#name >= (size or math.huge))
|
||||||
return name
|
return name
|
||||||
end
|
end
|
||||||
local lastClean, cleanInterval = os.time(), 1800 -- last clean time (all time are stored in seconds) and clean interval (30min)
|
local lastClean, cleanInterval = os.time(), config.cleanInterval or 1800 -- last clean time (all time are stored in seconds) and clean interval (30min)
|
||||||
local maxLifetime, defaultLifetime = 7776000, 86400 -- maximum lifetime of a data (3 month) and default (1 day)
|
local maxLifetime, defaultLifetime = config.maxLifetime or 15552000, config.defaultLifetime or 86400 -- maximum lifetime of a paste (6 month) and default (1 day)
|
||||||
local function clean() -- clean the database each cleanInterval
|
local function clean() -- clean the database each cleanInterval
|
||||||
local time = os.time()
|
local time = os.time()
|
||||||
if lastClean + cleanInterval < time then
|
if lastClean + cleanInterval < time then
|
||||||
|
|
@ -186,16 +187,16 @@ local function get(name, request) clean() -- get a paste (returns nil if non-exi
|
||||||
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 request.client:getpeername() ~= d.senderIp and d.burnOnRead then data[name] = nil end
|
if request.client:getpeername() ~= d.senderId and d.burnOnRead then data[name] = nil end -- burn on read (except if retrieved by original poster)
|
||||||
return d
|
return d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function post(paste, request) 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, paste 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.senderId = paste.senderId 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]
|
||||||
|
|
@ -214,11 +215,11 @@ local function highlight(code, lexer) -- Syntax highlighting; should returns the
|
||||||
else return "<style>"..extraStyle.."</style><pre><code>"..code:gsub("([\"&<>])",{["\""]=""",["&"]="&",["<"]="<",[">"]=">"}).."</code></pre>" end
|
else return "<style>"..extraStyle.."</style><pre><code>"..code:gsub("([\"&<>])",{["\""]=""",["&"]="&",["<"]="<",[">"]=">"}).."</code></pre>" end
|
||||||
end
|
end
|
||||||
-- Start!
|
-- Start!
|
||||||
httpd.start("*", 8155, { -- Pages
|
httpd.start(config.address or "*", config.port or 8155, { -- Pages
|
||||||
["/([^/]*)"] = function(request, name)
|
["/([^/]*)"] = function(request, name)
|
||||||
if forbiddenName[name] then return end
|
if forbiddenName[name] then return end
|
||||||
if request.method == "POST" and request.post.data then
|
if request.method == "POST" and request.post.data then
|
||||||
local name = post({ lifetime = (tonumber(request.post.lifetime) or defaultLifetime/3600)*3600, burnOnRead = request.post.burnOnRead == "on", data = request.post.data }, request)
|
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}, "" }
|
return { "303 See Other", {["Location"] = "/"..name}, "" }
|
||||||
end
|
end
|
||||||
return { "200 OK", {["Content-Type"] = "text/html"}, [[<!DOCTYPE html>
|
return { "200 OK", {["Content-Type"] = "text/html"}, [[<!DOCTYPE html>
|
||||||
|
|
@ -246,10 +247,10 @@ httpd.start("*", 8155, { -- Pages
|
||||||
["/g/(.+)"] = function(request, name) local d = get(name, request) 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, burnOnRead = request.post.burnOnRead == "on", data = request.post.data }, request)
|
local name, paste = 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\":"..paste.expire-os.time()..",\"burnOnRead\":"..tostring(paste.burnOnRead).."}\n" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
}, { -- Error pages
|
}, { -- Error pages
|
||||||
["404"] = { "404", {["Content-Type"] = "text/json"}, "{\"error\":\"page not found\"}\n" }, ["500"] = { "500", {["Content-Type"] = "text/json"}, "{\"error\":\"internal server error\"}\n" }
|
["404"] = { "404", {["Content-Type"] = "text/json"}, "{\"error\":\"page not found\"}\n" }, ["500"] = { "500", {["Content-Type"] = "text/json"}, "{\"error\":\"internal server error\"}\n" }
|
||||||
}, { timeout = 1, debug = true })
|
}, { timeout = config.timeout or 1, debug = config.debug or false })
|
||||||
|
|
|
||||||
Reference in a new issue