| Comment: | wrote mimelib, continued iterating on litepub support; tweets can now be imported into honk |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
c774e2c5a9739cd9fbe1c939815b9a59 |
| User & Date: | lexi on 2021-01-28 00:51:21 |
| Other Links: | manifest | tags |
|
2021-01-28
| ||
| 02:44 | add in a bunch of missing pqclears, because i am a *retard*, and wipe out a fuckton of memory leaks check-in: a4e71fdfda user: lexi tags: trunk | |
| 00:51 | wrote mimelib, continued iterating on litepub support; tweets can now be imported into honk check-in: c774e2c5a9 user: lexi tags: trunk | |
|
2021-01-25
| ||
| 14:38 | iterating check-in: 050ce7d4fc user: lexi tags: trunk | |
Modified api/lp/actor.t from [b336ed6430] to [c96b63a8f1].
9 9 "preferredUsername": %$handle, 10 10 "name": %$nym, 11 11 "summary": %$desc, 12 12 "alsoKnownAs": ["https://%+domain/@%+handle"], 13 13 "publicKey": { 14 14 "id": "%lpid#ident-rsa", 15 15 "owner": "%lpid", 16 - "publicKeyPem": %rsa 16 + "publicKeyPem": %$rsa 17 17 }, 18 18 "icon": { 19 19 "type": "Image", 20 20 "url": "https://%+domain%+avi" 21 21 }, 22 22 "capabilities": { "acceptsChatMessages": false }, 23 23 "discoverable": true, ................................................................................ 26 26 "outbox": "https://%+domain/api/lp/outbox/user/%uid", 27 27 "followers": "https://%+domain/api/lp/rel/%uid/followers", 28 28 "following": "https://%+domain/api/lp/rel/%uid/following" 29 29 }]]; 30 30 } 31 31 32 32 local pstr = lib.str.t 33 -terra cs(s: rawstring) return pstr {s, lib.str.sz(s)} end 33 +terra cs(s: rawstring) return pstr {s, lib.trn(s == nil,0,lib.str.sz(s))} end 34 34 35 35 local terra 36 36 api_lp_actor(co: &lib.srv.convo, actor: &lib.store.actor) 37 37 var lpid = co:stra(64) 38 38 lpid:lpush'https://':ppush(co.srv.cfg.domain):lpush'/user/':shpush(actor.id) 39 39 var uid = co:stra(32) uid:shpush(actor.id) -- dumb hack bc lazy FIXME 40 40 41 + var upk = lib.crypt.loadpriv(actor.key) 42 + var pem: lib.crypt.pemfile 43 + 44 + if not upk.ok then 45 + lib.warn("could not load user's keypair; this is a sign of a bug, a corrupt database, or a problem with mbedtls") 46 + else defer upk.val:free() 47 + if not lib.crypt.pem(true, &upk.val, &pem[0]) then 48 + pem[0] = 0; 49 + lib.warn('could not export actor certificate as PEM file; there is a bug, the database is corrupt, or there is a problem in mbedtls') 50 + end 51 + end 41 52 var body = tpl { 42 53 domain = co.srv.cfg.domain; 43 54 uid = uid:finalize(); 44 55 lpid = lpid:finalize(); 45 56 handle = cs(actor.handle); 46 57 nym = cs(actor.nym); 47 58 desc = cs(actor.bio); 48 59 avi = cs(actor.avatar); 49 - rsa = ''; 60 + rsa = cs(&pem[0]); 50 61 locked = 'false'; 51 62 } 52 63 53 64 co:json(body:poolstr(&co.srv.pool)) 65 + 54 66 end 55 67 return api_lp_actor
Modified api/lp/tweet.t from [1ada03554b] to [cbbfbd7e42].
4 4 local obj = lib.tpl.mk [[{ 5 5 "\@context": "https://@+domain/s/litepub.jsonld", 6 6 "type": "Note", 7 7 "id": "https://@+domain/post/@^pid", 8 8 "content": @$html, 9 9 "source": @$raw, 10 10 "attributedTo": "https://@+domain/user/@^uid", 11 - "published": "@pubtime" 11 + "actor": "https://@+domain/user/@^uid", 12 + "published": "@pubtime", 13 + "sensitive": false, 14 + "directMessage": false, 15 + "to": ["https://www.w3.org/ns/activitystreams#Public"], 16 + "summary": @$subj 12 17 @extra 13 18 }]] 14 19 15 20 local wrap = lib.tpl.mk [[{ 16 21 "\@context": "https://@+domain/s/litepub.jsonld", 17 22 "type": "@kind", 18 23 "actor": "https://@+domain/user/@^uid", 19 24 "published": "@pubtime", 20 25 "id": "https://@+domain/api/lp/act/@^aid", 26 + "to": ["https://www.w3.org/ns/activitystreams#Public"], 21 27 "object": @obj 22 28 }]] 23 29 24 30 local terra 25 -lp_tweet(co: &lib.srv.convo, p: &lib.store.post, act_wrap: bool) 26 - var opdate = lib.conv.datetime(&co.srv.pool, p.posted) 31 +lp_tweet(co: &lib.srv.convo, p: &lib.store.post, act_wrap: bool): pstr 32 + var opdate = lib.munge.datetime(&co.srv.pool, p.posted) 33 + 34 + var extra: lib.str.acc extra:pool(&co.srv.pool,256) 35 + 36 + if p.parent ~= 0 then 37 + extra:lpush ',"inReplyTo":"' 38 + var par = co.srv:post_fetch(p.parent) 39 + if not par then 40 + lib.warn('database integrity violation: broken parent reference') 41 + else defer par:free() 42 + if par().localpost then -- gen uri for parent 43 + extra:lpush'https://':qpush(co.srv.cfg.domain):lpush'/post/':shpush(p.parent) 44 + else extra:push(par().uri,0) end 45 + end 46 + extra:lpush'"' 47 + end 48 + 49 + extra:lpush ',"conversation":"' 50 + if p.convoheaduri ~= nil then 51 + extra:qpush(p.convoheaduri) 52 + else 53 + var cid: uint64 = 0 54 + if p.parent ~= 0 then 55 + var top = co.srv:thread_top_find(p.parent) 56 + var tp = co.srv:post_fetch(top) 57 + if not tp then 58 + lib.warn('database integrity violation: missing thread parent') 59 + cid = p.id 60 + else 61 + if tp().convoheaduri ~= nil then 62 + extra:push(tp().convoheaduri,0) 63 + elseif tp().localpost == false then 64 + extra:push(tp().uri,0) 65 + else cid = top end 66 + end 67 + else 68 + cid = p.id 69 + end 70 + if cid ~= 0 then 71 + extra:lpush'https://':qpush(co.srv.cfg.domain) 72 + :lpush'/post/':shpush(cid):lpush'/tree' 73 + end 74 + end 75 + extra:lpush'"' 27 76 28 77 var tweet = (obj { 29 78 domain = co.srv.cfg.domain, uid = p.author, pid = p.id; 30 79 html = lib.smackdown.html(&co.srv.pool, p.body, false); 31 - raw = p.body, pubtime = opdate, extra = ''; 80 + raw = p.body, pubtime = opdate, extra = extra:finalize(); 81 + subj = lib.trn(p.subject ~= nil, pstr(p.subject), pstr''); 32 82 }):poolstr(&co.srv.pool) 33 83 34 84 if act_wrap then 35 85 return (wrap { 36 86 domain = co.srv.cfg.domain, obj = tweet; 37 87 kind = lib.trn(p.rtdby == 0, 'Create', 'Announce'); 38 88 uid = lib.trn(p.rtdby == 0, p.author, p.rtdby); 39 89 aid = lib.trn(p.rtdby == 0, p.id, p.rtact); 40 90 pubtime = lib.trn(p.rtdby == 0, opdate, 41 - lib.conv.datetime(&co.srv.pool,p.rtdat)); 91 + lib.munge.datetime(&co.srv.pool,p.rtdat)); 42 92 }):poolstr(&co.srv.pool) 43 93 else 44 94 return tweet 45 95 end 46 96 end 47 97 48 98 return lp_tweet
Modified api/webfinger.t from [c64d390bdc] to [06153fd078].
10 10 "type": "text/html", "href": @$pfp } 11 11 ] 12 12 }]] 13 13 14 14 local terra 15 15 webfinger(co: &lib.srv.convo) 16 16 var res = co:pgetv('resource') 17 + lib.dbg('got webfinger request for resource ', {res.ptr,res.ct}) 17 18 if (not res) or not res:startswith 'acct:' then goto err end 18 19 19 20 var acct = res + 5 20 21 var svp = lib.str.find(acct, '@') 21 22 if svp:ref() then 22 23 acct.ct = (svp.ptr - acct.ptr) 23 24 svp:advance(1)
Modified backend/pgsql.t from [18ea7fb9d0] to [d6c61590d0].
457 457 where id = $1::bigint 458 458 ]] 459 459 }; 460 460 461 461 post_create = { 462 462 params = { 463 463 uint64, rawstring, rawstring, rawstring, 464 - uint64, uint64, rawstring 464 + uint64, uint64, rawstring, rawstring 465 465 }, sql = [[ 466 466 insert into parsav_posts ( 467 467 author, subject, acl, body, 468 468 parent, posted, discovered, 469 - circles, mentions, convoheaduri 469 + circles, mentions, convoheaduri, uri 470 470 ) values ( 471 471 $1::bigint, case when $2::text = '' then null else $2::text end, 472 472 $3::text, $4::text, 473 473 $5::bigint, $6::bigint, $6::bigint, 474 - array[]::bigint[], array[]::bigint[], $7::text 474 + array[]::bigint[], array[]::bigint[], $7::text, $8::text 475 475 ) returning id 476 476 ]]; -- TODO array handling 477 477 }; 478 478 479 479 post_destroy_prepare = { 480 480 params = {uint64}, cmd = true, sql = [[ 481 481 update parsav_posts set ................................................................................ 502 502 params = {uint64}, sql = [[ 503 503 select (p.post).* 504 504 from pg_temp.parsavpg_known_content as p 505 505 where (p.post).parent = $1::bigint and (p.post).rtdby = 0 506 506 order by (p.post).posted, (p.post).discovered asc 507 507 ]]; 508 508 }; 509 + 510 + thread_top_find = { 511 + params = {uint64}, sql = [[ 512 + with recursive tree(gen,id,par) as ( 513 + select 0, id, parent from parsav_posts where id = $1::bigint 514 + union 515 + select tree.gen + 1, p.id, p.parent from tree 516 + inner join parsav_posts as p on p.id = tree.par 517 + ) 518 + 519 + select id from tree order by gen desc limit 1 520 + ]]; 521 + }; 509 522 510 523 thread_latest_arrival_calc = { 511 524 params = {uint64}, sql = [[ 512 525 with recursive posts(id) as ( 513 526 select id from parsav_posts where parent = $1::bigint 514 527 union 515 528 select p.id from parsav_posts as p ................................................................................ 1022 1035 if ct == 0 then 1023 1036 lib.pq.PQclear(res) 1024 1037 return pqr {0, nil} 1025 1038 else 1026 1039 return pqr {ct, res} 1027 1040 end 1028 1041 end 1042 + q.exec.name = 'pgsql.' .. k .. '.exec' 1029 1043 end 1030 1044 1031 1045 local terra row_to_artifact(res: &pqr, i: intptr): lib.mem.ptr(lib.store.artifact) 1032 1046 var id = res:int(uint64,i,0) 1033 1047 var idbuf: int8[lib.math.shorthand.maxlen] 1034 1048 var idlen = lib.math.shorthand.gen(id, &idbuf[0]) 1035 1049 var desc = res:_string(i,2) ................................................................................ 1044 1058 m.ptr.rid = id 1045 1059 return m 1046 1060 end 1047 1061 1048 1062 local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post) 1049 1063 var subj: rawstring, sblen: intptr 1050 1064 var cvhu: rawstring, cvhlen: intptr 1065 + var uri: rawstring, urilen: intptr 1051 1066 if r:null(row,3) 1052 1067 then subj = nil sblen = 0 1053 1068 else subj = r:string(row,3) sblen = r:len(row,3)+1 1054 1069 end 1055 1070 if r:null(row,10) 1056 1071 then cvhu = nil cvhlen = 0 1057 1072 else cvhu = r:string(row,10) cvhlen = r:len(row,10)+1 1058 1073 end 1074 + if r:null(row,12) 1075 + then uri = nil urilen = 0 1076 + else uri = r:string(row,12) urilen = r:len(row,12)+1 1077 + end 1059 1078 var p = [ lib.str.encapsulate(lib.store.post, { 1060 1079 subject = { `subj, `sblen }; 1061 1080 acl = {`r:string(row,4), `r:len(row,4)+1}; 1062 1081 body = {`r:string(row,5), `r:len(row,5)+1}; 1063 1082 convoheaduri = { `cvhu, `cvhlen }; --FIXME 1083 + uri = { `uri, `urilen }; 1064 1084 }) ] 1065 1085 p.ptr.id = r:int(uint64,row,1) 1066 1086 p.ptr.author = r:int(uint64,row,2) 1067 1087 if r:null(row,6) 1068 1088 then p.ptr.posted = 0 1069 1089 else p.ptr.posted = r:int(uint64,row,6) 1070 1090 end ................................................................................ 1077 1097 else p.ptr.edited = r:int(uint64,row,8) 1078 1098 end 1079 1099 p.ptr.parent = r:int(uint64,row,9) 1080 1100 if r:null(row,11) 1081 1101 then p.ptr.chgcount = 0 1082 1102 else p.ptr.chgcount = r:int(uint32,row,11) 1083 1103 end 1084 - p.ptr.accent = r:int(int16,row,12) 1085 - p.ptr.rtdby = r:int(uint64,row,13) 1086 - p.ptr.rtdat = r:int(uint64,row,14) 1087 - p.ptr.rtact = r:int(uint64,row,15) 1088 - p.ptr.likes = r:int(uint32,row,16) 1089 - p.ptr.rts = r:int(uint32,row,17) 1090 - p.ptr.isreply = r:bool(row,18) 1104 + p.ptr.accent = r:int(int16,row,13) 1105 + p.ptr.rtdby = r:int(uint64,row,14) 1106 + p.ptr.rtdat = r:int(uint64,row,15) 1107 + p.ptr.rtact = r:int(uint64,row,16) 1108 + p.ptr.likes = r:int(uint32,row,17) 1109 + p.ptr.rts = r:int(uint32,row,18) 1110 + p.ptr.isreply = r:bool(row,19) 1091 1111 p.ptr.localpost = r:bool(row,0) 1092 1112 1093 1113 return p 1094 1114 end 1095 1115 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) 1096 1116 var a: lib.mem.ptr(lib.store.actor) 1097 1117 var av: rawstring, avlen: intptr 1098 1118 var nym: rawstring, nymlen: intptr 1099 1119 var bio: rawstring, biolen: intptr 1100 1120 var epi: rawstring, epilen: intptr 1121 + var key: &uint8, keylen: intptr 1101 1122 var origin: uint64 = 0 1102 1123 var handle = r:_string(row, 2) 1103 1124 if not r:null(row,3) then origin = r:int(uint64,row,3) end 1104 1125 1105 1126 var avia = lib.str.acc {buf=nil} 1106 1127 if origin == 0 then 1107 1128 avia:compose('/avi/',handle) ................................................................................ 1123 1144 bio = r:string(row,4) 1124 1145 biolen = r:len(row,4)+1 1125 1146 end 1126 1147 if r:null(row,9) then epilen = 0 epi = nil else 1127 1148 epi = r:string(row,9) 1128 1149 epilen = r:len(row,9)+1 1129 1150 end 1151 + if r:null(row,8) then 1152 + keylen = 0 key = nil 1153 + else 1154 + var k = r:bin(row,8) 1155 + keylen = k.ct key = k.ptr 1156 + end 1130 1157 a = [ lib.str.encapsulate(lib.store.actor, { 1131 1158 nym = {`nym, `nymlen}; 1132 1159 bio = {`bio, `biolen}; 1133 1160 epithet = {`epi, `epilen}; 1134 1161 avatar = {`av,`avlen}; 1135 1162 handle = {`handle.ptr, `handle.ct + 1}; 1136 1163 xid = {`r:string(row, 11); `r:len(row,11) + 1}; 1164 + key = {`key,`keylen}; 1137 1165 }) ] 1138 1166 a.ptr.id = r:int(uint64, row, 0); 1139 1167 a.ptr.rights = lib.store.rights_default(); 1140 1168 a.ptr.rights.rank = r:int(uint16, row, 6); 1141 1169 a.ptr.rights.quota = r:int(uint32, row, 7); 1142 1170 a.ptr.rights.invites = r:int(uint32, row, 12); 1143 1171 a.ptr.knownsince = r:int(int64,row, 10); 1144 1172 a.ptr.avatarid = r:int(uint64,row, 13); 1145 - if r:null(row,8) then 1146 - a.ptr.key.ct = 0 a.ptr.key.ptr = nil 1147 - else 1148 - a.ptr.key = r:bin(row,8) 1149 - end 1150 1173 a.ptr.origin = origin 1151 1174 if avia.buf ~= nil then avia:free() end 1152 1175 return a 1153 1176 end 1154 1177 1155 1178 local privmap = lib.store.powmap 1156 1179 ................................................................................ 1464 1487 blacklist = res:bool(i, 3); 1465 1488 pubkey = res:bin(i, 4); 1466 1489 } 1467 1490 end] 1468 1491 if rsakeys.sz > 0 then defer rsakeys:free() 1469 1492 for i=0, rsakeys.sz do var props = toprops(&rsakeys, i) 1470 1493 lib.dbg('loading next RSA pubkey') 1471 - var pub = lib.crypt.loadpub(props.pubkey.ptr, props.pubkey.ct) 1494 + var pub = lib.crypt.loadpub(props.pubkey) 1472 1495 if pub.ok then defer pub.val:free() 1473 1496 lib.dbg('checking pubkey against response') 1474 1497 var vfy, secl = lib.crypt.verify(&pub.val, token.ptr, token.ct, sig.ptr, sig.ct) 1475 1498 if vfy then 1476 1499 lib.dbg('signature verified') 1477 1500 if props.blacklist then lib.dbg('key blacklisted!') goto fail end 1478 1501 var dupname = lib.str.dup(props.name.ptr) ................................................................................ 1539 1562 1540 1563 post_create = [terra( 1541 1564 src: &lib.store.source, 1542 1565 post: &lib.store.post 1543 1566 ): uint64 1544 1567 var r = queries.post_create.exec(src, 1545 1568 post.author,post.subject,post.acl,post.body, 1546 - post.parent,post.posted,post.convoheaduri 1569 + post.parent,post.posted,post.convoheaduri,post.uri 1547 1570 ) 1548 1571 if r.sz == 0 then return 0 end 1549 1572 defer r:free() 1550 1573 var id = r:int(uint64,0,0) 1551 1574 post.source = src 1552 1575 return id 1553 1576 end]; ................................................................................ 2091 2114 if n.kind == lib.store.noticetype.react then 2092 2115 var react = r:_string(0,5) 2093 2116 lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N])) 2094 2117 end 2095 2118 2096 2119 return n 2097 2120 end]; 2121 + 2122 + thread_top_find = [terra( 2123 + src: &lib.store.source, 2124 + post: uint64 2125 + ): uint64 2126 + var r = queries.thread_top_find.exec(src,post) 2127 + if r.sz == 0 then return 0 end 2128 + defer r:free() 2129 + return r:int(uint64,0,0) 2130 + end]; 2098 2131 2099 2132 thread_latest_arrival_calc = [terra( 2100 2133 src: &lib.store.source, 2101 2134 post: uint64 2102 2135 ): lib.store.timepoint 2103 2136 var r = queries.thread_latest_arrival_calc.exec(src,post) 2104 2137 if r.sz == 0 or r:null(0,0) then return 0 end
Modified backend/schema/pgsql-views.sql from [b916bb0a63] to [a4ebd0c2e8].
106 106 body text, 107 107 posted bigint, 108 108 discovered bigint, 109 109 edited bigint, 110 110 parent bigint, 111 111 convoheaduri text, 112 112 chgcount integer, 113 + uri text, 113 114 -- ephemeral 114 115 accent smallint, 115 116 rtdby bigint, -- note that these must be 0 if the record 116 117 rtdat bigint, -- in question does not represent an RT! 117 118 rtid bigint, -- (this one too) 118 119 n_likes integer, 119 120 n_rts integer, ................................................................................ 124 125 pg_temp.parsavpg_translate_post(parsav_posts,bigint,bigint,bigint) 125 126 returns pg_temp.parsavpg_intern_post as $$ 126 127 select a.origin is null, 127 128 ($1).id, ($1).author, 128 129 ($1).subject,($1).acl, ($1).body, 129 130 ($1).posted, ($1).discovered, ($1).edited, 130 131 ($1).parent, ($1).convoheaduri,($1).chgcount, 131 - coalesce(c.value, -1)::smallint, 132 + ($1).uri, coalesce(c.value, -1)::smallint, 132 133 $2 as rtdby, $3 as rtdat, $4 as rtid, 133 134 re.likes, re.rts, 134 135 ($1).parent in (select id from parsav_posts) 135 136 from parsav_actors as a 136 137 left join parsav_actor_conf_ints as c 137 138 on c.key = 'ui-accent' and 138 139 c.uid = a.id
Modified backend/schema/pgsql.sql from [4c18dab250] to [abc8356ef1].
59 59 ); 60 60 create index on parsav_rights (actor); 61 61 comment on table parsav_rights is 62 62 'a backward-compatible list of every non-default privilege or deprivilege granted to a local user'; 63 63 64 64 create table parsav_posts ( 65 65 id <def:uniq>, 66 + uri text, -- null if local 66 67 author bigint references parsav_actors(id) on delete cascade, 67 68 subject text, 68 69 acl text not null default 'all', -- just store the script raw 🤷 69 70 body text, 70 71 posted bigint not null, 71 72 discovered bigint not null, 72 73 chgcount integer not null default 0,
Added convo.t version [ff68c1adfe].
1 +-- vim: ft=terra 2 +local srv = ... 3 +local pstring = lib.str.t 4 + 5 +local struct convo { 6 + srv: &srv 7 + con: &lib.net.mg_connection 8 + msg: &lib.net.mg_http_message 9 + aid: uint64 -- 0 if logged out 10 + aid_issue: lib.store.timepoint 11 + who: &lib.store.actor -- who we're logged in as, if aid ~= 0 12 + peer: lib.store.inet 13 + reqtype: lib.http.mime.t -- negotiated content type 14 + method: lib.http.method.t 15 + live_last: lib.store.timepoint 16 + uploads: lib.mem.vec(lib.http.upload) 17 + body: pstring 18 +-- cache 19 + ui_hue: uint16 20 + navbar: pstring 21 + actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries 22 +-- private 23 + varbuf: pstring 24 + vbofs: &int8 25 +} 26 + 27 +struct convo.page { 28 + title: pstring 29 + body: pstring 30 + class: pstring 31 + cache: bool 32 +} 33 + 34 +local usrdefs = { 35 + str = { 36 + ['acl-follow' ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'}; 37 + ['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'}; 38 + }; 39 +} 40 + 41 +terra convo:matchmime(mime: lib.http.mime.t): bool 42 + return self.reqtype == [lib.http.mime.none] 43 + or self.reqtype == mime 44 +end 45 + 46 +terra convo:usercfg_str(uid: uint64, setting: pstring): pstring 47 + var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting) 48 + if not set then 49 + [(function() 50 + local q = quote return pstring.null() end 51 + for key, dfl in pairs(usrdefs.str) do 52 + local rv 53 + if dfl.cfgfld then 54 + rv = quote 55 + var cf = self.srv.cfg.[dfl.cfgfld] 56 + in terralib.select(not cf, pstring([dfl.fallback]), cf) end 57 + elseif dfl.lit then rv = dfl.lit end 58 + q = quote 59 + if setting:cmp([key]) then return [rv] else [q] end 60 + end 61 + end 62 + return q 63 + end)()] 64 + else return set end 65 +end 66 + 67 +terra convo:uid2actor_live(uid: uint64) 68 + var actor = self.srv:actor_fetch_uid(uid) 69 + if actor:ref() then 70 + if self.aid ~= 0 and self.who.id ~= uid then 71 + actor(0).relationship = self.srv:actor_rel_calc(self.who.id, uid) 72 + else -- defensive branch 73 + actor(0).relationship = lib.store.relationship { 74 + agent = 0, patient = uid; 75 + rel = [lib.store.relation.null], 76 + recip = [lib.store.relation.null], 77 + } 78 + end 79 + end 80 + return actor 81 +end 82 + 83 +terra convo:uid2actor(uid: uint64) 84 + var actor: &lib.store.actor = nil 85 + for j = 0, self.actorcache.top do 86 + if uid == self.actorcache(j).ptr.id then 87 + actor = self.actorcache(j).ptr 88 + break 89 + end 90 + end 91 + if actor == nil then 92 + actor = self.actorcache:insert(self:uid2actor_live(uid)).ptr 93 + end 94 + return actor 95 +end 96 + 97 +terra convo:rawpage(code: uint16, pg: convo.page, hdrs: lib.mem.ptr(lib.http.header)) 98 + var doc = data.view.docskel { 99 + instance = self.srv.cfg.instance; 100 + title = pg.title; 101 + body = pg.body; 102 + class = pg.class; 103 + navlinks = self.navbar; 104 + attr = ''; 105 + } 106 + var attrbuf: int8[32] 107 + if self.aid ~= 0 and self.ui_hue ~= 323 then 108 + var hdecbuf: int8[21] 109 + var hdec = lib.math.decstr(self.ui_hue, &hdecbuf[20]) 110 + lib.str.cpy(&attrbuf[0], ' style="--hue:') 111 + lib.str.cpy(&attrbuf[14], hdec) 112 + var len = &hdecbuf[20] - hdec 113 + lib.str.cpy(&attrbuf[14] + len, '"') 114 + doc.attr = &attrbuf[0] 115 + end 116 + 117 + if self.method == [lib.http.method.head] 118 + then doc:head(self.con,code,hdrs) 119 + else doc:send(self.con,code,hdrs) 120 + end 121 +end 122 + 123 +terra convo:statpage(code: uint16, pg: convo.page) 124 + var hdrs = array( 125 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 126 + lib.http.header { key = 'Cache-Control', value = 'no-store' } 127 + ) 128 + self:rawpage(code,pg, [lib.mem.ptr(lib.http.header)] { 129 + ptr = &hdrs[0]; 130 + ct = [hdrs.type.N] - lib.trn(pg.cache,1,0); 131 + }) 132 +end 133 + 134 +terra convo:livepage(pg: convo.page, lastup: lib.store.timepoint) 135 + var nbuf: int8[21] 136 + var hdrs = array( 137 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 138 + lib.http.header { key = 'Cache-Control', value = 'no-store' }, 139 + lib.http.header { 140 + key = 'X-Live-Newest-Artifact'; 141 + value = lib.math.decstr(lastup, &nbuf[20]); 142 + }, 143 + lib.http.header { key = 'Content-Length', value = '0' } 144 + ) 145 + if self.live_last ~= 0 and self.live_last == lastup then 146 + lib.net.mg_printf(self.con, 'HTTP/1.1 %s', lib.http.codestr(200)) 147 + for i = 0, [hdrs.type.N] do 148 + lib.net.mg_printf(self.con, '%s: %s\r\n', hdrs[i].key, hdrs[i].value) 149 + end 150 + lib.net.mg_printf(self.con, '\r\n') 151 + else 152 + self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] { 153 + ptr = &hdrs[0], ct = 3 154 + }) 155 + end 156 +end 157 + 158 +terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end 159 + 160 +terra convo:bytestream_trusted(lockdown: bool, mime: pstring, data: lib.mem.ptr(uint8)) 161 + var lockhdr = "Content-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\n" 162 + if not lockdown then lockhdr = "" end 163 + 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) 164 + lib.net.mg_send(self.con, data.ptr, data.ct) 165 + lib.net.mg_send(self.con, '\r\n', 2) 166 +end 167 + 168 +terra convo:json(data: pstring) 169 + self:bytestream_trusted(false, 'application/activity+json; charset=utf-8', data:blob()) 170 +end 171 + 172 +terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8)) 173 + var ty = lib.mime.lookup(mime) 174 + if ty == nil then 175 + lib.dbg("mime type ", {mime.ptr,mime.ct}, ' not in database!') 176 + mime = 'application/x-octet-stream' 177 + else 178 + if not ty.safe then 179 + lib.dbg("mime type ", {mime.ptr,mime.ct}, ' not safe!') 180 + if ty.binary then 181 + mime = 'application/x-octet-stream' 182 + else 183 + mime = 'text/plain' 184 + end 185 + end 186 + end 187 + self:bytestream_trusted(true, mime, data) 188 +end 189 + 190 +terra convo:reroute_cookie(dest: rawstring, cookie: rawstring) 191 + var hdrs = array( 192 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 193 + lib.http.header { key = 'Location', value = dest }, 194 + lib.http.header { key = 'Set-Cookie', value = cookie } 195 + ) 196 + 197 + var body = data.view.docskel { 198 + instance = self.srv.cfg.instance.ptr; 199 + title = 'rerouting'; 200 + body = 'you are being redirected'; 201 + class = 'error'; 202 + navlinks = ''; 203 + attr = ''; 204 + } 205 + 206 + body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] { 207 + ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0) 208 + }) 209 +end 210 + 211 +terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end 212 + 213 +terra convo:installkey(dest: rawstring, aid: uint64) 214 + var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1] 215 + do var p = &sesskey[0] 216 + p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1]) 217 + p = p + lib.session.cookie_gen(self.srv.cfg.secret, aid, lib.osclock.time(nil), p) 218 + lib.dbg('sending cookie ',{&sesskey[0],15}) 219 + p = lib.str.ncpy(p, '; Path=/', 9) 220 + end 221 + self:reroute_cookie(dest, &sesskey[0]) 222 +end 223 + 224 +terra convo:stra(sz: intptr) -- convenience function 225 + var s: lib.str.acc 226 + s:pool(&self.srv.pool,sz) 227 + return s 228 +end 229 + 230 +convo.methods.qstr = macro(function(self, ...) -- convenience string builder 231 + local exp = {...} 232 + return `lib.str.acc{}:pcompose(&self.srv.pool, [exp]):finalize() 233 +end) 234 + 235 +terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 236 + if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end 237 + 238 + if self:matchmime(lib.http.mime.html) then 239 + var body = [convo.page] { 240 + title = self:qstr('error :: ', title); 241 + body = self:qstr('<div class="message"><img class="icon" src="/s/warn.svg"><h1>',title,'</h1><p>',msg,'</p></div>'); 242 + class = 'error'; 243 + cache = false; 244 + } 245 + 246 + self:statpage(code, body) 247 + else 248 + var pg = lib.http.page { respcode = code, body = pstring.null() } 249 + var ctt = lib.http.mime.none 250 + if self:matchmime(lib.http.mime.json) then ctt = lib.http.mime.json 251 + pg.body = ([lib.tpl.mk'{"_parsav_error":@$ekind, "_parsav_error_desc":@$edesc}'] 252 + {ekind = title, edesc = msg}):poolstr(&self.srv.pool) 253 + elseif self:matchmime(lib.http.mime.text) then ctt = lib.http.mime.text 254 + pg.body = self:qstr('error: ',title,'\n',msg) 255 + elseif self:matchmime(lib.http.mime.mkdown) then ctt = lib.http.mime.mkdown 256 + pg.body = self:qstr('# error :: ',title,'\n\n',msg) 257 + elseif self:matchmime(lib.http.mime.ansi) then ctt = lib.http.mime.ansi 258 + pg.body = self:qstr('\27[1;31merror :: ',title,'\27[m\n',msg) 259 + end 260 + var cthdr = lib.http.header { 'Content-Type', 'text/plain' } 261 + if ctt == lib.http.mime.none then 262 + pg.headers.ct = 0 263 + else 264 + pg.headers = lib.typeof(pg.headers) { &cthdr, 1 } 265 + switch ctt do 266 + escape 267 + for key,ty in ipairs(lib.mime.types) do 268 + if key ~= 'none' and lib.http.mime[key] ~= nil then 269 + emit quote case [ctt.type](lib.http.mime.[key]) then cthdr.value = [ty.id[1]] end end 270 + end 271 + end 272 + end 273 + end 274 + end 275 + pg:send(self.con) 276 + end 277 +end 278 + 279 +terra convo:fail(code: uint16) 280 + switch code do 281 + escape 282 + local stderrors = { 283 + {400, 'bad request', "the action you have attempted on this resource is not meaningful"}; 284 + {401, 'unauthorized', "this resource is not available at your clearance level"}; 285 + {403, 'forbidden', "we can neither confirm nor deny the existence of this resource"}; 286 + {404, 'resource not found', "that resource is not extant on or known to this server"}; 287 + {405, 'method not allowed', "the method you have attempted on this resource is not meaningful"}; 288 + {406, 'not acceptable', "none of the suggested content types are a viable representation of this resource"}; 289 + {500, 'internal server error', "parsav did a fucksy wucksy"}; 290 + } 291 + 292 + for i,v in ipairs(stderrors) do 293 + emit quote case uint16([v[1]]) then 294 + self:complain([v]) 295 + end end 296 + end 297 + end 298 + else self:complain(500,'unknown error','an unrecognized error was thrown. this is a bug') 299 + end 300 +end 301 + 302 +terra convo:confirm(title: pstring, msg: pstring, cancel: pstring) 303 + var conf = data.view.confirm { 304 + title = title; 305 + query = msg; 306 + cancel = cancel; 307 + } 308 + var ti: lib.str.acc ti:pcompose(&self.srv.pool,'confirm :: ', title) 309 + var body = conf:poolstr(&self.srv.pool) -- defer body:free() 310 + var cf = [convo.page] { 311 + title = ti:finalize(); 312 + class = 'query'; 313 + body = body; cache = false; 314 + } 315 + self:stdpage(cf) 316 + --cf.title:free() 317 +end 318 + 319 +convo.methods.assertpow = macro(function(self, pow) 320 + return quote 321 + var ok = true 322 + if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then 323 + ok = false 324 + self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action']) 325 + end 326 + in ok end 327 +end) 328 + 329 +local pstr2mg, mg2pstr 330 +do -- aaaaaaaaaaaaaaaaaaaaaaaa 331 + mgstr = lib.util.find(lib.net.mg_http_message.entries, function(v) 332 + if v.field == 'body' or v[1] == 'body' then return v.type end 333 + end) 334 + terra pstr2mg(p: pstring): mgstr 335 + return mgstr { ptr = p.ptr, len = p.ct } 336 + end 337 + terra mg2pstr(m: mgstr): pstring 338 + return pstring { ptr = m.ptr, ct = m.len } 339 + end 340 +end 341 + 342 +-- CALL ONLY ONCE PER VAR 343 +terra convo:postv_next(name: pstring, start: &pstring) 344 + if self.varbuf.ptr == nil then 345 + self.varbuf = self.srv.pool:alloc(int8, self.msg.body.len + self.msg.query.len) 346 + self.vbofs = self.varbuf.ptr 347 + end 348 + var conv = pstr2mg(@start) 349 + var o = lib.net.mg_http_get_var( 350 + &conv, 351 + name.ptr, self.vbofs, 352 + self.varbuf.ct - (self.vbofs - self.varbuf.ptr) 353 + ) 354 + if o > 0 then 355 + start:advance(name.ct + o + 2) 356 + var r = self.vbofs 357 + self.vbofs = self.vbofs + o + 1 358 + @(self.vbofs - 1) = 0 359 + var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o}) 360 + return norm.ptr, norm.ct 361 + else return nil, 0 end 362 +end 363 +terra convo:postv(name: pstring) 364 + var start = mg2pstr(self.msg.body) 365 + return self:postv_next(name, &start) 366 +end 367 +terra convo:ppostv(name: pstring) 368 + var s,l = self:postv(name) 369 + return pstring { ptr = s, ct = l } 370 +end 371 +do 372 + local struct postiter { co: &convo where: pstring name: pstring } 373 + terra convo:eachpostv(name: pstring) 374 + return postiter { co = self, where = mg2pstr(self.msg.body), name = name } 375 + end 376 + postiter.metamethods.__for = function(self, body) 377 + return quote 378 + while true do 379 + var str, len = self.co:postv_next(self.name, &self.where) 380 + if str == nil then break end 381 + [ body(`pstring {str, len}) ] 382 + end 383 + end 384 + end 385 +end 386 + 387 +terra convo:getv(name: rawstring) 388 + if self.varbuf.ptr == nil then 389 + self.varbuf = self.srv.pool:alloc(int8, self.msg.query.len + self.msg.body.len) 390 + self.vbofs = self.varbuf.ptr 391 + end 392 + var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) 393 + if o > 0 then 394 + var r = self.vbofs 395 + self.vbofs = self.vbofs + o + 1 396 + @(self.vbofs - 1) = 0 397 + var norm = lib.str.normalize([lib.mem.ptr(int8)]{ptr = r, ct = o}) 398 + return norm.ptr, norm.ct 399 + else return nil, 0 end 400 +end 401 +terra convo:pgetv(name: rawstring) 402 + var s,l = self:getv(name) 403 + return pstring { ptr = s, ct = l } 404 +end 405 + 406 +return convo
Modified crypt.t from [a12c25b6dd] to [530b761d29].
14 14 const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one 15 15 16 16 local ctx = lib.pk.mbedtls_pk_context 17 17 terra ctx:free() lib.pk.mbedtls_pk_free(self) end 18 18 19 19 local struct hashalg { id: uint8 bytes: intptr } 20 20 local m = { 21 - pemfile = uint8[const.maxpemsz]; 21 + pemfile = int8[const.maxpemsz]; 22 + derfile = uint8[const.maxdersz]; 22 23 const = const; 23 24 algsz = { 24 25 sha1 = 160/8; 25 26 sha256 = 256/8; 26 27 sha512 = 512/8; 27 28 sha384 = 384/8; 28 29 sha224 = 224/8; ................................................................................ 71 72 v = v % (to - from) + from -- only works with unsigned!! 72 73 in v end 73 74 end) 74 75 75 76 terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr) 76 77 return m.spray(dest,sz) end 77 78 78 -terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool 79 +terra m.pem(pub: bool, key: &ctx, buf: &int8): bool 79 80 if pub then 80 - return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0 81 + return lib.pk.mbedtls_pk_write_pubkey_pem(key, [&uint8](buf), const.maxpemsz) == 0 81 82 else 82 - return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0 83 + return lib.pk.mbedtls_pk_write_key_pem(key, [&uint8](buf), const.maxpemsz) == 0 83 84 end 84 85 end 85 86 86 87 local binblob = lib.mem.ptr(uint8) 87 88 terra m.der(pub: bool, key: &ctx, buf: &uint8): binblob 88 89 var ofs: ptrdiff 89 90 if pub then ................................................................................ 112 113 lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) 113 114 var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx) 114 115 lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537) 115 116 116 117 return pk 117 118 end 118 119 119 -terra m.loadpriv(buf: &uint8, len: intptr): lib.stat(ctx) 120 +local binblob = lib.mem.ptr(uint8) 121 +terra m.loadpriv(buf: binblob): lib.stat(ctx) 120 122 lib.dbg('parsing saved private key') 121 123 122 124 var pk: ctx 123 125 lib.pk.mbedtls_pk_init(&pk) 124 - var rt = lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0) 126 + var rt = lib.pk.mbedtls_pk_parse_key(&pk, buf.ptr, buf.ct, nil, 0) 125 127 if rt == 0 then 126 128 return [lib.stat(ctx)] { ok = true, val = pk } 127 129 else 128 130 lib.pk.mbedtls_pk_free(&pk) 129 - return [lib.stat(ctx)] { ok = false } 131 + return [lib.stat(ctx)] { ok = false, error = rt } 130 132 end 131 133 end 132 134 133 -terra m.loadpub(buf: &uint8, len: intptr): lib.stat(ctx) 135 +terra m.loadpub(buf: binblob): lib.stat(ctx) 134 136 lib.dbg('parsing saved key') 135 137 136 138 var pk: ctx 137 139 lib.pk.mbedtls_pk_init(&pk) 138 - var rt = lib.pk.mbedtls_pk_parse_public_key(&pk, buf, len) 140 + var rt = lib.pk.mbedtls_pk_parse_public_key(&pk, buf.ptr, buf.ct) 139 141 if rt == 0 then 140 142 return [lib.stat(ctx)] { ok = true, val = pk } 141 143 else 142 144 lib.pk.mbedtls_pk_free(&pk) 143 145 return [lib.stat(ctx)] { ok = false, error = rt } 144 146 end 145 147 end
Modified mgtool.t from [63607cdb2b] to [aa223ca2fb].
421 421 [ lib.emit(false, 1, 'usage: ', `argv[0], ' actor ', umode.type.helptxt.flags, ' <xid> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp { 422 422 { 'actor <xid> rank <value>', 'set an actor\'s rank to <value> (remote actors cannot exercise rank-related powers, but benefit from rank immunities)' }; 423 423 { 'actor <xid> degrade', 'alias for `actor <xid> rank 0`' }; 424 424 { 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' }; 425 425 { 'actor <xid> instantiate', 'instantiate a remote actor, retrieving their profile and posts even if no one follows them' }; 426 426 { 'actor <xid> proscribe', 'globally ban an actor from interacting with your server' }; 427 427 { 'actor <xid> rehabilitate', 'lift a proscription on an actor' }; 428 + { 'actor <xid> xkey [pem|der]', 'extract an actor\'s public key in either PEM or DER form' }; 428 429 { 'actor <xid> purge-all <confirm-str>', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' }; 429 430 }) ] 430 431 return 1 431 432 end 432 433 if umode.arglist.ct >= 2 then 433 434 var degrade = lib.str.cmp(umode.arglist(1),'degrade') == 0 434 435 var xid = umode.arglist(0) ................................................................................ 467 468 lib.warn('completely purging actor ', usr.ptr.xid, ' and all related content from database') 468 469 dlg:actor_purge_uid(usr.ptr.id) 469 470 lib.report('actor purged') 470 471 else goto cmderr end 471 472 else goto cmderr end 472 473 else goto cmderr end 473 474 elseif lib.str.cmp(mode.arglist(0),'user') == 0 then 475 + if mode.arglist.ct < 3 then goto cmderr end 474 476 var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0)) 475 477 if umode.help then 476 478 [ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp { 477 479 { 'user <handle> create', 'add a new user' }; 478 480 { 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' }; 479 481 { 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' }; 480 482 { 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' }; 481 483 { 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' }; 482 484 { 'user <handle> emasculate', 'strip all administrative powers and rank from a user' }; 483 485 { 'user <handle> forgive', 'restore all default powers to a user' }; 484 486 { 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'}; 487 + { 'user <handle> xkey [pem|der]', 'extract an user\'s *private* key in either PEM or DER form' }; 485 488 }) ] 486 489 return 1 487 490 end 488 491 var handle = umode.arglist(0) 489 492 var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)}) 490 493 if umode.arglist.ct == 2 and lib.str.cmp(umode.arglist(1),'create')==0 then 491 494 if usr:ref() then lib.bail('that user already exists') end ................................................................................ 524 527 end 525 528 end 526 529 end 527 530 end 528 531 529 532 usr.ptr.rights.powers = newprivs 530 533 dlg:actor_save_privs(usr.ptr) 534 + elseif lib.str.cmp(umode.arglist(1),'xkey') == 0 and umode.arglist.ct == 3 then 535 + if not usr then lib.bail('unknown handle') end 536 + if lib.str.cmp(umode.arglist(2),'pem') == 0 then 537 + var pk = lib.crypt.loadpriv(usr().key) 538 + if not pk.ok then 539 + lib.bail('could not parse key! this is probably a bug') 540 + end 541 + var pem: lib.crypt.pemfile 542 + if not lib.crypt.pem(false, &pk.val, &pem[0]) then 543 + lib.bail('could not convert key to PEM! this is probably a bug') 544 + end 545 + lib.io.send(1, pem, lib.str.sz(&pem[0])) 546 + pk.val:free() 547 + elseif lib.str.cmp(umode.arglist(2),'der') == 0 then 548 + -- TODO avoid dumping binary to tty 549 + lib.warn('dumping user\'s \x1b[1mprivate\x1b[m key!') 550 + lib.io.send(1, [&int8](usr().key.ptr), usr().key.ct) 551 + else lib.bail('invalid key format') end 531 552 elseif lib.str.cmp(umode.arglist(1),'auth') == 0 and umode.arglist.ct == 4 then 532 553 var reset = lib.str.cmp(umode.arglist(3),'reset') == 0 533 554 if reset or lib.str.cmp(umode.arglist(3),'new') == 0 then 534 555 -- FIXME enable resetting pws for users who have 535 556 -- not logged in yet 536 557 if not usr then lib.bail('unknown handle') end 537 558 if lib.str.cmp(umode.arglist(2),'pw') == 0 then
Modified mime.t from [b6a24abaaf] to [8a0a5cf230].
1 +-- vim: ft=terra 1 2 local knowntypes = { 2 - ['text/csrc'] = { 3 - ext = 'c', lang = 'c'; 4 - }; 5 - ['text/html'] = { 6 - ext = 'html', lang = 'html'; 7 - unsafe = true; 3 + html = { 4 + ext = 'html', kind = 'markup', unsafe = true, id = { 5 + 'text/html'; 6 + 'application/xhtml+xml'; 7 + 'application/vnd.wap.xhtml+xml'; 8 + }; 8 9 }; 9 - ['text/x-lua'] = { 10 - ext = 'lua', lang = 'lua'; 11 - }; 12 - ['text/markdown'] = { 10 + flash = { ext = 'swf', kind = 'vm_prog', id = 'application/x-shockwave-flash', unsafe = true, binary = true }; 11 + java = { ext = 'java', kind = 'vm_prog', id = 'application/java', unsafe = true, binary = true }; 12 + css = { ext = 'css', kind = 'lang', id = 'text/css'}; 13 + text = { ext = 'txt', kind = 'text', id = 'text/plain' }; 14 + c = { ext = 'c', kind = 'prog_lang', id = 'text/csrc' }; 15 + xml = { ext = 'xml', kind = 'markup', unsafe = true, id = 'text/xml' }; 16 + lua = { ext = 'lua', kind = 'prog_lang', id = 'text/x-lua' }; 17 + ansi = { ext = 'ans', kind = 'text', id = 'text/x-ansi', doc = true, binary = true}; 18 + mkdown = { ext = 'md', kind = 'text', doc = true; id = 'text/markdown'; 13 19 formatter = 'smackdown'; 14 - ext = 'md', doc = true; 20 + }; 21 + json = { 22 + ext = 'json', kind = 'lang', id = { 23 + 'application/json'; 24 + 'application/activity+json'; 25 + 'application/ld+json'; 26 + 'application/jrd+json'; 27 + }; 15 28 }; 29 + svg = { ext = 'svg', kind = 'image', id = 'image/svg+xml' }; 30 + webp = { ext = 'webp', kind = 'image', id = 'image/webp', binary = true }; 31 + png = { ext = 'png', kind = 'image', id = 'image/png', binary = true }; 32 + jpeg = { ext = 'jpg', kind = 'image', id = 'image/jpeg', binary = true }; 33 + 34 + -- wildcard 35 + none = { id = '*/*' }; 36 +} 37 + 38 +local idcache = {} 39 + 40 + 41 +local pstr = lib.str.t 42 +local filekind = lib.enum [[none image text lang prog_lang markup vm_prog]] 43 +local struct mime { 44 + key: pstr 45 + canonical: pstr 46 + safe: bool 47 + binary: bool 48 + ext: pstr 49 + kind: filekind.t 50 + output: lib.http.mime.t 51 +} 52 + 53 +local typestore = {} 54 +for typecode, ty in pairs(knowntypes) do 55 + ty.key = typecode 56 + if type(ty.id) == 'string' then ty.id = {ty.id} end 57 + for i, mime in ipairs(ty.id) do 58 + idcache[mime] = ty 59 + end 60 + 61 + local op = lib.http.mime[typecode] 62 + if op == nil then op = lib.http.mime.none end 63 + print(typecode,op) 64 + 65 + ty.offset = #typestore 66 + typestore[#typestore + 1] = `mime { 67 + key = typecode; 68 + canonical = [ty.id[1]]; 69 + safe = [not ty.unsafe]; 70 + ext = [ty.ext or `pstr{nil,0}]; 71 + kind = [ty.kind and filekind[ty.kind] or filekind.none]; 72 + binary = [ty.binary or false]; 73 + output = [op]; 74 + } 75 + 76 +end 77 + 78 +local typedex = global(`array([typestore])) 79 +local struct mimemapping { 80 + string: pstr 81 + type: &mime 82 +} 83 + 84 +local typemap_l = {} 85 +for mime, ty in pairs(idcache) do 86 + typemap_l[#typemap_l + 1] = `mimemapping { 87 + string = mime; 88 + type = &typedex[ [ty.offset] ]; 89 + } 90 + 91 +end 92 +local typemap = global(`array([typemap_l])); 93 + 94 + 95 +return { 96 + type = mime; 97 + types = knowntypes; 98 + tbl = idcache; 99 + typedex = typedex; 100 + lookup = terra(m: pstr): &mime 101 + for i=0, [#typemap_l] do 102 + if m:cmp(typemap[i].string) then 103 + lib.io.fmt('returning type %s %u\n', typemap[i].type.key, typemap[i].type.output) 104 + return typemap[i].type 105 + end 106 + end 107 + return nil 108 + end; 16 109 }
Name change from conv.t to munge.t.
Modified parsav.t from [76fc393228] to [3c7c1240d3].
254 254 if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe 255 255 elseif #tbl >= 2^16 then ty = uint32 256 256 elseif #tbl >= 2^8 then ty = uint16 end 257 257 local o = { t = ty, members = tbl } 258 258 local strings = {} 259 259 for i, name in ipairs(tbl) do 260 260 o[name] = `[ty]([i - 1]) 261 - strings[i] = `[lib.mem.ref(int8)]{ptr=[name], ct=[#name]} 261 + strings[i] = `[lib.str.t]{ptr=[name], ct=[#name]} 262 262 end 263 263 o._str = terra(val: ty) 264 264 var l = array([strings]) 265 265 return l[val] 266 266 end 267 267 return o 268 268 end ................................................................................ 440 440 lib.pq = lib.loadlib('libpq','libpq-fe.h') 441 441 lib.jc = lib.loadlib('json-c','json.h') 442 442 443 443 lib.load { 444 444 'mem', 'math', 'str', 'file', 'crypt', 'ipc'; 445 445 'http', 'html', 'session', 'tpl', 'store', 'acl'; 446 446 447 + 'mime'; -- mimetype database & whitelist 447 448 'smackdown'; -- md-alike parser 448 - 'conv'; -- miscellaneous conversion/munging functions 449 + 'munge'; -- miscellaneous conversion/munging functions 449 450 } 450 451 451 452 local be = {} 452 453 for _, b in pairs(config.backends) do 453 454 be[#be+1] = terralib.loadfile(string.format('backend/%s.t',b))() 454 455 end 455 456 lib.store.backends = global(`array([be]))
Modified route.t from [56f7ddd740] to [1bb0eb41f3].
282 282 elseif path.ct == 1 then 283 283 lib.render.docpage(co, rstring.null()) 284 284 else 285 285 co:complain(404, 'no such documentation', 'invalid documentation URL') 286 286 end 287 287 end 288 288 289 -terra http.tweet_page(co: &lib.srv.convo, path: hpath, meth: method.t) 289 +terra http.tweet_page(co: &lib.srv.convo, path: hpath) 290 290 var pid, ok = lib.math.shorthand.parse(path(1).ptr, path(1).ct) 291 291 if not ok then 292 292 co:complain(400, 'bad post ID', 'that post ID is not valid') 293 293 return 294 294 end 295 295 var post = co.srv:post_fetch(pid) 296 296 var rt: lib.store.notice ................................................................................ 306 306 end 307 307 defer post:free() -- NOP on null 308 308 309 309 if path.ct == 3 then 310 310 var lnk: lib.str.acc lnk:compose('/post/', path(1)) 311 311 var lnkp = lnk:finalize() defer lnkp:free() 312 312 if post:ref() and path(2):cmp(lib.str.lit 'snitch') then 313 - if meth_get(meth) then 313 + if meth_get(co.method) then 314 314 var ui = data.view.report { 315 315 badtweet = lib.render.tweet(co, post.ptr, nil); 316 316 clnk = lnkp; 317 317 } 318 318 319 319 co:stdpage([lib.srv.convo.page] { 320 320 title = 'post :: report'; ................................................................................ 326 326 end 327 327 return 328 328 elseif post:ref() and post(0).author ~= co.who.id then 329 329 co:complain(403, 'forbidden', 'you cannot alter other people\'s posts') 330 330 return 331 331 elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then 332 332 if not co:assertpow('edit') then return end 333 - if meth_get(meth) then 333 + if meth_get(co.method) then 334 334 lib.render.compose(co, post.ptr, nil) 335 335 return 336 - elseif meth == method.post then 336 + elseif co.method == method.post then 337 337 var newbody = co:postv('post')._0 338 338 var newacl = co:postv('acl')._0 339 339 var newsubj = co:postv('subject')._0 340 340 if newbody ~= nil then post(0).body = newbody end 341 341 if newacl ~= nil then post(0).acl = newacl end 342 342 if newsubj ~= nil then post(0).subject = newsubj end 343 343 post(0):save(true) 344 344 co:reroute(lnkp.ptr) 345 345 end 346 346 return 347 347 elseif path(2):cmp(lib.str.lit 'del') then 348 - if meth_get(meth) then 348 + if meth_get(co.method) then 349 349 var conf: data.view.confirm 350 350 if post:ref() then 351 351 conf = data.view.confirm { 352 - title = 'delete post'; 353 - query = 'are you sure you want to delete this post?'; 352 + title = 'delete post'; 353 + query = 'are you sure you want to delete this post?'; 354 354 cancel = lnkp 355 355 } 356 356 else 357 357 conf = data.view.confirm { 358 - title = 'cancel retweet'; 359 - query = 'are you sure you want to undo this retweet?'; 358 + title = 'cancel retweet'; 359 + query = 'are you sure you want to undo this retweet?'; 360 360 cancel = '/'; 361 361 } 362 362 end 363 363 var body = conf:poolstr(&co.srv.pool) --defer body:free() 364 364 co:stdpage([lib.srv.convo.page] { 365 - title = 'post :: delete'; 366 - class = 'query'; 365 + title = 'post :: delete'; 366 + class = 'query'; 367 367 body = body; cache = false; 368 368 }) 369 369 return 370 - elseif meth == method.post then 370 + elseif co.method == method.post then 371 371 var act = co:ppostv('act') 372 372 if act:cmp('confirm') then 373 373 if post:ref() then 374 374 post().source:post_destroy(post().id) 375 375 elseif rt.kind ~= 0 then 376 376 co.srv:post_act_cancel(pid) 377 377 end ................................................................................ 378 378 co:reroute('/') -- TODO maybe return to parent or conversation if possible 379 379 return 380 380 else goto badop end 381 381 end 382 382 else goto badurl end 383 383 end 384 384 385 - if post:ref() and meth == method.post then 385 + if post:ref() and co.method == method.post then 386 386 if co.aid == 0 then goto noauth end 387 387 var act = co:ppostv('act') 388 - if act:cmp( 'like') and not co.srv:post_liked_uid(co.who.id,pid) then 388 + if act:cmp('like') and not co.srv:post_liked_uid(co.who.id,pid) then 389 389 co.srv:post_like(co.who.id, pid, false) 390 390 post.ptr.likes = post.ptr.likes + 1 391 - elseif act:cmp( 'dislike') and co.srv:post_liked_uid(co.who.id,pid) then 391 + elseif act:cmp('dislike') and co.srv:post_liked_uid(co.who.id,pid) then 392 392 co.srv:post_like(co.who.id, pid, true) 393 393 post.ptr.likes = post.ptr.likes - 1 394 - elseif act:cmp( 'rt') then 394 + elseif act:cmp('rt') then 395 395 co.srv:post_retweet(co.who.id, pid, false) 396 396 post.ptr.rts = post.ptr.rts + 1 397 - elseif act:cmp( 'post') then 397 + elseif act:cmp('post') then 398 398 var replytext = co:ppostv('post') 399 399 var acl = co:ppostv('acl') 400 400 var subj = co:ppostv('subject') 401 - if not acl then acl = 'all' end 401 + if not acl then acl = 'all' end 402 402 if not replytext then goto badop end 403 403 404 404 var reply = lib.store.post { 405 405 author = co.who.id, parent = pid; 406 406 subject = subj.ptr, acl = acl.ptr, body = replytext.ptr; 407 407 } 408 408 409 409 reply:publish(co.srv) 410 410 else goto badop end 411 411 end 412 412 413 413 if not post then goto badurl end 414 414 415 - lib.render.tweet_page(co, path, post.ptr) 415 + if co:matchmime(lib.http.mime.html) then 416 + lib.render.tweet_page(co, path, post.ptr) 417 + elseif co:matchmime(lib.http.mime.json) then 418 + co:json(lib.api.lp.tweet(co, post.ptr, false)) 419 + else goto notacc end 416 420 do return end 417 421 418 422 ::noauth:: do co:fail(401) return end 419 423 ::badurl:: do co:fail(404) return end 420 424 ::badop :: do co:fail(405) return end 425 + ::notacc:: do co:fail(406) return end 421 426 end 422 427 423 428 local terra 424 429 credsec_for_uid(co: &lib.srv.convo, uid: uint64) 425 430 var act = co:ppostv('act') 426 431 if not act then return true end 427 432 lib.dbg('handling credential action') ................................................................................ 465 470 var fr = co.srv.pool:frame() 466 471 var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), nonce) 467 472 if not lib.math.truncate64(hmac.ptr, hmac.ct) == noncevld then 468 473 co:complain(403,'nice try','what exactly are you trying to accomplish here, buddy') 469 474 return false 470 475 end 471 476 472 - var pkres = lib.crypt.loadpub(rsapub.ptr,rsapub.ct+1) -- needs NUL 477 + var pkres = lib.crypt.loadpub(binblob{rsapub.ptr,rsapub.ct+1}) -- needs NUL 473 478 if not pkres.ok then 474 479 co:complain(400,'invalid key','the key you have supplied is not a valid PEM or DER file') 475 480 return false 476 481 end 477 482 var pk = pkres.val 478 483 defer pk:free() 479 484 ................................................................................ 972 977 else co:reroute_cookie('/','auth=; Path=/') 973 978 end 974 979 else -- hierarchical routes 975 980 var path = lib.http.hier(&co.srv.pool, uri) --defer path:free() 976 981 if path.ct > 1 and path(0):cmp('user') then 977 982 http.actor_profile_uid(co, path) 978 983 elseif path.ct > 1 and path(0):cmp('post') then 979 - http.tweet_page(co, path, meth) 984 + http.tweet_page(co, path) 980 985 elseif path(0):cmp('tl') then 981 986 http.timeline(co, path) 982 987 elseif path(0):cmp('.well-known') then 983 988 if path(1):cmp('webfinger') then 984 989 if not co:matchmime(lib.http.mime.json) then goto nacc end 985 990 lib.api.webfinger(co) 986 991 end
Modified srv.t from [68c9cc33d4] to [dafa2dc374].
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()
Modified store.t from [33ecd773b1] to [6e43eba049].
213 213 elseif self.mode == 3 then 214 214 return 0,0,self.to_idx,self.from_idx 215 215 else lib.bail('invalid mode on timeline range!') end 216 216 end 217 217 218 218 struct m.post { 219 219 id: uint64 220 + uri: str 220 221 author: uint64 221 222 subject: str 222 223 body: str 223 224 acl: str 224 225 posted: m.timepoint 225 226 discovered: m.timepoint 226 227 edited: m.timepoint ................................................................................ 489 490 circle_destroy: {&m.source, uint64, uint64} -> {} 490 491 circle_members_fetch_cid: {&m.source, &lib.mem.pool, uint64} -> lib.mem.ptr(uint64) 491 492 circle_members_fetch_name: {&m.source, &lib.mem.pool, uint64, pstring} -> lib.mem.ptr(uint64) 492 493 circle_members_add_uid: {&m.source, uint64, uint64} -> {} 493 494 circle_members_del_uid: {&m.source, uint64, uint64} -> {} 494 495 circle_memberships_uid: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle) 495 496 497 + thread_top_find: {&m.source, uint64} -> uint64 498 + -- NOTE: this won't work if conversations are broken across multiple data sources! 499 + -- if this is a thing that's likely to happen, the overlord-side wrapper for this 500 + -- function (srv.t) should implement a more sophisticated algorithm over all the 501 + -- data sources, instead of just stopping when one parent is found 496 502 thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint 497 503 498 504 artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 499 505 -- instantiate an artifact in the database, either installing a new 500 506 -- artifact or returning the id of an existing artifact with the same hash 501 507 -- artifact: bytea 502 508 -- mime: pstring
Modified str.t from [072253bf19] to [bff3416286].
567 567 var add, cont = disemvowel_codepoint(cur) 568 568 if add:ref() then acc:ppush(add) end 569 569 cur = cont 570 570 end 571 571 return acc:finalize() 572 572 end 573 573 574 -terra m.qesc(pool: &lib.mem.pool, str: m.t, wrap: bool): m.t 574 +terra m.acc:qesc(str: m.t, wrap: bool) 575 575 -- escape double-quotes 576 - var a: m.acc a:pool(pool, str.ct + str.ct/2) 577 - if wrap then a:lpush '"' end 576 + if wrap then self:lpush '"' end 578 577 for i=0, str.ct do 579 - if str(i) == @'"' then a:lpush '\\"' 580 - elseif str(i) == @'\\' then a:lpush '\\\\' 578 + if str(i) == @'"' then self:lpush '\\"' 579 + elseif str(i) == @'\\' then self:lpush '\\\\' 580 + elseif str(i) == @'\n' then self:lpush '\\n' 581 + elseif str(i) == @'\t' then self:lpush '\\t' 581 582 elseif str(i) < 0x20 then -- for json 582 583 var hex = lib.math.hexbyte(str(i)) 583 - a:lpush('\\u00'):push(&hex[0], 2) 584 - else a:push(str.ptr + i,1) end 584 + self:lpush('\\u00'):push(&hex[0], 2) 585 + else self:push(str.ptr + i,1) end 585 586 end 586 - if wrap then a:lpush '"' end 587 + if wrap then self:lpush '"' end 588 + return self 589 +end 590 + 591 +terra m.qesc(pool: &lib.mem.pool, str: m.t, wrap: bool): m.t 592 + -- convenience function 593 + var a: m.acc a:pool(pool, 2 + str.ct + str.ct/2) 594 + a:qesc(str,wrap) 587 595 return a:finalize() 588 596 end 597 + 598 +terra m.acc:qpush(str: m.t) 599 + -- convenience adaptor 600 + return self:qesc(str, false) 601 +end 589 602 590 603 return m
Modified tpl.t from [b153c48352] to [f930d7a874].
34 34 str = str:gsub('%s+[\n$]','') 35 35 str = str:gsub('\n','') 36 36 str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed 37 37 str = str:gsub(tplchar .. '%?([-%w]+)', function(file) 38 38 if not docs[file] then docs[file] = data.doc[file] end 39 39 return string.format('<a href="#help-%s" class="help">?</a>', file) 40 40 end) 41 + local detritus = "" 41 42 for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([+:!$#%^]?)([-a-zA-Z0-9_]+):?()') do 42 43 if string.sub(str,start-1,start-1) ~= '\\' then 43 - segs[#segs+1] = string.sub(str,last,start-1) 44 + local suffix = "" 45 + if mode == '$' then suffix = '"' end 46 + segs[#segs+1] = detritus .. string.sub(str,last,start-1) .. suffix 47 + detritus = '' 44 48 fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) } 45 49 last = stop 50 + if mode == '$' then detritus = '"' end 46 51 end 47 52 end 48 - segs[#segs+1] = string.sub(str,last) 53 + segs[#segs+1] = detritus .. string.sub(str,last) 49 54 50 55 for i, s in ipairs(segs) do 51 56 segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o) 52 57 constlen = constlen + string.len(segs[i]) 53 58 end 54 59 55 60 for n,d in pairs(docs) do ................................................................................ 114 119 senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end 115 120 appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end 116 121 if fields[idx] and fields[idx].mode then 117 122 local f = fields[idx] 118 123 local fp = `symself.[f.key] 119 124 local sanexp 120 125 local nulexp 121 - if f.mode == '$' then sanexp = `lib.str.qesc(pool, fp, true) 126 + if f.mode == '$' then sanexp = `lib.str.qesc(pool, fp, false) 127 + -- we use the detritus mechanism rather than the quote-wrap mechanism bc, apart 128 + -- from being faster, 0-length strings cannot be sanitized into -- >0-length 129 + -- strings due to how nullity is indicated (to wit, if fp == 0, ptr can be wild) 122 130 elseif f.mode == '+' then sanexp = `lib.str.qesc(pool, fp, false) 123 131 elseif f.mode == '#' then 124 132 sanexp = quote 125 133 var ibuf: int8[21] 126 134 var ptr = lib.math.decstr(fp, &ibuf[20]) 127 135 in pstr {ptr=ptr, ct=&ibuf[20] - ptr} end 128 136 elseif f.mode == '^' then