More work on refactor

This commit is contained in:
Robin Malley 2020-12-21 04:22:22 +00:00
parent 2cd10b6968
commit a640096bdd
38 changed files with 1829 additions and 106 deletions

View File

@ -11,10 +11,13 @@ domain=test.monster
lua_files=$(shell find src/lua -type f)
src_files=$(shell find src -type f) $(shell find conf -type f)
sql_files=$(shell find src/sql -type f)
test_files=$(shell find spec -type f)
built_tests=$(test_files:%=$(chroot_dir)%)
built_files=$(lua_files:src/lua/%.lua=$(chroot_dir)%.lua)
page_files=$(shell find src/pages -type f)
built_pages=$(page_files:src/pages/%.etlua=$(chroot_dir)pages/%.etlua)
built_sql=$(sql_files:src/sql/%.sql=$(chroot_dir)sql/%.sql)
built=$(built_files) $(built_sql) $(built_pages) $(built_tests)
all: $(chroot_dir) smr.so $(built_files) $(built_pages) $(built_sql)
echo $(built_files)
@ -69,5 +72,11 @@ $(built_pages): $(chroot_dir)pages/%.etlua : src/pages/%.etlua
$(built_sql): $(chroot_dir)sql/%.sql : src/sql/%.sql
cp $^ $@
$(built_tests) : $(chroot_dir)% : %
cp $^ $@
smr.so : $(src_files)
kodev build
test : $(built)
cd kore_chroot && busted

28
spec/home_spec.lua Normal file
View File

@ -0,0 +1,28 @@
--[[
Test the home page
]]
describe("smr",function()
describe("site home page",function()
it("detours configure",function()
local s = {}
local c = false
function configure(...)
local args = {...}
if args[1] == s then
c = true
end
end
local oldconfigure = configure
local index_get = require("index_get")
configure(s)
assert(c)
end)
end)
describe("author home page",function()
end)
end)

106
spec/pages_sanity_spec.lua Normal file
View File

@ -0,0 +1,106 @@
local pages = {
index = {
route = "/",
name = "home",
methods = {
GET={}
}
},
paste = {
route = "/_paste",
name = "post_story",
methods = {
GET={},
POST={}
}
},
edit = {
route = "/_edit",
name = "edit",
methods = {
GET={},
POST={},
}
},
--TODO:bio
login = {
route = "/_login",
name = "login",
methods = {
GET={},
POST={},
}
},
claim = {
route = "/_claim",
name = "claim",
methods = {
GET = {},
POST = {}
}
},
download = {
route = "/_download",
name = "download",
methods = {
GET = {},
}
},
preview = {
route = "/_preview",
name = "preview",
methods = {
POST = {},
}
},
search = {
route = "/_search",
name = "search",
methods = {
GET = {},
}
}
}
local request_stub_m = {
}
function http_response(req,errcode,str)
s = true
end
function http_request_get_host(reqstub)
return "localhost:8888"
end
function http_request_populate_post(reqstub)
reqstub.post_populated = true
end
describe("smr",function()
for name, obj in pairs(pages) do
describe("endpoint " .. name,function()
for method,parameters in pairs(obj.methods) do
describe("method " .. method,function()
local fname = string.format("%s_%s",name,string.lower(method))
it("should be named appropriately",function()
local f = assert(io.open(fname .. ".lua","r"))
end)
it("should run without errors",function()
require(fname)
end)
it("should return a function",function()
local pagefunc = assert(require(fname))
assert(type(pagefunc) == "function")
end)
it("calls http_response()",function()
local pagefunc = require(fname)
local s = false
local reqstub = {}
pagefunc(reqstub)
end)
end)
end
end)
end
end)

View File

@ -9,7 +9,7 @@ local stmnt_cache, stmnt_insert_cache
local oldconfigure = configure
function configure(...)
local cache = sqlassert(sql.open_memory())
local cache = util.sqlassert(sql.open_memory())
--A cache table to store rendered pages that do not need to be
--rerendered. In theory this could OOM the program eventually and start
--swapping to disk. TODO: fixme
@ -21,26 +21,26 @@ function configure(...)
dirty INTEGER
);
]]))
stmnt_cache = cache:prepare([[
stmnt_cache = assert(cache:prepare([[
SELECT data
FROM cache
WHERE
path = :path AND
((dirty = 0) OR (strftime('%s','now') - updated) < 20)
;
]])
stmnt_insert_cache = cache:prepare([[
]]))
stmnt_insert_cache = assert(cache:prepare([[
INSERT OR REPLACE INTO cache (
path, data, updated, dirty
) VALUES (
:path, :data, strftime('%s','now'), 0
);
]])
stmnt_dirty_cache = cache:prepare([[
]]))
stmnt_dirty_cache = assert(cache:prepare([[
UPDATE OR IGNORE cache
SET dirty = 1
WHERE path = :path;
]])
]]))
return oldconfigure(...)
end
@ -74,6 +74,7 @@ function ret.render(pagename,callback)
error("Failed to update cache for page " .. pagename)
end
stmnt_insert_cache:reset()
print("returning text from cache.render:",text)
return text
end

View File

@ -3,13 +3,16 @@ local sql = require("lsqlite3")
local pages = require("pages")
local db = require("db")
local queries = require("queries")
local util = require("util")
local sessionlib = require("session")
local config = require("config")
local stmnt_author_create
local oldconfigure = configure
function configure(...)
stmnt_author_create = assert(db.conn:prepare(queries.insert_author))
stmnt_author_create = util.sqlassert(db.conn:prepare(queries.insert_author))
return oldconfigure(...)
end
@ -39,16 +42,20 @@ local function claim_post(req)
}
stmnt_author_create:bind_blob(2,salt)
stmnt_author_create:bind_blob(3,hash)
local err = do_sql(stmnt_author_create)
local err = util.do_sql(stmnt_author_create)
if err == sql.DONE then
--We sucessfully made athe new author
print("success")
--We sucessfully made the new author
local id = stmnt_author_create:last_insert_rowid()
stmnt_author_create:reset()
--Give them a file back
http_response_header(req,"Content-Type","application/octet-stream")
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. name .. "." .. domain .. ".passfile\"")
local session = start_session(id)
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. name .. "." .. config.domain .. ".passfile\"")
local session = sessionlib.start(id)
text = password
print("session started, about to send password:",text)
http_response(req,200,text)
return
elseif err == sql.CONSTRAINT then
--If the creation failed, they probably just tried
--to use a name that was already taken
@ -62,5 +69,6 @@ local function claim_post(req)
}
end
stmnt_author_create:reset()
http_response(req,200,text)
end
return claim_post

View File

@ -2,11 +2,13 @@
local sql = require("lsqlite3")
local queries = require("queries")
local util = require("util")
local db = {}
local oldconfigure = configure
db.conn = util.sqlassert(sql.open("data/posts.db"))
function configure(...)
db.conn = sqlassert(sql.open("data/posts.db"))
--db.conn = sqlassert(sql.open("data/posts.db"))
--Create sql tables
assert(db.conn:exec(queries.create_table_authors))
@ -35,14 +37,16 @@ function configure(...)
assert(db.conn:exec(queries.create_index_tags))
--Store a cookie for logged in users. Logged in users can edit
--their own posts, and edit their biographies.
assert(db:exec(queries.create_table_session))
assert(db.conn:exec(queries.create_table_session))
print("Created db tables")
return configure(...)
return oldconfigure(...)
end
configure()
function db.close()
db.conn:close()
end
return db

45
src/lua/download_get.lua Normal file
View File

@ -0,0 +1,45 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local db = require("db")
local queries = require("queries")
local util = require("util")
local pages = require("pages")
local stmnt_download
local oldconfigure = configure
function configure(...)
stmnt_download = assert(db.conn:prepare(queries.select_download))
return oldconfigure(...)
end
local function download_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
print("host:",host,"path:",path)
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = util.decode_id(story)
print("Downloading", story_id)
stmnt_download:bind_names{
postid = story_id
}
local err = util.do_sql(stmnt_download)
if err == sql.DONE then
--No rows, story not found
http_responose(req,404,pages.nostory{path=story})
stmnt_download:reset()
return
end
assert(err == sql.ROW, "after doing download sql, result was not a row, was:" .. tostring(err))
local txt_compressed, title = unpack(stmnt_download:get_values())
local text = zlib.decompress(txt_compressed)
stmnt_download:reset()
http_response_header(req,"Content-Type","application/octet-stream")
local nicetitle = title:gsub("%W","_")
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. nicetitle .. ".txt\"")
http_response(req,200,text)
end
return download_get

View File

@ -6,22 +6,24 @@ local queries = require("queries")
local util = require("util")
local pages = require("pages")
local tags = require("tags")
local session = require("session")
local config = require("config")
local stmnt_edit
local oldconfigure = configure
function configure(...)
stmnt_edit = assert(db.conn:prepare(queries.select_edit))
return configure(...)
return oldconfigure(...)
end
local function edit_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local author, authorid = get_session(req)
local author, authorid = session.get(req)
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = decode_id(story)
local story_id = util.decode_id(story)
local ret
print("we want to edit story:",story)
@ -29,7 +31,7 @@ local function edit_get(req)
--sql-side. If we're not the owner, we'll get 0 rows back.
stmnt_edit:bind_names{
postid = story_id,
authorid = author_id
authorid = authorid
}
local err = util.do_sql(stmnt_edit)
if err == sql.DONE then
@ -55,10 +57,12 @@ local function edit_get(req)
markup = markup,
user = author,
isanon = isanon == 1,
domain = domain,
domain = config.domain,
story = story_id,
err = "",
tags = tags_txt
}
http_response(req,200,ret)
end
return edit_get

View File

@ -1,6 +1,7 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local db = require("db")
local queries = require("queries")
local pages = require("pages")
local parsers = require("parsers")
@ -8,6 +9,7 @@ local util = require("util")
local tagslib = require("tags")
local cache = require("cache")
local config = require("config")
local session = require("session")
local stmnt_author_of, stmnt_update_raw, stmnt_update
@ -22,7 +24,7 @@ end
local function edit_post(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local author, author_id = get_session(req)
local author, author_id = session.get(req)
http_request_populate_post(req)
local storyid = tonumber(assert(http_argument_get_string(req,"story")))
@ -34,7 +36,7 @@ local function edit_post(req)
stmnt_author_of:bind_names{
id = storyid
}
local err = do_sql(stmnt_author_of)
local err = util.do_sql(stmnt_author_of)
if err ~= sql.ROW then
stmnt_author_of:reset()
error("No author found for story:" .. storyid)
@ -59,7 +61,7 @@ local function edit_post(req)
assert(stmnt_update:bind_blob(2,compr) == sql.OK)
assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK)
assert(stmnt_update:bind(4,storyid) == sql.OK)
assert(do_sql(stmnt_update) == sql.DONE, "Failed to update text")
assert(util.do_sql(stmnt_update) == sql.DONE, "Failed to update text")
stmnt_update:reset()
tagslib.set(storyid,tags)
--[[
@ -83,3 +85,5 @@ local function edit_post(req)
http_response(req,303,"")
return
end
return edit_post

View File

@ -0,0 +1,16 @@
local cache = require("cache")
local config = require("config")
local pages = require("pages")
local function claim_get(req)
--Get the page to claim a name
local cachestr = string.format("%s/_claim",config.domain)
local text = cache.render(cachestr,function()
print("cache miss, rendering claim page")
return pages.claim{err=""}
end)
http_response(req,200,text)
end
return claim_get

View File

@ -0,0 +1,74 @@
local sql = require("lsqlite3")
local pages = require("pages")
local db = require("db")
local queries = require("queries")
local util = require("util")
local sessionlib = require("session")
local config = require("config")
local stmnt_author_create
local oldconfigure = configure
function configure(...)
stmnt_author_create = util.sqlassert(db.conn:prepare(queries.insert_author))
return oldconfigure(...)
end
local function claim_post(req)
--Actually claim a name
http_request_populate_post(req)
local name = assert(http_argument_get_string(req,"user"))
local text
--What in the world, Kore should be rejecting names that
--are not lower case & no symbols, but some still get through somehow.
if not name:match("^[a-z0-9]*$") then
print("Bad username:",name)
text = pages.claim{
err = "Usernames must match ^[a-z0-9]{1,30}$"
}
http_response(req,200,text)
return
end
local rngf = assert(io.open("/dev/urandom","rb"))
local passlength = string.byte(rngf:read(1)) + 64
local salt = rngf:read(64)
local password = rngf:read(passlength)
rngf:close()
local hash = sha3(salt .. password)
stmnt_author_create:bind_names{
name = name,
}
stmnt_author_create:bind_blob(2,salt)
stmnt_author_create:bind_blob(3,hash)
local err = util.do_sql(stmnt_author_create)
if err == sql.DONE then
print("success")
--We sucessfully made the new author
local id = stmnt_author_create:last_insert_rowid()
stmnt_author_create:reset()
--Give them a file back
http_response_header(req,"Content-Type","application/octet-stream")
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. name .. "." .. config.domain .. ".passfile\"")
local session = sessionlib.start(id)
text = password
print("session started, about to send password:",text)
http_response(req,200,text)
return
elseif err == sql.CONSTRAINT then
--If the creation failed, they probably just tried
--to use a name that was already taken
text = pages.claim {
err = "Failed to claim. That name may already be taken."
}
elseif err == sql.ERROR or err == sql.MISUSE then
--This is bad though
text = pages.claim {
err = "Failed to claim"
}
end
stmnt_author_create:reset()
http_response(req,200,text)
end
return claim_post

View File

@ -0,0 +1,45 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local db = require("db")
local queries = require("queries")
local util = require("util")
local pages = require("pages")
local stmnt_download
local oldconfigure = configure
function configure(...)
stmnt_download = assert(db.conn:prepare(queries.select_download))
return oldconfigure(...)
end
local function download_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
print("host:",host,"path:",path)
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = util.decode_id(story)
print("Downloading", story_id)
stmnt_download:bind_names{
postid = story_id
}
local err = util.do_sql(stmnt_download)
if err == sql.DONE then
--No rows, story not found
http_responose(req,404,pages.nostory{path=story})
stmnt_download:reset()
return
end
assert(err == sql.ROW, "after doing download sql, result was not a row, was:" .. tostring(err))
local txt_compressed, title = unpack(stmnt_download:get_values())
local text = zlib.decompress(txt_compressed)
stmnt_download:reset()
http_response_header(req,"Content-Type","application/octet-stream")
local nicetitle = title:gsub("%W","_")
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. nicetitle .. ".txt\"")
http_response(req,200,text)
end
return download_get

View File

@ -0,0 +1,68 @@
local zlib = require("zlib")
local sql = require("lsqlite3")
local db = require("db")
local queries = require("queries")
local util = require("util")
local pages = require("pages")
local tags = require("tags")
local session = require("session")
local config = require("config")
local stmnt_edit
local oldconfigure = configure
function configure(...)
stmnt_edit = assert(db.conn:prepare(queries.select_edit))
return oldconfigure(...)
end
local function edit_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local author, authorid = session.get(req)
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = util.decode_id(story)
local ret
print("we want to edit story:",story)
--Check that the logged in user is the owner of the story
--sql-side. If we're not the owner, we'll get 0 rows back.
stmnt_edit:bind_names{
postid = story_id,
authorid = authorid
}
local err = util.do_sql(stmnt_edit)
if err == sql.DONE then
--No rows, we're probably not the owner (it might
--also be because there's no such story)
ret = pages.cantedit{
path = story,
}
stmnt_edit:reset()
http_response(req,200,ret)
return
end
assert(err == sql.ROW)
local data = stmnt_edit:get_values()
local txt_compressed, markup, isanon, title = unpack(data)
local text = zlib.decompress(txt_compressed)
local tags = tags.get(story_id)
local tags_txt = table.concat(tags,";")
stmnt_edit:reset()
ret = pages.edit{
title = title,
text = text,
markup = markup,
user = author,
isanon = isanon == 1,
domain = config.domain,
story = story_id,
err = "",
tags = tags_txt
}
http_response(req,200,ret)
end
return edit_get

View File

@ -0,0 +1,89 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local db = require("db")
local queries = require("queries")
local pages = require("pages")
local parsers = require("parsers")
local util = require("util")
local tagslib = require("tags")
local cache = require("cache")
local config = require("config")
local session = require("session")
local stmnt_author_of, stmnt_update_raw, stmnt_update
local oldconfigure = configure
function configure(...)
stmnt_author_of = assert(db.conn:prepare(queries.select_author_of_post))
stmnt_update_raw = assert(db.conn:prepare(queries.update_raw))
stmnt_update = assert(db.conn:prepare(queries.update_post))
return oldconfigure(...)
end
local function edit_post(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local author, author_id = session.get(req)
http_request_populate_post(req)
local storyid = tonumber(assert(http_argument_get_string(req,"story")))
local title = assert(http_argument_get_string(req,"title"))
local text = assert(http_argument_get_string(req,"text"))
local pasteas = assert(http_argument_get_string(req,"pasteas"))
local markup = assert(http_argument_get_string(req,"markup"))
local tags_str = http_argument_get_string(req,"tags")
stmnt_author_of:bind_names{
id = storyid
}
local err = util.do_sql(stmnt_author_of)
if err ~= sql.ROW then
stmnt_author_of:reset()
error("No author found for story:" .. storyid)
end
local data = stmnt_author_of:get_values()
stmnt_author_of:reset()
local realauthor = data[1]
assert(realauthor == author_id) --Make sure the author of the story is the currently logged in user
local parsed = parsers[markup](text)
local compr_raw = zlib.compress(text)
local compr = zlib.compress(parsed)
local tags = {}
if tags_str then
tags = util.parse_tags(tags_str)
end
assert(stmnt_update_raw:bind_blob(1,compr_raw) == sql.OK)
assert(stmnt_update_raw:bind(2,markup) == sql.OK)
assert(stmnt_update_raw:bind(3,storyid) == sql.OK)
assert(util.do_sql(stmnt_update_raw) == sql.DONE, "Failed to update raw")
stmnt_update_raw:reset()
assert(stmnt_update:bind(1,title) == sql.OK)
assert(stmnt_update:bind_blob(2,compr) == sql.OK)
assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK)
assert(stmnt_update:bind(4,storyid) == sql.OK)
assert(util.do_sql(stmnt_update) == sql.DONE, "Failed to update text")
stmnt_update:reset()
tagslib.set(storyid,tags)
--[[
assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK)
do_sql(stmnt_drop_tags)
stmnt_drop_tags:reset()
for _,tag in pairs(tags) do
print("Looking at tag",tag)
assert(stmnt_ins_tag:bind(1,storyid) == sql.OK)
assert(stmnt_ins_tag:bind(2,tag) == sql.OK)
err = do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
]]
local id_enc = util.encode_id(storyid)
local loc = string.format("https://%s/%s",config.domain,id_enc)
cache.dirty(string.format("%s/%s",config.domain,id_enc)) -- This place to read this post
cache.dirty(string.format("%s",config.domain)) -- The site index (ex, if the author changed the paste from their's to "Anonymous", the cache should reflect that).
cache.dirty(string.format("%s.%s",author,config.domain)) -- The author's index, same reasoning as above.
http_response_header(req,"Location",loc)
http_response(req,303,"")
return
end
return edit_post

View File

@ -0,0 +1,115 @@
local sql = require("lsqlite3")
local cache = require("cache")
local queries = require("queries")
local db = require("db")
local util = require("util")
local config = require("config")
local pages = require("pages")
local libtags = require("tags")
local stmnt_index, stmnt_author, stmnt_author_bio
local oldconfigure = configure
function configure(...)
stmnt_index = assert(db.conn:prepare(queries.select_site_index))
--TODO: actually let authors edit their bio
stmnt_author_bio = assert(db.conn:prepare([[
SELECT authors.biography FROM authors WHERE authors.name = :author;
]]))
stmnt_author = assert(db.conn:prepare(queries.select_author_index))
return oldconfigure(...)
end
local function get_site_home(req)
print("Cache miss, rendering index")
stmnt_index:bind_names{}
local err = util.do_sql(stmnt_index)
local latest = {}
--err may be sql.ROW or sql.DONE if we don't have any stories yet
while err == sql.ROW do
local data = stmnt_index:get_values()
local storytags = libtags.get(data[1])
table.insert(latest,{
url = util.encode_id(data[1]),
title = data[2],
isanon = data[3] == 1,
posted = os.date("%B %d %Y",tonumber(data[4])),
author = data[5],
tags = storytags,
})
err = stmnt_index:step()
end
stmnt_index:reset()
return pages.index{
domain = config.domain,
stories = latest
}
end
local function get_author_home(req)
local host = http_request_get_host(req)
local subdomain = host:match("([^\\.]+)")
stmnt_author_bio:bind_names{author=subdomain}
local err = util.do_sql(stmnt_author_bio)
if err == sql.DONE then
print("No such author")
stmnt_author_bio:reset()
return pages.noauthor{
author = subdomain
}
end
print("err:",err)
assert(err == sql.ROW,"failed to get author:" .. subdomain .. " error:" .. tostring(err))
local data = stmnt_author_bio:get_values()
local bio = data[1]
stmnt_author_bio:reset()
print("Getting author's stories")
stmnt_author:bind_names{author=subdomain}
err = util.do_sql(stmnt_author)
print("err:",err)
local stories = {}
while err == sql.ROW do
local data = stmnt_author:get_values()
local id, title, time = unpack(data)
local tags = libtags.get(id)
table.insert(stories,{
url = util.encode_id(id),
title = title,
posted = os.date("%B %d %Y",tonumber(time)),
tags = tags,
})
err = stmnt_author:step()
end
stmnt_author:reset()
return pages.author_index{
domain=config.domain,
author=subdomain,
stories=stories,
bio=bio
}
end
local function index_get(req)
local method = http_method_text(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--Default home page
local subdomain = host:match("([^\\.]+)")
local text
if host == config.domain then
local cachepath = string.format("%s",config.domain)
text = cache.render(cachepath, function()
return get_site_home(req)
end)
else --author home page
local cachepath = string.format("%s.%s",subdomain,config.domain)
text = cache.render(cachepath, function()
return get_author_home(req)
end)
end
assert(text)
http_response(req,200,text)
end
return index_get

View File

@ -0,0 +1,17 @@
local config = require("config")
local cache = require("cache")
local config = require("config")
local pages = require("pages")
local function login_get(req)
--Just give them the login page
local ret = cache.render(string.format("%s/_login",config.domain),function()
return pages.login{
err = "",
}
end)
http_response(req,200,ret)
end
return login_get

View File

@ -0,0 +1,61 @@
local sql = require("lsqlite3")
local db = require("db")
local util = require("util")
local session = require("session")
local config = require("config")
local pages = require("pages")
local stmnt_author_acct
local oldconfigure = configure
function configure(...)
--Get the data we need to check if someone can log in
stmnt_author_acct = assert(db.conn:prepare([[
SELECT id, salt, passhash FROM authors WHERE name = :name;
]]))
return oldconfigure(...)
end
local function login_post(req)
--Try to log in
http_populate_multipart_form(req)
local name = assert(http_argument_get_string(req,"user"))
local pass = assert(http_file_get(req,"pass"))
stmnt_author_acct:bind_names{
name = name
}
local text
local err = util.do_sql(stmnt_author_acct)
if err == sql.ROW then
local id, salt, passhash = unpack(stmnt_author_acct:get_values())
stmnt_author_acct:reset()
local todigest = salt .. pass
local hash = sha3(todigest)
if hash == passhash then
local mysession = session.start(id)
http_response_cookie(req,"session",mysession,"/",0,0)
local loc = string.format("https://%s.%s",name,config.domain)
http_response_header(req,"Location",loc)
http_response(req,303,"")
return
else
text = pages.login{
err = "Incorrect username or password"
}
end
elseif err == sql.DONE then --Allows user enumeration, do we want this?
--Probably not a problem since all passwords are forced to be "good"
stmnt_author_acct:reset()
text = pages.login{
err = "Failed to find user:" .. name
}
else
stmnt_author_acct:reset()
error("Other sql error during login")
end
http_response(req,200,text)
end
return login_post

View File

@ -0,0 +1,96 @@
local config = require("config")
local session = require("session")
local pages = require("pages")
local cache = require("cache")
local function paste_get(req)
--Get the paste page
local host = http_request_get_host(req)
local text
local author,_ = session.get(req)
if host == config.domain and author then
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,config.domain))
http_response(req,303,"")
return
elseif host == config.domain and author == nil then
text = cache.render(string.format("%s/_paste",host),function()
print("Cache missing, rendering post page")
return pages.paste{
domain = config.domain,
err = "",
}
end)
http_response(req,200,text)
elseif host ~= config.domain and author then
text = pages.author_paste{
domain = config.domain,
user = author,
err = "",
text="",
}
elseif host ~= config.domain and author == nil then
http_response_header(req,"Location",string.format("https://%s/_paste",config.domain))
http_response(req,303,"")
else
error(string.format(
"Unable to find a good case for paste:%s,%s,%s",
host,
config.domain,
author
))
end
assert(text)
http_response(req,200,text)
--[=[
if host == config.domain then
local author,_ = get_session(req)
if author then
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,domain))
http_response(req,303,"")
return
else
--For an anonymous user
ret = cache.render(string.format("%s/_paste",host),function()
print("Cache missing, rendering post page")
return pages.paste{
domain = domain,
err = "",
}
end)
end
else
--Or for someone that's logged in
print("Looks like a logged in user wants to paste!")
local subdomain = host:match("([^%.]+)")
local author,_ = session.get(req)
print("subdomain:",subdomain,"author:",author)
--If they try to paste as an author, but are on the
--wrong subdomain, or or not logged in, redirect them
--to the right place. Their own subdomain for authors
--or the anonymous paste page for not logged in users.
if author == nil then
http_response_header(req,"Location","https://"..domain.."/_paste")
http_response(req,303,"")
return
end
if author ~= subdomain then
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,domain))
http_response(req,303,"")
return
end
assert(author == subdomain,"someone wants to paste as someone else")
--We're where we want to be, serve up this users's
--paste page. No cache, because how often is a user
--going to paste?
ret = pages.author_paste{
domain = domain,
user = author,
text = "",
err = "",
}
end
]=]
end
return paste_get

View File

@ -0,0 +1,194 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local util = require("util")
local parsers = require("parsers")
local config = require("config")
local queries = require("queries")
local db = require("db")
local cache = require("cache")
local tags = require("tags")
local session = require("session")
local stmnt_raw,stmnt_paste
local oldconfigure = configure
function configure(...)
stmnt_paste = assert(db.conn:prepare(queries.insert_post))
stmnt_raw = assert(db.conn:prepare(queries.insert_raw))
return oldconfigure(...)
end
local function anon_paste(req,ps)
--Public paste
--[[
This doesn't actually do much for IPv4 addresses,
since there are only 32 bits of address. Someone who
got a copy of the database could
just generate all 2^32 hashes and look up who posted
what. Use IPv6, Tor or I2P where possible. (but then I
guess it's harder to ban spammers... hmm..)
]]
--local ip = http_request_get_ip(req)
--local iphash = sha3(ip)
--Don't store this information for now, until I come up
--with a more elegent solution.
util.sqlbind(stmnt_paste,"bind_blob",1,ps.text)
--assert(stmnt_paste:bind_blob(1,text) == sql.OK)
util.sqlbind(stmnt_paste,"bind",2,ps.title)
--assert(stmnt_paste:bind(2,esctitle) == sql.OK)
util.sqlbind(stmnt_paste,"bind",3,-1)
--assert(stmnt_paste:bind(3,-1) == sql.OK)
util.sqlbind(stmnt_paste,"bind",4,true)
--assert(stmnt_paste:bind(4,true) == sql.OK)
util.sqlbind(stmnt_paste,"bind_blob",5,"")
--assert(stmnt_paste:bind_blob(5,"") == sql.OK)
err = util.do_sql(stmnt_paste)
stmnt_paste:reset()
if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid()
assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw)
stmnt_raw:reset()
if err ~= sql.DONE then
print("Failed to save raw text, but paste still went though")
end
tags.set(rowid,ps.tags)
--[[
for _,tag in pairs(ps.tags) do
print("tag 1:",stmnt_ins_tag:bind(1,rowid))
print("Looking at tag",tag)
print("tag 2:",stmnt_ins_tag:bind(2,tag))
err = util.do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
]]
local url = util.encode_id(rowid)
local loc = string.format("https://%s/%s",config.domain,url)
http_response_header(req,"Location",loc)
http_response(req,303,"")
cache.dirty(string.format("%s/%s",config.domain,url))
cache.dirty(string.format("%s",config.domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
ret = "Failed to paste: " .. tostring(err)
else
error("Error pasting:" .. tostring(err))
end
stmnt_paste:reset()
end
local function author_paste(req,ps)
--Author paste
local author, authorid = session.get(req)
if author == nil then
ret = pages.author_paste{
domain = domain,
author = subdomain,
err = "You are not logged in, you must be logged in to post as " .. subdomain .. ".",
text = text
}
end
local asanon = assert(http_argument_get_string(req,"pasteas"))
--No need to check if the author is posting to the
--"right" sudomain, just post it to the one they have
--the session key for.
assert(stmnt_paste:bind_blob(1,ps.text) == sql.OK)
assert(stmnt_paste:bind(2,ps.title) == sql.OK)
assert(stmnt_paste:bind(3,authorid) == sql.OK)
if asanon == "anonymous" then
assert(stmnt_paste:bind(4,true) == sql.OK)
else
assert(stmnt_paste:bind(4,false) == sql.OK)
end
assert(stmnt_paste:bind_blob(5,"") == sql.OK)
err = util.do_sql(stmnt_paste)
stmnt_paste:reset()
if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid()
assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw)
stmnt_raw:reset()
if err ~= sql.DONE then
print("Failed to save raw text, but paste still went through")
end
tags.set(rowid,ps.tags)
--[[
for _,tag in pairs(ps.tags) do
print("tag 1:",stmnt_ins_tag:bind(1,rowid))
print("Looking at tag",tag)
print("tag 2:",stmnt_ins_tag:bind(2,tag))
err = do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
]]
local url = util.encode_id(rowid)
local loc
if asanon == "anonymous" then
loc = string.format("https://%s/%s",config.domain,url)
else
loc = string.format("https://%s.%s/%s",author,config.domain,url)
end
http_response_header(req,"Location",loc)
http_response(req,303,"")
cache.dirty(string.format("%s.%s",author,config.domain))
cache.dirty(string.format("%s/%s",config.domain,url))
cache.dirty(string.format("%s",config.domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
ret = "Failed to paste: " .. tostring(err)
else
error("Error pasting:",err)
end
stmnt_paste:reset()
end
local function decodeentities(capture)
local n = tonumber(capture,16)
local c = string.char(n)
if escapes[c] then
return escapes[c]
else
return c
end
end
local function paste_post(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local ps = {}
--We're creatinga new paste
http_request_populate_post(req)
local title = assert(http_argument_get_string(req,"title"))
local text = assert(http_argument_get_string(req,"text"))
ps.markup = assert(http_argument_get_string(req,"markup"))
local tag_str = http_argument_get_string(req,"tags")
ps.tags = {}
if tag_str then
ps.tags = util.parse_tags(tag_str)
end
local pasteas
ps.raw = zlib.compress(text)
text = string.gsub(text,"%%(%x%x)",decodeentities)
text = parsers[ps.markup](text)
assert(text,"Failed to parse text")
text = zlib.compress(text)
assert(text,"Failed to compress text")
ps.text = text
local esctitle = string.gsub(title,"%%(%x%x)",decodeentities)
--Always sanatize the title with the plain parser. no markup
--in the title.
ps.title = parsers.plain(title)
if host == config.domain then
anon_paste(req,ps)
else
author_paste(req,ps)
end
end
--assert(ret)
--http_response(req,200,ret)
return paste_post

View File

@ -0,0 +1,33 @@
local parsers = require("parsers")
local tags = require("tags")
local util = require("util")
local pages = require("pages")
local config = require("config")
local function preview_post(req)
print("We want to preview a paste!")
local host = http_request_get_host(req)
local path = http_request_get_path(req)
http_request_populate_post(req)
local title = assert(http_argument_get_string(req,"title"))
local text = assert(http_argument_get_string(req,"text"))
local markup = assert(http_argument_get_string(req,"markup"))
local tag_str = http_argument_get_string(req,"tags")
local tags = {}
if tag_str then
tags = util.parse_tags(tag_str)
end
print("title:",title,"text:",text,"markup:",markup)
local parsed = parsers[markup](text)
local ret = pages.read{
domain = config.domain,
title = title,
author = "preview",
idp = "preview",
text = parsed,
tags = tags,
}
http_response(req,200,ret)
end
return preview_post

View File

@ -0,0 +1,169 @@
local sql = require("sqlite3")
local session = require("session")
local tags = require("tags")
local db = require("db")
local queries = require("queries")
local util = require("util")
local cache = require("cache")
local pages = require("pages")
local config = require("config")
local stmnt_read, stmnt_update_views, stmnt_comments
local oldconfigure = configure
function configure(...)
stmnt_read = assert(db.conn:prepare(queries.select_post))
stmnt_update_views = assert(db.conn:prepare(queries.update_views))
stmnt_comments = assert(db.conn:prepare(queries.select_comments))
return oldconfigure(...)
end
--[[
Increases a story's hit counter by 1
]]
local function add_view(storyid)
stmnt_update_views:bind_names{
id = storyid
}
local err = util.do_sql(stmnt_update_views)
assert(err == sql.DONE, "Failed to update view counter:"..tostring(err))
stmnt_update_views:reset()
end
--[[
Populates ps with story settings, returns true if story was found,
or nil if it wasn't
]]
local function populate_ps_story(req,ps)
--Make sure our story exists
stmnt_read:bind_names{
id = ps.storyid
}
local err = util.do_sql(stmnt_read)
if err == sql.DONE then
--We got no story
stmnt_read:reset()
error("No story by this name",ps.storyid)
return
end
--If we've made it here, we have a story. Populate our settings
--with title, text, ect.
assert(err == sql.ROW)
local title, storytext, tauthor, isanon, authorname, views = unpack(
stmnt_read:get_values()
)
ps.title = title
ps.text = zlib.decompress(storytext)
ps.tauthor = tauthor
ps.isanon = isanon == 1
ps.author = authorname
ps.views = views
stmnt_read:reset()
--Tags
ps.tags = tags.get(ps.storyid)
return true
end
--[[
Get the comments for a story
]]
local function get_comments(req,ps)
stmnt_comments:bind_names{
id = ps.storyid
}
err = util.do_sql(stmnt_comments)
local comments = {}
while err ~= sql.DONE do
local com_author, com_isanon, com_text = unpack(stmnt_comments:get_values())
table.insert(comments,{
author = com_author,
isanon = com_isanon == 1, --int to boolean
text = com_text
})
err = stmnt_comments:step()
end
stmnt_comments:reset()
return comments
end
--[[
The author is viewing their own story, give them an edit button
]]
local function read_get_author(req,storyid,author,authorid,comments)
end
--[[
An author is viewing a story, allow them to post comments as themselves
]]
local function read_get_loggedin(req,ps)
if ps.tauthor == ps.authorid then
--The story exists and we're logged in as the
--owner, display the edit button
return read_get_author(req,ps)
end
return pages.read(ps)
end
local function read_get(req)
--Pages settings
local ps = {
domain = config.domain,
host = http_request_get_host(req),
path = http_request_get_path(req),
method = http_method_text(req),
}
print("reading", ps.path)
--Get our story id
assert(string.len(ps.path) > 0,"Tried to read 0-length story id")
ps.idp = string.sub(ps.path,2)--remove leading "/"
ps.storyid = util.decode_id(ps.idp)
add_view(ps.storyid)
--If we're logged in, set author and authorid
local author, authorid = session.get(req)
if author and authorid then
ps.loggedauthor = author
ps.iam = author
ps.loggedauthorid = authorid
end
--If we need to show comments
http_request_populate_qs(req)
ps.show_comments = http_argument_get_string(req,"comments")
if ps.show_comments then
ps.comments = get_comments(req,ps)
end
local text
--normal story display
if (not ps.loggedauthor) then
print("not author")
local cachestr = string.format("%s%s%s",
ps.host,
ps.path,
ps.show_comments and "?comments=1" or ""
)
text = cache.render(cachestr,function()
populate_ps_story(req,ps)
local output = pages.read(ps)
assert(output,"failed to read page:" .. cachestr)
return output
end)
else --we are logged in, don't cache
print("is author")
populate_ps_story(req,ps)
print("tauthor was", ps.tauthor, "while author was:",ps.author)
ps.owner = (ps.loggedauthorid == ps.tauthor)
text = pages.read(ps)
end
assert(text)
http_response(req,200,text)
return
end
return read_get

View File

@ -0,0 +1,53 @@
local sql = require("sqlite3")
local cache = require("cache")
local session = require("session")
local util = require("util")
local db = require("db")
local queries = require("queries")
local config = require("config")
local stmnt_comment_insert
local oldconfigure = configure
function configure(...)
stmnt_comment_insert = assert(db.conn:prepare(queries.insert_comment))
return oldconfigure(...)
end
local function read_post(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--We're posting a comment
http_request_populate_post(req)
http_populate_cookies(req)
local author, authorid = session.get(req)
local comment_text = assert(http_argument_get_string(req,"text"))
local pasteas = assert(http_argument_get_string(req,"postas"))
local idp = string.sub(path,2)--remove leading "/"
local id = util.decode_id(idp)
local isanon = 1
--Even if an author is logged in, they may post their comment anonymously
if author and pasteas ~= "Anonymous" then
isanon = 0
end
stmnt_comment_insert:bind_names{
postid=id,
authorid = author and authorid or -1,
isanon = isanon,
comment_text = comment_text,
}
local err = util.do_sql(stmnt_comment_insert)
stmnt_comment_insert:reset()
if err ~= sql.DONE then
http_response(req,500,"Internal error, failed to post comment. Go back and try again.")
else
--When we post a comment, we need to dirty the cache for the "comments displayed" page.
cache.dirty(string.format("%s%s?comments=1",host,path))
local redir = string.format("https://%s%s?comments=1", config.domain, path)
http_response_header(req,"Location",redir)
http_response(req,303,"")
end
end
return read_post

View File

@ -0,0 +1,59 @@
local sql = require("lsqlite3")
local db = require("db")
local queries = require("queries")
local util = require("util")
local libtags = require("tags")
local pages = require("pages")
local config = require("config")
local stmnt_search
local oldconfigure = configure
function configure(...)
stmnt_search = assert(db.conn:prepare(queries.select_post_tags))
return oldconfigure(...)
end
local function search_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
http_request_populate_qs(req)
local tag = http_argument_get_string(req,"tag")
if tag then
stmnt_search:bind_names{
tag = tag
}
local results = {}
local err
repeat
err = stmnt_search:step()
if err == sql.BUSY then
coroutine.yield()
elseif err == sql.ROW then
local id, title, anon, time, author = unpack(stmnt_search:get_values())
local idp = util.encode_id(id)
local tags = libtags.get(id)
table.insert(results,{
id = idp,
title = title,
anon = anon,
time = os.date("%B %d %Y",tonumber(time)),
author = author,
tags = tags
})
elseif err == sql.DONE then
stmnt_search:reset()
else
error("Failed to search, sql error:" .. tostring(err))
end
until err == sql.DONE
local ret = pages.search{
domain = config.domain,
results = results,
tag = tag,
}
http_response(req,200,ret)
end
end
return search_get

View File

@ -6,6 +6,7 @@ local db = require("db")
local util = require("util")
local config = require("config")
local pages = require("pages")
local libtags = require("tags")
local stmnt_index, stmnt_author, stmnt_author_bio
@ -17,7 +18,7 @@ function configure(...)
SELECT authors.biography FROM authors WHERE authors.name = :author;
]]))
stmnt_author = assert(db.conn:prepare(queries.select_author_index))
return configure(...)
return oldconfigure(...)
end
local function get_site_home(req)
@ -28,9 +29,9 @@ local function get_site_home(req)
--err may be sql.ROW or sql.DONE if we don't have any stories yet
while err == sql.ROW do
local data = stmnt_index:get_values()
local storytags = tags.get(data[1])
local storytags = libtags.get(data[1])
table.insert(latest,{
url = encode_id(data[1]),
url = util.encode_id(data[1]),
title = data[2],
isanon = data[3] == 1,
posted = os.date("%B %d %Y",tonumber(data[4])),
@ -49,7 +50,7 @@ local function get_author_home(req)
local host = http_request_get_host(req)
local subdomain = host:match("([^\\.]+)")
stmnt_author_bio:bind_names{author=subdomain}
local err = do_sql(stmnt_author_bio)
local err = util.do_sql(stmnt_author_bio)
if err == sql.DONE then
print("No such author")
stmnt_author_bio:reset()
@ -64,15 +65,15 @@ local function get_author_home(req)
stmnt_author_bio:reset()
print("Getting author's stories")
stmnt_author:bind_names{author=subdomain}
err = do_sql(stmnt_author)
err = util.do_sql(stmnt_author)
print("err:",err)
local stories = {}
while err == sql.ROW do
local data = stmnt_author:get_values()
local id, title, time = unpack(data)
local tags = get_tags(id)
local tags = libtags.get(id)
table.insert(stories,{
url = encode_id(id),
url = util.encode_id(id),
title = title,
posted = os.date("%B %d %Y",tonumber(time)),
tags = tags,
@ -97,7 +98,7 @@ local function index_get(req)
local subdomain = host:match("([^\\.]+)")
local text
if host == config.domain then
local cachepath = string.format("%s",config.domain),
local cachepath = string.format("%s",config.domain)
text = cache.render(cachepath, function()
return get_site_home(req)
end)
@ -107,4 +108,8 @@ local function index_get(req)
return get_author_home(req)
end)
end
assert(text)
http_response(req,200,text)
end
return index_get

View File

@ -6,6 +6,9 @@ local et = require("etlua")
local sql = require("lsqlite3")
local zlib = require("zlib")
--stubs for overloading
function configure(...) end
--smr code
local cache = require("cache")
local pages = require("pages")
@ -22,10 +25,39 @@ for _,v in pairs(parser_names) do
parsers[v] = require("parser_" .. v)
end
]]
local endpoint_names = {
read = {"get","post"},
preview = {"post"},
index = {"get"},
paste = {"get","post"},
download = {"get"},
login = {"get","post"},
edit = {"get","post"},
claim = {"get","post"},
search = {"get"},
}
local endpoints = {}
for name, methods in pairs(endpoint_names) do
for _,method in pairs(methods) do
local epn = string.format("%s_%s",name,method)
endpoints[epn] = require("endpoints." .. epn)
end
end
--pages
read_get = require("read_get")
read_post = require("read_post")
preview_post = require("preview_post")
index_get = require("index_get")
paste_get = require("paste_get")
paste_post = require("paste_post")
download_get = require("download_get")
login_get = require("login_get")
login_post = require("login_post")
edit_get = require("edit_get")
edit_post = require("edit_post")
claim_get = require("claim_get")
claim_post = require("claim_post")
search_get = require("search_get")
--local db,cache --databases
--local domain = "test.monster:8888" --The domain to write links as
--[[
@ -117,7 +149,8 @@ end
print("Hello from init.lua")
function configure()
local oldconfigure = configure
function configure(...)
--db = sqlassert(sql.open("data/posts.db"))
----db = sqlassert(sql.open_memory())
--cache = sqlassert(sql.open_memory())
@ -128,7 +161,7 @@ function configure()
local one = zlib.compress(msg)
local two = zlib.decompress(one)
assert(two == msg, "zlib not working as expected")
oldconfigure(...)
--Create sql tables
--assert(db:exec(queries.create_table_authors))
--Create a fake "anonymous" user, so we don't run into trouble
@ -205,10 +238,10 @@ function configure()
]]))
]=]
--Create a new post
stmnt_paste = assert(db:prepare(queries.insert_post))
--stmnt_paste = assert(db:prepare(queries.insert_post))
--Keep a copy of the plain text of a post so we can edit it later
--It might also be useful for migrations, if that ever needs to happen
stmnt_raw = assert(db:prepare(queries.insert_raw))
--stmnt_raw = assert(db:prepare(queries.insert_raw))
--Tags for a story
--[[
stmnt_ins_tag = assert(db:prepare(queries.insert_tag))
@ -216,9 +249,9 @@ function configure()
stmnt_drop_tags = assert(db:prepare(queries.delete_tags))
]]
--Get the data we need to display the edit screen
stmnt_edit = assert(db:prepare(queries.select_edit))
--stmnt_edit = assert(db:prepare(queries.select_edit))
--Get the data we need when someone wants to download a paste
stmnt_download = assert(db:prepare(queries.select_download))
--stmnt_download = assert(db:prepare(queries.select_download))
--When we update a post, store the plaintext again
--stmnt_update_raw = assert(db:prepare(queries.update_raw))
--Should we really reset the update time every time someone makes a post?
@ -226,10 +259,10 @@ function configure()
--If it gets abused I can disable it I guess.
--stmnt_update = assert(db:prepare(queries.update_post))
--Check sessions for login support
stmnt_insert_session = assert(db:prepare(queries.insert_session))
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
--stmnt_insert_session = assert(db:prepare(queries.insert_session))
--stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
--Search by tag name
stmnt_search = assert(db:prepare(queries.select_post_tags))
--stmnt_search = assert(db:prepare(queries.select_post_tags))
--only refresh pages at most once every 10 seconds
--[=[
stmnt_cache = cache:prepare([[
@ -445,6 +478,10 @@ function home(req)
print("Hello from lua!")
print("Method:", http_method_text(req))
local method = http_method_text(req)
if method == "GET" then
endpoints.index_get(req)
end
--[=[
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local text
@ -522,6 +559,7 @@ function home(req)
end
assert(text)
http_response(req,200,text)
]=]
end
--We prevent people from changing their password file, this way we don't really
@ -530,6 +568,7 @@ end
--a while, but whatever.
function claim(req)
local method = http_method_text(req)
--[[
local host = http_request_get_host(req)
local path = http_request_get_path(req)
if host ~= domain then
@ -539,13 +578,19 @@ function claim(req)
end
assert(host == domain)
local text
]]
if method == "GET" then
endpoints.claim_get(req)
--[=[
--Get the page to claim a name
text = cache.render(string.format("%s/_claim",domain),function()
print("cache miss, rendering claim page")
return pages.claim{err=""}
end)
]=]
elseif method == "POST" then
endpoints.claim_post(req)
--[=[
--Actually claim a name
http_request_populate_post(req)
local name = assert(http_argument_get_string(req,"user"))
@ -593,18 +638,21 @@ function claim(req)
}
end
stmnt_author_create:reset()
]=]
end
assert(text)
http_response(req,200,text)
--assert(text)
--http_response(req,200,text)
end
function paste(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--local host = http_request_get_host(req)
--local path = http_request_get_path(req)
local method = http_method_text(req)
local err
local ret
--local err
--local ret
if method == "GET" then
endpoints.paste_get(req)
--[=[
--Get the paste page
if host == domain then
local author,_ = get_session(req)
@ -654,7 +702,10 @@ function paste(req)
err = "",
}
end
]=]
elseif method == "POST" then
endpoints.paste_post(req)
--[=[
--We're creatinga new paste
http_request_populate_post(req)
local title = assert(http_argument_get_string(req,"title"))
@ -795,9 +846,11 @@ function paste(req)
end
stmnt_paste:reset()
end
]=]
end
assert(ret)
http_response(req,200,ret)
--assert(ret)
--http_response(req,200,ret)
end
--A helper function for below
@ -873,11 +926,11 @@ local function read_story(host,path,idp,show_comments,iam)
end
function read(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--local host = http_request_get_host(req)
--local path = http_request_get_path(req)
local method = http_method_text(req)
if method == "GET" then
read_get(req)
endpoints.read_get(req)
--[=[
local idp = string.sub(path,2)--remove leading "/"
assert(string.len(path) > 0,"Tried to read 0-length story id")
@ -942,7 +995,7 @@ function read(req)
return
]=]
elseif method == "POST" then
read_post(req)
endpoints.read_post(req)
--[=[
--We're posting a comment
http_request_populate_post(req)
@ -979,9 +1032,10 @@ function read(req)
end
function login(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--local host = http_request_get_host(req)
--local path = http_request_get_path(req)
local method = http_method_text(req)
--[[
if host ~= domain then
--Don't allow logging into subdomains, I guess
http_response_header(req,"Location",string.format("https://%s/_login",domain))
@ -989,14 +1043,20 @@ function login(req)
return
end
local text
]]
if method == "GET" then
endpoints.login_get(req)
--[=[
--Just give them the login page
text = cache.render(string.format("%s/_login",domain),function()
return pages.login{
err = "",
}
end)
]=]
elseif method == "POST" then
endpoints.login_post(req)
--[=[
--Try to log in
http_populate_multipart_form(req)
local name = assert(http_argument_get_string(req,"user"))
@ -1032,19 +1092,22 @@ function login(req)
stmnt_author_acct:reset()
error("Other sql error during login")
end
]=]
end
assert(text)
http_response(req,200,text)
--assert(text)
--http_response(req,200,text)
end
--Edit a story
function edit(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
--local host = http_request_get_host(req)
--local path = http_request_get_path(req)
local method = http_method_text(req)
local author, author_id = get_session(req)
local ret
--local author, author_id = get_session(req)
--local ret
if method == "GET" then
endpoints.edit_get(req)
--[=[
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = decode_id(story)
@ -1084,7 +1147,10 @@ function edit(req)
err = "",
tags = tags_txt
}
]=]
elseif method == "POST" then
endpoints.edit_post(req)
--[=[
http_request_populate_post(req)
local storyid = tonumber(assert(http_argument_get_string(req,"story")))
local title = assert(http_argument_get_string(req,"title"))
@ -1141,9 +1207,10 @@ function edit(req)
http_response_header(req,"Location",loc)
http_response(req,303,"")
return
]=]
end
assert(ret)
http_response(req,200,ret)
--assert(ret)
--http_response(req,200,ret)
end
--TODO
@ -1163,6 +1230,8 @@ function teardown()
end
function download(req)
endpoints.download_get(req)
--[[
local host = http_request_get_host(req)
local path = http_request_get_path(req)
print("host:",host,"path:",path)
@ -1188,10 +1257,11 @@ function download(req)
local nicetitle = title:gsub("%W","_")
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. nicetitle .. ".txt\"")
http_response(req,200,text)
]]
end
function preview(req)
preview_post(req)
endpoints.preview_post(req)
--[[
print("We want to preview a paste!")
local host = http_request_get_host(req)
@ -1220,6 +1290,8 @@ function preview(req)
end
function search(req)
endpoints.search_get(req)
--[=[
local host = http_request_get_host(req)
local path = http_request_get_path(req)
http_request_populate_qs(req)
@ -1259,6 +1331,7 @@ function search(req)
}
http_response(req,200,ret)
end
]=]
end
print("Done with init.lua")

View File

@ -1,11 +1,17 @@
local config = require("config")
local cache = require("cache")
local config = require("config")
local pages = require("pages")
local function login_get(req)
--Just give them the login page
return cache.render(string.format("%s/_login",domain),function()
local ret = cache.render(string.format("%s/_login",config.domain),function()
return pages.login{
err = "",
}
end)
http_response(req,200,ret)
end
return login_get

View File

@ -2,6 +2,9 @@ local sql = require("lsqlite3")
local db = require("db")
local util = require("util")
local session = require("session")
local config = require("config")
local pages = require("pages")
local stmnt_author_acct
@ -12,7 +15,7 @@ function configure(...)
SELECT id, salt, passhash FROM authors WHERE name = :name;
]]))
return configure(...)
return oldconfigure(...)
end
local function login_post(req)
@ -31,9 +34,9 @@ local function login_post(req)
local todigest = salt .. pass
local hash = sha3(todigest)
if hash == passhash then
local session = start_session(id)
http_response_cookie(req,"session",session,"/",0,0)
local loc = string.format("https://%s.%s",name,domain)
local mysession = session.start(id)
http_response_cookie(req,"session",mysession,"/",0,0)
local loc = string.format("https://%s.%s",name,config.domain)
http_response_header(req,"Location",loc)
http_response(req,303,"")
return

View File

@ -1,10 +1,13 @@
local config = require("config")
local session = require("session")
local pages = require("pages")
local cache = require("cache")
local function paste_get(req)
--Get the paste page
local host = http_request_get_host(req)
local text
local author,_ = get_session(req)
local author,_ = session.get(req)
if host == config.domain and author then
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,config.domain))
http_response(req,303,"")
@ -13,15 +16,21 @@ local function paste_get(req)
text = cache.render(string.format("%s/_paste",host),function()
print("Cache missing, rendering post page")
return pages.paste{
domain = domain,
domain = config.domain,
err = "",
}
end)
http_response(req,200,text)
elseif host ~= config.domain and author then
text = pages.author_paste{
domain = config.domain,
user = author,
err = "",
text="",
}
elseif host ~= config.domain and author == nil then
http_response_header(req,"Location",string.format("https://%s/_paste",config.domain))
http_response(req,303,"")
else
error(string.format(
"Unable to find a good case for paste:%s,%s,%s",
@ -30,6 +39,9 @@ local function paste_get(req)
author
))
end
assert(text)
http_response(req,200,text)
--[=[
if host == config.domain then
local author,_ = get_session(req)
if author then
@ -51,7 +63,7 @@ local function paste_get(req)
--Or for someone that's logged in
print("Looks like a logged in user wants to paste!")
local subdomain = host:match("([^%.]+)")
local author,_ = get_session(req)
local author,_ = session.get(req)
print("subdomain:",subdomain,"author:",author)
--If they try to paste as an author, but are on the
--wrong subdomain, or or not logged in, redirect them
@ -78,4 +90,7 @@ local function paste_get(req)
err = "",
}
end
]=]
end
return paste_get

194
src/lua/paste_post.lua Normal file
View File

@ -0,0 +1,194 @@
local sql = require("lsqlite3")
local zlib = require("zlib")
local util = require("util")
local parsers = require("parsers")
local config = require("config")
local queries = require("queries")
local db = require("db")
local cache = require("cache")
local tags = require("tags")
local session = require("session")
local stmnt_raw,stmnt_paste
local oldconfigure = configure
function configure(...)
stmnt_paste = assert(db.conn:prepare(queries.insert_post))
stmnt_raw = assert(db.conn:prepare(queries.insert_raw))
return oldconfigure(...)
end
local function anon_paste(req,ps)
--Public paste
--[[
This doesn't actually do much for IPv4 addresses,
since there are only 32 bits of address. Someone who
got a copy of the database could
just generate all 2^32 hashes and look up who posted
what. Use IPv6, Tor or I2P where possible. (but then I
guess it's harder to ban spammers... hmm..)
]]
--local ip = http_request_get_ip(req)
--local iphash = sha3(ip)
--Don't store this information for now, until I come up
--with a more elegent solution.
util.sqlbind(stmnt_paste,"bind_blob",1,ps.text)
--assert(stmnt_paste:bind_blob(1,text) == sql.OK)
util.sqlbind(stmnt_paste,"bind",2,ps.title)
--assert(stmnt_paste:bind(2,esctitle) == sql.OK)
util.sqlbind(stmnt_paste,"bind",3,-1)
--assert(stmnt_paste:bind(3,-1) == sql.OK)
util.sqlbind(stmnt_paste,"bind",4,true)
--assert(stmnt_paste:bind(4,true) == sql.OK)
util.sqlbind(stmnt_paste,"bind_blob",5,"")
--assert(stmnt_paste:bind_blob(5,"") == sql.OK)
err = util.do_sql(stmnt_paste)
stmnt_paste:reset()
if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid()
assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw)
stmnt_raw:reset()
if err ~= sql.DONE then
print("Failed to save raw text, but paste still went though")
end
tags.set(rowid,ps.tags)
--[[
for _,tag in pairs(ps.tags) do
print("tag 1:",stmnt_ins_tag:bind(1,rowid))
print("Looking at tag",tag)
print("tag 2:",stmnt_ins_tag:bind(2,tag))
err = util.do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
]]
local url = util.encode_id(rowid)
local loc = string.format("https://%s/%s",config.domain,url)
http_response_header(req,"Location",loc)
http_response(req,303,"")
cache.dirty(string.format("%s/%s",config.domain,url))
cache.dirty(string.format("%s",config.domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
ret = "Failed to paste: " .. tostring(err)
else
error("Error pasting:" .. tostring(err))
end
stmnt_paste:reset()
end
local function author_paste(req,ps)
--Author paste
local author, authorid = session.get(req)
if author == nil then
ret = pages.author_paste{
domain = domain,
author = subdomain,
err = "You are not logged in, you must be logged in to post as " .. subdomain .. ".",
text = text
}
end
local asanon = assert(http_argument_get_string(req,"pasteas"))
--No need to check if the author is posting to the
--"right" sudomain, just post it to the one they have
--the session key for.
assert(stmnt_paste:bind_blob(1,ps.text) == sql.OK)
assert(stmnt_paste:bind(2,ps.title) == sql.OK)
assert(stmnt_paste:bind(3,authorid) == sql.OK)
if asanon == "anonymous" then
assert(stmnt_paste:bind(4,true) == sql.OK)
else
assert(stmnt_paste:bind(4,false) == sql.OK)
end
assert(stmnt_paste:bind_blob(5,"") == sql.OK)
err = util.do_sql(stmnt_paste)
stmnt_paste:reset()
if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid()
assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw)
stmnt_raw:reset()
if err ~= sql.DONE then
print("Failed to save raw text, but paste still went through")
end
tags.set(rowid,ps.tags)
--[[
for _,tag in pairs(ps.tags) do
print("tag 1:",stmnt_ins_tag:bind(1,rowid))
print("Looking at tag",tag)
print("tag 2:",stmnt_ins_tag:bind(2,tag))
err = do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
]]
local url = util.encode_id(rowid)
local loc
if asanon == "anonymous" then
loc = string.format("https://%s/%s",config.domain,url)
else
loc = string.format("https://%s.%s/%s",author,config.domain,url)
end
http_response_header(req,"Location",loc)
http_response(req,303,"")
cache.dirty(string.format("%s.%s",author,config.domain))
cache.dirty(string.format("%s/%s",config.domain,url))
cache.dirty(string.format("%s",config.domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
ret = "Failed to paste: " .. tostring(err)
else
error("Error pasting:",err)
end
stmnt_paste:reset()
end
local function decodeentities(capture)
local n = tonumber(capture,16)
local c = string.char(n)
if escapes[c] then
return escapes[c]
else
return c
end
end
local function paste_post(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
local ps = {}
--We're creatinga new paste
http_request_populate_post(req)
local title = assert(http_argument_get_string(req,"title"))
local text = assert(http_argument_get_string(req,"text"))
ps.markup = assert(http_argument_get_string(req,"markup"))
local tag_str = http_argument_get_string(req,"tags")
ps.tags = {}
if tag_str then
ps.tags = util.parse_tags(tag_str)
end
local pasteas
ps.raw = zlib.compress(text)
text = string.gsub(text,"%%(%x%x)",decodeentities)
text = parsers[ps.markup](text)
assert(text,"Failed to parse text")
text = zlib.compress(text)
assert(text,"Failed to compress text")
ps.text = text
local esctitle = string.gsub(title,"%%(%x%x)",decodeentities)
--Always sanatize the title with the plain parser. no markup
--in the title.
ps.title = parsers.plain(title)
if host == config.domain then
anon_paste(req,ps)
else
author_paste(req,ps)
end
end
--assert(ret)
--http_response(req,200,ret)
return paste_post

View File

@ -2,6 +2,7 @@ local parsers = require("parsers")
local tags = require("tags")
local util = require("util")
local pages = require("pages")
local config = require("config")
local function preview_post(req)
print("We want to preview a paste!")
@ -19,7 +20,7 @@ local function preview_post(req)
print("title:",title,"text:",text,"markup:",markup)
local parsed = parsers[markup](text)
local ret = pages.read{
domain = domain,
domain = config.domain,
title = title,
author = "preview",
idp = "preview",

View File

@ -4,6 +4,10 @@ local session = require("session")
local tags = require("tags")
local db = require("db")
local queries = require("queries")
local util = require("util")
local cache = require("cache")
local pages = require("pages")
local config = require("config")
local stmnt_read, stmnt_update_views, stmnt_comments
@ -12,7 +16,7 @@ function configure(...)
stmnt_read = assert(db.conn:prepare(queries.select_post))
stmnt_update_views = assert(db.conn:prepare(queries.update_views))
stmnt_comments = assert(db.conn:prepare(queries.select_comments))
return configure(...)
return oldconfigure(...)
end
@ -23,7 +27,7 @@ local function add_view(storyid)
stmnt_update_views:bind_names{
id = storyid
}
local err = do_sql(stmnt_update_views)
local err = util.do_sql(stmnt_update_views)
assert(err == sql.DONE, "Failed to update view counter:"..tostring(err))
stmnt_update_views:reset()
end
@ -37,28 +41,28 @@ local function populate_ps_story(req,ps)
stmnt_read:bind_names{
id = ps.storyid
}
local err = do_sql(stmnt_read)
local err = util.do_sql(stmnt_read)
if err == sql.DONE then
--We got no story
stmnt_read:reset()
return nil
error("No story by this name",ps.storyid)
return
end
--If we've made it here, we have a story. Populate our settings
--with title, text, ect.
assert(err == sql.ROW)
add_view(storyid)
local title, storytext, tauthor, isanon, authorname, views = unpack(
stmnt_read:get_values()
)
ps.title = title
ps.storytext = zlib.decompress(storytext)
ps.text = zlib.decompress(storytext)
ps.tauthor = tauthor
ps.isanon = isanon == 1
ps.authorname = authorname
ps.author = authorname
ps.views = views
stmnt_read:reset()
--Tags
ps.tags = tags.get(id)
ps.tags = tags.get(ps.storyid)
return true
end
@ -66,7 +70,22 @@ end
Get the comments for a story
]]
local function get_comments(req,ps)
stmnt_comments:bind_names{
id = ps.storyid
}
err = util.do_sql(stmnt_comments)
local comments = {}
while err ~= sql.DONE do
local com_author, com_isanon, com_text = unpack(stmnt_comments:get_values())
table.insert(comments,{
author = com_author,
isanon = com_isanon == 1, --int to boolean
text = com_text
})
err = stmnt_comments:step()
end
stmnt_comments:reset()
return comments
end
--[[
@ -92,21 +111,25 @@ end
local function read_get(req)
--Pages settings
local ps = {
domain = config.domain,
host = http_request_get_host(req),
path = http_request_get_path(req),
method = http_method_text(req),
}
print("reading", ps.path)
--Get our story id
assert(string.len(ps.path) > 0,"Tried to read 0-length story id")
local idp = string.sub(ps.path,2)--remove leading "/"
ps.storyid = util.decode_id(idp)
ps.idp = string.sub(ps.path,2)--remove leading "/"
ps.storyid = util.decode_id(ps.idp)
add_view(ps.storyid)
--If we're logged in, set author and authorid
local author, authorid = session.get(req)
if author and authorid then
ps.author = author
ps.authorid = authorid
ps.loggedauthor = author
ps.iam = author
ps.loggedauthorid = authorid
end
--If we need to show comments
@ -116,20 +139,26 @@ local function read_get(req)
ps.comments = get_comments(req,ps)
end
local text
--normal story display
if (not ps.author) then
if (not ps.loggedauthor) then
print("not author")
local cachestr = string.format("%s%s%s",
ps.host,
ps.path,
ps.show_comments and "?comments=1" or ""
)
local text = cache.render(cachestr,function()
text = cache.render(cachestr,function()
populate_ps_story(req,ps)
return pages.read(ps)
local output = pages.read(ps)
assert(output,"failed to read page:" .. cachestr)
return output
end)
else --we are logged in, don't cache
print("is author")
populate_ps_story(req,ps)
ps.owner = (ps.author == ps.tauthor)
print("tauthor was", ps.tauthor, "while author was:",ps.author)
ps.owner = (ps.loggedauthorid == ps.tauthor)
text = pages.read(ps)
end
assert(text)

View File

@ -44,7 +44,7 @@ local function read_post(req)
else
--When we post a comment, we need to dirty the cache for the "comments displayed" page.
cache.dirty(string.format("%s%s?comments=1",host,path))
local redir = string.format("https://%s%s?comments=1", domain, path)
local redir = string.format("https://%s%s?comments=1", config.domain, path)
http_response_header(req,"Location",redir)
http_response(req,303,"")
end

59
src/lua/search_get.lua Normal file
View File

@ -0,0 +1,59 @@
local sql = require("lsqlite3")
local db = require("db")
local queries = require("queries")
local util = require("util")
local libtags = require("tags")
local pages = require("pages")
local config = require("config")
local stmnt_search
local oldconfigure = configure
function configure(...)
stmnt_search = assert(db.conn:prepare(queries.select_post_tags))
return oldconfigure(...)
end
local function search_get(req)
local host = http_request_get_host(req)
local path = http_request_get_path(req)
http_request_populate_qs(req)
local tag = http_argument_get_string(req,"tag")
if tag then
stmnt_search:bind_names{
tag = tag
}
local results = {}
local err
repeat
err = stmnt_search:step()
if err == sql.BUSY then
coroutine.yield()
elseif err == sql.ROW then
local id, title, anon, time, author = unpack(stmnt_search:get_values())
local idp = util.encode_id(id)
local tags = libtags.get(id)
table.insert(results,{
id = idp,
title = title,
anon = anon,
time = os.date("%B %d %Y",tonumber(time)),
author = author,
tags = tags
})
elseif err == sql.DONE then
stmnt_search:reset()
else
error("Failed to search, sql error:" .. tostring(err))
end
until err == sql.DONE
local ret = pages.search{
domain = config.domain,
results = results,
tag = tag,
}
http_response(req,200,ret)
end
end
return search_get

View File

@ -2,12 +2,13 @@ local sql = require("lsqlite3")
local db = require("db")
local util = require("util")
local queries = require("queries")
local oldconfigure = configure
local stmnt_get_session, stmnt_insert_session
function configure(...)
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
stmnt_get_session = assert(db.conn:prepare(queries.select_valid_sessions))
stmnt_insert_session = assert(db.conn:prepare(queries.insert_session))
return oldconfigure(...)
end
@ -41,7 +42,7 @@ end
--[[
Start a session for someone who logged in
]]
local function start_session(who)
function session.start(who)
local rngf = assert(io.open("/dev/urandom","rb"))
local session_t = {}
for i = 1,64 do

View File

@ -1,3 +1,5 @@
local sql = require("lsqlite3")
local db = require("db")
local queries = require("queries")
local util = require("util")
@ -12,7 +14,7 @@ function configure(...)
stmnt_get_tags = assert(db.conn:prepare(queries.select_tags))
stmnt_drop_tags = assert(db.conn:prepare(queries.delete_tags))
return configure(...)
return oldconfigure(...)
end
@ -41,6 +43,7 @@ function tags.set(storyid,tags)
assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK)
util.do_sql(stmnt_drop_tags)
stmnt_drop_tags:reset()
local err
for _,tag in pairs(tags) do
print("Looking at tag",tag)
assert(stmnt_ins_tag:bind(1,storyid) == sql.OK)
@ -48,6 +51,9 @@ function tags.set(storyid,tags)
err = util.do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset()
end
if err ~= sql.DONE then
print("Failed to save tags, but paste and raw still went through")
end
end

View File

@ -1,4 +1,6 @@
local sql = require("lsqlite3")
local util = {}
--[[
@ -19,16 +21,34 @@ Continuously tries to perform an sql statement until it goes through
function util.do_sql(stmnt)
if not stmnt then error("No statement",2) end
local err
local i = 0
repeat
err = stmnt:step()
print("After stepping, err is", err)
if err == sql.BUSY then
i = i + 1
coroutine.yield()
end
until(err ~= sql.BUSY)
until(err ~= sql.BUSY or i > 10)
assert(i < 10, "Database busy")
return err
end
--[[
Binds an argument to as statement with nice error reporting on failure
stmnt :: sql.stmnt - the prepared sql statemnet
call :: string - a string "bind" or "bind_blob"
position :: number - the argument position to bind to
data :: string - The data to bind
]]
function util.sqlbind(stmnt,call,position,data)
assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call)
local f = stmnt[call](stmnt,position,data)
if f ~= sql.OK then
error(string.format("Failed to %s at %d with %q: %s", call, position, data, db:errmsg()),2)
end
end
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
--no underscore because we use that for our operative pages
local url_characters =

View File

@ -22,7 +22,7 @@
<%- title %>
</h2>
<h3>
<% if isanon then -%>
<% if isanon or author == nil then -%>
By Anonymous
<% else -%>
By <a href="https://<%= author %>.<%= domain %>"><%= author %></a>

View File

@ -45,18 +45,31 @@ KORE_SECCOMP_FILTER("app",
KORE_SYSCALL_ALLOW(pwrite64),
KORE_SYSCALL_ALLOW(fdatasync),
KORE_SYSCALL_ALLOW(unlinkat),
KORE_SYSCALL_ALLOW(mremap)
KORE_SYSCALL_ALLOW(mremap),
KORE_SYSCALL_ALLOW(newfstatat)
);
int
errhandeler(lua_State *L){
printf("Error: %s\n",lua_tostring(L,1));
lua_getglobal(L,"debug");
lua_getglobal(L,"print");
lua_getfield(L,-2,"traceback");
lua_call(L,0,1);
lua_call(L,1,0);
lua_pop(L,1);
printf("Error: %s\n",lua_tostring(L,1));//"error"
lua_getglobal(L,"debug");//"error",{debug}
lua_getglobal(L,"print");//"error",{debug},print()
lua_getfield(L,-2,"traceback");//"error",{debug},print(),traceback()
lua_call(L,0,1);//"error",{debug},print(),"traceback"
lua_call(L,1,0);//"error",{debug}
printf("Called print()\n");
lua_getfield(L,-1,"traceback");//"error",{debug},traceback()
printf("got traceback\n");
lua_call(L,0,1);//"error",{debug},"traceback"
lua_pushstring(L,"\n");
printf("called traceback\n");
lua_pushvalue(L,-4);//"error",{debug},"traceback","error"
printf("pushed error\n");
lua_concat(L,3);//"error",{debug},"traceback .. error"
printf("concated\n");
int ref = luaL_ref(L,LUA_REGISTRYINDEX);//"error",{debug}
lua_pop(L,2);//
lua_rawgeti(L,LUA_REGISTRYINDEX,ref);//"traceback .. error"
return 1;
}