| Comment: | milestone |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
419d1a1ebe4ca565be0130e83bed0022 |
| User & Date: | lexi on 2020-12-22 23:01:30 |
| Other Links: | manifest | tags |
|
2020-12-25
| ||
| 03:59 | big ol iteration check-in: 5b3a03ad34 user: lexi tags: trunk | |
|
2020-12-22
| ||
| 23:01 | milestone check-in: 419d1a1ebe user: lexi tags: trunk | |
|
2020-12-21
| ||
| 01:08 | continued iteration check-in: 25e05466d5 user: lexi tags: trunk | |
Modified backend/pgsql.t from [2e402a5b93] to [17bd63f8c3].
29 29 bio, rank, quota, key 30 30 from parsav_actors 31 31 where id = $1::bigint 32 32 ]]; 33 33 }; 34 34 35 35 actor_fetch_xid = { 36 - params = {rawstring}, sql = [[ 36 + params = {lib.mem.ptr(int8)}, sql = [[ 37 37 select a.id, a.nym, a.handle, a.origin, 38 - a.bio, a.rank, a.quota, a.key, $1::text, 38 + a.bio, a.rank, a.quota, a.key, 39 + coalesce(a.handle || '@' || s.domain, 40 + '@' || a.handle) as xid, 39 41 40 42 coalesce(s.domain, 41 43 (select value from parsav_config 42 44 where key='domain' limit 1)) as domain 43 45 44 46 from parsav_actors as a 45 47 left join parsav_servers as s 46 48 on a.origin = s.id 47 49 48 50 where $1::text = (a.handle || '@' || domain) or 49 51 $1::text = ('@' || a.handle || '@' || domain) or 50 - (a.origin is null and $1::text = ('@' || a.handle)) 52 + (a.origin is null and 53 + $1::text = a.handle or 54 + $1::text = ('@' || a.handle)) 51 55 ]]; 52 56 }; 53 57 54 58 actor_enum_local = { 55 59 params = {}, sql = [[ 56 60 select id, nym, handle, origin, 57 61 bio, rank, quota, key, ................................................................................ 62 66 ]]; 63 67 }; 64 68 65 69 actor_enum = { 66 70 params = {}, sql = [[ 67 71 select a.id, a.nym, a.handle, a.origin, 68 72 a.bio, a.rank, a.quota, a.key, 69 - a.handle ||'@'|| 70 - coalesce(s.domain, 71 - (select value from parsav_config 72 - where key='domain' limit 1)) as xid 73 + coalesce(a.handle || '@' || s.domain, 74 + '@' || a.handle) as xid 73 75 from parsav_actors a 74 76 left join parsav_servers s on s.id = a.origin 75 77 ]]; 76 78 }; 79 + 80 + actor_auth_how = { 81 + params = {rawstring, lib.store.inet}, sql = [[ 82 + with mts as (select a.kind from parsav_auth as a 83 + left join parsav_actors as u on u.id = a.uid 84 + where (a.uid is null or u.handle = $1::text or ( 85 + a.uid = 0 and a.name = $1::text 86 + )) and 87 + (a.netmask is null or a.netmask >> $2::inet) and 88 + blacklist = false) 89 + 90 + select 91 + (select count(*) from mts where kind like 'pw-%') > 0, 92 + (select count(*) from mts where kind like 'otp-%') > 0, 93 + (select count(*) from mts where kind like 'challenge-%') > 0, 94 + (select count(*) from mts where kind = 'trust') > 0 95 + ]]; -- cheat 96 + }; 97 + 98 + actor_session_fetch = { 99 + params = {uint64, lib.store.inet}, sql = [[ 100 + select a.id, a.nym, a.handle, a.origin, 101 + a.bio, a.rank, a.quota, a.key, 102 + coalesce(a.handle || '@' || s.domain, 103 + '@' || a.handle) as xid, 104 + 105 + au.restrict, 106 + array['post' ] <@ au.restrict as can_post, 107 + array['edit' ] <@ au.restrict as can_edit, 108 + array['acct' ] <@ au.restrict as can_acct, 109 + array['upload'] <@ au.restrict as can_upload, 110 + array['censor'] <@ au.restrict as can_censor, 111 + array['admin' ] <@ au.restrict as can_admin 112 + 113 + from parsav_auth au 114 + left join parsav_actors a on au.uid = a.id 115 + left join parsav_servers s on a.origin = s.id 116 + 117 + where au.aid = $1::bigint and au.blacklist = false and 118 + (au.netmask is null or au.netmask >> $2::inet) 119 + ]]; 120 + }; 77 121 } 78 122 79 123 local struct pqr { 80 124 sz: intptr 81 125 res: &lib.pq.PGresult 82 126 } 83 127 terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end ................................................................................ 85 129 return (lib.pq.PQgetisnull(self.res, row, col) == 1) 86 130 end 87 131 terra pqr:len(row: intptr, col: intptr) 88 132 return lib.pq.PQgetlength(self.res, row, col) 89 133 end 90 134 terra pqr:cols() return lib.pq.PQnfields(self.res) end 91 135 terra pqr:string(row: intptr, col: intptr) -- not to be exported!! 136 + if self:null(row,col) then return nil end 92 137 var v = lib.pq.PQgetvalue(self.res, row, col) 93 138 -- var r: lib.mem.ptr(int8) 94 139 -- r.ct = lib.str.sz(v) 95 140 -- r.ptr = v 96 141 return v 97 142 end 98 143 terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE 99 144 return [lib.mem.ptr(uint8)] { 100 145 ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col)); 101 146 ct = lib.pq.PQgetlength(self.res, row, col); 102 147 } 103 148 end 104 149 terra pqr:String(row: intptr, col: intptr) -- suitable to be exported 150 + if self:null(row,col) then return [lib.mem.ptr(int8)] {ptr=nil,ct=0} end 105 151 var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) } 106 152 s.ct = lib.pq.PQgetlength(self.res, row, col) 107 153 return s 108 154 end 109 155 terra pqr:bool(row: intptr, col: intptr) 110 156 var v = lib.pq.PQgetvalue(self.res, row, col) 111 157 if @v == 0x01 then return true else return false end ................................................................................ 176 222 local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {} 177 223 for i, ty in ipairs(q.params) do 178 224 args[i] = symbol(ty) 179 225 ft[i] = `1 180 226 if ty == rawstring then 181 227 counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]])) 182 228 casts[i] = `[&int8]([args[i]]) 229 + elseif ty == lib.store.inet then -- assume not CIDR 230 + counters[i] = `lib.trn([args[i]].pv == 4,4,16)+4 231 + casts[i] = quote 232 + var ipbuf: int8[20] 233 + ;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf)) 234 + in &ipbuf[0] end 235 + elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then 236 + counters[i] = `[args[i]].ct 237 + casts[i] = `[&int8]([args[i]].ptr) 183 238 elseif ty:isintegral() then 184 239 counters[i] = ty.bytes 185 240 casts[i] = `[&int8](&[args[i]]) 186 241 fixers[#fixers + 1] = quote 187 242 --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]]) 188 243 [args[i]] = lib.math.netswap(ty, [args[i]]) 189 244 end ................................................................................ 212 267 return pqr {ct, res} 213 268 end 214 269 end 215 270 end 216 271 217 272 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) 218 273 var a: lib.mem.ptr(lib.store.actor) 274 + 219 275 if r:cols() >= 8 then 220 276 a = [ lib.str.encapsulate(lib.store.actor, { 221 - nym = {`r:string(row, 1); `r:len(row,1) + 1}; 277 + nym = {`r:string(row,1), `r:len(row,1)+1}; 278 + bio = {`r:string(row,4), `r:len(row,4)+1}; 222 279 handle = {`r:string(row, 2); `r:len(row,2) + 1}; 223 - bio = {`r:string(row, 4); `r:len(row,4) + 1}; 224 280 xid = {`r:string(row, 8); `r:len(row,8) + 1}; 225 281 }) ] 226 282 else 227 283 a = [ lib.str.encapsulate(lib.store.actor, { 228 - nym = {`r:string(row, 1); `r:len(row,1) + 1}; 284 + nym = {`r:string(row,1), `r:len(row,1)+1}; 285 + bio = {`r:string(row,4), `r:len(row,4)+1}; 229 286 handle = {`r:string(row, 2); `r:len(row,2) + 1}; 230 - bio = {`r:string(row, 4); `r:len(row,4) + 1}; 231 287 }) ] 232 288 a.ptr.xid = nil 233 289 end 234 290 a.ptr.id = r:int(uint64, row, 0); 235 291 a.ptr.rights = lib.store.rights_default(); 236 292 a.ptr.rights.rank = r:int(uint16, row, 5); 237 293 a.ptr.rights.quota = r:int(uint32, row, 6); ................................................................................ 345 401 return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil } 346 402 else defer r:free() 347 403 var a = row_to_actor(&r, 0) 348 404 a.ptr.source = src 349 405 return a 350 406 end 351 407 end]; 408 + 409 + actor_fetch_xid = [terra(src: &lib.store.source, xid: lib.mem.ptr(int8)) 410 + var r = queries.actor_fetch_xid.exec(src, xid) 411 + if r.sz == 0 then 412 + return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil } 413 + else defer r:free() 414 + var a = row_to_actor(&r, 0) 415 + a.ptr.source = src 416 + return a 417 + end 418 + end]; 352 419 353 420 actor_enum = [terra(src: &lib.store.source) 354 421 var r = queries.actor_enum.exec(src) 355 422 if r.sz == 0 then 356 423 return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil } 357 424 else defer r:free() 358 425 var mem = lib.mem.heapa([&lib.store.actor], r.sz) ................................................................................ 373 440 end]; 374 441 375 442 actor_auth_how = [terra( 376 443 src: &lib.store.source, 377 444 ip: lib.store.inet, 378 445 username: rawstring 379 446 ) 380 - var authview = src:conf_get('auth-source') defer authview:free() 381 - var a: lib.str.acc defer a:free() 382 - a:compose('with mts as (select a.kind from ',authview,[' ' .. sqlsquash [[as a 383 - left join parsav_actors as u on u.id = a.uid 384 - where (a.uid is null or u.handle = $1::text or ( 385 - a.uid = 0 and a.name = $1::text 386 - )) and 387 - (a.netmask is null or a.netmask >> $2::inet) and 388 - blacklist = false) 389 - 390 - select 391 - (select count(*) from mts where kind like 'pw-%') > 0, 392 - (select count(*) from mts where kind like 'otp-%') > 0, 393 - (select count(*) from mts where kind like 'challenge-%') > 0, 394 - (select count(*) from mts where kind = 'trust') > 0 ]]]) -- cheat 395 447 var cs: lib.store.credset cs:clear(); 396 - var ipbuf: int8[20] 397 - ;[pqt[lib.store.inet](false)](ip, [&uint8](&ipbuf)) 398 - var ipbl: intptr if ip.pv == 4 then ipbl = 8 else ipbl = 20 end 399 - var params = arrayof(rawstring, username, [&int8](&ipbuf)) 400 - var params_sz = arrayof(int, lib.str.sz(username), ipbl) 401 - var params_ft = arrayof(int, 1, 1) 402 - var res = lib.pq.PQexecParams([&lib.pq.PGconn](src.handle), a.buf, 2, nil, 403 - params, params_sz, params_ft, 1) 404 - if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then 405 - if res == nil then 406 - lib.bail('grievous error occurred checking for auth methods') 407 - end 408 - lib.bail('could not get auth methods for user ',username,':\n',lib.pq.PQresultErrorMessage(res)) 409 - end 410 - var r = pqr { res = res, sz = lib.pq.PQntuples(res) } 448 + var r = queries.actor_auth_how.exec(src, username, ip) 411 449 if r.sz == 0 then return cs end -- just in case 450 + defer r:free() 412 451 (cs.pw << r:bool(0,0)) 413 452 (cs.otp << r:bool(0,1)) 414 453 (cs.challenge << r:bool(0,2)) 415 454 (cs.trust << r:bool(0,3)) 416 - lib.pq.PQclear(res) 417 455 return cs 418 456 end]; 419 457 420 458 actor_auth_pw = [terra( 421 459 src: &lib.store.source, 422 460 ip: lib.store.inet, 423 461 username: rawstring, 424 462 cred: rawstring 425 463 ) 426 - var authview = src:conf_get('auth-source') defer authview:free() 427 - var a: lib.str.acc defer a:free() 428 - a:compose('select a.aid from ',authview,[' ' .. sqlsquash [[as a 464 + var q = [[select a.aid from parsav_auth as a 429 465 left join parsav_actors as u on u.id = a.uid 430 466 where (a.uid is null or u.handle = $1::text or ( 431 467 a.uid = 0 and a.name = $1::text 432 468 )) and 433 469 (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and 434 470 (a.netmask is null or a.netmask >> $4::inet) 435 - order by blacklist desc limit 1]]]) 471 + order by blacklist desc limit 1]] 436 472 437 - [ checksha(`src.handle, `a.buf, 256, ip, username, cred) ] -- most common 438 - [ checksha(`src.handle, `a.buf, 512, ip, username, cred) ] -- most secure 439 - [ checksha(`src.handle, `a.buf, 384, ip, username, cred) ] -- weird 440 - [ checksha(`src.handle, `a.buf, 224, ip, username, cred) ] -- weirdest 473 + [ checksha(`src.handle, q, 256, ip, username, cred) ] -- most common 474 + [ checksha(`src.handle, q, 512, ip, username, cred) ] -- most secure 475 + [ checksha(`src.handle, q, 384, ip, username, cred) ] -- weird 476 + [ checksha(`src.handle, q, 224, ip, username, cred) ] -- weirdest 441 477 442 478 -- TODO: check pbkdf2-hmac 443 479 -- TODO: check OTP 444 480 return 0 445 481 end]; 482 + 483 + actor_session_fetch = [terra( 484 + src: &lib.store.source, 485 + aid: uint64, 486 + ip : lib.store.inet 487 + ): { lib.stat(lib.store.auth), lib.mem.ptr(lib.store.actor) } 488 + var r = queries.actor_session_fetch.exec(src, aid, ip) 489 + if r.sz == 0 then goto fail end 490 + do defer r:free() 491 + 492 + if r:null(0,0) then goto fail end 493 + 494 + var a = row_to_actor(&r, 0) 495 + a.ptr.source = src 496 + 497 + var au = [lib.stat(lib.store.auth)] { ok = true } 498 + au.val.aid = aid 499 + au.val.uid = a.ptr.id 500 + if not r:null(0,10) then -- restricted? 501 + au.val.privs:clear() 502 + (au.val.privs.post << r:bool(0,11)) 503 + (au.val.privs.edit << r:bool(0,12)) 504 + (au.val.privs.acct << r:bool(0,13)) 505 + (au.val.privs.upload << r:bool(0,14)) 506 + (au.val.privs.censor << r:bool(0,15)) 507 + (au.val.privs.admin << r:bool(0,16)) 508 + else au.val.privs:fill() end 509 + 510 + return au, a 511 + end 512 + 513 + ::fail:: return [lib.stat (lib.store.auth) ] { ok = false }, 514 + [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 } 515 + end]; 446 516 } 447 517 448 518 return b
Modified cmdparse.t from [bad20dd1d0] to [50677a3c0c].
2 2 return function(tbl) 3 3 local options = terralib.types.newstruct('options') do 4 4 local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end 5 5 local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n' 6 6 options.entries = { 7 7 {field = 'arglist', type = lib.mem.ptr(rawstring)} 8 8 } 9 - local shortcases, longcases, init = {}, {}, {} 9 + local shortcases, longcases, init, verifiers = {}, {}, {}, {} 10 10 local self = symbol(&options) 11 11 local arg = symbol(rawstring) 12 - local idxo = symbol(uint) 12 + local idx = symbol(uint) 13 + local argv = symbol(&rawstring) 14 + local argc = symbol(int) 15 + local optstack = symbol(intptr) 13 16 local skip = label() 14 17 local sanitize = function(s) return s:gsub('_','-') end 15 18 for o,desc in pairs(tbl) do 16 19 local consume = desc[3] or 0 17 20 options.entries[#options.entries + 1] = { 18 - field = o, type = (consume > 0) and uint or bool 21 + field = o, type = (consume > 0) and &rawstring or bool 19 22 } 20 23 helpstr = helpstr .. string.format(' -%s --%s: %s\n', 21 24 desc[1], sanitize(o), desc[2]) 22 25 end 23 26 for o,desc in pairs(tbl) do 24 27 local flag = desc[1] 25 28 local consume = desc[3] or 0 26 - init[#init + 1] = quote [self].[o] = [consume > 0 and 0 or false] end 27 - local ch if consume > 0 then ch = quote 28 - [self].[o] = idxo 29 - idxo = idxo + consume 30 - end else ch = quote 29 + init[#init + 1] = quote [self].[o] = [(consume > 0 and `nil) or false] end 30 + local ch if consume > 0 then 31 + ch = quote 32 + [self].[o] = argv+(idx+1+optstack) 33 + optstack = optstack + consume 34 + end 35 + verifiers[#verifiers+1] = quote 36 + var terminus = argv + argc 37 + if [self].[o] ~= nil and [self].[o] >= terminus then 38 + lib.bail(['missing argument for command line option ' .. sanitize(o)]) 39 + end 40 + end 41 + else ch = quote 31 42 [self].[o] = true 32 43 end end 33 44 shortcases[#shortcases + 1] = quote 34 45 case [int8]([string.byte(flag)]) then [ch] end 35 46 end 36 47 longcases[#longcases + 1] = quote 37 48 if lib.str.cmp([arg]+2, [sanitize(o)]) == 0 then [ch] goto [skip] end 38 49 end 39 50 end 40 51 terra options:free() self.arglist:free() end 41 - options.methods.parse = terra([self], argc: int, argv: &rawstring) 52 + options.methods.parse = terra([self], [argc], [argv]) 42 53 [init] 43 54 var parseopts = true 44 - var [idxo] = 1 55 + var [optstack] = 0 45 56 self.arglist = lib.mem.heapa(rawstring, argc) 46 57 var finalargc = 0 47 - for i=1,argc do 48 - var [arg] = argv[i] 49 - if arg[0] == ('-')[0] and parseopts then 50 - if arg[1] == ('-')[0] then -- long option 58 + for [idx]=1,argc do 59 + var [arg] = argv[idx] 60 + if optstack > 0 then optstack = optstack - 1 goto [skip] end 61 + if arg[0] == @'-' and parseopts then 62 + if arg[1] == @'-' then -- long option 51 63 if arg[2] == 0 then -- last option 52 64 parseopts = false 53 65 else [longcases] end 54 66 else -- short options 55 - var j = 1 56 - while arg[j] ~= 0 do 67 + var j = 1 while arg[j] ~= 0 do 57 68 switch arg[j] do [shortcases] end 58 69 j = j + 1 59 70 end 60 71 end 61 72 else 62 73 self.arglist.ptr[finalargc] = arg 63 74 finalargc = finalargc + 1 64 75 end 65 76 ::[skip]:: 66 77 end 78 + [verifiers] 67 79 if finalargc == 0 then self.arglist:free() 68 80 else self.arglist:resize(finalargc) end 69 81 end 70 82 options.helptxt = helpstr 71 83 end 72 84 return options 73 85 end
Modified config.lua from [53779583db] to [7cb566b503].
36 36 id = u.rndstr(6); 37 37 release = u.ingest('release'); 38 38 when = os.date(); 39 39 }; 40 40 feat = {}; 41 41 backends = defaultlist('parsav_backends', 'pgsql'); 42 42 braingeniousmode = false; 43 + embeds = { 44 + {'style.css', 'text/css'}; 45 + }; 43 46 } 44 47 if u.ping '.fslckout' or u.ping '_FOSSIL_' then 45 48 if u.ping '_FOSSIL_' then default_os = 'windows' end 46 49 conf.build.branch = u.exec { 'fossil', 'branch', 'current' } 47 50 conf.build.checkout = (u.exec { 'fossil', 'sql', 48 51 [[select value from localdb.vvar where name = 'checkout-hash']] 49 52 }):gsub("^'(.*)'$", '%1')
Modified crypt.t from [709e2a6426] to [48369b50e0].
13 13 const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to 14 14 15 15 local ctx = lib.pk.mbedtls_pk_context 16 16 17 17 local struct hashalg { id: uint8 bytes: intptr } 18 18 local m = { 19 19 pemfile = uint8[const.maxpemsz]; 20 - alg = { 21 - sha1 = `hashalg {id = lib.md.MBEDTLS_MD_SHA1; bytes = 160/8}; 22 - sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = 256/8}; 23 - sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = 512/8}; 24 - sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = 384/8}; 25 - sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = 224/8}; 26 - -- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!! 27 - }; 20 + algsz = { 21 + sha1 = 160/8; 22 + sha256 = 256/8; 23 + sha512 = 512/8; 24 + sha384 = 384/8; 25 + sha224 = 224/8; 26 + } 28 27 } 28 +m.alg = { 29 + sha1 = `hashalg {id = lib.md.MBEDTLS_MD_SHA1; bytes = m.algsz.sha1}; 30 + sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = m.algsz.sha256}; 31 + sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = m.algsz.sha512}; 32 + sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = m.algsz.sha384}; 33 + sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = m.algsz.sha224}; 34 + -- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!! 35 +}; 29 36 local callbacks = {} 30 37 if config.feat.randomizer == 'kern' then 31 38 local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff); 32 39 terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int 33 40 return rnd(dest, sz, 0) 34 41 end 35 42 elseif config.feat.randomizer == 'devfs' then
Modified file.t from [fc7770c3f7] to [a196c3def2].
1 1 -- vim: ft=terra 2 2 -- TODO: add support for windows IO calls 3 3 local handle_type = int 4 4 local posix = terralib.includec 'fcntl.h' 5 5 local unistd = terralib.includec 'unistd.h' 6 +local mm = terralib.includec 'sys/mman.h' 6 7 7 8 struct file { 8 9 handle: handle_type 9 10 read: bool 10 11 write: bool 11 12 } 12 13 ................................................................................ 61 62 elseif wh == [file.seek.eof] then 62 63 whence = unistd.SEEK_END 63 64 else lib.bail('invalid seek mode') end 64 65 65 66 return unistd.lseek(self.handle, ofs, whence) 66 67 end; 67 68 } 69 + 70 +terra file:len(): intptr 71 + var cur = self:seek(0, [file.seek.ofs]) 72 + var sz = self:seek(0, [file.seek.eof]) 73 + self:seek(cur, [file.seek.abs]) 74 + return sz 75 +end 76 + 77 +local struct mapping { 78 + addr: &opaque 79 + sz: intptr 80 +} 81 +terra mapping:unmap() 82 + lib.dbg('releasing file mapping') 83 + return mm.munmap(self.addr, self.sz) 84 +end 85 +-- provide for syncing mechanism? 86 + 87 +terra file:mapin(ofs: intptr, sz: intptr) 88 + var prot = 0 89 + if self.read then prot = mm.PROT_READ end 90 + if self.write then prot = prot or mm.PROT_WRITE end 91 + if sz == 0 then sz = self:len() end 92 + lib.dbg('mapping file into memory') 93 + return mapping { 94 + addr = mm.mmap(nil, sz, prot, mm.MAP_PRIVATE, self.handle, ofs); 95 + sz = sz; 96 + } 97 +end 68 98 69 99 return file
Modified http.t from [6d6c40fc94] to [e5e590a634].
1 1 -- vim: ft=terra 2 2 local m = {} 3 3 local util = dofile('common.lua') 4 4 5 +m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' } 6 + 7 +m.findheader = terralib.externfunction('mg_http_get_header', {&lib.net.mg_http_message, rawstring} -> &lib.mem.ref(int8)) -- unfortunately necessary to access this function, as its return type conflicts with a function name 8 + 5 9 struct m.header { 6 10 key: rawstring 7 11 value: rawstring 8 12 } 9 13 struct m.page { 10 14 respcode: uint16 11 15 body: lib.mem.ptr(int8) ................................................................................ 15 19 local resps = { 16 20 [200] = 'OK'; 17 21 [201] = 'Created'; 18 22 [301] = 'Moved Permanently'; 19 23 [302] = 'Found'; 20 24 [303] = 'See Other'; 21 25 [307] = 'Temporary Redirect'; 22 - [404] = 'Not Found'; 26 + [400] = 'Bad Request'; 23 27 [401] = 'Unauthorized'; 28 + [402] = 'Payment Required'; 24 29 [403] = 'Forbidden'; 30 + [404] = 'Not Found'; 31 + [405] = 'Method Not Allowed'; 32 + [406] = 'Not Acceptable'; 25 33 [418] = 'I\'m a teapot'; 26 34 [405] = 'Method Not Allowed'; 27 35 [500] = 'Internal Server Error'; 28 36 } 29 37 local resptext = symbol(rawstring) 30 38 local resplen = symbol(intptr) 31 39 local respbranches = {} ................................................................................ 36 44 end 37 45 end 38 46 m.codestr = terra(code: uint16) 39 47 var [resptext] var [resplen] 40 48 switch code do [respbranches] end 41 49 return resptext, resplen 42 50 end 51 + 52 +terra m.hier(uri: lib.mem.ptr(int8)): lib.mem.ptr(lib.mem.ref(int8)) 53 + if uri.ct == 0 then return [lib.mem.ptr(lib.mem.ref(int8))] { ptr = nil, ct = 0 } end 54 + var sz = 1 55 + var start = 0 if uri.ptr[0] == @'/' then start = 1 end 56 + for i = start, uri.ct do if uri.ptr[i] == @'/' then sz = sz + 1 end end 57 + var lst = lib.mem.heapa([lib.mem.ref(int8)], sz) 58 + if sz == 0 then 59 + lst.ptr[0].ptr = uri.ptr 60 + lst.ptr[0].ct = uri.ct 61 + return lst 62 + end 63 + 64 + var idx: intptr = 0 65 + var len: intptr = 0 66 + lst.ptr[0].ptr = uri.ptr 67 + for i = 0, uri.ct do 68 + if uri.ptr[i] == @'/' then 69 + if len == 0 then 70 + lst.ptr[idx].ptr = lst.ptr[idx].ptr + 1 71 + goto skip 72 + end 73 + lst.ptr[idx].ct = len 74 + idx = idx + 1 75 + lst.ptr[idx].ptr = uri.ptr + i + 1 76 + len = 0 77 + else 78 + len = len + 1 79 + end 80 + ::skip::end 81 + lst.ptr[idx].ct = len 82 + 83 + return lst 84 +end 85 + 43 86 m.page.methods = { 44 87 free = terra(self: &m.page) 45 88 self.body:free() 46 89 self.headers:free() 47 90 end; 48 91 send = terra(self: &m.page, con: &lib.net.mg_connection) 49 92 var code: rawstring
Modified makefile from [40c8adaeb1] to [2d2acfe121].
24 24 mkdir $@ 25 25 # generate a shim static library so mongoose cooperates 26 26 # with the build apparatus. note that parsav is designed 27 27 # to be fronted by a real web server like nginx if SSL 28 28 # is to be used, so we don't turn on SSL in mongoose 29 29 lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h 30 30 $(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \ 31 - -DMG_ENABLE_THREADS \ 32 - -DMG_ENABLE_IPV6 \ 33 - -DMG_ENABLE_HTTP_WEBDAV \ 31 + -DMG_ENABLE_THREADS=1 \ 32 + -DMG_ENABLE_IPV6=1 \ 33 + -DMG_ENABLE_HTTP_WEBDAV=1 \ 34 34 -DMG_ENABLE_HTTP_WEBSOCKET=0 35 35 ar rcs $@ lib/mongoose/*.o 36 36 ranlib $@ 37 37 38 38 lib/json-c/Makefile: lib/json-c lib/json-c/CMakeLists.txt 39 39 cd lib/json-c && cmake . 40 40 lib/json-c/libjson-c.a: lib/json-c/Makefile
Modified math.t from [f642fd73ba] to [f661c3d77e].
78 78 for i = 0, len do 79 79 var v, ok = m.shorthand.cval(s[i]) 80 80 if ok == false then return 0, false end 81 81 val = (val * 64) + v 82 82 end 83 83 return val, true 84 84 end 85 + 86 +terra m.truncate64(val: &uint8, len: intptr): uint64 87 + var r: uint64 = 0 88 + for i=0, len do 89 + r = r << 3 90 + r = r + @val 91 + val = val + 1 92 + end 93 + return r 94 +end 95 + 96 +m.biggest = macro(function(a,b) 97 + local ty = a.tree.type 98 + if b.tree.type.bytes > ty.bytes then ty = b.tree.type end 99 + return quote 100 + var _a = [a] 101 + var _b = [b] 102 + var r: ty if _a > _b then r = _a else r = _b end 103 + in r end 104 +end) 105 + 106 +m.smallest = macro(function(a,b) 107 + local ty = a.tree.type 108 + if b.tree.type.bytes < ty.bytes then ty = b.tree.type end 109 + return quote 110 + var _a = [a] 111 + var _b = [b] 112 + var r: ty if _a < _b then r = _a else r = _b end 113 + in r end 114 +end) 85 115 86 116 terra m.hexdigit(hb: uint8): int8 87 117 var a = hb and 0x0F 88 118 if a < 10 then return 0x30 + a 89 119 else return 0x61 + (a-10) end 90 120 end 91 121
Modified mem.t from [07d2cb4a7e] to [e41dd1991a].
15 15 local p = m.ptr(ty:astype()) 16 16 return `p { 17 17 ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz)); 18 18 ct = sz; 19 19 } 20 20 end) 21 21 22 -m.ptr = terralib.memoize(function(ty) 23 - local t = terralib.types.newstruct(string.format('ptr<%s>', ty)) 22 +local function mkptr(ty, dyn) 23 + local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', ty)) 24 24 t.entries = { 25 25 {'ptr', &ty}; 26 26 {'ct', intptr}; 27 27 } 28 28 t.ptr_basetype = ty 29 29 local recurse = false 30 30 if ty:isstruct() then 31 31 if ty.methods.free then recurse = true end 32 32 end 33 33 t.metamethods.__not = macro(function(self) 34 34 return `self.ptr 35 35 end) 36 - t.methods = { 37 - free = terra(self: &t): bool 38 - [recurse and quote 39 - self.ptr:free() 40 - end or {}] 41 - if self.ct > 0 then 42 - m.heapf(self.ptr) 43 - self.ct = 0 44 - return true 36 + if dyn then 37 + t.methods = { 38 + free = terra(self: &t): bool 39 + [recurse and quote 40 + self.ptr:free() 41 + end or {}] 42 + if self.ct > 0 then 43 + m.heapf(self.ptr) 44 + self.ct = 0 45 + return true 46 + end 47 + return false 48 + end; 49 + init = terra(self: &t, newct: intptr): bool 50 + var nv = [&ty](m.heapa_raw(sizeof(ty) * newct)) 51 + if nv ~= nil then 52 + self.ptr = nv 53 + self.ct = newct 54 + return true 55 + else return false end 56 + end; 57 + resize = terra(self: &t, newct: intptr): bool 58 + var nv: &ty 59 + if self.ct > 0 60 + then nv = [&ty](m.heapr_raw(self.ptr, sizeof(ty) * newct)) 61 + else nv = [&ty](m.heapa_raw(sizeof(ty) * newct)) 62 + end 63 + if nv ~= nil then 64 + self.ptr = nv 65 + self.ct = newct 66 + return true 67 + else return false end 68 + end; 69 + } 70 + terra t:ensure(n: intptr) 71 + if self.ptr == nil then 72 + if not self:init(n) then return t {ptr=nil,ct=0} end 45 73 end 46 - return false 47 - end; 48 - init = terra(self: &t, newct: intptr): bool 49 - var nv = [&ty](m.heapa_raw(sizeof(ty) * newct)) 50 - if nv ~= nil then 51 - self.ptr = nv 52 - self.ct = newct 53 - return true 54 - else return false end 55 - end; 56 - resize = terra(self: &t, newct: intptr): bool 57 - var nv: &ty 58 - if self.ct > 0 59 - then nv = [&ty](m.heapr_raw(self.ptr, sizeof(ty) * newct)) 60 - else nv = [&ty](m.heapa_raw(sizeof(ty) * newct)) 74 + if self.ct >= n then return @self end 75 + self:resize(n) 76 + return @self 77 + end 78 + end 79 + terra t:advance(n: intptr) 80 + self.ptr = self.ptr + n 81 + self.ct = self.ct - n 82 + return self.ptr 83 + end 84 + if not ty:isstruct() then 85 + terra t:cmp_raw(other: &ty) 86 + for i=0, self.ct do 87 + if self.ptr[i] ~= other[i] then return false end 61 88 end 62 - if nv ~= nil then 63 - self.ptr = nv 64 - self.ct = newct 65 - return true 66 - else return false end 67 - end; 68 - } 89 + return true 90 + end 91 + terra t:cmp(other: t) 92 + if other.ct ~= self.ct then return false end 93 + return self:cmp_raw(other.ptr) 94 + end 95 + end 69 96 return t 70 -end) 97 +end 98 + 99 +m.ptr = terralib.memoize(function(ty) return mkptr(ty, true) end) 100 +m.ref = terralib.memoize(function(ty) return mkptr(ty, false) end) 71 101 72 102 m.vec = terralib.memoize(function(ty) 73 103 local v = terralib.types.newstruct(string.format('vec<%s>', ty.name)) 74 104 v.entries = { 75 105 {field = 'storage', type = m.ptr(ty)}; 76 106 {field = 'sz', type = intptr}; 77 107 {field = 'run', type = intptr};
Modified parsav.t from [fcc06cebed] to [41c6f93980].
4 4 local buildopts, buildargs = util.parseargs{...} 5 5 config = dofile('config.lua') 6 6 7 7 lib = { 8 8 init = {}; 9 9 load = function(lst) 10 10 for _, l in pairs(lst) do 11 - lib[l] = terralib.loadfile(l .. '.t')() 11 + local path = {} 12 + for m in l:gmatch('([^:]+)') do path[#path+1]=m end 13 + local tgt = lib 14 + for i=1,#path-1 do 15 + if tgt[path[i]] == nil then tgt[path[i]] = {} end 16 + tgt = tgt[path[i]] 17 + end 18 + tgt[path[#path]] = terralib.loadfile(l:gsub(':','/') .. '.t')() 12 19 end 13 20 end; 14 21 loadlib = function(name,hdr) 15 22 local p = config.pkg[name] 16 23 -- for _,v in pairs(p.dylibs) do 17 24 -- terralib.linklibrary(p.libdir .. '/' .. v) 18 25 -- end ................................................................................ 22 29 return macro(function(v,...) 23 30 for ty,fn in pairs(tbl) do 24 31 if v.tree.type == ty then return fn(v,...) end 25 32 end 26 33 return (tbl[false])(v,...) 27 34 end) 28 35 end; 29 - emit_unitary = function(fd,...) 36 + emit_unitary = function(nl,fd,...) 30 37 local code = {} 31 38 for i,v in ipairs{...} do 32 39 if type(v) == 'string' or type(v) == 'number' then 33 40 local str = tostring(v) 34 41 code[#code+1] = `lib.io.send(2, str, [#str]) 35 42 elseif type(v) == 'table' and #v == 2 then 36 43 code[#code+1] = `lib.io.send(2, [v[1]], [v[2]]) ................................................................................ 38 45 local str = tostring(v:asvalue()) 39 46 code[#code+1] = `lib.io.send(2, str, [#str]) 40 47 else 41 48 code[#code+1] = quote var n = v in 42 49 lib.io.send(2, n, lib.str.sz(n)) end 43 50 end 44 51 end 45 - code[#code+1] = `lib.io.send(fd, '\n', 1) 52 + if nl then code[#code+1] = `lib.io.send(fd, '\n', 1) end 46 53 return code 47 54 end; 48 - emitv = function(fd,...) 55 + emitv = function(nl,fd,...) 49 56 local vec = {} 50 57 local defs = {} 51 58 for i,v in ipairs{...} do 52 59 local str, ct 53 60 if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then 54 61 if v.tree.type.convertible == 'tuple' then 55 62 str = `v._0 ................................................................................ 66 73 else--if v.tree:is 'constant' then 67 74 str = tostring(v:asvalue()) 68 75 end 69 76 ct = ct or #str 70 77 end 71 78 vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct} 72 79 end 73 - vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} 80 + if nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} end 74 81 return quote 75 82 [defs] 76 83 var strs = array( [vec] ) 77 84 in lib.uio.writev(fd, strs, [#vec]) end 78 85 end; 79 86 trn = macro(function(cond, i, e) 80 87 return quote 81 88 var c: bool = [cond] 82 89 var r: i.tree.type 83 90 if c == true then r = i else r = e end 84 91 in r end 92 + end); 93 + coalesce = macro(function(...) 94 + local args = {...} 95 + local ty = args[1].tree.type 96 + local val = symbol(ty) 97 + local empty if ty.type == 'integer' 98 + then empty = `0 99 + else empty = `nil 100 + end 101 + local exp = quote val = [empty] end 102 + 103 + for i=#args, 1, -1 do 104 + local v = args[i] 105 + exp = quote 106 + if [v] ~= [empty] 107 + then val = v 108 + else [exp] 109 + end 110 + end 111 + end 112 + 113 + local q = quote 114 + var [val] 115 + [exp] 116 + in val end 117 + return q 85 118 end); 86 119 proc = { 87 120 exit = terralib.externfunction('exit', int -> {}); 88 121 getenv = terralib.externfunction('getenv', rawstring -> rawstring); 89 122 }; 90 123 io = { 91 124 send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff); ................................................................................ 97 130 str = { sz = terralib.externfunction('strlen', rawstring -> intptr) }; 98 131 copy = function(tbl) 99 132 local new = {} 100 133 for k,v in pairs(tbl) do new[k] = v end 101 134 setmetatable(new, getmetatable(tbl)) 102 135 return new 103 136 end; 137 + osclock = terralib.includec 'time.h'; 104 138 } 105 139 if config.posix then 106 140 lib.uio = terralib.includec 'sys/uio.h'; 107 141 lib.emit = lib.emitv -- use more efficient call where available 108 142 else lib.emit = lib.emit_unitary end 109 143 144 +local starttime = global(lib.osclock.time_t) 145 +local lastnoisetime = global(lib.osclock.time_t) 110 146 local noise = global(uint8,1) 111 147 local noise_header = function(code,txt,mod) 112 148 if mod then 113 - return string.format('\27[%s;1m(parsav::%s %s)\27[m ', code,mod,txt) 149 + return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt) 114 150 else 115 - return string.format('\27[%s;1m(parsav %s)\27[m ', code,txt) 151 + return string.format('\27[%s;1m(%s)\27[m ', code,txt) 152 + end 153 +end 154 + 155 +local terra timehdr() 156 + var now = lib.osclock.time(nil) 157 + var diff = now - lastnoisetime 158 + if diff > 30 then -- print cur time 159 + lastnoisetime = now 160 + var curtime: int8[26] 161 + lib.osclock.ctime_r(&now, &curtime[0]) 162 + for i=0,26 do if curtime[i] == @'\n' then curtime[i] = 0 break end end -- :/ 163 + [ lib.emit(false, 2, '\27[1m[', `&curtime[0], ']\27[;36m\n +00 ') ] 164 + else -- print time since last msg 165 + var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0) 166 + [ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ] 116 167 end 117 168 end 169 + 118 170 local defrep = function(level,n,code) 119 171 return macro(function(...) 120 - local q = lib.emit(2, noise_header(code,n), ...) 121 - return quote 122 - if noise >= level then [q] end 123 - end 172 + local q = lib.emit(true, 2, noise_header(code,n), ...) 173 + return quote if noise >= level then timehdr(); [q] end end 124 174 end); 125 175 end 126 176 lib.dbg = defrep(3,'debug', '32') 127 177 lib.report = defrep(2,'info', '35') 128 178 lib.warn = defrep(1,'warn', '33') 129 179 lib.bail = macro(function(...) 130 - local q = lib.emit(2, noise_header('31','fatal'), ...) 180 + local q = lib.emit(true, 2, noise_header('31','fatal'), ...) 131 181 return quote 132 - [q] 182 + timehdr(); [q] 133 183 lib.proc.exit(1) 134 184 end 135 185 end); 136 186 lib.stat = terralib.memoize(function(ty) 137 187 local n = struct { 138 188 ok: bool 139 189 union { ................................................................................ 159 209 lib.set = function(tbl) 160 210 local bytes = math.ceil(#tbl / 8) 161 211 local o = {} 162 212 for i, name in ipairs(tbl) do o[name] = i end 163 213 local struct set { _store: uint8[bytes] } 164 214 local struct bit { _v: intptr _set: &set} 165 215 terra set:clear() for i=0,bytes do self._store[i] = 0 end end 216 + terra set:fill() for i=0,bytes do self._store[i] = 0xFF end end 166 217 set.members = tbl 167 218 set.name = string.format('set<%s>', table.concat(tbl, '|')) 168 219 set.metamethods.__entrymissing = macro(function(val, obj) 169 220 if o[val] == nil then error('value ' .. val .. ' not in set') end 170 221 return `bit { _v=[o[val] - 1], _set = &obj } 171 222 end) 172 223 set.methods.dump = macro(function(self) ................................................................................ 219 270 lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h') 220 271 lib.md = lib.loadlib('mbedtls','mbedtls/md.h') 221 272 lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h') 222 273 lib.net = lib.loadlib('mongoose','mongoose.h') 223 274 lib.pq = lib.loadlib('libpq','libpq-fe.h') 224 275 225 276 lib.load { 226 - 'mem', 'str', 'file', 'math', 'crypt'; 227 - 'http', 'tpl', 'store'; 277 + 'mem', 'math', 'str', 'file', 'crypt'; 278 + 'http', 'session', 'tpl', 'store'; 228 279 } 229 280 230 281 local be = {} 231 282 for _, b in pairs(config.backends) do 232 283 be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')() 233 284 end 234 285 lib.store.backends = global(`array([be])) 235 286 236 287 lib.cmdparse = terralib.loadfile('cmdparse.t')() 237 -lib.srv = terralib.loadfile('srv.t')() 238 288 239 289 do local collate = function(path,f, ...) 240 290 return loadfile(path..'/'..f..'.lua')(path, ...) 241 291 end 242 292 data = { 243 293 view = collate('view','load'); 294 + static = {}; 295 + stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content 244 296 } end 297 +for i,e in ipairs(config.embeds) do local v = e[1] 298 + local fh = io.open('static/' .. v,'r') 299 + if fh == nil then error('static file ' .. v .. ' missing') end 300 + data.static[v] = fh:read('*a') fh:close() 301 +end 245 302 246 303 -- slightly silly -- because we're using plain lua to gather up 247 304 -- the template sources, they have to be actually turned into 248 305 -- templates in the terra code, so we "mutate" them here 249 306 for k,v in pairs(data.view) do 250 307 local t = lib.tpl.mk { body = v, id = 'view/'..k } 251 308 data.view[k] = t 252 309 end 253 310 254 -local pemdump = macro(function(pub, kp) 255 - local msg = (pub:asvalue() and ' * emitting public key\n') or ' * emitting private key\n' 256 - return quote 257 - var buf: lib.crypt.pemfile 258 - lib.mem.zero(buf) 259 - lib.crypt.pem(pub, &kp, buf) 260 - lib.emit(msg, buf, '\n') 261 - --lib.io.send(1, msg, [#msg]) 262 - --lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf))) 263 - --lib.io.send(1, '\n', 1) 264 - end 265 -end) 311 +lib.load { 312 + 'srv'; 313 + 'render:profile'; 314 + 'render:userpage'; 315 + 'route'; 316 +} 266 317 267 318 do 268 319 local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) 269 320 terra version() lib.io.send(1, p, [#p]) end 270 321 end 322 + 271 323 terra noise_init() 324 + starttime = lib.osclock.time(nil) 325 + lastnoisetime = 0 272 326 var n = lib.proc.getenv('parsav_noise') 273 327 if n ~= nil then 274 328 if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then 275 329 noise = n[0] - 0x30 276 330 return 277 331 end 278 332 end ................................................................................ 280 334 end 281 335 282 336 local options = lib.cmdparse { 283 337 version = {'V', 'display information about the binary build and exit'}; 284 338 quiet = {'q', 'do not print to standard out'}; 285 339 help = {'h', 'display this list'}; 286 340 backend_file = {'b', 'init from specified backend file', 1}; 341 + static_dir = {'S', 'directory with overrides for static content', 1}; 342 + builtin_data = {'B', 'do not load static content overrides at runtime under any circumstances'}; 287 343 } 288 344 345 + 346 +local static_setup = quote end 347 +local mapin = quote end 348 +local odir = symbol(rawstring) 349 +local pathbuf = symbol(lib.str.acc) 350 +for i,e in ipairs(config.embeds) do local v = e[1] 351 + local d = data.static[v] 352 + static_setup = quote [static_setup] 353 + ([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] { ptr = [d], ct = [#d] }) 354 + end 355 + mapin = quote [mapin] 356 + var osz = pathbuf.sz 357 + pathbuf:push([v],[#v]) 358 + var f = lib.file.open(pathbuf.buf, [lib.file.mode.read]) 359 + if f.ok then defer f.val:close() 360 + var map = f.val:mapin(0,0) -- currently maps are leaked, maybe do something more elegant in future 361 + lib.report('loading static override content from ', pathbuf.buf) 362 + ([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] { 363 + ptr = [rawstring](map.addr); 364 + ct = map.sz; 365 + }) 366 + end 367 + pathbuf.sz = osz 368 + end 369 +end 370 +local terra static_init(mode: &options) 371 + [static_setup] 372 + if mode.builtin_data then return end 373 + 374 + var [odir] = lib.proc.getenv('parsav_override_dir') 375 + if mode.static_dir ~= nil then 376 + odir=@mode.static_dir 377 + end 378 + if odir == nil then return end 379 + 380 + var [pathbuf] defer pathbuf:free() 381 + pathbuf:compose(odir,'/') 382 + [mapin] 383 +end 384 + 289 385 terra entry(argc: int, argv: &rawstring): int 290 386 if argc < 1 then lib.bail('bad invocation!') end 291 387 292 388 noise_init() 293 389 [lib.init] 294 390 295 391 -- shut mongoose the fuck up 296 392 lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil) 297 - var srv: lib.srv 393 + var srv: lib.srv.overlord 298 394 299 395 do var mode: options 300 396 mode:parse(argc,argv) defer mode:free() 397 + static_init(&mode) 301 398 if mode.version then version() return 0 end 302 399 if mode.help then 303 400 lib.io.send(1, [options.helptxt], [#options.helptxt]) 304 401 return 0 305 402 end 306 403 var cnf: rawstring 307 - if mode.backend_file ~= 0 308 - then if mode.arglist.ct >= mode.backend_file 309 - then cnf = mode.arglist.ptr[mode.backend_file - 1] 310 - else lib.bail('bad invocation, backend file not specified') end 404 + if mode.backend_file ~= nil 405 + then cnf = @mode.backend_file 311 406 else cnf = lib.proc.getenv('parsav_backend_file') 312 407 end 313 408 if cnf == nil then cnf = "backend.conf" end 314 409 315 410 srv:start(cnf) 316 411 end 317 412
Added render/profile.t version [a405db9158].
1 +-- vim: ft=terra 2 +local terra 3 +render_profile(actor: &lib.store.actor) 4 + var profile = data.view.profile { 5 + nym = lib.coalesce(actor.nym, actor.handle); 6 + bio = lib.coalesce(actor.bio, "tall, dark, and mysterious"); 7 + xid = actor.xid; 8 + avatar = "/no-avatars-yet.png"; 9 + 10 + nposts = '0', nfollows = '0'; 11 + nfollowers = '0', nmutuals = '0'; 12 + tweetday = 'novembuary 67th'; 13 + } 14 + 15 + return profile:tostr() 16 +end 17 + 18 +return render_profile
Added render/userpage.t version [052285d84c].
1 +-- vim: ft=terra 2 +local terra 3 +render_userpage(co: &lib.srv.convo, actor: &lib.store.actor) 4 + var ti: lib.str.acc defer ti:free() 5 + if co.aid ~= 0 and co.who.id == actor.id then 6 + ti:compose('my profile') 7 + else 8 + ti:compose('profile :: ', actor.handle) 9 + end 10 + var pftxt = lib.render.profile(actor) defer pftxt:free() 11 + 12 + var doc = data.view.docskel { 13 + instance = co.srv.cfg.instance.ptr; 14 + title = ti.buf; 15 + body = pftxt.ptr; 16 + class = 'profile'; 17 + } 18 + 19 + var hdrs = array( 20 + lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 21 + ) 22 + doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 23 +end 24 + 25 +return render_userpage
Added route.t version [70d0da6b8e].
1 +-- vim: ft=terra 2 +local r = lib.srv.route 3 +local method = lib.http.method 4 +local http = {} 5 + 6 +terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 7 + var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 } 8 + for i=2,uri.ct do 9 + if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end 10 + end 11 + if handle.ct == 0 then 12 + handle.ct = uri.ct - 2 13 + uri:advance(uri.ct) 14 + else 15 + if handle.ct + 2 < uri.ct then 16 + uri:advance(handle.ct + 2) 17 + --uri.ptr = uri.ptr + (handle.ct + 2) 18 + --uri.ct = uri.ct - (handle.ct + 2) 19 + end 20 + end 21 + 22 + lib.dbg('looking up user by xid "', {handle.ptr,handle.ct} ,'", path: ', {uri.ptr,uri.ct}) 23 + 24 + var path = lib.http.hier(uri) defer path:free() 25 + for i=0,path.ct do 26 + lib.dbg('got path component ', {path.ptr[i].ptr, path.ptr[i].ct}) 27 + end 28 + 29 + var actor = co.srv:actor_fetch_xid(handle) 30 + if actor.ptr == nil then 31 + co:complain(404,'no such user','no such user known to this server') 32 + return 33 + end 34 + defer actor:free() 35 + 36 + lib.render.userpage(co, actor.ptr) 37 +end 38 + 39 +terra http.actor_profile_uid(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), meth: method.t) 40 + if path.ct < 2 then 41 + co:complain(404,'bad url','invalid user url') 42 + return 43 + end 44 + 45 + var uid, ok = lib.math.shorthand.parse(path.ptr[1].ptr, path.ptr[1].ct) 46 + if not ok then 47 + co:complain(400, 'bad user ID', 'that user ID is not valid') 48 + return 49 + end 50 + 51 + var actor = co.srv:actor_fetch_uid(uid) 52 + if actor.ptr == nil then 53 + co:complain(404, 'no such user', 'no user by that ID is known to this instance') 54 + return 55 + end 56 + defer actor:free() 57 + 58 + lib.render.userpage(co, actor.ptr) 59 +end 60 + 61 +do local branches = quote end 62 + local filename, flen = symbol(&int8), symbol(intptr) 63 + local page = symbol(lib.http.page) 64 + local send = label() 65 + local storage = data.stmap 66 + for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2] 67 + local d = data.static[id] 68 + branches = quote [branches]; 69 + if lib.str.ncmp(filename, id, lib.math.biggest([#id], flen)) == 0 then 70 + page.headers.ptr[0].value = mime; 71 + page.body = [lib.mem.ptr(int8)] { 72 + ptr = storage[([i-1])].ptr; 73 + ct = storage[([i-1])].ct; 74 + } 75 + goto [send] 76 + end 77 + end 78 + end 79 + terra http.static_content(co: &lib.srv.convo, [filename], [flen]) 80 + var hdrs = array(lib.http.header{'Content-Type',nil}) 81 + var [page] = lib.http.page { 82 + respcode = 200; 83 + headers = [lib.mem.ptr(lib.http.header)] { 84 + ptr = &hdrs[0], ct = 1 85 + } 86 + } 87 + [branches] 88 + do return false end 89 + ::[send]:: page:send(co.con) return true 90 + end 91 +end 92 + 93 +http.static_content:printpretty() 94 + 95 +-- entry points 96 +terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 97 + if uri.ptr[0] ~= @'/' then 98 + co:complain(404, 'what the hell', 'how did you do that') 99 + elseif uri.ptr[1] == @'@' then 100 + http.actor_profile_xid(co, uri, meth) 101 + elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then 102 + if meth ~= method.get then goto wrongmeth end 103 + if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end 104 + else 105 + var path = lib.http.hier(uri) defer path:free() 106 + if path.ptr[0]:cmp(lib.str.lit('user')) then 107 + http.actor_profile_uid(co, path, meth) 108 + else goto notfound end 109 + end 110 + 111 + ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this path') do return end 112 + ::notfound:: co:complain(404, 'not found', 'no such resource available') do return end 113 +end
Modified schema.sql from [6d12737279] to [636689e0dd].
1 1 \prompt 'domain name: ' domain 2 +\prompt 'instance name: ' inst 2 3 \prompt 'bind to socket: ' bind 3 4 \qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.' 4 5 \prompt 'admin actor: ' admin 5 -\qecho 'you will need to create an authentication view mapping your user database to something parsav can understand; see auth.sql for an example. enter the name of the view to use.' 6 -\prompt 'auth view: ' auth 6 +\qecho 'you will need to create an authentication view named parsav_auth mapping your user database to something parsav can understand; see auth.sql for an example.' 7 7 8 8 begin; 9 9 10 10 drop table if exists parsav_config; 11 11 create table if not exists parsav_config ( 12 12 key text primary key, 13 13 value text 14 14 ); 15 15 16 16 insert into parsav_config (key,value) values 17 17 ('bind',:'bind'), 18 18 ('domain',:'domain'), 19 - ('auth-source',:'auth'), 19 + ('instance-name',:'inst'), 20 20 ('administrator',:'admin'), 21 21 ('server-secret', encode( 22 22 digest(int8send((2^63 * (random()*2 - 1))::bigint), 23 23 'sha512'), 'base64')); 24 24 25 25 -- note that valid ids should always > 0, as 0 is reserved for null 26 26 -- on the client side, vastly simplifying code
Added session.t version [58f0eab21d].
1 +-- vim: ft=terra 2 +-- sessions are implemented so as to avoid any local data storage. they 3 +-- are tracked by storing an encrypted cookie which contains an authid, 4 +-- a login epoch time, and a truncated hmac code authenticating both, all 5 +-- encoded using Shorthand. we need functions to generate and parse these 6 + 7 +local m = { 8 + maxlen = lib.math.shorthand.maxlen*3 + 2; 9 + maxage = 2 * 60 * 60; -- 2 hours 10 +} 11 + 12 +terra m.cookie_gen(secret: lib.mem.ptr(int8), authid: uint64, time: uint64, out: &int8): intptr 13 + var ptr = out 14 + ptr = ptr + lib.math.shorthand.gen(authid, ptr) 15 + @ptr = @'.' ptr = ptr + 1 16 + ptr = ptr + lib.math.shorthand.gen(time, ptr) 17 + @ptr = @'.' ptr = ptr + 1 18 + var len = ptr - out 19 + var hash: uint8[lib.crypt.algsz.sha256] 20 + lib.crypt.hmac(lib.crypt.alg.sha256, 21 + [lib.mem.ptr(uint8)] {ptr = [&uint8](secret.ptr), ct = secret.ct}, 22 + [lib.mem.ptr( int8)] {ptr = out, ct = len}, 23 + &hash[0]) 24 + ptr = ptr + lib.math.shorthand.gen(lib.math.truncate64(hash, [hash.type.N]), ptr) 25 + return ptr - out 26 +end 27 + 28 +terra m.cookie_interpret(secret: lib.mem.ptr(int8), c: lib.mem.ptr(int8), now: uint64): uint64 -- returns either 0 or a valid authid 29 + var authid_sz = lib.str.cspan(c.ptr, lib.str.lit '.', c.ct) 30 + if authid_sz == 0 then return 0 end 31 + if authid_sz + 1 > c.ct then return 0 end 32 + var time_sz = lib.str.cspan(c.ptr+authid_sz+1, lib.str.lit '.', c.ct - (authid_sz+1)) 33 + if time_sz == 0 then return 0 end 34 + if (authid_sz + time_sz + 2) > c.ct then return 0 end 35 + var hash_sz = c.ct - (authid_sz + time_sz + 2) 36 + 37 + var knownhash: uint8[lib.crypt.algsz.sha256] 38 + lib.crypt.hmac(lib.crypt.alg.sha256, 39 + [lib.mem.ptr(uint8)] {ptr = [&uint8](secret.ptr), ct = secret.ct}, 40 + [lib.mem.ptr( int8)] {ptr = c.ptr, ct = c.ct - hash_sz}, 41 + &knownhash[0]) 42 + 43 + var authid, authok = lib.math.shorthand.parse(c.ptr, authid_sz) 44 + var time, timeok = lib.math.shorthand.parse(c.ptr + authid_sz + 1, time_sz) 45 + var hash, hashok = lib.math.shorthand.parse(c.ptr + c.ct - hash_sz, hash_sz) 46 + if not (timeok and authok and hashok) then return 0 end 47 + if lib.math.truncate64(knownhash, [knownhash.type.N]) ~= hash then return 0 end 48 + if now - time > m.maxage then return 0 end 49 + 50 + return authid 51 +end 52 + 53 +return m
Modified srv.t from [aed7239c9c] to [ed3d5ec62e].
1 1 -- vim: ft=terra 2 2 local util = dofile 'common.lua' 3 + 4 +local struct srv 5 +local struct cfgcache { 6 + secret: lib.mem.ptr(int8) 7 + instance: lib.mem.ptr(int8) 8 + overlord: &srv 9 +} 3 10 local struct srv { 4 11 sources: lib.mem.ptr(lib.store.source) 5 12 webmgr: lib.net.mg_mgr 6 13 webcon: &lib.net.mg_connection 14 + cfg: cfgcache 7 15 } 8 16 9 -local handle = { 10 - http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 11 - switch event do 12 - case lib.net.MG_EV_HTTP_MSG then 13 - lib.dbg('routing HTTP request') 14 - var msg = [&lib.net.mg_http_message](p) 15 - 16 - end 17 - end 18 - end; 19 -} 20 -local char = macro(function(ch) return `[string.byte(ch:asvalue())] end) 21 -local terra cfg(s: &srv, befile: rawstring) 22 - lib.report('configuring backends from ', befile) 23 - 24 - var fr = lib.file.open(befile, [lib.file.mode.read]) 25 - if fr.ok == false then 26 - lib.bail('could not open configuration file ', befile) 27 - end 28 - 29 - var f = fr.val 30 - var c: lib.mem.vec(lib.store.source) c:init(8) 31 - var text: lib.str.acc text:init(64) 32 - do var buf: int8[64] 33 - while true do 34 - var ct = f:read(buf, [buf.type.N]) 35 - if ct == 0 then break end 36 - text:push(buf, ct) 37 - end 38 - end 39 - f:close() 40 - 41 - var cur = text.buf 42 - var segs: tuple(&int8, &int8)[3] = array( 43 - {[&int8](0),[&int8](0)}, 44 - {[&int8](0),[&int8](0)}, 45 - {[&int8](0),[&int8](0)} 46 - ) 47 - var segdup = [terra(s: {rawstring, rawstring}) 48 - var sz = s._1 - s._0 49 - var str = s._0 50 - return [lib.mem.ptr(int8)] { 51 - ptr = lib.str.ndup(str, sz); 52 - ct = sz; 53 - } 54 - end] 55 - var fld = 0 56 - while (cur - text.buf) < text.sz do 57 - if segs[fld]._0 == nil then 58 - if not (@cur == char(' ') or @cur == char('\t') or @cur == char('\n')) then 59 - segs[fld] = {cur, nil} 60 - end 61 - else 62 - if fld < 2 and @cur == char(' ') or @cur == char('\t') then 63 - segs[fld]._1 = cur 64 - fld = fld + 1 65 - segs[fld] = {nil, nil} 66 - elseif @cur == char('\n') or cur == text.buf + (text.sz-1) then 67 - if fld < 2 then lib.bail('incomplete backend line in ', befile) else 68 - segs[fld]._1 = cur 69 - var src = c:new() 70 - src.id = segdup(segs[0]) 71 - src.string = segdup(segs[2]) 72 - src.backend = nil 73 - for i = 0,[lib.store.backends.type.N] do 74 - if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then 75 - src.backend = &lib.store.backends[i] 76 - break 77 - end 78 - end 79 - if src.backend == nil then 80 - lib.bail('unknown backend in ', befile) 81 - end 82 - src.handle = nil 83 - fld = 0 84 - segs[0] = {nil, nil} 85 - end 86 - end 87 - end 88 - cur = cur + 1 89 - end 90 - text:free() 91 - 92 - s.sources = c:crush() 17 +terra cfgcache:free() -- :/ 18 + self.secret:free() 19 + self.instance:free() 93 20 end 94 21 95 ---srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring) 96 --- self.sources.ptr[0]:conf_set(key, val) 97 ---end 98 - 99 -terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) 100 - var cs: lib.store.credset cs:clear() 101 - for i=0,self.sources.ct do 102 - var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn) 103 - cs = cs + set 104 - end 105 - return cs 106 -end 107 22 srv.metamethods.__methodmissing = macro(function(meth, self, ...) 108 23 local primary, ptr, stat, simple, oid = 0,1,2,3,4 109 24 local tk, rt = primary 110 25 local expr = {...} 111 26 for _,f in pairs(lib.store.backend.entries) do 112 27 local fn = f.field or f[1] 113 28 local ft = f.type or f[2] ................................................................................ 146 61 if [ok] then break 147 62 else r = empty end 148 63 end 149 64 end 150 65 in r end 151 66 end 152 67 end) 68 + 69 +local struct convo { 70 + srv: &srv 71 + con: &lib.net.mg_connection 72 + msg: &lib.net.mg_http_message 73 + aid: uint64 -- 0 if logged out 74 + who: &lib.store.actor -- who we're logged in as, if aid ~= 0 75 +} 76 + 77 +-- this is unfortunately necessary to work around a terra bug 78 +-- it can't seem to handle forward-declarations of structs in C 79 + 80 +local getpeer 81 +do local struct strucheader { 82 + next: &lib.net.mg_connection 83 + mgr: &lib.net.mg_mgr 84 + peer: lib.net.mg_addr 85 + } 86 + terra getpeer(con: &lib.net.mg_connection) 87 + return [&strucheader](con).peer 88 + end 89 +end 90 + 91 +terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 92 + var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }) 93 + 94 + var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free() 95 + var body = data.view.docskel { 96 + instance = self.srv.cfg.instance.ptr; 97 + title = ti.buf; 98 + body = msg; 99 + class = 'error'; 100 + } 101 + 102 + if body.body == nil then 103 + body.body = "i'm sorry, dave. i can't let you do that" 104 + end 105 + 106 + body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { 107 + ptr = &hdrs[0], ct = [hdrs.type.N] 108 + }) 109 +end 110 + 111 +local urimatch = macro(function(uri, ptn) 112 + return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1) 113 +end) 114 + 115 +local route = {} -- these are defined in route.t, as they need access to renderers 116 +terra route.dispatch_http :: {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {} 117 + 118 +local handle = { 119 + http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 120 + var server = [&srv](ext) 121 + var mgpeer = getpeer(con) 122 + var peer = lib.store.inet { port = mgpeer.port; } 123 + if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end 124 + if peer.pv == 6 then 125 + for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end 126 + else -- v4 127 + @[&uint32](&peer.v4) = mgpeer.ip 128 + end 129 + -- the peer property is currently broken and there is precious 130 + -- little i can do about this -- it always reports a peer v4 IP 131 + -- of 0.0.0.0, altho the port seems to come through correctly. 132 + -- for now i'm leaving it as is, but note that netmask restrictions 133 + -- WILL NOT WORK until upstream gets its shit together. FIXME 134 + 135 + switch event do 136 + case lib.net.MG_EV_HTTP_MSG then 137 + lib.dbg('routing HTTP request') 138 + var msg = [&lib.net.mg_http_message](p) 139 + var co = convo { 140 + con = con, srv = server, msg = msg; 141 + aid = 0, who = nil; 142 + } 143 + 144 + -- we need to check if there's any cookies sent with the request, 145 + -- and if so, whether they contain any credentials. this will be 146 + -- used to set the auth parameters in the http conversation 147 + var cookies_p = lib.http.findheader(msg, 'Cookie') 148 + if cookies_p ~= nil then 149 + var cookies = cookies_p.ptr 150 + var key = [lib.mem.ref(int8)] {ptr = cookies, ct = 0} 151 + var val = [lib.mem.ref(int8)] {ptr = nil, ct = 0} 152 + var i = 0 while i < cookies_p.ct and 153 + cookies[i] ~= 0 and 154 + cookies[i] ~= @'\r' and 155 + cookies[i] ~= @'\n' do -- cover all the bases 156 + if val.ptr == nil then 157 + if cookies[i] == @'=' then 158 + key.ct = (cookies + i) - key.ptr 159 + val.ptr = cookies + i + 1 160 + end 161 + i = i + 1 162 + else 163 + if cookies[i] == @';' then 164 + val.ct = (cookies + i) - val.ptr 165 + if lib.str.ncmp(key.ptr, 'auth', key.ct) == 0 then 166 + goto foundcookie 167 + end 168 + 169 + i = i + 1 170 + i = lib.str.ffw(cookies + i, cookies_p.ct - i) - cookies 171 + key.ptr = cookies + i 172 + val.ptr = nil 173 + else i = i + 1 end 174 + end 175 + end 176 + if val.ptr == nil then goto nocookie end 177 + val.ct = (cookies + i) - val.ptr 178 + if lib.str.ncmp(key.ptr, 'auth', key.ct) ~= 0 then 179 + goto nocookie 180 + end 181 + ::foundcookie:: do 182 + var aid = lib.session.cookie_interpret(server.cfg.secret, 183 + [lib.mem.ptr(int8)]{ptr=val.ptr,ct=val.ct}, 184 + lib.osclock.time(nil)) 185 + if aid ~= 0 then co.aid = aid end 186 + end ::nocookie::; 187 + end 188 + 189 + if co.aid ~= 0 then 190 + var sess, usr = co.srv:actor_session_fetch(co.aid, peer) 191 + if sess.ok == false then co.aid = 0 else co.who = usr.ptr end 192 + end 193 + 194 + var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free() 195 + var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1) 196 + 197 + var uri = uridec 198 + if urideclen == -1 then 199 + for i = 0,msg.uri.len do 200 + if msg.uri.ptr[i] == @'+' 201 + then uri.ptr[i] = @' ' 202 + else uri.ptr[i] = msg.uri.ptr[i] 203 + end 204 + end 205 + uri.ct = msg.uri.len 206 + else uri.ct = urideclen end 207 + lib.dbg('routing URI ', {uri.ptr, uri.ct}) 208 + 209 + if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then 210 + route.dispatch_http(&co, uri, [lib.http.method.get]) 211 + else 212 + co:complain(400,'unknown method','you have submitted an invalid http request') 213 + end 214 + 215 + if co.aid ~= 0 then lib.mem.heapf(co.who) end 216 + end 217 + end 218 + end; 219 +} 220 + 221 +local terra cfg(s: &srv, befile: rawstring) 222 + lib.report('configuring backends from ', befile) 223 + 224 + var fr = lib.file.open(befile, [lib.file.mode.read]) 225 + if fr.ok == false then 226 + lib.bail('could not open configuration file ', befile) 227 + end 228 + 229 + var f = fr.val 230 + var c: lib.mem.vec(lib.store.source) c:init(8) 231 + var text: lib.str.acc text:init(64) 232 + do var buf: int8[64] 233 + while true do 234 + var ct = f:read(buf, [buf.type.N]) 235 + if ct == 0 then break end 236 + text:push(buf, ct) 237 + end 238 + end 239 + f:close() 240 + 241 + var cur = text.buf 242 + var segs: tuple(&int8, &int8)[3] = array( 243 + {[&int8](0),[&int8](0)}, 244 + {[&int8](0),[&int8](0)}, 245 + {[&int8](0),[&int8](0)} 246 + ) 247 + var segdup = [terra(s: {rawstring, rawstring}) 248 + var sz = s._1 - s._0 249 + var str = s._0 250 + return [lib.mem.ptr(int8)] { 251 + ptr = lib.str.ndup(str, sz); 252 + ct = sz; 253 + } 254 + end] 255 + var fld = 0 256 + while (cur - text.buf) < text.sz do 257 + if segs[fld]._0 == nil then 258 + if not (@cur == @' ' or @cur == @'\t' or @cur == @'\n') then 259 + segs[fld] = {cur, nil} 260 + end 261 + else 262 + if fld < 2 and @cur == @' ' or @cur == @'\t' then 263 + segs[fld]._1 = cur 264 + fld = fld + 1 265 + segs[fld] = {nil, nil} 266 + elseif @cur == @'\n' or cur == text.buf + (text.sz-1) then 267 + if fld < 2 then lib.bail('incomplete backend line in ', befile) else 268 + segs[fld]._1 = cur 269 + var src = c:new() 270 + src.id = segdup(segs[0]) 271 + src.string = segdup(segs[2]) 272 + src.backend = nil 273 + for i = 0,[lib.store.backends.type.N] do 274 + if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then 275 + src.backend = &lib.store.backends[i] 276 + break 277 + end 278 + end 279 + if src.backend == nil then 280 + lib.bail('unknown backend in ', befile) 281 + end 282 + src.handle = nil 283 + fld = 0 284 + segs[0] = {nil, nil} 285 + end 286 + end 287 + end 288 + cur = cur + 1 289 + end 290 + text:free() 291 + 292 + if c.sz > 0 then 293 + s.sources = c:crush() 294 + else 295 + s.sources.ptr = nil 296 + s.sources.ct = 0 297 + end 298 +end 299 + 300 +terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) 301 + var cs: lib.store.credset cs:clear() 302 + for i=0,self.sources.ct do 303 + var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn) 304 + cs = cs + set 305 + end 306 + return cs 307 +end 308 + 309 +terra cfgcache.methods.load :: {&cfgcache} -> {} 310 +terra cfgcache:init(o: &srv) 311 + self.overlord = o 312 + self:load() 313 +end 153 314 154 315 srv.methods.start = terra(self: &srv, befile: rawstring) 155 316 cfg(self, befile) 156 317 var success = false 318 + if self.sources.ct == 0 then lib.bail('no data sources specified') end 157 319 for i=0,self.sources.ct do var src = self.sources.ptr + i 158 320 lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')') 159 321 src.handle = src.backend.open(src) 160 322 if src.handle ~= nil then success = true end 161 323 end 162 324 if not success then 163 325 lib.bail('could not connect to any data sources!') 164 326 end 327 + 328 + self.cfg:init(self) 165 329 166 330 var dbbind = self:conf_get('bind') 167 331 var envbind = lib.proc.getenv('parsav_bind') 168 332 var bind: rawstring 169 333 if envbind ~= nil then 170 334 bind = envbind 171 335 elseif dbbind.ptr ~= nil then 172 336 bind = dbbind.ptr 173 337 else bind = '[::]:10917' end 174 338 175 339 lib.report('binding to ', bind) 176 340 lib.net.mg_mgr_init(&self.webmgr) 177 - self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil) 341 + self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self) 178 342 343 + var buf: int8[lib.session.maxlen] 344 + var len = lib.session.cookie_gen(self.cfg.secret, 9139084444658983115ULL, lib.osclock.time(nil), &buf[0]) 345 + buf[len] = 0 346 + 347 + var authid = lib.session.cookie_interpret(self.cfg.secret, [lib.mem.ptr(int8)] {ptr=buf, ct=len}, lib.osclock.time(nil)) 348 + lib.io.fmt('generated cookie %s -- got authid %llu\n', buf, authid) 179 349 180 350 if dbbind.ptr ~= nil then dbbind:free() end 181 351 end 182 352 183 353 srv.methods.poll = terra(self: &srv) 184 354 lib.net.mg_mgr_poll(&self.webmgr,1000) 185 355 end ................................................................................ 189 359 for i=0,self.sources.ct do var src = self.sources.ptr + i 190 360 lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')') 191 361 src:close() 192 362 end 193 363 self.sources:free() 194 364 end 195 365 196 -return srv 366 +terra cfgcache:load() 367 + self.instance = self.overlord:conf_get('instance-name') 368 + self.secret = self.overlord:conf_get('server-secret') 369 +end 370 + 371 +return { 372 + overlord = srv; 373 + convo = convo; 374 + route = route; 375 +}
Added static/style.scss version [a7ffc6f8bf].
Modified store.t from [2c2e954f5c] to [213b3d2729].
10 10 }; 11 11 relation = lib.enum { 12 12 'follow', 'mute', 'block' 13 13 }; 14 14 credset = lib.set { 15 15 'pw', 'otp', 'challenge', 'trust' 16 16 }; 17 + privset = lib.set { 18 + 'post', 'edit', 'acct', 'upload', 'censor', 'admin' 19 + } 17 20 } 18 21 19 22 local str = rawstring --lib.mem.ptr(int8) 20 23 21 24 struct m.source 22 25 23 26 struct m.rights { ................................................................................ 28 31 -- user powers -- default on 29 32 login: bool 30 33 visible: bool 31 34 post: bool 32 35 shout: bool 33 36 propagate: bool 34 37 upload: bool 38 + acct: bool 39 + edit: bool 35 40 36 41 -- admin powers -- default off 37 42 ban: bool 38 43 config: bool 39 44 censor: bool 40 45 suspend: bool 41 46 rebrand: bool -- modify site's brand identity ................................................................................ 140 145 end 141 146 142 147 struct m.auth { 143 148 aid: uint64 144 149 uid: uint64 145 150 aname: str 146 151 netmask: m.inet 147 - restrict: lib.mem.ptr(rawstring) 152 + privs: m.privset 148 153 blacklist: bool 149 154 } 150 - 151 155 152 156 -- backends only handle content on the local server 153 157 struct m.backend { id: rawstring 154 158 open: &m.source -> &opaque 155 159 close: &m.source -> {} 156 160 157 161 conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) 158 162 conf_set: {&m.source, rawstring, rawstring} -> {} 159 163 conf_reset: {&m.source, rawstring} -> {} 160 164 161 165 actor_save: {&m.source, m.actor} -> bool 162 166 actor_create: {&m.source, m.actor} -> bool 163 - actor_fetch_xid: {&m.source, rawstring} -> lib.mem.ptr(m.actor) 167 + actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) 164 168 actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor) 165 169 actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) 166 170 actor_enum: {&m.source} -> lib.mem.ptr(&m.actor) 167 171 actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor) 168 172 169 173 actor_auth_how: {&m.source, m.inet, rawstring} -> m.credset 170 174 -- returns a set of auth method categories that are available for a ................................................................................ 183 187 -- fingerprint: rawstring 184 188 actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 185 189 -- handles API authentication 186 190 -- origin: inet 187 191 -- handle: rawstring 188 192 -- key: rawstring (X-API-Key) 189 193 actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth) 194 + actor_session_fetch: {&m.source, uint64, m.inet} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)} 195 + -- retrieves an auth record + actor combo suitable by AID suitable 196 + -- for determining session validity & caps 197 + -- aid: uint64 198 + -- origin: inet 190 199 191 200 actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) 192 201 actor_conf_int: cnf(intptr, lib.stat(intptr)) 193 202 194 203 post_save: {&m.source, &m.post} -> bool 195 204 post_create: {&m.source, &m.post} -> bool 196 205 actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post)
Modified str.t from [4b8724b0aa] to [c91733fef5].
1 1 -- vim: ft=terra 2 2 -- string.t: string classes 3 +local util = dofile('common.lua') 3 4 4 5 local m = { 5 6 sz = terralib.externfunction('strlen', rawstring -> intptr); 6 7 cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); 7 8 ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); 8 9 cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); 9 10 ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); 10 11 dup = terralib.externfunction('strdup',rawstring -> rawstring); 11 12 ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring); 12 13 fmt = terralib.externfunction('asprintf', 13 14 terralib.types.funcpointer({&rawstring,rawstring},{int},true)); 14 15 bfmt = terralib.externfunction('sprintf', 15 16 terralib.types.funcpointer({rawstring,rawstring},{int},true)); 17 + span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring); 16 18 } 17 19 18 20 (lib.mem.ptr(int8)).metamethods.__cast = function(from,to,e) 19 21 if from == &int8 then 20 22 return `[lib.mem.ptr(int8)]{ptr = e, ct = m.sz(e)} 21 23 elseif to == &int8 then 22 24 return e.ptr ................................................................................ 31 33 } 32 34 33 35 local terra biggest(a: intptr, b: intptr) 34 36 if a > b then return a else return b end 35 37 end 36 38 37 39 terra m.acc:init(run: intptr) 38 - lib.dbg('initializing string accumulator') 40 + --lib.dbg('initializing string accumulator') 39 41 self.buf = [rawstring](lib.mem.heapa_raw(run)) 40 42 self.run = run 41 43 self.space = run 42 44 self.sz = 0 43 45 return self 44 46 end; 45 47 46 48 terra m.acc:free() 47 - lib.dbg('freeing string accumulator') 49 + --lib.dbg('freeing string accumulator') 48 50 if self.buf ~= nil and self.space > 0 then 49 51 lib.mem.heapf(self.buf) 50 52 end 51 53 end; 52 54 53 55 terra m.acc:crush() 54 56 lib.dbg('crushing string accumulator') ................................................................................ 65 67 pt.ct = self.sz 66 68 self.buf = nil 67 69 self.sz = 0 68 70 return pt 69 71 end; 70 72 71 73 terra m.acc:push(str: rawstring, len: intptr) 72 - var llen = len 74 + --var llen = len 73 75 if str == nil then return self end 74 - if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output 75 - lib.dbg('pushing "',{str,llen},'" onto accumulator') 76 + --if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output 77 + -- lib.dbg('pushing "',{str,llen},'" onto accumulator') 76 78 if self.buf == nil then self:init(self.run) end 77 79 if len == 0 then len = m.sz(str) end 78 80 if len >= self.space - self.sz then 79 81 self.space = self.space + biggest(self.run,len + 1) 80 82 self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space)) 81 83 end 82 84 lib.mem.cpy(self.buf + self.sz, str, len) 83 85 self.sz = self.sz + len 84 86 self.buf[self.sz] = 0 85 87 return self 86 88 end; 89 + 90 +m.lit = macro(function(str) 91 + return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]} 92 +end) 93 + 87 94 m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) 88 95 self:push(str.ptr, str.ct) return self end; 89 96 m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) 90 97 self:push(str.ptr, str.ct) str:free() return self end; 91 98 m.acc.methods.compose = macro(function(self, ...) 92 99 local minlen = 0 93 100 local pstrs = {} ................................................................................ 138 145 local ptr = symbol(&int8) 139 146 local box = symbol(&m.box(ty)) 140 147 local memreq_exp = `0 141 148 local copiers = {} 142 149 for k,v in pairs(vals) do 143 150 local ty = (`box.obj.[k]).tree.type 144 151 local kp 152 + local isnull, nullify 145 153 if ty.ptr_basetype then 146 - kp = quote [box].obj.[k] = [ty] { [ptr] = [&ty.ptr_basetype]([ptr]) } ; end 154 + kp = quote [box].obj.[k] = [ty] { ptr = [&ty.ptr_basetype]([ptr]) } ; end 155 + nullify = quote [box].obj.[k] = [ty] { ptr = nil, ct = 0 } end 147 156 else 148 157 kp = quote [box].obj.[k] = [ty]([ptr]) ; end 158 + nullify = quote [box].obj.[k] = nil end 149 159 end 150 160 151 161 local cpy 152 162 if type(v) ~= 'table' or #v ~= 2 then 153 163 cpy = quote [kp] ; [ptr] = m.cpy(ptr, v) end 164 + isnull = `v == nil 154 165 end 155 166 if type(v) == 'string' then 156 167 memreq_const = memreq_const + #v + 1 168 + isnull = `false 157 169 elseif type(v) == 'table' and v.tree and (v.tree.type.ptr_basetype == int8 or v.tree.type.ptr_basetype == uint8) then 158 170 cpy = quote [kp]; [ptr] = [&int8](lib.mem.cpy([ptr], [v].ptr, [v].ct)) end 159 171 if ty.ptr_basetype then 160 172 cpy = quote [cpy]; [box].obj.[k].ct = [v].ct end 161 173 end 174 + isnull = `[v].ptr == nil 162 175 elseif type(v) == 'table' and v.asvalue and type(v:asvalue()) == 'string' then 163 176 local str = tostring(v:asvalue()) 164 177 memreq_const = memreq_const + #str + 1 178 + isnull = `false 165 179 elseif type(v) == 'table' and #v == 2 then 166 180 local str,sz = v[1],v[2] 167 181 if type(sz) == 'number' then 168 182 memreq_const = memreq_const + sz 169 183 elseif type(sz:asvalue()) == 'number' then 170 184 memreq_const = memreq_const + sz:asvalue() 171 185 else memreq_exp = `[sz] + [memreq_exp] end 172 186 173 187 cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end 174 188 if ty.ptr_basetype then 175 189 cpy = quote [cpy]; [box].obj.[k].ct = sz end 176 190 end 191 + isnull = `[str] == nil 177 192 else 178 193 memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL 194 + isnull = `v == nil 179 195 if ty.ptr_basetype then 180 196 cpy = quote [cpy]; [box].obj.[k].ct = m.sz(v) end 181 197 end 182 198 end 183 - copiers[#copiers + 1] = cpy 199 + 200 + copiers[#copiers + 1] = quote 201 + if [isnull] then [nullify] 202 + else [cpy] end 203 + end 184 204 end 185 205 186 206 return quote 187 207 var sz: intptr = memreq_const + [memreq_exp] 188 208 var [box] = [&m.box(ty)](lib.mem.heapa_raw(sz)) 189 209 var [ptr] = [box].storage 190 210 [copiers] 191 211 in [lib.mem.ptr(ty)] { ct = 1, ptr = &([box].obj) } end 192 212 end 213 + 214 +terra m.cspan(str: lib.mem.ptr(int8), reject: lib.mem.ref(int8), maxlen: intptr) 215 + for i=0, lib.math.smallest(maxlen,str.ct) do 216 + if str.ptr[i] == 0 then return 0 end 217 + for j=0, reject.ct do 218 + if str.ptr[i] == reject.ptr[j] then return i end 219 + end 220 + end 221 + return maxlen 222 +end 223 + 224 +terra m.ffw(str: &int8, maxlen: intptr) 225 + while maxlen > 0 and @str ~= 0 and 226 + (@str == @' ' or @str == @'\t' or @str == @'\n') do 227 + str = str + 1 228 + maxlen = maxlen - 1 229 + end 230 + return str 231 +end 232 + 233 +terra m.ffw_unsafe(str: &int8) 234 + while @str ~= 0 and 235 + (@str == @' ' or @str == @'\t' or @str == @'\n') do 236 + str = str + 1 237 + end 238 + return str 239 +end 193 240 194 241 return m
Modified view/docskel.tpl from [1d38dac966] to [004398018e].
1 1 <!doctype html> 2 2 <html> 3 3 <head> 4 4 <title>@instance :: @title</title> 5 - <link rel="stylesheet" href="/style.css"> 5 + <link rel="stylesheet" href="/s/style.css"> 6 6 </head> 7 - <body> 7 + <body class="@class"> 8 8 <h1>@title</h1> 9 9 @body 10 10 </body> 11 11 </html>
Modified view/load.lua from [f2a65d7b61] to [53cfafaa7c].
2 2 -- file that indexes the templates manually, and 3 3 -- copies them into a data structure we can then 4 4 -- create templates from when we return to terra 5 5 local path = ... 6 6 local sources = { 7 7 'docskel'; 8 8 'tweet'; 9 + 'profile'; 9 10 } 10 11 11 12 local ingest = function(filename) 12 13 local hnd = io.open(path..'/'..filename) 13 14 local txt = hnd:read('*a') 14 15 io.close(hnd) 15 16 txt = txt:gsub('([^\\])!%b[]', '%1')
Added view/profile.tpl version [abc7153f4d].
1 +<div class="profile"> 2 + <div class="banner"> 3 + <img class="avatar" src="@avatar"> 4 + <div class="id">@nym [@xid]</div> 5 + <div class="bio"> 6 + @bio 7 + </div> 8 + </div> 9 + <table class="stats"> 10 + <tr><th>posts</th> <td>@nposts</td></tr> 11 + <tr><th>following</th> <td>@nfollows</td></tr> 12 + <tr><th>followers</th> <td>@nfollowers</td></tr> 13 + <tr><th>mutuals</th> <td>@nmutuals</td></tr> 14 + <tr><th>account created</th> <td>@tweetday</td></tr> 15 + </table> 16 + <div class="menu"> 17 + <a href="/\@@xid">posts</a> 18 + <a href="/\@@xid/media">media</a> 19 + <a href="/\@@xid/follows">follows</a> 20 + <a href="/\@@xid/chat">chat</a> 21 + </div> 22 +</div>
Modified view/tweet.tpl from [59ae4e4ad9] to [4ad2e9eaa9].
1 1 <div class="post"> 2 2 <div class="detail"> 3 3 <a class="username" href="@link"> 4 - <span class="nym">@nym</span> <span class="handle">[@handle]</span> 4 + <span class="nym">@nym</span> <span class="handle">[@xid]</span> 5 5 </div> 6 6 <div class="when">@when</div> 7 7 </div> 8 8 <div class="content"> 9 9 <img class="avatar" src="@avatar"> 10 10 <div class="text">@text</div> 11 11 </div> 12 12 </div>