Index: common.lua ================================================================== --- common.lua +++ common.lua @@ -22,11 +22,54 @@ local t = fd:read('*a') return chomp(t), fd:close() end end -local function dump(v,pfx,cyc) +local function copy(a) + local new = {} + for k,v in pairs(a) do new[k] = v end + return new +end + +local function cat(a,b) + a = copy(a) + local ofs = #a + for k,v in pairs(b) do + if type(k) == 'number' then + a[k+ofs] = v + else a[k] = v end + end + return a +end + +local function search(tbl,pred,lst,path) + lst = lst or {} path = path or {} + if type(pred) ~= 'function' then + local val = pred + pred = function(a,k) + if type(a) == 'table' and a ~= val then return end + return a == val + end + end + for k,v in pairs(tbl) do + local res = pred(v,k) + local np = cat(path, {tbl}) + if res == true then + table.insert(lst, { + key = k; + value = v; + parent = tbl; + path = np; + }) + elseif res == nil then + search(v,pred,lst,np) + end + end + return lst +end + +local function dump(v,pfx,cyc,ismeta) pfx = pfx or '' cyc = cyc or {} local np = pfx .. ' ' if type(v) == 'table' then @@ -39,15 +82,24 @@ local str = '' for k,v in pairs(v) do local tkey, tval = dump(k,np,cyc), dump(v,np,cyc) str = str .. string.format('%s[%s] = %s\n', np, tkey,tval) end - return '{\n' .. str .. pfx .. '}\n' + local meta = '' + if getmetatable(v) then + meta = dump(getmetatable(v),pfx,cyc,true) .. '::' + end + if ismeta then + return string.format('%s<|\n%s%s|>',meta,str,pfx) + else + return meta..'{\n' .. str .. pfx .. '}\n' + end else return string.format('%s', v) end end + local ping = function(path) local f = io.open(path) if f then f:close() return true end return false end @@ -70,16 +122,14 @@ end math.randomseed(seed) else math.randomseed(os.time()) end return { - exec = exec; dump = dump; - ping = ping; - chomp = chomp; - map = map; - tobool = tobool; + exec = exec, ping = ping; + map = map, copy = copy, cat = cat, search = search; + chomp = chomp, tobool = tobool; find = function(lst,pred) for k,v in pairs(lst) do local test = pred(v,k) if test then return test end end @@ -96,14 +146,17 @@ return s end; append = function(a,b) for _, v in pairs(b) do a[#a+1] = v end end; - has = function(haystack,needle,eq) - eq = eq or function(a,b) return a == b end + has = function(haystack,pred) + if type(pred) ~= 'function' then + local val = pred + pred = function(a) return a == val end + end for k,v in pairs(haystack) do - if eq(needle,v) then return k end + if pred(v,k) then return k,v end end end; keys = function(ary) local kt = {} for k,v in pairs(ary) do kt[#kt+1] = k end Index: mem.t ================================================================== --- mem.t +++ mem.t @@ -108,10 +108,13 @@ end terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro? terra t:ref() return self.ptr ~= nil end t.metamethods.__not = macro(function(self) return `not self:ref() end) t.metamethods.__apply = macro(function(self,idx) return `self.ptr[ [idx or 0] ] end) + t.metamethods.__add = terra(self: &t, sz: intptr): t + var n = @self n:advance(sz) return n + end t.metamethods.__update = macro(function(self,idx,rhs) return quote self.ptr[idx] = rhs end end) t.metamethods.__cast = function(from,to,exp) if to == t then if from == niltype then return `t.null() Index: route.t ================================================================== --- route.t +++ route.t @@ -925,12 +925,41 @@ ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end end local json = {} -terra json.webfinger(co: &lib.srv.convo) - +do wftpl = lib.tpl.mk [[{ + "subject": @$subj, + "links": [ + { "rel": "self", "type": "application/ld+json", "href": @$href } + ] + }]] + terra json.webfinger(co: &lib.srv.convo) + var res = co:pgetv('resource') + if (not res) or not res:startswith 'acct:' then goto err end + + -- technically we should look this user up in the database to make sure + -- they actually exist, buuut that's costly and i doubt that's actually + -- necessary for webfinger to do its job. so we cheat and just do string + -- munging so lookups are as cheap as possible. TODO make sure this works + -- in practice and doesn't cause any weird security problems + var acct = res + 5 + var svp = lib.str.find(acct, '@') + if svp:ref() then + acct.ct = (svp.ptr - acct.ptr) + svp:advance(1) + if not svp:cmp(co.srv.cfg.domain) then goto err end + end + var tp = wftpl { + subj = res; + href = co:qstr('https://', co.srv.cfg.domain, '/@', acct); + } + co:json(tp:poolstr(&co.srv.pool)) + + do return end -- error conditions + ::err:: do co:json('{}') return end + end 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}) Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -10,10 +10,11 @@ pol_autoherald: bool credmgd: bool maxupsz: intptr poolinitsz: intptr instance: pstring + domain: pstring overlord: &srv ui_cue_staff: pstring ui_cue_founder: pstring ui_hue: uint16 nranks: uint16 @@ -30,13 +31,14 @@ cfg: cfgcache id: rawstring pool: lib.mem.pool } -terra cfgcache:free() -- :/ +terra cfgcache:free() -- :/ TODO replace with pool self.secret:free() self.instance:free() + self.domain:free() self.ui_cue_staff:free() self.ui_cue_founder:free() self.usrdef_pol_follow:free() self.usrdef_pol_follow_req:free() end @@ -509,10 +511,12 @@ terra route.dispatch_http :: {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {} local mimetypes = { {'html', 'text/html'}; {'json', 'application/json'}; + {'json', 'application/ld+json'}; + {'json', 'application/activity+json'}; {'mkdown', 'text/markdown'}; {'text', 'text/plain'}; {'ansi', 'text/x-ansi'}; } @@ -531,29 +535,35 @@ local handle = { http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque) var server = [&srv](userdata) var mgpeer = getpeer(con) - var peer = lib.store.inet { port = mgpeer.port; } - if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end - if peer.pv == 6 then - for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end - else -- v4 - @[&uint32](&peer.v4) = mgpeer.ip - end + -- var pbuf: int8[128] + -- the peer property is currently broken and there is precious -- little i can do about this -- it always reports a peer v4 IP - -- of 0.0.0.0, altho the port seems to come through correctly. - -- for now i'm leaving it as is, but note that netmask restrictions - -- WILL NOT WORK until upstream gets its shit together. FIXME + -- of 0.0.0.0 for v6 connections, altho the port seems to come + -- through correctly. -- for now i'm leaving it as is, but note + -- that netmask restrictions WILL NOT WORK until upstream gets + -- its shit together. FIXME -- needs to check for an X-Forwarded-For header from nginx and -- use that instead of the peer iff peer is ::1/127.1 FIXME -- maybe also haproxy support? switch event_kind do case lib.net.MG_EV_HTTP_MSG then + -- lib.net.mg_ntoa(&mgpeer,&pbuf[0],127) + -- lib.dbg('got connection from client ',&pbuf[0]) + var peer = lib.store.inet { port = mgpeer.port; } + if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end + if peer.pv == 6 then + for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end + else -- v4 + @[&uint32](&peer.v4) = mgpeer.ip + end + lib.dbg('routing HTTP request') var msg = [&lib.net.mg_http_message](event) var co = convo { con = con, srv = server, msg = msg; aid = 0, aid_issue = 0, who = nil; @@ -1112,10 +1122,11 @@ return default end terra cfgcache:load() self.instance = self.overlord:conf_get('instance-name') + self.domain = self.overlord:conf_get('domain') self.secret = self.overlord:conf_get('server-secret') self.pol_reg = self:cfbool('policy-self-register', false) self.pol_autoherald = self:cfbool('policy-self-herald', true) Index: str.t ================================================================== --- str.t +++ str.t @@ -61,10 +61,17 @@ for i = 0, sz do if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end if self.ptr[i] ~= other.ptr[i] then return false end end return true + end + terra ty:startswith(other: ty) + for i=0, other.ct do + if other(i) == 0 then return true end + if other(i) ~= self(i) then return false end + end + return true end terra ty:ffw() var newp = m.ffw(self.ptr,self.ct) var newct = self.ct - (newp - self.ptr) return ty { ptr = newp, ct = newct } @@ -561,7 +568,23 @@ if add:ref() then acc:ppush(add) end cur = cont end return acc:finalize() end + +terra m.qesc(pool: &lib.mem.pool, str: m.t): m.t + -- escape double-quotes + var a: m.acc a:pool(pool, str.ct + str.ct/2) + a:lpush '"' + for i=0, str.ct do + if str(i) == @'"' then a:lpush '\\"' + elseif str(i) == @'\\' then a:lpush '\\\\' + elseif str(i) < 0x20 then -- for json + var hex = lib.math.hexbyte(str(i)) + a:lpush('\\u00'):push(&hex[0], 2) + else a:push(str.ptr + i,1) end + end + a:lpush '"' + return a:finalize() +end return m Index: tpl.t ================================================================== --- tpl.t +++ tpl.t @@ -36,11 +36,11 @@ str = str:gsub(' ?', file) end) - for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)([-a-zA-Z0-9_]+)()') do + for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!$]?)([-a-zA-Z0-9_]+)()') do if string.sub(str,start-1,start-1) ~= '\\' then segs[#segs+1] = string.sub(str,last,start-1) fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) } last = stop end @@ -75,11 +75,13 @@ field = fld.key; type = lib.mem.ptr(int8); } end kfac[fld.key] = (kfac[fld.key] or 0) + 1 - sanmode[fld.key] = fld.mode == ':' and 6 or fld.mode == '!' and 5 or 1 + sanmode[fld.key] = fld.mode == ':' and 6 + or fld.mode == '!' and 5 + or fld.mode == '$' and 2 or 1 end for key, fac in pairs(kfac) do local sanfac = sanmode[key] tallyup[#tallyup + 1] = quote @@ -101,27 +103,33 @@ senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end if fields[idx] and fields[idx].mode then local f = fields[idx] local fp = `symself.[f.key] + local sanexp + if f.mode == '$' then + sanexp = `lib.str.qesc(pool, fp) + else + sanexp = `lib.html.sanitize(pool, fp, [f.mode == ':']) + end copiers[#copiers+1] = quote if fp.ct > 0 then - var san = lib.html.sanitize(pool, fp, [f.mode == ':']) + var san = sanexp [cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct) --san:free() end end senders[#senders+1] = quote if fp.ct > 0 then - var san = lib.html.sanitize(pool, fp, [f.mode == ':']) + var san = sanexp lib.net.mg_send([destcon], san.ptr, san.ct) --san:free() end end appenders[#appenders+1] = quote if fp.ct > 0 then - var san = lib.html.sanitize(pool, fp, [f.mode == ':']) + var san = sanexp [accumulator]:ppush(san) --san:free() end end elseif fields[idx] then