Differences From
Artifact [68c9cc33d4]:
1 1 -- vim: ft=terra
2 2 local util = lib.util
3 3 local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' }
4 4 local pstring = lib.mem.ptr(int8)
5 -local mimetypes = {
6 - {'html', 'text/html'};
7 - {'json', 'application/json'};
8 - {'json', 'application/activity+json'};
9 - {'json', 'application/ld+json'};
10 - {'mkdown', 'text/markdown'};
11 - {'text', 'text/plain'};
12 - {'ansi', 'text/x-ansi'};
13 -}
14 5
15 6 local struct srv
16 7 local struct cfgcache {
17 8 secret: pstring
18 9 pol_sec: secmode.t
19 10 pol_reg: bool
20 11 pol_autoherald: bool
................................................................................
149 140
150 141 terra lib.store.post:publish(s: &srv)
151 142 self:comp()
152 143 self.posted = lib.osclock.time(nil)
153 144 self.discovered = self.posted
154 145 self.chgcount = 0
155 146 self.edited = 0
147 + self.uri = nil -- only for foreign posts
148 + self.convoheaduri = nil -- ditto
156 149 self.id = s:post_create(self)
157 150 return self.id
158 151 end
159 152
160 -local struct convo {
161 - srv: &srv
162 - con: &lib.net.mg_connection
163 - msg: &lib.net.mg_http_message
164 - aid: uint64 -- 0 if logged out
165 - aid_issue: lib.store.timepoint
166 - who: &lib.store.actor -- who we're logged in as, if aid ~= 0
167 - peer: lib.store.inet
168 - reqtype: lib.http.mime.t -- negotiated content type
169 - method: lib.http.method.t
170 - live_last: lib.store.timepoint
171 - uploads: lib.mem.vec(lib.http.upload)
172 - body: lib.str.t
173 --- cache
174 - ui_hue: uint16
175 - navbar: lib.mem.ptr(int8)
176 - actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries
177 --- private
178 - varbuf: lib.mem.ptr(int8)
179 - vbofs: &int8
180 -}
181 -
182 -struct convo.page {
183 - title: pstring
184 - body: pstring
185 - class: pstring
186 - cache: bool
187 -}
188 -
189 -local usrdefs = {
190 - str = {
191 - ['acl-follow' ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'};
192 - ['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'};
193 - };
194 -}
195 -
196 -terra convo:matchmime(mime: lib.http.mime.t): bool
197 - return self.reqtype == [lib.http.mime.none]
198 - or self.reqtype == mime
199 -end
200 -
201 -terra convo:usercfg_str(uid: uint64, setting: pstring): pstring
202 - var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting)
203 - if not set then
204 - [(function()
205 - local q = quote return pstring.null() end
206 - for key, dfl in pairs(usrdefs.str) do
207 - local rv
208 - if dfl.cfgfld then
209 - rv = quote
210 - var cf = self.srv.cfg.[dfl.cfgfld]
211 - in terralib.select(not cf, pstring([dfl.fallback]), cf) end
212 - elseif dfl.lit then rv = dfl.lit end
213 - q = quote
214 - if setting:cmp([key]) then return [rv] else [q] end
215 - end
216 - end
217 - return q
218 - end)()]
219 - else return set end
220 -end
221 -
153 +local convo = terralib.loadfile 'convo.t'(srv)
222 154 -- this is unfortunately necessary to work around a terra bug
223 155 -- it can't seem to handle forward-declarations of structs in C
224 156
225 157 local getpeer
226 158 do local struct strucheader {
227 159 next: &lib.net.mg_connection
228 160 mgr: &lib.net.mg_mgr
................................................................................
229 161 peer: lib.net.mg_addr
230 162 }
231 163 terra getpeer(con: &lib.net.mg_connection)
232 164 return [&strucheader](con).peer
233 165 end
234 166 end
235 167
236 -terra convo:uid2actor_live(uid: uint64)
237 - var actor = self.srv:actor_fetch_uid(uid)
238 - if actor:ref() then
239 - if self.aid ~= 0 and self.who.id ~= uid then
240 - actor(0).relationship = self.srv:actor_rel_calc(self.who.id, uid)
241 - else -- defensive branch
242 - actor(0).relationship = lib.store.relationship {
243 - agent = 0, patient = uid;
244 - rel = [lib.store.relation.null],
245 - recip = [lib.store.relation.null],
246 - }
247 - end
248 - end
249 - return actor
250 -end
251 -
252 -terra convo:uid2actor(uid: uint64)
253 - var actor: &lib.store.actor = nil
254 - for j = 0, self.actorcache.top do
255 - if uid == self.actorcache(j).ptr.id then
256 - actor = self.actorcache(j).ptr
257 - break
258 - end
259 - end
260 - if actor == nil then
261 - actor = self.actorcache:insert(self:uid2actor_live(uid)).ptr
262 - end
263 - return actor
264 -end
265 -
266 -terra convo:rawpage(code: uint16, pg: convo.page, hdrs: lib.mem.ptr(lib.http.header))
267 - var doc = data.view.docskel {
268 - instance = self.srv.cfg.instance;
269 - title = pg.title;
270 - body = pg.body;
271 - class = pg.class;
272 - navlinks = self.navbar;
273 - attr = '';
274 - }
275 - var attrbuf: int8[32]
276 - if self.aid ~= 0 and self.ui_hue ~= 323 then
277 - var hdecbuf: int8[21]
278 - var hdec = lib.math.decstr(self.ui_hue, &hdecbuf[20])
279 - lib.str.cpy(&attrbuf[0], ' style="--hue:')
280 - lib.str.cpy(&attrbuf[14], hdec)
281 - var len = &hdecbuf[20] - hdec
282 - lib.str.cpy(&attrbuf[14] + len, '"')
283 - doc.attr = &attrbuf[0]
284 - end
285 -
286 - if self.method == [lib.http.method.head]
287 - then doc:head(self.con,code,hdrs)
288 - else doc:send(self.con,code,hdrs)
289 - end
290 -end
291 -
292 -terra convo:statpage(code: uint16, pg: convo.page)
293 - var hdrs = array(
294 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
295 - lib.http.header { key = 'Cache-Control', value = 'no-store' }
296 - )
297 - self:rawpage(code,pg, [lib.mem.ptr(lib.http.header)] {
298 - ptr = &hdrs[0];
299 - ct = [hdrs.type.N] - lib.trn(pg.cache,1,0);
300 - })
301 -end
302 -
303 -terra convo:livepage(pg: convo.page, lastup: lib.store.timepoint)
304 - var nbuf: int8[21]
305 - var hdrs = array(
306 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
307 - lib.http.header { key = 'Cache-Control', value = 'no-store' },
308 - lib.http.header {
309 - key = 'X-Live-Newest-Artifact';
310 - value = lib.math.decstr(lastup, &nbuf[20]);
311 - },
312 - lib.http.header { key = 'Content-Length', value = '0' }
313 - )
314 - if self.live_last ~= 0 and self.live_last == lastup then
315 - lib.net.mg_printf(self.con, 'HTTP/1.1 %s', lib.http.codestr(200))
316 - for i = 0, [hdrs.type.N] do
317 - lib.net.mg_printf(self.con, '%s: %s\r\n', hdrs[i].key, hdrs[i].value)
318 - end
319 - lib.net.mg_printf(self.con, '\r\n')
320 - else
321 - self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] {
322 - ptr = &hdrs[0], ct = 3
323 - })
324 - end
325 -end
326 -
327 -terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end
328 -
329 -terra convo:bytestream_trusted(lockdown: bool, mime: pstring, data: lib.mem.ptr(uint8))
330 - var lockhdr = "Content-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\n"
331 - if not lockdown then lockhdr = "" end
332 - lib.net.mg_printf(self.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n%sX-Content-Options: nosniff\r\n\r\n", mime.ct, mime.ptr, data.ct + 2, lockhdr)
333 - lib.net.mg_send(self.con, data.ptr, data.ct)
334 - lib.net.mg_send(self.con, '\r\n', 2)
335 -end
336 -
337 -terra convo:json(data: pstring)
338 - self:bytestream_trusted(false, 'application/activity+json; charset=utf-8', data:blob())
339 -end
340 -
341 -terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8))
342 - -- TODO this is not a satisfactory solution; it's a bandaid on a gaping
343 - -- chest wound. ultimately we need to compile a whitelist of safe mime
344 - -- types as part of mimelib, but that is no small task. for now, this
345 - -- will keep the patient from immediately bleeding out
346 - if mime:cmp('text/html') or
347 - mime:cmp('text/xml') or
348 - mime:cmp('application/xhtml+xml') or
349 - mime:cmp('application/vnd.wap.xhtml+xml')
350 - then -- danger will robinson
351 - mime = 'text/plain'
352 - elseif mime:cmp('application/x-shockwave-flash') then
353 - mime = 'application/octet-stream'
354 - end
355 - self:bytestream_trusted(true, mime, data)
356 -end
357 -
358 -terra convo:reroute_cookie(dest: rawstring, cookie: rawstring)
359 - var hdrs = array(
360 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
361 - lib.http.header { key = 'Location', value = dest },
362 - lib.http.header { key = 'Set-Cookie', value = cookie }
363 - )
364 -
365 - var body = data.view.docskel {
366 - instance = self.srv.cfg.instance.ptr;
367 - title = 'rerouting';
368 - body = 'you are being redirected';
369 - class = 'error';
370 - navlinks = '';
371 - attr = '';
372 - }
373 -
374 - body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] {
375 - ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0)
376 - })
377 -end
378 -
379 -terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end
380 -
381 -terra convo:installkey(dest: rawstring, aid: uint64)
382 - var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1]
383 - do var p = &sesskey[0]
384 - p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1])
385 - p = p + lib.session.cookie_gen(self.srv.cfg.secret, aid, lib.osclock.time(nil), p)
386 - lib.dbg('sending cookie ',{&sesskey[0],15})
387 - p = lib.str.ncpy(p, '; Path=/', 9)
388 - end
389 - self:reroute_cookie(dest, &sesskey[0])
390 -end
391 -
392 -terra convo:stra(sz: intptr) -- convenience function
393 - var s: lib.str.acc
394 - s:pool(&self.srv.pool,sz)
395 - return s
396 -end
397 -
398 -convo.methods.qstr = macro(function(self, ...) -- convenience string builder
399 - local exp = {...}
400 - return `lib.str.acc{}:pcompose(&self.srv.pool, [exp]):finalize()
401 -end)
402 -
403 -terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
404 - if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end
405 -
406 - if self:matchmime(lib.http.mime.html) then
407 - var body = [convo.page] {
408 - title = self:qstr('error :: ', title);
409 - body = self:qstr('<div class="message"><img class="icon" src="/s/warn.svg"><h1>',title,'</h1><p>',msg,'</p></div>');
410 - class = 'error';
411 - cache = false;
412 - }
413 -
414 - self:statpage(code, body)
415 - else
416 - var pg = lib.http.page { respcode = code, body = pstring.null() }
417 - var ctt = lib.http.mime.none
418 - if self:matchmime(lib.http.mime.json) then ctt = lib.http.mime.json
419 - pg.body = ([lib.tpl.mk'{"_parsav_error":@$ekind, "_parsav_error_desc":@$edesc}']
420 - {ekind = title, edesc = msg}):poolstr(&self.srv.pool)
421 - elseif self:matchmime(lib.http.mime.text) then ctt = lib.http.mime.text
422 - pg.body = self:qstr('error: ',title,'\n',msg)
423 - elseif self:matchmime(lib.http.mime.mkdown) then ctt = lib.http.mime.mkdown
424 - pg.body = self:qstr('# error :: ',title,'\n\n',msg)
425 - elseif self:matchmime(lib.http.mime.ansi) then ctt = lib.http.mime.ansi
426 - pg.body = self:qstr('\27[1;31merror :: ',title,'\27[m\n',msg)
427 - end
428 - var cthdr = lib.http.header { 'Content-Type', 'text/plain' }
429 - if ctt == lib.http.mime.none then
430 - pg.headers.ct = 0
431 - else
432 - pg.headers = lib.typeof(pg.headers) { &cthdr, 1 }
433 - switch ctt do
434 - case [ctt.type](lib.http.mime.json) then
435 - cthdr.value = 'application/json'
436 - end
437 - escape
438 - for i,v in ipairs(mimetypes) do local key,mime = v[1],v[2]
439 - if key ~= 'json' then
440 - emit quote case [ctt.type](lib.http.mime.[key]) then cthdr.value = [mime] end end
441 - end
442 - end
443 - end
444 - end
445 - end
446 - pg:send(self.con)
447 - end
448 -end
449 -
450 -terra convo:fail(code: uint16)
451 - switch code do
452 - escape
453 - local stderrors = {
454 - {400, 'bad request', "the action you have attempted on this resource is not meaningful"};
455 - {401, 'unauthorized', "this resource is not available at your clearance level"};
456 - {403, 'forbidden', "we can neither confirm nor deny the existence of this resource"};
457 - {404, 'resource not found', "that resource is not extant on or known to this server"};
458 - {405, 'method not allowed', "the method you have attempted on this resource is not meaningful"};
459 - {406, 'not acceptable', "none of the suggested content types are a viable representation of this resource"};
460 - {500, 'internal server error', "parsav did a fucksy wucksy"};
461 - }
462 -
463 - for i,v in ipairs(stderrors) do
464 - emit quote case uint16([v[1]]) then
465 - self:complain([v])
466 - end end
467 - end
468 - end
469 - else self:complain(500,'unknown error','an unrecognized error was thrown. this is a bug')
470 - end
471 -end
472 -
473 -terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
474 - var conf = data.view.confirm {
475 - title = title;
476 - query = msg;
477 - cancel = cancel;
478 - }
479 - var ti: lib.str.acc ti:pcompose(&self.srv.pool,'confirm :: ', title)
480 - var body = conf:poolstr(&self.srv.pool) -- defer body:free()
481 - var cf = [convo.page] {
482 - title = ti:finalize();
483 - class = 'query';
484 - body = body; cache = false;
485 - }
486 - self:stdpage(cf)
487 - --cf.title:free()
488 -end
489 -
490 -convo.methods.assertpow = macro(function(self, pow)
491 - return quote
492 - var ok = true
493 - if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
494 - ok = false
495 - self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
496 - end
497 - in ok end
498 -end)
499 -
500 -local pstr2mg, mg2pstr
501 -do -- aaaaaaaaaaaaaaaaaaaaaaaa
502 - mgstr = lib.util.find(lib.net.mg_http_message.entries, function(v)
503 - if v.field == 'body' or v[1] == 'body' then return v.type end
504 - end)
505 - terra pstr2mg(p: pstring): mgstr
506 - return mgstr { ptr = p.ptr, len = p.ct }
507 - end
508 - terra mg2pstr(m: mgstr): pstring
509 - return pstring { ptr = m.ptr, ct = m.len }
510 - end
511 -end
512 -
513 --- CALL ONLY ONCE PER VAR
514 -terra convo:postv_next(name: pstring, start: &pstring)
515 - if self.varbuf.ptr == nil then
516 - self.varbuf = self.srv.pool:alloc(int8, self.msg.body.len + self.msg.query.len)
517 - self.vbofs = self.varbuf.ptr
518 - end
519 - var conv = pstr2mg(@start)
520 - var o = lib.net.mg_http_get_var(
521 - &conv,
522 - name.ptr, self.vbofs,
523 - self.varbuf.ct - (self.vbofs - self.varbuf.ptr)
524 - )
525 - if o > 0 then
526 - start:advance(name.ct + o + 2)
527 - var r = self.vbofs
528 - self.vbofs = self.vbofs + o + 1
529 - @(self.vbofs - 1) = 0
530 - var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o})
531 - return norm.ptr, norm.ct
532 - else return nil, 0 end
533 -end
534 -terra convo:postv(name: pstring)
535 - var start = mg2pstr(self.msg.body)
536 - return self:postv_next(name, &start)
537 -end
538 -terra convo:ppostv(name: pstring)
539 - var s,l = self:postv(name)
540 - return pstring { ptr = s, ct = l }
541 -end
542 -do
543 - local struct postiter { co: &convo where: pstring name: pstring }
544 - terra convo:eachpostv(name: pstring)
545 - return postiter { co = self, where = mg2pstr(self.msg.body), name = name }
546 - end
547 - postiter.metamethods.__for = function(self, body)
548 - return quote
549 - while true do
550 - var str, len = self.co:postv_next(self.name, &self.where)
551 - if str == nil then break end
552 - [ body(`pstring {str, len}) ]
553 - end
554 - end
555 - end
556 -end
557 -
558 -terra convo:getv(name: rawstring)
559 - if self.varbuf.ptr == nil then
560 - self.varbuf = self.srv.pool:alloc(int8, self.msg.query.len + self.msg.body.len)
561 - self.vbofs = self.varbuf.ptr
562 - end
563 - var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
564 - if o > 0 then
565 - var r = self.vbofs
566 - self.vbofs = self.vbofs + o + 1
567 - @(self.vbofs - 1) = 0
568 - var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o})
569 - return norm.ptr, norm.ct
570 - else return nil, 0 end
571 -end
572 -terra convo:pgetv(name: rawstring)
573 - var s,l = self:getv(name)
574 - return pstring { ptr = s, ct = l }
575 -end
576 -
577 168 local route = {} -- these are defined in route.t, as they need access to renderers
578 169 terra route.dispatch_http :: {&convo, lib.mem.ptr(int8)} -> {}
579 170
580 -local mimevar = symbol(lib.mem.ref(int8))
581 -local mimeneg = `lib.http.mime.none
582 -
583 -for i, t in ipairs(mimetypes) do
584 - local name, mime = t[1], t[2]
585 - mimeneg = quote
586 - var ret: lib.http.mime.t
587 - if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then
588 - ret = [lib.http.mime[name]]
589 - else ret = [mimeneg] end
590 - in ret end
591 -end
592 -
593 171 local handle = {
594 172 http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque)
595 173 var server = [&srv](userdata)
596 174 var mgpeer = getpeer(con)
597 175 -- var pbuf: int8[128]
598 176
599 177 -- the peer property is currently broken and there is precious
................................................................................
634 212 co.body.ptr = msg.body.ptr co.body.ct = msg.body.len
635 213
636 214 -- first, check for an accept header. if it's there, we need to
637 215 -- iterate over the values and pick the highest-priority one
638 216 do var acc = lib.http.findheader(msg, 'Accept')
639 217 -- TODO handle q-value
640 218 if acc ~= nil and acc.ptr ~= nil then
641 - var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
219 + var mimevar = [pstring] { ptr = acc.ptr }
220 + lib.dbg('accept header is ', {acc.ptr,acc.ct})
642 221 var i = 0 while i < acc.ct do
643 222 if acc.ptr[i] == @',' or acc.ptr[i] == @';' then
644 223 mimevar.ct = (acc.ptr+i) - mimevar.ptr
645 - var t = [mimeneg]
646 - if t ~= lib.http.mime.none then
647 - co.reqtype = t
224 + var mk = lib.mime.lookup(mimevar)
225 + if mk ~= nil and mk.output ~= lib.http.mime.none then
226 + co.reqtype = mk.output
648 227 goto foundtype
649 228 end
650 229
651 230 if acc.ptr[i] == @';' then -- fast-forward over q
652 231 for j=i+1,acc.ct do i=j
653 232 if acc.ptr[j] == @',' then break end
654 233 end
................................................................................
661 240
662 241 mimevar.ptr = acc.ptr + i + 1
663 242 end
664 243 i=i+1
665 244 end
666 245 if co.reqtype == lib.http.mime.none then
667 246 mimevar.ct = acc.ct - (mimevar.ptr - acc.ptr)
668 - co.reqtype = [mimeneg]
669 - if co.reqtype == lib.http.mime.none then
670 - co.reqtype = lib.http.mime.html
247 + var mk = lib.mime.lookup(mimevar)
248 + if mk ~= nil and mk.output ~= lib.http.mime.none then
249 + co.reqtype = mk.output
671 250 end
672 251 end
673 - else co.reqtype = lib.http.mime.html end
252 + end
674 253 ::foundtype::end
675 254
676 255 -- we need to check if there's any cookies sent with the request,
677 256 -- and if so, whether they contain any credentials. this will be
678 257 -- used to set the auth parameters in the http conversation
679 258 var cookies_p = lib.http.findheader(msg, 'Cookie')
680 259 if cookies_p ~= nil and cookies_p.ptr ~= nil then
................................................................................
878 457 end
879 458 bsr:free()
880 459 upmap:free()
881 460 end
882 461 end
883 462 end
884 463
464 + var mtt = lib.http.mime._str(co.reqtype)
465 + lib.dbg('routing with negotiated type of ', {mtt.ptr,mtt.ct})
885 466 route.dispatch_http(&co, uri)
886 467
887 468 ::fail::
888 469 if co.uploads.run > 0 then
889 470 for i=0,co.uploads.sz do
890 471 co.uploads(i).filename:free()
891 472 co.uploads(i).field:free()