-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method
local pstring = lib.mem.ptr(int8)
local rstring = lib.mem.ref(int8)
local hpath = lib.mem.ptr(rstring)
local http = {}
terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
for i=2,uri.ct do
if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
end
if handle.ct == 0 then
handle.ct = uri.ct - 2
uri:advance(uri.ct)
else
if handle.ct + 2 < uri.ct then
uri:advance(handle.ct + 2)
--uri.ptr = uri.ptr + (handle.ct + 2)
--uri.ct = uri.ct - (handle.ct + 2)
end
end
lib.dbg('looking up user by xid "', {handle.ptr,handle.ct} ,'", path: ', {uri.ptr,uri.ct})
var path = lib.http.hier(uri) defer path:free()
for i=0,path.ct do
lib.dbg('got path component ', {path.ptr[i].ptr, path.ptr[i].ct})
end
var actor = co.srv:actor_fetch_xid(handle)
if actor.ptr == nil then
co:complain(404,'no such user','no such user known to this server')
return
end
defer actor:free()
lib.render.userpage(co, actor.ptr)
end
terra http.actor_profile_uid(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), meth: method.t)
if path.ct < 2 then
co:complain(404,'bad url','invalid user url')
return
end
var uid, ok = lib.math.shorthand.parse(path.ptr[1].ptr, path.ptr[1].ct)
if not ok then
co:complain(400, 'bad user ID', 'that user ID is not valid')
return
end
var actor = co.srv:actor_fetch_uid(uid)
if actor.ptr == nil then
co:complain(404, 'no such user', 'no user by that ID is known to this instance')
return
end
defer actor:free()
lib.render.userpage(co, actor.ptr)
end
terra http.login_form(co: &lib.srv.convo, meth: method.t)
if meth == method.get then
-- request a username
lib.render.login(co, nil, nil, lib.str.plit(nil))
elseif meth == method.post then
var usn, usnl = co:postv('user')
var am, aml = co:postv('authmethod')
var chrs, chrsl = co:postv('response')
var cs, authok = co.srv:actor_auth_how(co.peer, usn)
var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] {
ptr = usn, ct = usnl
})
if authok == false then
lib.render.login(co, nil, nil, lib.str.plit'access denied')
return
end
var fakeact = false
var fakeactor: lib.store.actor
if act.ptr == nil then
-- the user is known to us but has not yet claimed an
-- account on the server. create a template for the
-- account that will be created once they log in
fakeact = true
fakeactor = lib.store.actor {
id = 0, handle = usn, nym = nil;
origin = 0, bio = nil;
key = [lib.mem.ptr(uint8)] {ptr=nil, ct=0};
epithet = nil;
}
act.ct = 1
act.ptr = &fakeactor
act.ptr.rights = lib.store.rights_default()
end
if am == nil then
-- pick an auth method
lib.render.login(co, act.ptr, &cs, lib.str.plit(nil))
else var aid: uint64 = 0
lib.dbg('authentication attempt beginning')
-- attempt login with provided method
if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
aid = co.srv:actor_auth_pw(co.peer,
[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
lib.dbg('using otp auth')
-- ยทยทยท --
else
lib.dbg('invalid auth method')
end
-- error out
if aid == 0 then
lib.render.login(co, nil, nil, lib.str.plit 'authentication failure')
else
var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1]
do var p = &sesskey[0]
p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1])
p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p)
lib.dbg('sending cookie',&sesskey[0])
p = lib.str.ncpy(p, '; Path=/', 9)
end
co:reroute_cookie('/', &sesskey[0])
end
end
if act.ptr ~= nil and fakeact == false then act:free() end
else
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
end
return
end
terra http.post_compose(co: &lib.srv.convo, meth: method.t)
if not co:assertpow('post') then return end
--if co.who.rights.powers.post() == false then
--co:complain(403,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action')
if meth == method.get then
lib.render.compose(co, nil)
elseif meth == method.post then
var text, textlen = co:postv("post")
var acl, acllen = co:postv("acl")
var subj, subjlen = co:postv("subject")
if text == nil or acl == nil then
co:complain(405, 'invalid post', 'every post must have at least body text and an ACL')
return
end
if subj == nil then subj = '' end
var p = lib.store.post {
author = co.who.id, acl = acl;
body = text, subject = subj;
}
var newid = co.srv:post_create(&p)
var idbuf: int8[lib.math.shorthand.maxlen]
var idlen = lib.math.shorthand.gen(newid, idbuf)
var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
co:reroute(redirto.buf)
end
end
terra http.timeline(co: &lib.srv.convo, mode: hpath)
lib.render.timeline(co,lib.trn(mode.ptr == nil, rstring{ptr=nil}, mode.ptr[1]))
end
terra http.documentation(co: &lib.srv.convo, path: hpath)
if path.ct == 2 then
lib.render.docpage(co,path(1))
elseif path.ct == 1 then
lib.render.docpage(co, rstring.null())
else
co:complain(404, 'no such documentation', 'invalid documentation URL')
end
end
terra http.configure(co: &lib.srv.convo, path: hpath)
lib.render.conf(co,path)
end
do local branches = quote end
local filename, flen = symbol(&int8), symbol(intptr)
local page = symbol(lib.http.page)
local send = label()
local storage = data.stmap
for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
local d = data.static[id]
branches = quote [branches];
if lib.str.ncmp(filename, id, lib.math.biggest([#id], flen)) == 0 then
page.headers.ptr[0].value = mime;
page.body = [lib.mem.ptr(int8)] {
ptr = storage[([i-1])].ptr;
ct = storage[([i-1])].ct;
}
goto [send]
end
end
end
terra http.static_content(co: &lib.srv.convo, [filename], [flen])
var hdrs = array(lib.http.header{'Content-Type',nil})
var [page] = lib.http.page {
respcode = 200;
headers = [lib.mem.ptr(lib.http.header)] {
ptr = &hdrs[0], ct = 1
}
}
[branches]
do return false end
::[send]:: page:send(co.con) return true
end
end
terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8))
-- TODO retrieve user avatars
co:reroute('/s/default-avatar.webp')
end
-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
co.navbar = lib.render.nav(co)
-- some routes are non-hierarchical, and can be resolved with a simple strcmp
-- we run through those first before giving up and parsing the URI
if uri.ptr[0] ~= @'/' then
co:complain(404, 'what the hell', 'how did you do that')
return
elseif uri.ct == 1 then -- root
if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
http.login_form(co, meth)
else
-- FIXME display home screen
http.timeline(co, hpath {ptr=nil})
goto notfound
end
return
elseif uri.ptr[1] == @'@' then
http.actor_profile_xid(co, uri, meth)
return
elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
if meth ~= method.get then goto wrongmeth end
if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
return
elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
return
elseif lib.str.ncmp('/compose', uri.ptr, lib.math.biggest(uri.ct,8)) == 0 then
if co.aid == 0 then co:reroute('/login') return end
http.post_compose(co,meth)
return
elseif lib.str.ncmp('/login', uri.ptr, lib.math.biggest(uri.ct,6)) == 0 then
if co.aid == 0
then http.login_form(co, meth)
else co:reroute('/')
end
return
elseif lib.str.ncmp('/logout', uri.ptr, lib.math.biggest(uri.ct,7)) == 0 then
if co.aid == 0
then goto notfound
else co:reroute_cookie('/','auth=; Path=/')
end
return
else -- hierarchical routes
var path = lib.http.hier(uri) defer path:free()
if path.ptr[0]:cmp(lib.str.lit('user')) then
http.actor_profile_uid(co, path, meth)
elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
http.timeline(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('doc')) then
if meth ~= method.get and meth ~= method.head then goto wrongmeth end
http.documentation(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('conf')) then
if co.aid == 0 then goto unauth end
http.configure(co,path)
else goto notfound end
return
end
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
::unauth:: co:complain(401, 'unauthorized', 'this content is not available at your clearance level') do return end
end