| Comment: | permissions work now |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
bbfea467bf467820010635f449f683a1 |
| User & Date: | lexi on 2020-12-27 02:31:30 |
| Other Links: | manifest | tags |
|
2020-12-27
| ||
| 04:08 | look ma, im tweetin check-in: 8f954221a1 user: lexi tags: trunk | |
| 02:31 | permissions work now check-in: bbfea467bf user: lexi tags: trunk | |
|
2020-12-25
| ||
| 23:37 | iteration and important api adjustments check-in: f9559a83fc user: lexi tags: trunk | |
Modified backend/pgsql.t from [1740166796] to [36b1398523].
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 ... 161 162 163 164 165 166 167 168 169 170 171 172 173 174 ... 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 ... 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 ... 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 ... 652 653 654 655 656 657 658 659 660 661 |
where $1::text = (a.handle || '@' || domain) or
$1::text = ('@' || a.handle || '@' || domain) or
(a.origin is null and
$1::text = a.handle or
$1::text = ('@' || a.handle))
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid from parsav_auth as a
left join parsav_actors as u on u.id = a.uid
where (a.uid is null or u.handle = $1::text or (
a.uid = 0 and a.name = $1::text
)) and
(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
(a.netmask is null or a.netmask >> $4::inet)
order by blacklist desc limit 1
................................................................................
left join parsav_actors a on au.uid = a.id
left join parsav_servers s on a.origin = s.id
where au.aid = $1::bigint and au.blacklist = false and
(au.netmask is null or au.netmask >> $2::inet)
]];
};
post_create = {
params = {uint64, rawstring, rawstring, rawstring}, sql = [[
insert into parsav_posts (
author, subject, acl, body,
posted, discovered,
circles, mentions
................................................................................
terra pqr:len(row: intptr, col: intptr)
return lib.pq.PQgetlength(self.res, row, col)
end
terra pqr:cols() return lib.pq.PQnfields(self.res) end
terra pqr:string(row: intptr, col: intptr) -- not to be exported!!
if self:null(row,col) then return nil end
var v = lib.pq.PQgetvalue(self.res, row, col)
-- var r: lib.mem.ptr(int8)
-- r.ct = lib.str.sz(v)
-- r.ptr = v
return v
end
terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
return [lib.mem.ptr(uint8)] {
ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
ct = lib.pq.PQgetlength(self.res, row, col);
}
end
terra pqr:String(row: intptr, col: intptr) -- suitable to be exported
if self:null(row,col) then return [lib.mem.ptr(int8)] {ptr=nil,ct=0} end
var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) }
s.ct = lib.pq.PQgetlength(self.res, row, col)
return s
end
terra pqr:bool(row: intptr, col: intptr)
var v = lib.pq.PQgetvalue(self.res, row, col)
if @v == 0x01 then return true else return false end
end
terra pqr:cidr(row: intptr, col: intptr)
................................................................................
else
a.ptr.key = r:bin(row,8)
end
if r:null(row,3) then a.ptr.origin = 0
else a.ptr.origin = r:int(uint64,row,3) end
return a
end
local checksha = function(src, hash, origin, username, pw)
local validate = function(kind, cred, credlen)
return quote
var r = queries.actor_auth_pw.exec(
[&lib.store.source](src),
username,
kind,
[lib.mem.ptr(int8)] {ptr=[&int8](cred), ct=credlen},
origin)
if r.sz > 0 then -- found a record! stop here
var aid = r:int(uint64, 0,0)
r:free()
return aid
end
end
end
local out = symbol(uint8[64])
local vdrs = {}
................................................................................
end];
actor_auth_pw = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: lib.mem.ptr(int8),
cred: lib.mem.ptr(int8)
): uint64
[ checksha(`src, 256, ip, username, cred) ] -- most common
[ checksha(`src, 512, ip, username, cred) ] -- most secure
[ checksha(`src, 384, ip, username, cred) ] -- weird
[ checksha(`src, 224, ip, username, cred) ] -- weirdest
-- TODO: check pbkdf2-hmac
-- TODO: check OTP
return 0
end];
actor_stats = [terra(src: &lib.store.source, uid: uint64)
var r = queries.actor_stats.exec(src, uid)
if r.sz == 0 then lib.bail('error fetching actor stats!') end
var s: lib.store.actor_stats
s.posts = r:int(uint64, 0, 0)
................................................................................
end
var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL
return ret
end];
}
return b
|
> > > > > > > > > > > > > > > > > > > > > > > | > > > > > > < < < > > > > > > > > | > | | > > > > > > > > > > > > | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 ... 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 ... 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 ... 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 ... 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 ... 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 |
where $1::text = (a.handle || '@' || domain) or
$1::text = ('@' || a.handle || '@' || domain) or
(a.origin is null and
$1::text = a.handle or
$1::text = ('@' || a.handle))
]];
};
actor_create = {
params = {
rawstring, rawstring, uint64, lib.store.timepoint,
rawstring, rawstring, lib.mem.ptr(uint8),
rawstring, uint16, uint32
};
sql = [[
insert into parsav_actors (
nym,handle,
origin,knownsince,
bio,avataruri,key,
title,rank,quota
) values ($1::text, $2::text,
case when $3::bigint = 0 then null
else $3::bigint end,
to_timestamp($4::bigint),
$5::bigint, $6::bigint, $7::bytea,
$8::text, $9::smallint, $10::integer
) returning id
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name from parsav_auth as a
left join parsav_actors as u on u.id = a.uid
where (a.uid is null or u.handle = $1::text or (
a.uid = 0 and a.name = $1::text
)) and
(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
(a.netmask is null or a.netmask >> $4::inet)
order by blacklist desc limit 1
................................................................................
left join parsav_actors a on au.uid = a.id
left join parsav_servers s on a.origin = s.id
where au.aid = $1::bigint and au.blacklist = false and
(au.netmask is null or au.netmask >> $2::inet)
]];
};
actor_powers_fetch = {
params = {uint64}, sql = [[
select key, allow from parsav_rights where actor = $1::bigint
]]
};
post_create = {
params = {uint64, rawstring, rawstring, rawstring}, sql = [[
insert into parsav_posts (
author, subject, acl, body,
posted, discovered,
circles, mentions
................................................................................
terra pqr:len(row: intptr, col: intptr)
return lib.pq.PQgetlength(self.res, row, col)
end
terra pqr:cols() return lib.pq.PQnfields(self.res) end
terra pqr:string(row: intptr, col: intptr) -- not to be exported!!
if self:null(row,col) then return nil end
var v = lib.pq.PQgetvalue(self.res, row, col)
return v
end
terra pqr:_string(row: intptr, col: intptr) -- not to be exported!!
if self:null(row,col) then return pstring.null() end
return pstring {
ptr = lib.pq.PQgetvalue (self.res, row, col);
ct = lib.pq.PQgetlength(self.res, row, col);
}
end
terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
if self:null(row,col) then return [lib.mem.ptr(uint8)].null() end
return [lib.mem.ptr(uint8)] {
ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
ct = lib.pq.PQgetlength(self.res, row, col);
}
end
terra pqr:String(row: intptr, col: intptr) -- suitable to be exported
if self:null(row,col) then return pstring.null() end
var s = pstring {
ptr = lib.str.dup(self:string(row,col));
ct = lib.pq.PQgetlength(self.res, row, col);
}
return s
end
terra pqr:bool(row: intptr, col: intptr)
var v = lib.pq.PQgetvalue(self.res, row, col)
if @v == 0x01 then return true else return false end
end
terra pqr:cidr(row: intptr, col: intptr)
................................................................................
else
a.ptr.key = r:bin(row,8)
end
if r:null(row,3) then a.ptr.origin = 0
else a.ptr.origin = r:int(uint64,row,3) end
return a
end
local privmap = {}
do local struct pt { name:pstring, priv:lib.store.powerset }
for k,v in pairs(lib.store.powerset.members) do
privmap[#privmap + 1] = quote
var ps: lib.store.powerset ps:clear()
(ps.[v] << true)
in pt {name = lib.str.plit(v), priv = ps} end
end end
local checksha = function(src, hash, origin, username, pw)
local validate = function(kind, cred, credlen)
return quote
var r = queries.actor_auth_pw.exec(
[&lib.store.source](src),
username,
kind,
[lib.mem.ptr(int8)] {ptr=[&int8](cred), ct=credlen},
origin)
if r.sz > 0 then -- found a record! stop here
var aid = r:int(uint64, 0,0)
var uid = r:int(uint64, 0,1)
var name = r:String(0,2)
r:free()
return aid, uid, name
end
end
end
local out = symbol(uint8[64])
local vdrs = {}
................................................................................
end];
actor_auth_pw = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: lib.mem.ptr(int8),
cred: lib.mem.ptr(int8)
): {uint64, uint64, pstring}
[ checksha(`src, 256, ip, username, cred) ] -- most common
[ checksha(`src, 512, ip, username, cred) ] -- most secure
[ checksha(`src, 384, ip, username, cred) ] -- weird
[ checksha(`src, 224, ip, username, cred) ] -- weirdest
-- TODO: check pbkdf2-hmac
-- TODO: check OTP
return 0, 0, pstring.null()
end];
actor_stats = [terra(src: &lib.store.source, uid: uint64)
var r = queries.actor_stats.exec(src, uid)
if r.sz == 0 then lib.bail('error fetching actor stats!') end
var s: lib.store.actor_stats
s.posts = r:int(uint64, 0, 0)
................................................................................
end
var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL
return ret
end];
actor_powers_fetch = [terra(
src: &lib.store.source,
uid: uint64
): lib.store.powerset
var powers = lib.store.rights_default().powers
var map = array([privmap])
var r = queries.actor_powers_fetch.exec(src, uid)
for i=0, r.sz do
for j=0, [map.type.N] do
var pn = r:_string(i,0)
if map[j].name:cmp(pn) then
if r:bool(i,1)
then powers = powers + map[j].priv
else powers = powers - map[j].priv
end
end
end
end
return powers
end];
actor_create = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): uint64
var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.title, ac.rights.rank, ac.rights.quota)
if r.sz == 0 then lib.bail('failed to create actor!') end
return r:int(uint64,0,0)
end];
actor_auth_register_uid = nil; -- not necessary for view-based auth
}
return b
|
Modified common.lua from [3d397d42e6] to [e762fc8997].
92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
for _, v in pairs(b) do a[#a+1] = v end
end;
has = function(haystack,needle,eq)
eq = eq or function(a,b) return a == b end
for k,v in pairs(haystack) do
if eq(needle,v) then return k end
end
end;
ingest = function(f)
local h = io.open(f, 'r')
if h == nil then return nil end
local txt = f:read('*a') f:close()
return chomp(txt)
end;
|
> > > > > |
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
for _, v in pairs(b) do a[#a+1] = v end end; has = function(haystack,needle,eq) eq = eq or function(a,b) return a == b end for k,v in pairs(haystack) do if eq(needle,v) then return k end end end; keys = function(ary) local kt = {} for k,v in pairs(ary) do kt[#kt+1] = k end return kt end; ingest = function(f) local h = io.open(f, 'r') if h == nil then return nil end local txt = f:read('*a') f:close() return chomp(txt) end; |
Modified config.lua from [dc89401662] to [29834379ec].
27
28
29
30
31
32
33
34
35
36
37
38
39
40
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
dist = default('parsav_dist', coalesce(
os.getenv('NIX_PATH') and 'nixos',
os.getenv('NIX_STORE') and 'nixos',
''));
tgttrip = default('parsav_arch_triple'); -- target triple, used in xcomp
tgtcpu = default('parsav_arch_cpu'); -- target cpu, used in xcomp
tgthf = u.tobool(default('parsav_arch_armhf',true));
outform = default('parsav_emit_type', 'o');
endian = default('parsav_arch_endian', 'little');
build = {
id = u.rndstr(6);
release = u.ingest('release');
when = os.date();
};
................................................................................
embeds = {
{'style.css', 'text/css'};
{'default-avatar.webp', 'image/webp'};
{'padlock.webp', 'image/webp'};
{'warn.webp', 'image/webp'};
};
}
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
if u.ping '_FOSSIL_' then default_os = 'windows' end
conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
conf.build.checkout = (u.exec { 'fossil', 'sql',
[[select value from localdb.vvar where name = 'checkout-hash']]
}):gsub("^'(.*)'$", '%1')
end
|
>
>
>
>
>
>
>
|
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
dist = default('parsav_dist', coalesce(
os.getenv('NIX_PATH') and 'nixos',
os.getenv('NIX_STORE') and 'nixos',
''));
tgttrip = default('parsav_arch_triple'); -- target triple, used in xcomp
tgtcpu = default('parsav_arch_cpu'); -- target cpu, used in xcomp
tgthf = u.tobool(default('parsav_arch_armhf',true));
doc = {
online = u.tobool(default('parsav_online_documentation',true));
offline = u.tobool(default('parsav_offline_documentation',true));
};
outform = default('parsav_emit_type', 'o');
endian = default('parsav_arch_endian', 'little');
build = {
id = u.rndstr(6);
release = u.ingest('release');
when = os.date();
};
................................................................................
embeds = {
{'style.css', 'text/css'};
{'default-avatar.webp', 'image/webp'};
{'padlock.webp', 'image/webp'};
{'warn.webp', 'image/webp'};
};
}
if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then
conf.braingeniousmode = true -- SOUND GENERAL QUARTERS
end
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
if u.ping '_FOSSIL_' then default_os = 'windows' end
conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
conf.build.checkout = (u.exec { 'fossil', 'sql',
[[select value from localdb.vvar where name = 'checkout-hash']]
}):gsub("^'(.*)'$", '%1')
end
|
Modified crypt.t from [48369b50e0] to [bf3957f4f4].
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
rawcode = terra(code: int)
if code < 0 then code = -code end
return code and 0xFF80
end;
toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE;
}
const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to
local ctx = lib.pk.mbedtls_pk_context
local struct hashalg { id: uint8 bytes: intptr }
local m = {
pemfile = uint8[const.maxpemsz];
algsz = {
sha1 = 160/8;
sha256 = 256/8;
sha512 = 512/8;
sha384 = 384/8;
sha224 = 224/8;
}
................................................................................
terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool
if pub then
return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
else
return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
end
end
m.destroy = lib.dispatch {
[ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end;
[false] = function(ptr) return `ptr:free() end;
}
|
>
>
>
>
>
>
>
>
>
>
|
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
rawcode = terra(code: int) if code < 0 then code = -code end return code and 0xFF80 end; toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE; } const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one local ctx = lib.pk.mbedtls_pk_context local struct hashalg { id: uint8 bytes: intptr } local m = { pemfile = uint8[const.maxpemsz]; const = const; algsz = { sha1 = 160/8; sha256 = 256/8; sha512 = 512/8; sha384 = 384/8; sha224 = 224/8; } ................................................................................ terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool if pub then return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0 else return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0 end end terra m.der(pub: bool, key: &ctx, buf: &uint8): intptr if pub then return lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz) else return lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz) end end m.destroy = lib.dispatch { [ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end; [false] = function(ptr) return `ptr:free() end; } |
Added doc/acl.md version [ea4e893c12].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# access control to help limit who can see your post (and how far it is propagated), `parsav` uses **ACL expressions**. this is roughly equivalent to scoping in pleroma or GNU social terms. an ACL expression consists of one or more space-separated terms, each of which match a certain set of users. a term can be negated by prefixing it with `~`, a tilde character, so `~all` matches nobody, and `~followed` matches users you do not follow. * **all**: matches any and all users * **local**: matches users who belong to this instance * **mutuals**: matches users you follow who also follow you * **followed**: matches users you follow * **followers**: matches users who follow you * **groupies**: matches users who follow you, but whom you do not follow * **mentioned**: matches users who are mentioned in the post * **staff**: matches instance staff (equivalent to `~%0`) * **admin**: matches the individual named as the instance administrator, if any * **@**`handle`: matches the user `handle` * **+**`circle`: matches users you have categorized under `circle` * **#**`room`: matches users who are members of `room` * **%**`rank`: matches users of `rank` or higher (e.g. `%3` matches users of rank 3, 2, and 1). as a special case, `%0` matches ordinary users * **#**`room`**%**`rank`: matches users who hold `rank` in `room` * **<**`title`**>**: matches peers of the net who have been created `title` by the sovereign * **#**`room`**<**`title`**>**: matches peers of the chat who have been created `title` by `room` staff to evaluate an ACL expression, `parsav` reads each term from start to finish. for each term, it considers whether it describes the user who is attempting to access the content. if the term matches, its policy is applied and the expression completes. if the term doesn't match, the server proceeds on to the next term and the process repeats until it finds a matching term or runs out of terms, applying the fallback policy. **policy** is whether a term grants or denies access. the default term policy is **allow**, but you can control the policy with the keywords `allow` and `deny`. if a term finishes evaluating without any match being found, a fallback policy is applied; this fallback is the opposite of whatever the current policy is. this sounds confusing but makes ACL expressions much more intuitive; `allow @bob` and `deny trent` do exactly what you'd expect — the former allows bob and only bob in; the latter denies access only to trent, but grants access to the rest of the world. expressions must contain at least one term to be valid. if they consist only of policy keywords, they will be rejected. in effect, this all means that an ACL expression can be treated as a simple list of who is allowed to view your post. for instance, an expression of `local` means only local users can view it. however, much more complex expressions are possible. * `deny groupies allow +illuminati`: permits access to the illuminati, but excluding those members who are groupies * `+illuminati deny groupies`: allows access to everyone but groupies (unless they're in the illuminati) * `@eve @alice@nowhere.tld deny @bob @trent@witches.live`: grants access to eve and alice, but locks out bob and trent * `<grand duke> #4th-intl<comrade>`: restricts the post to the eyes of the Fourth International's secret cabal of anointed comrades and the grand dukes of the Empire * `deny ~%3`: blocks a post from being seen by anyone with a staff rank level below 3 **limitations:** to inhibit potential denial-of-service attacks, ACL expressions can be a maximum of 128 characters, can contain at most 16 words, and cannot trigger queries against other servers. all information needed to evaluate an ACL expression must be known locally. this is particularly relevant with respect to rooms. |
Added doc/invocation.md version [2a91b27d48].
> |
1 |
# daemon invocation
|
Added doc/load.lua version [68f6a9a498].
> > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
local path = ... local sources = { -- user section acl = {title = 'access control lists'}; -- admin section --overview = {title = 'server overview', priv = 'config'}; invocation = {title = 'daemon invocation', priv = 'config'}; --backends = {title = 'storage backends', priv = 'config'}; --pgsql = {title = 'pgsql', priv = 'config', parent = 'backends'}; } local util = dofile 'common.lua' local ingest = function(filename) return (util.exec { 'cmark', '--smart', '--unsafe', (path..'/'..filename) }):gsub('\n','') end local doc = {} for n,meta in pairs(sources) do doc[n] = { name = n; text = ingest(n .. '.md'); meta = meta; } end return doc |
Modified http.t from [654249752e] to [7092b409fb].
1 2 3 4 5 6 7 8 9 10 |
-- vim: ft=terra
local m = {}
local util = dofile('common.lua')
m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' }
m.mime = lib.enum {
'html'; -- default
'json';
'mkdown';
'text';
|
| |
1 2 3 4 5 6 7 8 9 10 |
-- vim: ft=terra
local m = {}
local util = lib.util
m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' }
m.mime = lib.enum {
'html'; -- default
'json';
'mkdown';
'text';
|
Modified mem.t from [15de92d877] to [a177326f1c].
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
...
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
{'ct', intptr};
}
t.ptr_basetype = ty
local recurse = false
--if ty:isstruct() then
--if ty.methods.free then recurse = true end
--end
t.metamethods.__not = macro(function(self)
return `self.ptr
end)
if dyn then
t.methods = {
free = terra(self: &t): bool
[recurse and quote
self.ptr:free()
end or {}]
if self.ct > 0 then
................................................................................
end
end
terra t:advance(n: intptr)
self.ptr = self.ptr + n
self.ct = self.ct - n
return self.ptr
end
if not ty:isstruct() then
terra t:cmp_raw(other: &ty)
for i=0, self.ct do
if self.ptr[i] ~= other[i] then return false end
end
return true
end
|
<
<
<
>
>
>
>
|
52
53
54
55
56
57
58
59
60
61
62
63
64
65
...
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
{'ct', intptr};
}
t.ptr_basetype = ty
local recurse = false
--if ty:isstruct() then
--if ty.methods.free then recurse = true end
--end
if dyn then
t.methods = {
free = terra(self: &t): bool
[recurse and quote
self.ptr:free()
end or {}]
if self.ct > 0 then
................................................................................
end
end
terra t:advance(n: intptr)
self.ptr = self.ptr + n
self.ct = self.ct - n
return self.ptr
end
terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro?
terra t:ref() return self.ptr ~= nil end
t.metamethods.__not = macro(function(self) return `not self:ref() end)
t.metamethods.__apply = macro(function(self,idx) return `self.ptr[idx] end)
if not ty:isstruct() then
terra t:cmp_raw(other: &ty)
for i=0, self.ct do
if self.ptr[i] ~= other[i] then return false end
end
return true
end
|
Modified parsav.md from [eb5d145ae6] to [ae23203c4b].
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 .. 28 29 30 31 32 33 34 35 36 37 38 39 40 41 .. 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 .. 95 96 97 98 99 100 101 102 103 104 105 |
## backends parsav is designed to be storage-agnostic, and can draw data from multiple backends at a time. backends can be enabled or disabled at compile time to avoid unnecessary dependencies. * postgresql ## dependencies * mongoose * json-c * mbedtls * **postgresql backend:** * postgresql-libs additional build-time dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary: * inkscape, for rendering out UI graphics * cwebp (libwebp package), for transforming inkscape PNGs to webp * sassc, for compiling the SCSS stylesheet into its final CSS all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR. distributions will also be made in the form of tarballed object code and assembly listings for various common platforms, currently including x86-32/64, arm7hf, aarch64, riscv, mips32/64, and ppc64/64le. ................................................................................ also note that, while parsav has a flag to build with ASAN, ASAN has proven unusable for most purposes as it routinely reports false positive buffer-heap-overflows. if you figure out how to defuckulate this, i will be overjoyed. ## building first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD. postgresql-libs must be installed systemwide, as `parsav` does not currently provide for statically compiling and linking it ## configuring the `parsav` configuration is comprised of two components: the backends list and the config store. the backends list is a simple text file that tells `parsav` which data sources to draw from. the config store is a key-value store which contains the rest of the server's configuration, and is loaded from the backends. the configuration store can be spread across the backends; backends will be checked for configuration keys according to the order in which they are listed. changes to the configuration store affect parsav in real time; you only need to restart the server if you make a change to the backend list. eventually, we'll add a command-line tool `parsav-cfg` to enable easy modification of the configuration store from the command line; for now, you'll need to modify the database by hand or use the online administration menu. the schema.sql file contains commands to prompt for various important values like the name of your administrative user. ................................................................................ netmask cidr, blacklist bool ) `aid` is a unique value identifying the authentication method. it must be deterministic -- values based on time of creation or a hash of `uid`+`kind`+`cred` are ideal. `uid` is the identifier of the user the row specifies credentials for. `kind` is a string indicating the credential type, and `cred` is the content of that credential.for the meaning of these fields and use of this structure, see **authentication** below. ## authentication in the most basic case, an authentication record would be something like `{uid = 123, kind = "pw-sha512", cred = "12bf90…a10e"}`. but `parsav` is not restricted to username-password authentication, and in addition to various hashing styles, it also will support more esoteric forms of authentcation. any individual user can have as many auth rows as she likes. there is also a `restrict` field, which is normally null, but can be specified in order to restrict a particular credential to certain operations, such as posting tweets or updating a bio. `blacklist` indicates that any attempt to authenticate that matches this row will be denied, regardless of whether it matches other rows. if `netmask` is present, this authentication will only succeed if it comes from the specified IP mask. `uid` can also be `0` (not null, which matches any user!), indicating that there is not yet a record in `parsav_actors` for this account. if this is the case, `name` must contain the handle of the account to be created when someone attempts to log in with this credential. whether `name` is used in the authentication process depends on whether the authentication method accepts a username. all rows with the same `uid` *must* have the same `name`. below is a full list of authentication types we intend to support. a checked box indicates the scheme has been implemented. * ☑ pw-sha{512,384,256,224}: an ordinary password, hashed with the appropriate algorithm * ☐ pw-{sha1,md5,clear} (insecure, must be manually enabled at compile time with the config variable `parsav_let_me_be_a_dumbass="i know what i'm doing"`) * ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 * ☐ api-digest-sha{…}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. * ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key * ☐ tls-cert-fp: a fingerprint of a client certificate * ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user * ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. ................................................................................ parsav needs more storage backends, as it currently supports only postgres. some possibilities, in order of priority, are: * plain text/filesystem storage * lmdb * sqlite3 * generic odbc * lua * ldap?? possibly just for users * cdb (for static content, maybe?) * mariadb/mysql * the various nosql horrors, e.g. redis, mongo, and so on |
> | | | | | > > > | > > | | > > > > > > | |
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 .. 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 .. 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 ... 107 108 109 110 111 112 113 114 115 116 117 |
## backends parsav is designed to be storage-agnostic, and can draw data from multiple backends at a time. backends can be enabled or disabled at compile time to avoid unnecessary dependencies. * postgresql ## dependencies * runtime * mongoose * json-c * mbedtls * **postgresql backend:** * postgresql-libs * compile-time * cmark (commonmark implementation), for transformation of the help files, whose source is in commonmark. online documentation transforms these into html and embeds them in the binary; cmark is also used to to produce the troff source which is used to build the offline documentation. disable with `parsav_online_documentation=no parsav_offline_documentation=no` * troff implementation (tested with groff but as far as i know we don't need any groff-specific extensions) to produce PDFs and manpages from the cmark-generated intermediate forms. disable with `parsav_offline_documentation=no` additional preconfigure dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary: * inkscape, for rendering out UI graphics * cwebp (libwebp package), for transforming inkscape PNGs to webp * sassc, for compiling the SCSS stylesheet into its final CSS all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR. distributions will also be made in the form of tarballed object code and assembly listings for various common platforms, currently including x86-32/64, arm7hf, aarch64, riscv, mips32/64, and ppc64/64le. ................................................................................ also note that, while parsav has a flag to build with ASAN, ASAN has proven unusable for most purposes as it routinely reports false positive buffer-heap-overflows. if you figure out how to defuckulate this, i will be overjoyed. ## building first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD. postgresql-libs must be installed systemwide, as `parsav` does not currently provide for statically compiling and linking it if you use nixos and wish to build the pdf documentation, you're going to have to do a bit of extra work (but you're used to that, aren't you). for some incomprehensible reason, the groff package on nix is split up, seemingly randomly, with many crucial output devices relegated to the "perl" output of the package, which is not installed by default (and `nix-env -iA nixos.groff.perl` doesn't work either; i don't know why either). you'll have to instantiate and install the outputs directly by path, e.g. `nix-env -i /nix/store/*groff*/` to get everything you need into your profile. alas, the battle is not over: you also need to change the environment variables `GROFF_FONT_PATH` and `GROFF_TMAC_PATH` to point at the `font` and `tmac` subdirs of `~/.nix-profile/share/groff/$groff_version/`. once this is done, invoking `groff -Tpdf` will work as expected. ## configuring the `parsav` configuration is comprised of two components: the backends list and the config store. the backends list is a simple text file that tells `parsav` which data sources to draw from. the config store is a key-value store which contains the rest of the server's configuration, and is loaded from the backends. the configuration store can be spread across the backends; backends will be checked for configuration keys according to the order in which they are listed. changes to the configuration store affect parsav in real time; you only need to restart the server if you make a change to the backend list. eventually, we'll add a command-line tool `parsav-cfg` to enable easy modification of the configuration store from the command line; for now, you'll need to modify the database by hand or use the online administration menu. the schema.sql file contains commands to prompt for various important values like the name of your administrative user. ................................................................................ netmask cidr, blacklist bool ) `aid` is a unique value identifying the authentication method. it must be deterministic -- values based on time of creation or a hash of `uid`+`kind`+`cred` are ideal. `uid` is the identifier of the user the row specifies credentials for. `kind` is a string indicating the credential type, and `cred` is the content of that credential.for the meaning of these fields and use of this structure, see **authentication** below. ## authentication in the most basic case, an authentication record would be something like `{uid = 123, kind = "pw-sha512", cred = "\x12bf90…a10e"::bytea}`. but `parsav` is not restricted to username-password authentication, and in addition to various hashing styles, it also will support more esoteric forms of authentcation. any individual user can have as many auth rows as she likes. there is also a `restrict` field, which is normally null, but can be specified in order to restrict a particular credential to certain operations, such as posting tweets or updating a bio. `blacklist` indicates that any attempt to authenticate that matches this row will be denied, regardless of whether it matches other rows. if `netmask` is present, this authentication will only succeed if it comes from the specified IP mask. `uid` can also be `0` (not null, which matches any user!), indicating that there is not yet a record in `parsav_actors` for this account. if this is the case, `name` must contain the handle of the account to be created when someone attempts to log in with this credential. whether `name` is used in the authentication process depends on whether the authentication method accepts a username. all rows with the same `uid` *must* have the same `name`. below is a full list of authentication types we intend/hope to one day support. contributors should consider this a to-do list. a checked box indicates the scheme has been implemented. * ☑ pw-sha{512,384,256,224}: an ordinary password, hashed with the appropriate algorithm * ☐ pw-{sha1,md5,clear} (insecure, must be manually enabled at compile time with the config variable `parsav_let_me_be_a_dumbass="i know what i'm doing"`) * ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 * ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server * ☐ pw-extern-cyrus: try to authenticate against saslauthd * ☐ pw-extern-dovecot: try to authenticate against a dovecot SASL socket * ☐ pw-extern-krb5: abuse MIT kerberos as a password verifier * ☐ pw-extern-imap: abuse an email server as a password verifier * (extra credit) ☐ pw-extern-radius: verify a user against a radius server * ☐ api-digest-sha{…}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. * ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key * ☐ tls-cert-fp: a fingerprint of a client certificate * ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user * ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. ................................................................................ parsav needs more storage backends, as it currently supports only postgres. some possibilities, in order of priority, are: * plain text/filesystem storage * lmdb * sqlite3 * generic odbc * lua * ldap for auth (and maybe actors?) * cdb (for static content, maybe?) * mariadb/mysql * the various nosql horrors, e.g. redis, mongo, and so on |
Modified parsav.t from [579976ae23] to [8c471232dd].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .. 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 .. 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 ... 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 ... 260 261 262 263 264 265 266 267 268 269 270 271 272 273 ... 310 311 312 313 314 315 316 317 318 319 320 321 322 323 ... 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
-- vim: ft=terra
local util = dofile('common.lua')
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')
lib = {
init = {};
load = function(lst)
for _, l in pairs(lst) do
local path = {}
for m in l:gmatch('([^:]+)') do path[#path+1]=m end
local tgt = lib
for i=1,#path-1 do
if tgt[path[i]] == nil then tgt[path[i]] = {} end
................................................................................
local str = tostring(v:asvalue())
code[#code+1] = `lib.io.send(2, str, [#str])
else
code[#code+1] = quote var n = v in
lib.io.send(2, n, lib.str.sz(n)) end
end
end
if nl then code[#code+1] = `lib.io.send(fd, '\n', 1) end
return code
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
local str, ct
................................................................................
else--if v.tree:is 'constant' then
str = tostring(v:asvalue())
end
ct = ct or #str
end
vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
end
if nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} end
return quote
[defs]
var strs = array( [vec] )
in lib.uio.writev(fd, strs, [#vec]) end
end;
trn = macro(function(cond, i, e)
return quote
................................................................................
var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
end
end
local defrep = function(level,n,code)
return macro(function(...)
local q = lib.emit(true, 2, noise_header(code,n), ...)
return quote if noise >= level then timehdr(); [q] end end
end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
................................................................................
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] or other._store[i]
end
end
return quote [q] in new end
end)
bit.metamethods.__cast = function(from,to,e)
local q = quote var s = e
in (s._set._store[s._v/8] and (1 << s._v % 8)) end
if to == bit then error('casting to bit is not meaningful')
elseif to == bool then return `([q] ~= 0)
elseif to:isintegral() then return q
................................................................................
lib.cmdparse = terralib.loadfile('cmdparse.t')()
do local collate = function(path,f, ...)
return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
view = collate('view','load');
static = {};
stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content
} end
for i,e in ipairs(config.embeds) do local v = e[1]
local fh = io.open('static/' .. v,'r')
if fh == nil then error('static file ' .. v .. ' missing') end
................................................................................
'render:nav';
'render:login';
'render:profile';
'render:userpage';
'render:compose';
'render:tweet';
'render:timeline';
'route';
}
do
local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
terra version() lib.io.send(1, p, [#p]) end
end
|
| | > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .. 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 .. 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 ... 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 ... 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 ... 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 ... 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
-- vim: ft=terra
local util = dofile('common.lua')
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')
lib = {
init = {}, util = util;
load = function(lst)
for _, l in pairs(lst) do
local path = {}
for m in l:gmatch('([^:]+)') do path[#path+1]=m end
local tgt = lib
for i=1,#path-1 do
if tgt[path[i]] == nil then tgt[path[i]] = {} end
................................................................................
local str = tostring(v:asvalue())
code[#code+1] = `lib.io.send(2, str, [#str])
else
code[#code+1] = quote var n = v in
lib.io.send(2, n, lib.str.sz(n)) end
end
end
if nl == true then code[#code+1] = `lib.io.send(fd, '\n', 1)
elseif nl then code[#code+1] = `lib.io.send(fd, nl, [#nl]) end
return code
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
local str, ct
................................................................................
else--if v.tree:is 'constant' then
str = tostring(v:asvalue())
end
ct = ct or #str
end
vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
end
if nl == true then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1}
elseif nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](nl), iov_len = [#nl]} end
return quote
[defs]
var strs = array( [vec] )
in lib.uio.writev(fd, strs, [#vec]) end
end;
trn = macro(function(cond, i, e)
return quote
................................................................................
var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
end
end
local defrep = function(level,n,code)
return macro(function(...)
local fn = (...).filename
local ln = tostring((...).linenumber)
local dbgtag = string.format('\27[35m · \27[34m%s:\27[1m%s\27[m\n', fn,ln)
local q = lib.emit(level < 3 and true or dbgtag, 2, noise_header(code,n), ...)
return quote if noise >= level then timehdr(); [q] end end
end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
................................................................................
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] or other._store[i]
end
end
return quote [q] in new end
end)
set.metamethods.__and = macro(function(self,other)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] and other._store[i]
end
end
return quote [q] in new end
end)
set.metamethods.__not = macro(function(self)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = not self._store[i]
end
end
return quote [q] in new end
end)
set.metamethods.__sub = macro(function(self,other)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] and (not other._store[i])
end
end
return quote [q] in new end
end)
bit.metamethods.__cast = function(from,to,e)
local q = quote var s = e
in (s._set._store[s._v/8] and (1 << s._v % 8)) end
if to == bit then error('casting to bit is not meaningful')
elseif to == bool then return `([q] ~= 0)
elseif to:isintegral() then return q
................................................................................
lib.cmdparse = terralib.loadfile('cmdparse.t')()
do local collate = function(path,f, ...)
return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
doc = collate('doc','load');
view = collate('view','load');
static = {};
stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content
} end
for i,e in ipairs(config.embeds) do local v = e[1]
local fh = io.open('static/' .. v,'r')
if fh == nil then error('static file ' .. v .. ' missing') end
................................................................................
'render:nav';
'render:login';
'render:profile';
'render:userpage';
'render:compose';
'render:tweet';
'render:timeline';
'render:docpage';
'route';
}
do
local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
terra version() lib.io.send(1, p, [#p]) end
end
|
Added render/docpage.t version [7e1168b387].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
-- vim: ft=terra local page = lib.srv.convo.page local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local P = lib.str.plit local R = lib.str.lit local topics = lib.util.keys(data.doc) local topicidxt = {} table.sort(topics) -- because deterministic builds are good local branches = {} for i,k in pairs(topics) do topicidxt[k] = i local par = data.doc[k].meta.parent if par then branches[par] = branches[par] or {} local br = branches[par] br[#br+1] = k end end local struct pgpair { content:page name:pref title:pref priv:lib.store.powerset parent:intptr } local pages = symbol(pgpair[#topics]) local allpages = {} for i,v in ipairs(topics) do local t = data.doc[v] local par = 0 if t.meta.parent then par = topicidxt[t.meta.parent] end local restrict = symbol(lib.store.powerset) local setbits = quote restrict:clear() end if t.meta.priv then if type(t.meta.priv) ~= 'table' then t.meta.priv = {t.meta.priv} end for _,v in pairs(t.meta.priv) do setbits = quote [setbits]; (restrict.[v] << true) end end end allpages[i] = quote var [restrict]; [setbits] in pgpair { name = R(v); parent = par; priv = restrict; title = R(t.meta.title); content = page { title = ['documentation :: ' .. t.meta.title]; body = [ t.text ]; class = P'doc article'; }; } end end local terra showpage(co: &lib.srv.convo, id: pref) var [pages] = array([allpages]) for i=0,[pages.type.N] do if pages[i].name:cmp(id) then co:stdpage(pages[i].content) return end end -- else co:complain(404,'not found', 'no help article with that identifier is available') end local terra pushbranches(list: &lib.str.acc, idx: intptr, ps: lib.store.powerset): {} var [pages] = array([allpages]) var started = false for i=0,[pages.type.N] do if pages[i].parent == idx+1 and (pages[i].priv:sz() == 0 or (ps and pages[i].priv):sz() > 0) then if not started then started = true list:lpush('<ul>') end list:lpush('<li><a href="/doc/'):rpush(pages[i].name):lpush('">') :rpush(pages[i].title):lpush('</a>') pushbranches(list, i, ps) list:lpush('</li>') end end if started then list:lpush('</ul>') end end local terra render_docpage(co: &lib.srv.convo, pg: pref) var nullprivs: lib.store.powerset nullprivs:clear() if not pg then -- display index var list: lib.str.acc list:compose('<ul>') var [pages] = array([allpages]) for i=0,[pages.type.N] do if pages[i].parent == 0 and (pages[i].priv:sz() == 0 or (co.aid ~= 0 and (co.who.rights.powers and pages[i].priv):sz() > 0)) then list:lpush('<li><a href="/doc/'):rpush(pages[i].name):lpush('">') :rpush(pages[i].title):lpush('</a>') if co.aid ~= 0 then pushbranches(&list, i, co.who.rights.powers) else pushbranches(&list, i, nullprivs) end list:lpush('</li>') end end list:lpush('</ul>') var bp = list:finalize() co:stdpage(page { title = 'documentation'; body = bp; class = P'doc listing'; }) bp:free() else showpage(co, pg) end end return render_docpage |
Modified render/nav.t from [450b73f673] to [27572ae99b].
3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
render_nav(co: &lib.srv.convo)
var t: lib.str.acc t:init(64)
if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
t:lpush('<a href="/">timeline</a>')
end
if co.who ~= nil then
t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0)
t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/help">help</a> <a href="/logout">log out</a>')
else
t:lpush('<a href="/help">help</a> <a href="/login">log in</a>')
end
return t:finalize()
end
return render_nav
|
| | |
3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
render_nav(co: &lib.srv.convo)
var t: lib.str.acc t:init(64)
if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
t:lpush('<a href="/">timeline</a>')
end
if co.who ~= nil then
t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0)
t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/doc">docs</a> <a href="/logout">log out</a>')
else
t:lpush('<a href="/doc">docs</a> <a href="/login">log in</a>')
end
return t:finalize()
end
return render_nav
|
Modified render/profile.t from [30c7a0b6c6] to [d063b74f92].
15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
actor.xid, '/chat">chat</a>')
if co.who.rights.powers:affect_users() then
aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17)
end
auxp = aux:finalize()
else
aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>')
end
var avistr: lib.str.acc if actor.origin == 0 then
avistr:compose('/avi/',actor.handle)
end
var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0])
var strfbuf: int8[28*4]
|
> |
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
actor.xid, '/chat">chat</a>')
if co.who.rights.powers:affect_users() then
aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17)
end
auxp = aux:finalize()
else
aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>')
auxp = aux:finalize()
end
var avistr: lib.str.acc if actor.origin == 0 then
avistr:compose('/avi/',actor.handle)
end
var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0])
var strfbuf: int8[28*4]
|
Modified render/userpage.t from [7b79f9904c] to [9774faf032].
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var ti: lib.str.acc
if co.aid ~= 0 and co.who.id == actor.id then
ti:compose('my profile')
else
ti:compose('profile :: ', actor.handle)
end
var pftxt = lib.render.profile(co,actor) defer pftxt:free()
var doc = data.view.docskel {
instance = co.srv.cfg.instance;
title = ti:finalize();
body = pftxt;
class = lib.str.plit 'profile';
navlinks = co.navbar;
}
var hdrs = array(
lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
)
doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
doc.title:free()
end
return render_userpage
|
< < < | > | < | < < < < | |
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var ti: lib.str.acc
if co.aid ~= 0 and co.who.id == actor.id then
ti:compose('my profile')
else
ti:compose('profile :: ', actor.handle)
end
var pftxt = lib.render.profile(co,actor) defer pftxt:free()
var tiptr = ti:finalize()
co:stdpage([lib.srv.convo.page] {
title = tiptr; body = pftxt;
class = lib.str.plit 'profile';
})
tiptr:free()
end
return render_userpage
|
Modified route.t from [4d69f275c0] to [d6af263481].
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 ... 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 ... 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
co:reroute(redirto.buf)
end
end
terra http.timeline(co: &lib.srv.convo, mode: hpath)
lib.render.timeline(co,lib.trn(mode.ptr == nil, rstring{ptr=nil}, mode.ptr[1]))
return
end
do local branches = quote end
local filename, flen = symbol(&int8), symbol(intptr)
local page = symbol(lib.http.page)
local send = label()
local storage = data.stmap
................................................................................
co:reroute('/s/default-avatar.webp')
end
-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
co.navbar = lib.render.nav(co)
lib.dbg('got nav ', {co.navbar.ptr,co.navbar.ct}, "||", co.navbar.ptr)
-- some routes are non-hierarchical, and can be resolved with a simple strcmp
-- we run through those first before giving up and parsing the URI
if uri.ptr[0] ~= @'/' then
co:complain(404, 'what the hell', 'how did you do that')
return
elseif uri.ct == 1 then -- root
if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
................................................................................
return
else -- hierarchical routes
var path = lib.http.hier(uri) defer path:free()
if path.ptr[0]:cmp(lib.str.lit('user')) then
http.actor_profile_uid(co, path, meth)
elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
http.timeline(co, path)
else goto notfound end
return
end
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end
|
| > > > > > > > > > < > > > |
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 ... 212 213 214 215 216 217 218 219 220 221 222 223 224 225 ... 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
co:reroute(redirto.buf)
end
end
terra http.timeline(co: &lib.srv.convo, mode: hpath)
lib.render.timeline(co,lib.trn(mode.ptr == nil, rstring{ptr=nil}, mode.ptr[1]))
end
terra http.documentation(co: &lib.srv.convo, path: hpath)
if path.ct == 2 then
lib.render.docpage(co,path(1))
elseif path.ct == 1 then
lib.render.docpage(co, rstring.null())
else
co:complain(404, 'no such documentation', 'invalid documentation URL')
end
end
do local branches = quote end
local filename, flen = symbol(&int8), symbol(intptr)
local page = symbol(lib.http.page)
local send = label()
local storage = data.stmap
................................................................................
co:reroute('/s/default-avatar.webp')
end
-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
co.navbar = lib.render.nav(co)
-- some routes are non-hierarchical, and can be resolved with a simple strcmp
-- we run through those first before giving up and parsing the URI
if uri.ptr[0] ~= @'/' then
co:complain(404, 'what the hell', 'how did you do that')
return
elseif uri.ct == 1 then -- root
if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
................................................................................
return
else -- hierarchical routes
var path = lib.http.hier(uri) defer path:free()
if path.ptr[0]:cmp(lib.str.lit('user')) then
http.actor_profile_uid(co, path, meth)
elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
http.timeline(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('doc')) then
if meth ~= method.get and meth ~= method.head then goto wrongmeth end
http.documentation(co, path)
else goto notfound end
return
end
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end
|
Modified schema.sql from [6bf306c986] to [097969b0cb].
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
);
drop table if exists parsav_rights cascade;
create table parsav_rights (
key text,
actor bigint references parsav_actors(id)
on delete cascade,
allow boolean,
primary key (key,actor)
);
insert into parsav_actors (handle,rank,quota) values (:'admin',1,0);
insert into parsav_rights (actor,key,allow)
select (select id from parsav_actors where handle=:'admin'), a.column1, a.column2 from (values
('ban',true),
('config',true),
('censor',true),
('suspend',true),
('rebrand',true)
) as a;
drop table if exists parsav_posts cascade;
create table parsav_posts (
id bigint primary key default (1+random()*(2^63-1))::bigint,
author bigint references parsav_actors(id)
|
| > | > > > |
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
); drop table if exists parsav_rights cascade; create table parsav_rights ( key text, actor bigint references parsav_actors(id) on delete cascade, allow boolean not null, scope bigint, -- for future expansion primary key (key,actor) ); insert into parsav_actors (handle,rank,quota) values (:'admin',1,0); insert into parsav_rights (actor,key,allow) select (select id from parsav_actors where handle=:'admin'), a.column1, a.column2 from (values ('purge',true), ('config',true), ('censor',true), ('suspend',true), ('cred',true), ('elevate',true), ('demote',true), ('rebrand',true) ) as a; drop table if exists parsav_posts cascade; create table parsav_posts ( id bigint primary key default (1+random()*(2^63-1))::bigint, author bigint references parsav_actors(id) |
Modified srv.t from [d14bb4b6ce] to [8c264568c9].
1 2 3 4 5 6 7 8 9 10 .. 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 ... 163 164 165 166 167 168 169 170 171 172 173 174 175 176 ... 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 ... 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 |
-- vim: ft=terra local util = dofile 'common.lua' local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' } local struct srv local struct cfgcache { secret: lib.mem.ptr(int8) instance: lib.mem.ptr(int8) overlord: &srv pol_sec: secmode.t pol_reg: bool ................................................................................ elseif rt.stat_basetype then tk = stat elseif rt.ptr_basetype then tk = ptr end break end end local r = symbol(rt) if tk == primary then return quote var [r] for i=0,self.sources.ct do var src = self.sources.ptr + i if src.handle ~= nil and src.backend.[meth] ~= nil then r = src:[meth]([expr]) goto success end end lib.bail(['no active backends provide critical capability ' .. meth .. '!']) ::success::; in r end else local ok, empty if tk == ptr then ok = `r.ptr ~= nil empty = `[rt]{ptr=nil,ct=0} elseif tk == stat then ok = `r.ok == true ................................................................................ body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { ptr = &hdrs[0], ct = [hdrs.type.N] }) body.title:free() body.body:free() end -- CALL ONLY ONCE PER VAR terra convo:postv(name: rawstring) if self.varbuf.ptr == nil then self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) self.vbofs = self.varbuf.ptr end ................................................................................ lib.osclock.time(nil)) if aid ~= 0 then co.aid = aid end end ::nocookie::; end if co.aid ~= 0 then var sess, usr = co.srv:actor_session_fetch(co.aid, peer) if sess.ok == false then co.aid = 0 else co.who = usr.ptr end end var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free() var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1) var uri = uridec if urideclen == -1 then ................................................................................ return stats end terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) var cs: lib.store.credset cs:clear() var ok = false for i=0,self.sources.ct do var set, iok = self.sources.ptr[i]:actor_auth_how(ip, usn) if iok then cs = cs + set ok = iok end end return cs, ok end terra cfgcache.methods.load :: {&cfgcache} -> {} terra cfgcache:init(o: &srv) self.overlord = o self:load() end srv.methods.start = terra(self: &srv, befile: rawstring) |
| > > | | > > > > > > > > > > > > > > > > > > > > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 .. 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 ... 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 ... 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 ... 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
-- vim: ft=terra local util = lib.util local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' } local pstring = lib.mem.ptr(int8) local struct srv local struct cfgcache { secret: lib.mem.ptr(int8) instance: lib.mem.ptr(int8) overlord: &srv pol_sec: secmode.t pol_reg: bool ................................................................................ elseif rt.stat_basetype then tk = stat elseif rt.ptr_basetype then tk = ptr end break end end local r = symbol(rt) local succ = label() if tk == primary then return quote var [r] for i=0,self.sources.ct do var src = self.sources.ptr + i if src.handle ~= nil and src.backend.[meth] ~= nil then r = src:[meth]([expr]) goto [succ] end end lib.bail(['no active backends provide critical capability ' .. meth .. '!']) ::[succ]::; in r end else local ok, empty if tk == ptr then ok = `r.ptr ~= nil empty = `[rt]{ptr=nil,ct=0} elseif tk == stat then ok = `r.ok == true ................................................................................ body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { ptr = &hdrs[0], ct = [hdrs.type.N] }) body.title:free() body.body:free() end struct convo.page { title: pstring body: pstring class: pstring } terra convo:stdpage(pg: convo.page) var doc = data.view.docskel { instance = self.srv.cfg.instance; title = pg.title; body = pg.body; class = pg.class; navlinks = self.navbar; } var hdrs = array( lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } ) doc:send(self.con,200,[lib.mem.ptr(lib.http.header)] {ct = [hdrs.type.N], ptr = &hdrs[0]}) end -- CALL ONLY ONCE PER VAR terra convo:postv(name: rawstring) if self.varbuf.ptr == nil then self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) self.vbofs = self.varbuf.ptr end ................................................................................ lib.osclock.time(nil)) if aid ~= 0 then co.aid = aid end end ::nocookie::; end if co.aid ~= 0 then var sess, usr = co.srv:actor_session_fetch(co.aid, peer) if sess.ok == false then co.aid = 0 else co.who = usr.ptr co.who.rights.powers = server:actor_powers_fetch(co.who.id) end end var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free() var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1) var uri = uridec if urideclen == -1 then ................................................................................ return stats end terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) var cs: lib.store.credset cs:clear() var ok = false for i=0,self.sources.ct do var set, iok = self.sources(i):actor_auth_how(ip, usn) if iok then cs = cs + set ok = iok end end return cs, ok end terra srv:actor_auth_pw(ip: lib.store.inet, user: pstring, pw: pstring): uint64 for i=0,self.sources.ct do if self.sources(i).backend ~= nil and self.sources(i).backend.actor_auth_pw ~= nil then var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) if aid ~= 0 then if uid == 0 then lib.dbg('new user just logged in, creating account entry') var kbuf: uint8[lib.crypt.const.maxdersz] var newkp = lib.crypt.genkp() var privsz = lib.crypt.der(false,&newkp,&kbuf[0]) var na = lib.store.actor { id = 0; nym = nil; handle = newhnd.ptr; origin = 0; bio = nil; avatar = nil; knownsince = lib.osclock.time(nil); rights = lib.store.rights_default(); title = nil, key = [lib.mem.ptr(uint8)] { ptr = &kbuf[0], ct = privsz }; } var newuid: uint64 if self.sources(i).backend.actor_create ~= nil then newuid = self.sources(i):actor_create(&na) else newuid = self:actor_create(&na) end if self.sources(i).backend.actor_auth_register_uid ~= nil then self.sources(i):actor_auth_register_uid(aid,newuid) end end return aid end end end return 0 end --9twh8y94i5c1qqr7hxu20fyd terra cfgcache.methods.load :: {&cfgcache} -> {} terra cfgcache:init(o: &srv) self.overlord = o self:load() end srv.methods.start = terra(self: &srv, befile: rawstring) |
Modified static/style.scss from [1dafd96ed6] to [9520b92c84].
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
463
464
465
466
467
468
469
|
display: block;
position: relative;
min-height: calc(100vh - 1.1in);
margin-top: 0;
margin-bottom: 0;
padding: 0 0.4in;
padding-top: 1.1in;
background-color: adjust-color($color, $lightness: -45%, $alpha: 0.4);
border: {
left: 1px solid black;
right: 1px solid black;
}
}
................................................................................
background: linear-gradient(to left, tone(-55%,-0.5), transparent);
}
}
a[href].rawlink {
@extend %teletype;
}
|
>
>
>
>
>
>
>
|
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
...
464
465
466
467
468
469
470
471
472
473
474
475
476
|
display: block; position: relative; min-height: calc(100vh - 1.1in); margin-top: 0; margin-bottom: 0; padding: 0 0.4in; padding-top: 1.1in; padding-bottom: 0.1in; background-color: adjust-color($color, $lightness: -45%, $alpha: 0.4); border: { left: 1px solid black; right: 1px solid black; } } ................................................................................ background: linear-gradient(to left, tone(-55%,-0.5), transparent); } } a[href].rawlink { @extend %teletype; } body.doc main { @extend %serif; li { margin-top: 0.05in; } li:first-child { margin-top: 0; } } |
Modified store.t from [4782518865] to [5b8778f17f].
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 .. 61 62 63 64 65 66 67 68 69 70 71 72 73 74 ... 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
terra m.powerset:affect_users() return self.purge() or self.censor() or self.suspend() or self.elevate() or self.demote() or self.rebrand() or self.cred() end local str = rawstring --lib.mem.ptr(int8) struct m.source struct m.rights { rank: uint16 -- lower = more powerful except 0 = regular user -- creating staff automatically assigns rank immediately below you quota: uint32 -- # of allowed tweets per day; 0 = no limit ................................................................................ struct m.actor { id: uint64 nym: str handle: str origin: uint64 bio: str avatar: str knownsince: m.timepoint rights: m.rights key: lib.mem.ptr(uint8) -- ephemera xid: str ................................................................................ open: &m.source -> &opaque close: &m.source -> {} conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) conf_set: {&m.source, rawstring, rawstring} -> {} conf_reset: {&m.source, rawstring} -> {} actor_save: {&m.source, m.actor} -> bool actor_create: {&m.source, m.actor} -> bool actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor) actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) actor_enum: {&m.source} -> lib.mem.ptr(&m.actor) actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor) actor_stats: {&m.source, uint64} -> m.actor_stats actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool} -- returns a set of auth method categories that are available for a -- given user from a certain origin -- origin: inet -- username: rawstring actor_auth_otp: {&m.source, m.inet, rawstring, rawstring} -> uint64 actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } -> uint64 -- handles password-based logins against hashed passwords -- origin: inet -- handle: rawstring -- token: rawstring actor_auth_tls: {&m.source, m.inet, rawstring} -> uint64 -- handles implicit authentication performed as part of an TLS connection -- origin: inet -- fingerprint: rawstring actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 -- handles API authentication -- origin: inet -- handle: rawstring -- key: rawstring (X-API-Key) actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth) actor_session_fetch: {&m.source, uint64, m.inet} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)} -- retrieves an auth record + actor combo suitable by AID suitable -- for determining session validity & caps -- aid: uint64 -- origin: inet actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) actor_conf_int: cnf(intptr, lib.stat(intptr)) post_save: {&m.source, &m.post} -> {} post_create: {&m.source, &m.post} -> uint64 actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) |
| > > | | | > | > | > > > > > > > > |
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 .. 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ... 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
terra m.powerset:affect_users() return self.purge() or self.censor() or self.suspend() or self.elevate() or self.demote() or self.rebrand() or self.cred() end local str = rawstring local pstr = lib.mem.ptr(int8) struct m.source struct m.rights { rank: uint16 -- lower = more powerful except 0 = regular user -- creating staff automatically assigns rank immediately below you quota: uint32 -- # of allowed tweets per day; 0 = no limit ................................................................................ struct m.actor { id: uint64 nym: str handle: str origin: uint64 bio: str title: str avatar: str knownsince: m.timepoint rights: m.rights key: lib.mem.ptr(uint8) -- ephemera xid: str ................................................................................ open: &m.source -> &opaque close: &m.source -> {} conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) conf_set: {&m.source, rawstring, rawstring} -> {} conf_reset: {&m.source, rawstring} -> {} actor_save: {&m.source, &m.actor} -> bool actor_create: {&m.source, &m.actor} -> uint64 actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor) actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) actor_enum: {&m.source} -> lib.mem.ptr(&m.actor) actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor) actor_stats: {&m.source, uint64} -> m.actor_stats actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool} -- returns a set of auth method categories that are available for a -- given user from a certain origin -- origin: inet -- username: rawstring actor_auth_otp: {&m.source, m.inet, rawstring, rawstring} -> {uint64, uint64, pstr} actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } -> {uint64, uint64, pstr} -- handles password-based logins against hashed passwords -- origin: inet -- handle: rawstring -- token: rawstring actor_auth_tls: {&m.source, m.inet, rawstring} -> {uint64, uint64, pstr} -- handles implicit authentication performed as part of an TLS connection -- origin: inet -- fingerprint: rawstring actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 -> {uint64, uint64, pstr} -- handles API authentication -- origin: inet -- handle: rawstring -- key: rawstring (X-API-Key) actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth) actor_powers_fetch: {&m.source, uint64} -> m.powerset actor_session_fetch: {&m.source, uint64, m.inet} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)} -- retrieves an auth record + actor combo suitable by AID suitable -- for determining session validity & caps -- aid: uint64 -- origin: inet actor_auth_register_uid: {&m.source, uint64, uint64} -> {} -- notifies the backend module of the UID that has been assigned for -- an authentication ID -- aid: uint64 -- uid: uint64 actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) actor_conf_int: cnf(intptr, lib.stat(intptr)) post_save: {&m.source, &m.post} -> {} post_create: {&m.source, &m.post} -> uint64 actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) |
Modified str.t from [c5ea2451a4] to [5af1afba76].
1
2
3
4
5
6
7
8
9
10
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
-- vim: ft=terra -- string.t: string classes local util = dofile('common.lua') local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local m = { sz = terralib.externfunction('strlen', rawstring -> intptr); cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); ................................................................................ return `[lib.mem.ptr(int8)] {ptr = nil, ct = 0} end end) m.acc.methods.lpush = macro(function(self,str) return `self:push([str:asvalue()], [#(str:asvalue())]) end) m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) self:push(str.ptr, str.ct) return self end; m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) self:push(str.ptr, str.ct) str:free() return self end; m.acc.methods.compose = macro(function(self, ...) local minlen = 0 local pstrs = {} for i,v in ipairs{...} do |
|
>
>
|
1
2
3
4
5
6
7
8
9
10
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
-- vim: ft=terra -- string.t: string classes local util = lib.util local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local m = { sz = terralib.externfunction('strlen', rawstring -> intptr); cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); ................................................................................ return `[lib.mem.ptr(int8)] {ptr = nil, ct = 0} end end) m.acc.methods.lpush = macro(function(self,str) return `self:push([str:asvalue()], [#(str:asvalue())]) end) m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) self:push(str.ptr, str.ct) return self end; m.acc.methods.rpush = terra(self: &m.acc, str: lib.mem.ref(int8)) self:push(str.ptr, str.ct) return self end; m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) self:push(str.ptr, str.ct) str:free() return self end; m.acc.methods.compose = macro(function(self, ...) local minlen = 0 local pstrs = {} for i,v in ipairs{...} do |
Modified tpl.t from [3e776df34f] to [ba911a8ebc].
1
2
3
4
5
6
7
8
9
10
11
12
13
..
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
-- vim: ft=terra -- string template generator: -- returns a function that fills out a template -- with the strings given local util = dofile 'common.lua' local m = {} function m.mk(tplspec) local str if type(tplspec) == 'string' then str = tplspec tplspec = {} else str = tplspec.body end ................................................................................ tplchar_o = string.gsub(tplchar_o,'%%','%%%%') tplchar = string.gsub(tplchar, '.', function(c) if magic[c] then return '%' .. c end end) local last = 1 local fields = {} local segs = {} local constlen = 0 -- strip out all irrelevant whitespace to tidy things up -- TODO: find way to exclude <pre> tags? str = str:gsub('[\n^]%s+','') str = str:gsub('%s+[\n$]','') str = str:gsub('\n','') str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do if string.sub(str,start-1,start-1) ~= '\\' then segs[#segs+1] = string.sub(str,last,start-1) fields[#segs] = key last = stop end end segs[#segs+1] = string.sub(str,last) for i, s in ipairs(segs) do segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o) constlen = constlen + string.len(segs[i]) end local runningtally = symbol(intptr) local tallyup = {quote var [runningtally] = 1 + constlen end} local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or '')) local symself = symbol(&rec) |
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
..
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
-- vim: ft=terra -- string template generator: -- returns a function that fills out a template -- with the strings given local util = lib.util local m = {} function m.mk(tplspec) local str if type(tplspec) == 'string' then str = tplspec tplspec = {} else str = tplspec.body end ................................................................................ tplchar_o = string.gsub(tplchar_o,'%%','%%%%') tplchar = string.gsub(tplchar, '.', function(c) if magic[c] then return '%' .. c end end) local last = 1 local fields = {} local segs = {} local docs = {} local constlen = 0 -- strip out all irrelevant whitespace to tidy things up -- TODO: find way to exclude <pre> tags? str = str:gsub('[\n^]%s+','') str = str:gsub('%s+[\n$]','') str = str:gsub('\n','') str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed str = str:gsub(tplchar .. '%?([-%w]+)', function(file) if not docs[file] then docs[file] = data.doc[file] end return string.format('<a href="#help-%s" class="help">?</a>', file) end) for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do if string.sub(str,start-1,start-1) ~= '\\' then segs[#segs+1] = string.sub(str,last,start-1) fields[#segs] = key last = stop end end segs[#segs+1] = string.sub(str,last) for i, s in ipairs(segs) do segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o) constlen = constlen + string.len(segs[i]) end for n,d in pairs(docs) do local html = string.format( '<div id="help-%s" class="modal"> <a href="#0">close</a> <div>%s</div></div>', n, d.text ) segs[#segs] = segs[#segs] .. html constlen = constlen + #html end local runningtally = symbol(intptr) local tallyup = {quote var [runningtally] = 1 + constlen end} local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or '')) local symself = symbol(&rec) |
Modified view/compose.tpl from [09c6180294] to [bb642e2999].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<form class="compose" method="post"> <img src="/avi/@handle"> <textarea autofocus name="post" placeholder="it was a dark and stormy night…">@content</textarea> <input required type="text" name="acl" class="acl" value="@acl"> <a href="#aclhelp" class="help">?</a> <button type="submit">commit</button> </form> <div id="aclhelp" class="modal"> <a href="#0">close</a> <div> <p>to control who can see your post (and how far it is propagated), <code>parsav</code> uses <strong>ACL expressions</strong>. this is roughly equivalent to scoping in pleroma or GNU social terms. an ACL expression consists of one or more space-separated terms, each of which match a certain set of users. a term can be negated by prefixing it with <code>~</code>, a tilde character, so <code>~all</code> matches nobody, and <code>~followed</code> matches users you do not follow.</p> <ul> <li><strong>all</strong>: matches any and all users</li> <li><strong>local</strong>: matches users who belong to this instance</li> <li><strong>mutuals</strong>: matches users you follow who also follow you</li> <li><strong>followed</strong>: matches users you follow</li> <li><strong>followers</strong>: matches users who follow you</li> <li><strong>groupies</strong>: matches users who follow you, but whom you do not follow</li> <li><strong>mentioned</strong>: matches users who are mentioned in the post</li> <li><strong>staff</strong>: matches instance staff (equivalent to <code>~%0</code>)</li> <li><strong>admin</strong>: matches the individual named as the instance administrator, if any</li> <li><strong>\@</strong><em>handle</em>: matches the user <em>handle</em></li> <li><strong>+</strong><em>circle</em>: matches users you have categorized under <em>circle</em></li> <li><strong>#</strong><em>room</em>: matches users who are members of <em>room</em></li> <li><strong>%</strong><em>rank</em>: matches users of <em>rank</em> or higher (e.g. <code>%3</code> matches users of rank 3, 2, and 1). as a special case, <code>%0</code> matches ordinary users</li> <li><strong>#</strong><em>room</em><strong>%</strong><em>rank</em>: matches users who hold <em>rank</em> in <em>room</em></li> <li><strong><</strong><em>title</em><strong>></strong>: matches peers of the net who have been created <em>title</em> by the sovereign</li> <li><strong>#</strong><em>room</em><strong><</strong><em>title</em><strong>></strong>: matches peers of the chat who have been created <em>title</em> by <em>room</em> staff</li> </ul> <p>to evaluate an ACL expression, <code>parsav</code> reads each term from start to finish. for each term, it considers whether it describes the user who is attempting to access the content. if the term matches, its policy is applied and the expression completes. if the term doesn't match, the server proceeds on to the next term and the process repeats until it finds a matching term or runs out of terms, applying the fallback policy.</p> <p><strong>policy</strong> is whether a term grants or denies access. the default term policy is <strong>allow</strong>, but you can control the policy with the keywords <code>allow</code> and <code>deny</code>. if a term finishes evaluating without any match being found, a fallback policy is applied; this fallback is the opposite of whatever the current policy is. this sounds confusing but makes ACL expressions much more intuitive; <code>allow \@bob</code> and <code>deny trent</code> do exactly what you'd expect &em; the former allows bob and only bob in; the latter denies access only to trent, but grants access to the rest of the world.</p> <p>expressions must contain at least one term to be valid. if they consist only of policy keywords, they will be rejected.</p> <p>in effect, this all means that an ACL expression can be treated as a simple list of who is allowed to view your post. for instance, an expression of <code>local</code> means only local users can view it. however, much more complex expressions are possible.</p> <ul> <li><code>deny groupies allow +illuminati</code>: permits access to the illuminati, but excluding those members who are groupies</li> <li><code>+illuminati deny groupies</code>: allows access to everyone but groupies (unless they're in the illuminati)</li> <li><code>\@eve \@alice\@nowhere.tld deny \@bob \@trent\@witches.live</code>: grants access to eve and alice, but locks out bob and trent</li> <li><code><grand duke> #4th-intl<comrade></code>: restricts the post to the eyes of the Fourth International's secret cabal of anointed comrades and the grand dukes of the Empire</li> <li><code>deny ~%3</code>: blocks a post from being seen by anyone with a staff rank level below 3</li> </ul> <p><strong>limitations:</strong> to inhibit potential denial-of-service attacks, ACL expressions can be a maximum of 128 characters, can contain at most 16 words, and cannot trigger queries against other servers. all information needed to evaluate an ACL expression must be known locally. this is particularly relevant with respect to rooms.</p> </div></div> |
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
1 2 3 4 5 6 |
<form class="compose" method="post">
<img src="/avi/@handle">
<textarea autofocus name="post" placeholder="it was a dark and stormy night…">@content</textarea>
<input required type="text" name="acl" class="acl" value="@acl"> @?acl
<button type="submit">commit</button>
</form>
|