From a640096bdd6e69f2bef6c0421bf6adc32d79cbb4 Mon Sep 17 00:00:00 2001 From: Robin Malley Date: Mon, 21 Dec 2020 04:22:22 +0000 Subject: [PATCH] More work on refactor --- Makefile | 9 ++ spec/home_spec.lua | 28 +++++ spec/pages_sanity_spec.lua | 106 ++++++++++++++++ src/lua/cache.lua | 15 +-- src/lua/claim_post.lua | 18 ++- src/lua/db.lua | 10 +- src/lua/download_get.lua | 45 +++++++ src/lua/edit_get.lua | 14 ++- src/lua/edit_post.lua | 10 +- src/lua/endpoints/claim_get.lua | 16 +++ src/lua/endpoints/claim_post.lua | 74 +++++++++++ src/lua/endpoints/download_get.lua | 45 +++++++ src/lua/endpoints/edit_get.lua | 68 ++++++++++ src/lua/endpoints/edit_post.lua | 89 +++++++++++++ src/lua/endpoints/index_get.lua | 115 +++++++++++++++++ src/lua/endpoints/login_get.lua | 17 +++ src/lua/endpoints/login_post.lua | 61 +++++++++ src/lua/endpoints/paste_get.lua | 96 ++++++++++++++ src/lua/endpoints/paste_post.lua | 194 +++++++++++++++++++++++++++++ src/lua/endpoints/preview_post.lua | 33 +++++ src/lua/endpoints/read_get.lua | 169 +++++++++++++++++++++++++ src/lua/endpoints/read_post.lua | 53 ++++++++ src/lua/endpoints/search_get.lua | 59 +++++++++ src/lua/index_get.lua | 21 ++-- src/lua/init.lua | 137 +++++++++++++++----- src/lua/login_get.lua | 8 +- src/lua/login_post.lua | 11 +- src/lua/paste_get.lua | 25 +++- src/lua/paste_post.lua | 194 +++++++++++++++++++++++++++++ src/lua/preview_post.lua | 3 +- src/lua/read_get.lua | 63 +++++++--- src/lua/read_post.lua | 2 +- src/lua/search_get.lua | 59 +++++++++ src/lua/session.lua | 7 +- src/lua/tags.lua | 8 +- src/lua/util.lua | 22 +++- src/pages/read.etlua | 2 +- src/smr.c | 29 +++-- 38 files changed, 1829 insertions(+), 106 deletions(-) create mode 100644 spec/home_spec.lua create mode 100644 spec/pages_sanity_spec.lua create mode 100644 src/lua/download_get.lua create mode 100644 src/lua/endpoints/claim_get.lua create mode 100644 src/lua/endpoints/claim_post.lua create mode 100644 src/lua/endpoints/download_get.lua create mode 100644 src/lua/endpoints/edit_get.lua create mode 100644 src/lua/endpoints/edit_post.lua create mode 100644 src/lua/endpoints/index_get.lua create mode 100644 src/lua/endpoints/login_get.lua create mode 100644 src/lua/endpoints/login_post.lua create mode 100644 src/lua/endpoints/paste_get.lua create mode 100644 src/lua/endpoints/paste_post.lua create mode 100644 src/lua/endpoints/preview_post.lua create mode 100644 src/lua/endpoints/read_get.lua create mode 100644 src/lua/endpoints/read_post.lua create mode 100644 src/lua/endpoints/search_get.lua create mode 100644 src/lua/paste_post.lua create mode 100644 src/lua/search_get.lua diff --git a/Makefile b/Makefile index c2496bb..7f8ed35 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/spec/home_spec.lua b/spec/home_spec.lua new file mode 100644 index 0000000..01f120e --- /dev/null +++ b/spec/home_spec.lua @@ -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) diff --git a/spec/pages_sanity_spec.lua b/spec/pages_sanity_spec.lua new file mode 100644 index 0000000..35b38da --- /dev/null +++ b/spec/pages_sanity_spec.lua @@ -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) diff --git a/src/lua/cache.lua b/src/lua/cache.lua index 6290cef..4880135 100644 --- a/src/lua/cache.lua +++ b/src/lua/cache.lua @@ -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 diff --git a/src/lua/claim_post.lua b/src/lua/claim_post.lua index a950fe8..ae4d149 100644 --- a/src/lua/claim_post.lua +++ b/src/lua/claim_post.lua @@ -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 diff --git a/src/lua/db.lua b/src/lua/db.lua index f8dee8c..3810e3e 100644 --- a/src/lua/db.lua +++ b/src/lua/db.lua @@ -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 diff --git a/src/lua/download_get.lua b/src/lua/download_get.lua new file mode 100644 index 0000000..48f4593 --- /dev/null +++ b/src/lua/download_get.lua @@ -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 diff --git a/src/lua/edit_get.lua b/src/lua/edit_get.lua index 600e0e1..1881a58 100644 --- a/src/lua/edit_get.lua +++ b/src/lua/edit_get.lua @@ -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 diff --git a/src/lua/edit_post.lua b/src/lua/edit_post.lua index 7bb093e..0e612d8 100644 --- a/src/lua/edit_post.lua +++ b/src/lua/edit_post.lua @@ -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 diff --git a/src/lua/endpoints/claim_get.lua b/src/lua/endpoints/claim_get.lua new file mode 100644 index 0000000..cd13079 --- /dev/null +++ b/src/lua/endpoints/claim_get.lua @@ -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 diff --git a/src/lua/endpoints/claim_post.lua b/src/lua/endpoints/claim_post.lua new file mode 100644 index 0000000..ae4d149 --- /dev/null +++ b/src/lua/endpoints/claim_post.lua @@ -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 diff --git a/src/lua/endpoints/download_get.lua b/src/lua/endpoints/download_get.lua new file mode 100644 index 0000000..48f4593 --- /dev/null +++ b/src/lua/endpoints/download_get.lua @@ -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 diff --git a/src/lua/endpoints/edit_get.lua b/src/lua/endpoints/edit_get.lua new file mode 100644 index 0000000..1881a58 --- /dev/null +++ b/src/lua/endpoints/edit_get.lua @@ -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 diff --git a/src/lua/endpoints/edit_post.lua b/src/lua/endpoints/edit_post.lua new file mode 100644 index 0000000..0e612d8 --- /dev/null +++ b/src/lua/endpoints/edit_post.lua @@ -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 diff --git a/src/lua/endpoints/index_get.lua b/src/lua/endpoints/index_get.lua new file mode 100644 index 0000000..104d09b --- /dev/null +++ b/src/lua/endpoints/index_get.lua @@ -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 diff --git a/src/lua/endpoints/login_get.lua b/src/lua/endpoints/login_get.lua new file mode 100644 index 0000000..e9d279d --- /dev/null +++ b/src/lua/endpoints/login_get.lua @@ -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 diff --git a/src/lua/endpoints/login_post.lua b/src/lua/endpoints/login_post.lua new file mode 100644 index 0000000..46d7504 --- /dev/null +++ b/src/lua/endpoints/login_post.lua @@ -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 diff --git a/src/lua/endpoints/paste_get.lua b/src/lua/endpoints/paste_get.lua new file mode 100644 index 0000000..b371a0b --- /dev/null +++ b/src/lua/endpoints/paste_get.lua @@ -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 diff --git a/src/lua/endpoints/paste_post.lua b/src/lua/endpoints/paste_post.lua new file mode 100644 index 0000000..243cf6e --- /dev/null +++ b/src/lua/endpoints/paste_post.lua @@ -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 diff --git a/src/lua/endpoints/preview_post.lua b/src/lua/endpoints/preview_post.lua new file mode 100644 index 0000000..e3cb8da --- /dev/null +++ b/src/lua/endpoints/preview_post.lua @@ -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 diff --git a/src/lua/endpoints/read_get.lua b/src/lua/endpoints/read_get.lua new file mode 100644 index 0000000..3874b9a --- /dev/null +++ b/src/lua/endpoints/read_get.lua @@ -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 diff --git a/src/lua/endpoints/read_post.lua b/src/lua/endpoints/read_post.lua new file mode 100644 index 0000000..2fde5ef --- /dev/null +++ b/src/lua/endpoints/read_post.lua @@ -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 diff --git a/src/lua/endpoints/search_get.lua b/src/lua/endpoints/search_get.lua new file mode 100644 index 0000000..4c340a3 --- /dev/null +++ b/src/lua/endpoints/search_get.lua @@ -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 diff --git a/src/lua/index_get.lua b/src/lua/index_get.lua index 7e65fe0..104d09b 100644 --- a/src/lua/index_get.lua +++ b/src/lua/index_get.lua @@ -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 diff --git a/src/lua/init.lua b/src/lua/init.lua index 9bb7161..ec457e1 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -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") diff --git a/src/lua/login_get.lua b/src/lua/login_get.lua index 4ad9c90..e9d279d 100644 --- a/src/lua/login_get.lua +++ b/src/lua/login_get.lua @@ -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 diff --git a/src/lua/login_post.lua b/src/lua/login_post.lua index d8cc902..46d7504 100644 --- a/src/lua/login_post.lua +++ b/src/lua/login_post.lua @@ -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 diff --git a/src/lua/paste_get.lua b/src/lua/paste_get.lua index 440f722..b371a0b 100644 --- a/src/lua/paste_get.lua +++ b/src/lua/paste_get.lua @@ -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 diff --git a/src/lua/paste_post.lua b/src/lua/paste_post.lua new file mode 100644 index 0000000..243cf6e --- /dev/null +++ b/src/lua/paste_post.lua @@ -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 diff --git a/src/lua/preview_post.lua b/src/lua/preview_post.lua index b7886be..e3cb8da 100644 --- a/src/lua/preview_post.lua +++ b/src/lua/preview_post.lua @@ -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", diff --git a/src/lua/read_get.lua b/src/lua/read_get.lua index 2fbed09..3874b9a 100644 --- a/src/lua/read_get.lua +++ b/src/lua/read_get.lua @@ -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) diff --git a/src/lua/read_post.lua b/src/lua/read_post.lua index 81907da..2fde5ef 100644 --- a/src/lua/read_post.lua +++ b/src/lua/read_post.lua @@ -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 diff --git a/src/lua/search_get.lua b/src/lua/search_get.lua new file mode 100644 index 0000000..4c340a3 --- /dev/null +++ b/src/lua/search_get.lua @@ -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 diff --git a/src/lua/session.lua b/src/lua/session.lua index 7912d01..b6a5c4a 100644 --- a/src/lua/session.lua +++ b/src/lua/session.lua @@ -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 diff --git a/src/lua/tags.lua b/src/lua/tags.lua index 9612c66..9ee026b 100644 --- a/src/lua/tags.lua +++ b/src/lua/tags.lua @@ -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 diff --git a/src/lua/util.lua b/src/lua/util.lua index a798141..32da51f 100644 --- a/src/lua/util.lua +++ b/src/lua/util.lua @@ -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 = diff --git a/src/pages/read.etlua b/src/pages/read.etlua index e1d3fa7..5224b19 100644 --- a/src/pages/read.etlua +++ b/src/pages/read.etlua @@ -22,7 +22,7 @@ <%- title %>

- <% if isanon then -%> + <% if isanon or author == nil then -%> By Anonymous <% else -%> By <%= author %> diff --git a/src/smr.c b/src/smr.c index 3e14a5c..c1486e9 100644 --- a/src/smr.c +++ b/src/smr.c @@ -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; }