parsav  Check-in [26937ca853]

Overview
Comment:first steps towards litepub support
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 26937ca853a6373e44a47f9114b99bca0d1dd75b60969185903da51dc50bc273
User & Date: lexi on 2021-01-25 12:40:08
Other Links: manifest | tags
Context
2021-01-25
12:41
commit missing file check-in: bd5794c0cc user: lexi tags: trunk
12:40
first steps towards litepub support check-in: 26937ca853 user: lexi tags: trunk
2021-01-24
23:18
enable webfinger check-in: 64ae6724c2 user: lexi tags: trunk
Changes

Added api/lp/actor.t version [b336ed6430].















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local tpl = lib.tpl.mk {
	sigil = '%';
	body = [[{
		"@context": "https://%+domain/s/litepub.jsonld",
		"type": "Person",
		"id": "%lpid",
		"url": "https://%+domain/@%+handle",
		"preferredUsername": %$handle,
		"name": %$nym,
		"summary": %$desc,
		"alsoKnownAs": ["https://%+domain/@%+handle"],
		"publicKey": {
			"id": "%lpid#ident-rsa",
			"owner": "%lpid",
			"publicKeyPem": %rsa
		},
		"icon": {
			"type": "Image",
			"url": "https://%+domain%+avi"
		},
		"capabilities": { "acceptsChatMessages": false },
		"discoverable": true,
		"manuallyApprovedFollowers": %locked,
		"inbox": "https://%+domain/api/lp/inbox/user/%uid",
		"outbox": "https://%+domain/api/lp/outbox/user/%uid",
		"followers": "https://%+domain/api/lp/rel/%uid/followers",
		"following": "https://%+domain/api/lp/rel/%uid/following"
	}]];
}

local pstr = lib.str.t
terra cs(s: rawstring) return pstr {s, lib.str.sz(s)} end

local terra 
api_lp_actor(co: &lib.srv.convo, actor: &lib.store.actor)
	var lpid = co:stra(64)
	lpid:lpush'https://':ppush(co.srv.cfg.domain):lpush'/user/':shpush(actor.id)
	var uid = co:stra(32) uid:shpush(actor.id) -- dumb hack bc lazy FIXME

	var body = tpl {
		domain = co.srv.cfg.domain;
		uid = uid:finalize();
		lpid = lpid:finalize();
		handle = cs(actor.handle);
		nym = cs(actor.nym);
		desc = cs(actor.bio);
		avi = cs(actor.avatar);
		rsa = '';
		locked = 'false';
	}

	co:json(body:poolstr(&co.srv.pool))
end
return api_lp_actor

Added api/lp/outbox.t version [7ed4c4752c].



























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local pstr = lib.str.t

local terra 
lp_outbox(co: &lib.srv.convo, uri: pstr, here: lib.mem.ptr(lib.str.ref))
	var path = lib.str.qesc(&co.srv.pool,uri,false)
	var json = co:stra(512)
	json:lpush '{"@context": "https://':ppush(co.srv.cfg.domain):lpush'/s/litepub.jsonld","id":"https://'
	    :ppush(co.srv.cfg.domain):ppush(path)
		:lpush '"'
	var at = co:pgetv('at')
	lib.dbg('api path ',
	{here(0).ptr,here(0).ct}, ' / ',
	{here(1).ptr,here(1).ct})
	json:lpush',"current":"https://':ppush(co.srv.cfg.domain):ppush(path):lpush'?at=top"'
	if not at then
		json:lpush',"type":"OrderedCollection","first":"https://':ppush(co.srv.cfg.domain):ppush(path):lpush'?at=top"'
	else
		if here(0):cmp 'user' and here.ct > 1 then
			var uid, uidok = lib.math.shorthand.parse(here(1).ptr, here(1).ct)
			if not uidok then goto e404 end
			var user = co.srv:actor_fetch_uid(uid)
			if not user then goto e404 end
			var time: lib.store.timepoint
			if at:cmp('top') then
				time = lib.osclock.time(nil)
			else
				var tp, ok = lib.math.decparse(at)
				if ok then time = tp end
			end
			lib.io.fmt('from-time: %llu\n', time)
			var posts = co.srv:post_enum_author_uid(uid, lib.store.range {
				mode = 1; -- time -> idx
				from_time = time;
				to_idx = 65;
			})
			var oldest = time
			json:lpush',"partOf":"https://':ppush(co.srv.cfg.domain):ppush(path)
			    :lpush'","type":"CollectionPage","orderedItems":['
			if posts.sz > 0 then defer posts:free()
				for i=0, lib.math.smallest(posts.sz,64) do
					if i~=0 then json:lpush',' end
					json:ppush(lib.api.lp.tweet(co,posts(i).ptr,true))
					oldest = lib.math.smallest(posts(i)().posted, oldest)
				end
			end
			json:lpush'],"totalItems":':ipush(posts.sz)
			if oldest ~= time and oldest > 0 and posts.sz > 64 then
				json:lpush',"next":"https://':ppush(co.srv.cfg.domain):ppush(path)
				    :lpush'?at=':ipush(oldest-1):lpush'"'
			end

		else goto e404 end -- TODO
	end
	json:lpush[[}]]
	co:json(json:finalize())
	do return end
	::e404:: do co:fail(404) return end
end

return lp_outbox

Added api/lp/tweet.t version [e0805dcd2f].





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local pstr = lib.str.t

local obj = lib.tpl.mk [[{
	"\@context": "https://@+domain/s/litepub.jsonld",
	"type": "Note",
	"id": "https://@+domain/post/@^pid",
	"content": @$html,
	"source": @$raw,
	"attributedTo": "https://@+domain/user/@^uid",
	"published": "@pubtime"
	@extra
}]]

local wrap = lib.tpl.mk [[{
	"\@context": "https://@+domain/s/litepub.jsonld",
	"type": "@kind",
	"actor": "https://@+domain/user/@^uid",
	"published": "@$pubtime",
	"id": "https://@+domain/api/lp/act/@^aid",
	"object": @obj
}]]

local terra 
lp_tweet(co: &lib.srv.convo, p: &lib.store.post, act_wrap: bool)
	
	var tweet = (obj {
		domain = co.srv.cfg.domain, uid = p.author, pid = p.id;
		html = lib.smackdown.html(&co.srv.pool, p.body, false);
		raw = p.body, pubtime = '', extra = '';
	}):poolstr(&co.srv.pool)

	if act_wrap then
		return (wrap {
			domain = co.srv.cfg.domain;
			kind = lib.trn(p.rtdby == 0, 'Create', 'Announce');
			uid = lib.trn(p.rtdby == 0, p.author, p.rtdby);
			aid = lib.trn(p.rtdby == 0, p.id, p.rtact);
			pubtime = '', obj = tweet;
		}):poolstr(&co.srv.pool)
	else
		return tweet
	end
end

return lp_tweet

Added api/webfinger.t version [c64d390bdc].

































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
wftpl = lib.tpl.mk [[{
	"subject": @$subj,
	"aliases": [ @$href, @$pfp ],
	"links": [
		{ "rel": "self", "type": "application/activity+json", "href": @$href },
		{ "rel": "self",
			"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "href": @$href },
		{ "rel": "http://webfinger.net/rel/profile-page",
			"type": "text/html", "href": @$pfp }
	]
}]]

local terra 
webfinger(co: &lib.srv.convo)
	var res = co:pgetv('resource')
	if (not res) or not res:startswith 'acct:' then goto err end
	
	var acct = res + 5
	var svp = lib.str.find(acct, '@')
	if svp:ref() then
		acct.ct = (svp.ptr - acct.ptr)
		svp:advance(1)
		if not svp:cmp(co.srv.cfg.domain) then goto err end
	end

	var actor = co.srv:actor_fetch_xid(acct)
	if not actor then goto err end
	do defer actor:free()
		if actor().origin ~= 0 then goto err end
		var href = co:stra(64)
		var pfp = co:stra(64)
		href:lpush'https://':ppush(co.srv.cfg.domain):lpush'/user/':shpush(actor().id)
		pfp:lpush'https://':ppush(co.srv.cfg.domain):lpush'/@':ppush(acct)

		var tp = wftpl {
			subj = res;
			href = href:finalize();
			pfp = pfp:finalize();
		}
		co:json(tp:poolstr(&co.srv.pool))

		return
	end
	-- error conditions
	::err:: do co:json('{}') return end
end
return webfinger

Modified config.lua from [74b801cbf1] to [f9545a425d].

62
63
64
65
66
67
68


69
70
71
72
73
74
75
		{'icon.svg', 'image/svg+xml'};
		{'padlock.svg', 'image/svg+xml'};
		{'warn.svg', 'image/svg+xml'};
		{'query.webp', 'image/webp'};
		{'reply.webp', 'image/webp'};
		{'file.webp', 'image/webp'};
		{'follow.webp', 'image/webp'};


		-- keep in mind before you add anything to this list: these are not
		-- just files parsav can access, they are files that are *kept in
		-- memory* for fast access the entire time parsav is running, and
		-- which need to be loaded into memory before the program can even
		-- start. it's imperative to keep these as small and few in number
		-- as is realistically possible.
	};







>
>







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
		{'icon.svg', 'image/svg+xml'};
		{'padlock.svg', 'image/svg+xml'};
		{'warn.svg', 'image/svg+xml'};
		{'query.webp', 'image/webp'};
		{'reply.webp', 'image/webp'};
		{'file.webp', 'image/webp'};
		{'follow.webp', 'image/webp'};

		{'litepub.jsonld', 'application/ld+json; charset=utf-8'};
		-- keep in mind before you add anything to this list: these are not
		-- just files parsav can access, they are files that are *kept in
		-- memory* for fast access the entire time parsav is running, and
		-- which need to be loaded into memory before the program can even
		-- start. it's imperative to keep these as small and few in number
		-- as is realistically possible.
	};

Modified math.t from [12858be43d] to [60b64e2aea].

1
2
3
4
5
6
7

8
9
10
11
12
13
14
..
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
-- vim: ft=terra
local m = {
	shorthand = {maxlen = 14};
	ll = {
		ctpop_u8 = terralib.intrinsic('llvm.ctpop.i8', uint8 -> uint8);
	};
}


local pstring = lib.mem.ptr(int8)

-- swap in place -- faster on little endian
m.netswap_ip = macro(function(ty, src, dest)
	if ty:astype().type ~= 'integer' then error('bad type') end
	local bytes = ty:astype().bytes
................................................................................
	var o = n
	for i=0,fac do n = n * o end
	return n
end

terra m.shorthand.gen(val: uint64, dest: rawstring): ptrdiff
	var lst = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ:abcdefghijklmnopqrstuvwxyz"
	var buf: int8[m.shorthand.maxlen]
	var ptr = [&int8](buf)
	while val ~= 0 do
		var v = val % 64
		@ptr = lst[v]
		ptr = ptr + 1
		val = val / 64
	end



<
<
<

>







 







|







1
2
3



4
5
6
7
8
9
10
11
12
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
-- vim: ft=terra
local m = {
	shorthand = {maxlen = 14};



}
m.shorthand.t = int8[m.shorthand.maxlen]

local pstring = lib.mem.ptr(int8)

-- swap in place -- faster on little endian
m.netswap_ip = macro(function(ty, src, dest)
	if ty:astype().type ~= 'integer' then error('bad type') end
	local bytes = ty:astype().bytes
................................................................................
	var o = n
	for i=0,fac do n = n * o end
	return n
end

terra m.shorthand.gen(val: uint64, dest: rawstring): ptrdiff
	var lst = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ:abcdefghijklmnopqrstuvwxyz"
	var buf: m.shorthand.t
	var ptr = [&int8](buf)
	while val ~= 0 do
		var v = val % 64
		@ptr = lst[v]
		ptr = ptr + 1
		val = val / 64
	end

Modified parsav.t from [dd405c9e82] to [1d36a792dc].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
106
107
108
109
110
111
112

113
114
115
116
117
118
119
...
496
497
498
499
500
501
502





503
504
505
506
507
508
509
			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
				tgt = tgt[path[i]]
			end
			local chunk = terralib.loadfile(l:gsub(':','/') .. '.t')
			if chunk ~= nil then
				tgt[path[#path]:gsub('-','_')] = chunk()
				print(' \27[1m[ \27[32mok\27[;1m ]\27[m')
			else
				print(' \27[1m[\27[31mfail\27[;1m]\27[m')
				os.exit(2)
			end
		end
................................................................................
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end
	end);

	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty
		if ty.ptr_basetype then empty = `[ty]{ptr=nil,ct=0}
		elseif ty.type == 'integer' then empty = `0
................................................................................

	'render:conf:profile';
	'render:conf:circles';
	'render:conf:sec';
	'render:conf:users';
	'render:conf:avi';
	'render:conf';





	'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







|







 







>







 







>
>
>
>
>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
			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
				tgt = tgt[path[i]]
			end
			local chunk = terralib.loadfile(l:gsub(':','/') .. '.t')
			if chunk ~= nil then 
				tgt[path[#path]:gsub('-','_')] = chunk()
				print(' \27[1m[ \27[32mok\27[;1m ]\27[m')
			else
				print(' \27[1m[\27[31mfail\27[;1m]\27[m')
				os.exit(2)
			end
		end
................................................................................
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end
	end);
	typeof = macro(function(exp) return exp.tree.type end);
	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty
		if ty.ptr_basetype then empty = `[ty]{ptr=nil,ct=0}
		elseif ty.type == 'integer' then empty = `0
................................................................................

	'render:conf:profile';
	'render:conf:circles';
	'render:conf:sec';
	'render:conf:users';
	'render:conf:avi';
	'render:conf';

	'api:lp:actor';
	'api:lp:tweet';
	'api:lp:outbox';
	'api:webfinger';
	'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

Modified render/profile.t from [c63a8aaea6] to [74b4c77645].

195
196
197
198
199
200
201

202
203
204
205
206
207
208
...
215
216
217
218
219
220
221

222
223
224
225
226
227
228
...
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
	end

	if relationship.recip.follow() then
		comments:lpush('<li style="--co:30">follows you</li>')
	end

	var circpanel: lib.str.acc

	if co.aid ~= 0 then
		circpanel = co:stra(128)
		var allcircs = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
		if allcircs:ref() then
			var mycircs = co.srv:circle_memberships_uid(&co.srv.pool, co.who.id, actor.id)
			for i=0, allcircs.ct do
				circpanel:lpush '<label><input type="checkbox" name="circle" value="'
................................................................................
					end
				end
				circpanel:lpush '> '
						 :ppush(allcircs(i).name)
						 :lpush '</label>'
			end
		end

	end

	var profile = data.view.profile {
		nym = fullname;
		bio = bio;
		xid = cs(actor.xid);
		avatar = cs(actor.avatar);
................................................................................
		nfollowers = sn_followers, nmutuals = sn_mutuals;
		tweetday = cs(timestr);
		timephrase = lib.trn(actor.origin == 0, pstr 'joined', pstr 'known since');

		remarks = '';

		auxbtn = auxp;
		circles = circpanel:finalize();
		relations = relbtns:finalize();
		sanctions = sancbtns:finalize();
	}
	if comments.sz > 0 then profile.remarks = comments:finalize() end

	var ret = profile:poolstr(&co.srv.pool)
	-- auxp:free() 
	--if actor.bio ~= nil then bio:free() end
	--if comments.sz > 0 then profile.remarks:free() end
	return ret
end

return render_profile







>







 







>







 







|













195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
	end

	if relationship.recip.follow() then
		comments:lpush('<li style="--co:30">follows you</li>')
	end

	var circpanel: lib.str.acc
	var circstr = pstr.null()
	if co.aid ~= 0 then
		circpanel = co:stra(128)
		var allcircs = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
		if allcircs:ref() then
			var mycircs = co.srv:circle_memberships_uid(&co.srv.pool, co.who.id, actor.id)
			for i=0, allcircs.ct do
				circpanel:lpush '<label><input type="checkbox" name="circle" value="'
................................................................................
					end
				end
				circpanel:lpush '> '
						 :ppush(allcircs(i).name)
						 :lpush '</label>'
			end
		end
		circstr = circpanel:finalize()
	end

	var profile = data.view.profile {
		nym = fullname;
		bio = bio;
		xid = cs(actor.xid);
		avatar = cs(actor.avatar);
................................................................................
		nfollowers = sn_followers, nmutuals = sn_mutuals;
		tweetday = cs(timestr);
		timephrase = lib.trn(actor.origin == 0, pstr 'joined', pstr 'known since');

		remarks = '';

		auxbtn = auxp;
		circles = circstr;
		relations = relbtns:finalize();
		sanctions = sancbtns:finalize();
	}
	if comments.sz > 0 then profile.remarks = comments:finalize() end

	var ret = profile:poolstr(&co.srv.pool)
	-- auxp:free() 
	--if actor.bio ~= nil then bio:free() end
	--if comments.sz > 0 then profile.remarks:free() end
	return ret
end

return render_profile

Modified route.t from [325df84c8e] to [db07af2616].

93
94
95
96
97
98
99








100
101
102
103
104
105
106
107
...
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
...
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
...
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
...
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
...
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968

969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
...
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010

1011
1012









1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028

1029
	if not go or go(0) ~= @'/' then
		lib.render.user_page(co, actor, &rel)
	else
		co:reroute(go.ptr)
	end
end









terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
	for i=2,uri.ct do
		if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
	end
	if handle.ct == 0 then
		handle.ct = uri.ct - 2
		uri:advance(uri.ct)
................................................................................
	var actor = co.srv:actor_fetch_xid(handle)
	if actor.ptr == nil then
		co:complain(404,'no such user','no such user known to this server')
		return
	end
	defer actor:free()

	http.actor_profile(co,actor.ptr,meth)
end

terra http.actor_profile_uid (
	co: &lib.srv.convo,
	path: lib.mem.ptr(lib.mem.ref(int8)),
	meth: method.t
)
	if path.ct < 2 then
		co:complain(404,'bad url','invalid user url')
		return
	end

	var uid, ok = lib.math.shorthand.parse(path.ptr[1].ptr, path.ptr[1].ct)
................................................................................
	var actor = co.srv:actor_fetch_uid(uid)
	if actor.ptr == nil then
		co:complain(404, 'no such user', 'no user by that ID is known to this instance')
		return
	end
	defer actor:free()

	http.actor_profile(co,actor.ptr,meth)
end

terra http.login_form(co: &lib.srv.convo, meth: method.t)
	if meth_get(meth) then
		-- request a username
		lib.render.login(co, nil, nil, pstring.null())
	elseif meth == method.post then
................................................................................
				lib.render.login(co, nil, nil,  'authentication failure')
			else
				co:installkey('/',aid)
			end
		end
		if act.ptr ~= nil and fakeact == false then act:free() end
	else
		::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
	end
	return
end

terra http.post_compose(co: &lib.srv.convo, meth: method.t)
	if not co:assertpow('post') then return end
	--if co.who.rights.powers.post() == false then
................................................................................
	if meth_get(meth) then
		lib.render.compose(co, nil, nil)
	elseif meth == method.post then
		var text, textlen = co:postv("post")
		var acl, acllen = co:postv("acl")
		var subj, subjlen = co:postv("subject")
		if text == nil or acl == nil then
			co:complain(405, 'invalid post', 'every post must have at least body text and an ACL')
			return
		end
		if subj == nil then subj = '' end

		var p = lib.store.post {
			author = co.who.id, acl = acl;
			body = text, subject = subj;
................................................................................
	end

	if not post then goto badurl end

	lib.render.tweet_page(co, path, post.ptr)
	do return end

	::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end
	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end

local terra 
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
	var act = co:ppostv('act')
	if not act then return true end
	lib.dbg('handling credential action')
................................................................................
	do defer data:free() defer mime:free()
		co:bytestream(mime,data)
	return end

	::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end
end

local json = {}

do wftpl = lib.tpl.mk [[{
		"subject": @$subj,
		"links": [
			{ "rel": "self", "type": "application/ld+json", "href": @$href }
		]
	}]]
	terra json.webfinger(co: &lib.srv.convo)
		var res = co:pgetv('resource')
		if (not res) or not res:startswith 'acct:' then goto err end
		
		-- technically we should look this user up in the database to make sure
		-- they actually exist, buuut that's costly and i doubt that's actually
		-- necessary for webfinger to do its job. so we cheat and just do string
		-- munging so lookups are as cheap as possible. TODO make sure this works
		-- in practice and doesn't cause any weird security problems
		var acct = res + 5
		var svp = lib.str.find(acct, '@')
		if svp:ref() then
			acct.ct = (svp.ptr - acct.ptr)
			svp:advance(1)
			if not svp:cmp(co.srv.cfg.domain) then goto err end
		end
		var tp = wftpl {
			subj = res;
			href = co:qstr('https://', co.srv.cfg.domain, '/@', acct);
		}
		co:json(tp:poolstr(&co.srv.pool))

		do return end -- error conditions
		::err:: do co:json('{}') return end
	end
end

-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
	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 == nil or uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
	elseif uri.ct == 1 then -- root
		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else http.timeline(co, hpath {ptr=nil,ct=0}) end
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
		if not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
	elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
................................................................................
		if co.aid == 0
			then goto notfound
			else co:reroute_cookie('/','auth=; Path=/')
		end
	else -- hierarchical routes
		var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
		if path.ct > 1 and path(0):cmp('user') then
			http.actor_profile_uid(co, path, meth)
		elseif path.ct > 1 and path(0):cmp('post') then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp('tl') then
			http.timeline(co, path)
		elseif path(0):cmp('.well-known') then
			if path(1):cmp('webfinger') then

				json.webfinger(co)
			end









		elseif path(0):cmp('media') then
			if co.aid == 0 then goto unauth end
			http.media_manager(co, path, meth, co.who.id)
		elseif path(0):cmp('doc') then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp('conf') then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)
		else goto notfound end
	end
	do 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
	::unauth:: co:complain(401, 'unauthorized', 'this content is not available at your clearance level') do return end

end







>
>
>
>
>
>
>
>
|







 







|




|
<







 







|







 







|







 







|







 







|
|
|







 







<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|




>








|







 







|






>
|

>
>
>
>
>
>
>
>
>













|
|
|
>

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
...
122
123
124
125
126
127
128
129
130
131
132
133
134

135
136
137
138
139
140
141
...
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
...
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
...
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
...
928
929
930
931
932
933
934

935

































936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
...
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
	if not go or go(0) ~= @'/' then
		lib.render.user_page(co, actor, &rel)
	else
		co:reroute(go.ptr)
	end
end

terra http.actor_dispatch_mime(co: &lib.srv.convo, actor: &lib.store.actor)
	if co:matchmime(lib.http.mime.html) then
		http.actor_profile(co,actor,co.method)
	elseif co:matchmime(lib.http.mime.json) then
		lib.api.lp.actor(co, actor)
	else co:fail(406) end
end

terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8))
	var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
	for i=2,uri.ct do
		if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
	end
	if handle.ct == 0 then
		handle.ct = uri.ct - 2
		uri:advance(uri.ct)
................................................................................
	var actor = co.srv:actor_fetch_xid(handle)
	if actor.ptr == nil then
		co:complain(404,'no such user','no such user known to this server')
		return
	end
	defer actor:free()

	http.actor_dispatch_mime(co, actor.ptr)
end

terra http.actor_profile_uid (
	co: &lib.srv.convo,
	path: lib.mem.ptr(lib.mem.ref(int8))

)
	if path.ct < 2 then
		co:complain(404,'bad url','invalid user url')
		return
	end

	var uid, ok = lib.math.shorthand.parse(path.ptr[1].ptr, path.ptr[1].ct)
................................................................................
	var actor = co.srv:actor_fetch_uid(uid)
	if actor.ptr == nil then
		co:complain(404, 'no such user', 'no user by that ID is known to this instance')
		return
	end
	defer actor:free()

	http.actor_dispatch_mime(co, actor.ptr)
end

terra http.login_form(co: &lib.srv.convo, meth: method.t)
	if meth_get(meth) then
		-- request a username
		lib.render.login(co, nil, nil, pstring.null())
	elseif meth == method.post then
................................................................................
				lib.render.login(co, nil, nil,  'authentication failure')
			else
				co:installkey('/',aid)
			end
		end
		if act.ptr ~= nil and fakeact == false then act:free() end
	else
		::wrongmeth:: co:fail(405) do return end
	end
	return
end

terra http.post_compose(co: &lib.srv.convo, meth: method.t)
	if not co:assertpow('post') then return end
	--if co.who.rights.powers.post() == false then
................................................................................
	if meth_get(meth) then
		lib.render.compose(co, nil, nil)
	elseif meth == method.post then
		var text, textlen = co:postv("post")
		var acl, acllen = co:postv("acl")
		var subj, subjlen = co:postv("subject")
		if text == nil or acl == nil then
			co:complain(400, 'invalid post', 'every post must have at least body text and an ACL')
			return
		end
		if subj == nil then subj = '' end

		var p = lib.store.post {
			author = co.who.id, acl = acl;
			body = text, subject = subj;
................................................................................
	end

	if not post then goto badurl end

	lib.render.tweet_page(co, path, post.ptr)
	do return end

	::noauth:: do co:fail(401) return end
	::badurl:: do co:fail(404) return end
	::badop :: do co:fail(405) return end
end

local terra 
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
	var act = co:ppostv('act')
	if not act then return true end
	lib.dbg('handling credential action')
................................................................................
	do defer data:free() defer mime:free()
		co:bytestream(mime,data)
	return end

	::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end
end




































-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8))
	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
	var meth = co.method -- TODO unfuck this legacy bat shit
	if uri.ptr == nil or uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
	elseif uri.ct == 1 then -- root
		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else http.timeline(co, hpath {ptr=nil,ct=0}) end
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri)
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
		if not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
	elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
................................................................................
		if co.aid == 0
			then goto notfound
			else co:reroute_cookie('/','auth=; Path=/')
		end
	else -- hierarchical routes
		var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
		if path.ct > 1 and path(0):cmp('user') then
			http.actor_profile_uid(co, path)
		elseif path.ct > 1 and path(0):cmp('post') then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp('tl') then
			http.timeline(co, path)
		elseif path(0):cmp('.well-known') then
			if path(1):cmp('webfinger') then
				if not co:matchmime(lib.http.mime.json) then goto nacc end
				lib.api.webfinger(co)
			end
		elseif path(0):cmp('api') then
			if path(1):cmp('parsav') then -- native API
			elseif path(1):cmp('v1') then -- mastodon client api :/
			elseif path(1):cmp('lp') then -- litepub endpoints
				if path(2):cmp('outbox') then
					lib.api.lp.outbox(co,uri,path + 3)
				elseif path(2):cmp('inbox') then
				end
			else goto notfound end
		elseif path(0):cmp('media') then
			if co.aid == 0 then goto unauth end
			http.media_manager(co, path, meth, co.who.id)
		elseif path(0):cmp('doc') then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp('conf') then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)
		else goto notfound end
	end
	do return end

	::wrongmeth:: co:fail(405) do return end
	::nacc     :: co:fail(406) do return end
	::notfound :: co:fail(404) do return end
	::unauth   :: co:fail(401) do return end
end

Modified srv.t from [557555fd0b] to [68c9cc33d4].

1
2
3
4










5
6
7
8
9
10
11
...
178
179
180
181
182
183
184





185
186
187
188
189
190
191
...
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
...
370
371
372
373
374
375
376











377
378
379


380
381
382
383
384
385
386
387
388
389


































390
391
392





















393
394
395
396
397
398
399
...
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
...
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
...
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
-- 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: pstring
	pol_sec: secmode.t
	pol_reg: bool
	pol_autoherald: bool
	credmgd: bool
................................................................................

local usrdefs = {
	str = {
		['acl-follow'    ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'};
		['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'};
	};
}






terra convo:usercfg_str(uid: uint64, setting: pstring): pstring
	var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting)
	if not set then
		[(function()
			local q = quote return pstring.null() end
			for key, dfl in pairs(usrdefs.str) do
................................................................................
	if not lockdown then lockhdr = "" end
	lib.net.mg_printf(self.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n%sX-Content-Options: nosniff\r\n\r\n", mime.ct, mime.ptr, data.ct + 2, lockhdr)
	lib.net.mg_send(self.con, data.ptr, data.ct)
	lib.net.mg_send(self.con, '\r\n', 2)
end

terra convo:json(data: pstring)
	self:bytestream_trusted(false, 'application/ld+json', data:blob())
end

terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8))
	-- TODO this is not a satisfactory solution; it's a bandaid on a gaping
	-- chest wound. ultimately we need to compile a whitelist of safe mime
	-- types as part of mimelib, but that is no small task. for now, this
	-- will keep the patient from immediately bleeding out
................................................................................
		p = p + lib.session.cookie_gen(self.srv.cfg.secret, aid, lib.osclock.time(nil), p)
		lib.dbg('sending cookie ',{&sesskey[0],15})
		p = lib.str.ncpy(p, '; Path=/', 9)
	end
	self:reroute_cookie(dest, &sesskey[0])
end
 











terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
	if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end



	var ti: lib.str.acc ti:compose('error :: ', title)
	var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.svg"><h1>',title,'</h1><p>',msg,'</p></div>')
	var body = [convo.page] {
		title = ti:finalize();
		body = bo:finalize();
		class = 'error';
		cache = false;
	}

	self:statpage(code, body)



































	body.title:free()
	body.body:free()





















end

terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
	var conf = data.view.confirm {
		title = title;
		query = msg;
		cancel = cancel;
................................................................................
		class = 'query';
		body = body; cache = false;
	}
	self:stdpage(cf)
	--cf.title:free()
end

terra convo:stra(sz: intptr) -- convenience function
	var s: lib.str.acc
	s:pool(&self.srv.pool,sz)
	return s
end

convo.methods.qstr = macro(function(self, ...) -- convenience string builder
	local exp = {...}
	return `lib.str.acc{}:pcompose(&self.srv.pool, [exp]):finalize()
end)

convo.methods.assertpow = macro(function(self, pow)
	return quote
		var ok = true
		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
			ok = false
			self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
		end
................................................................................
end
terra convo:pgetv(name: rawstring)
	var s,l = self:getv(name)
	return pstring { ptr = s, ct = l }
end

local route = {} -- these are defined in route.t, as they need access to renderers
terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {}

local mimetypes = {
	{'html', 'text/html'};
	{'json', 'application/json'};
	{'json', 'application/ld+json'};
	{'json', 'application/activity+json'};
	{'mkdown', 'text/markdown'};
	{'text', 'text/plain'};
	{'ansi', 'text/x-ansi'};
}

local mimevar = symbol(lib.mem.ref(int8))
local mimeneg = `lib.http.mime.none

for i, t in ipairs(mimetypes) do
	local name, mime = t[1], t[2]
	mimeneg = quote
................................................................................
							end
							bsr:free()
							upmap:free()
						end
					end
				end

				route.dispatch_http(&co, uri, co.method)

				::fail::
				if co.uploads.run > 0 then
					for i=0,co.uploads.sz do
						co.uploads(i).filename:free()
						co.uploads(i).field:free()
					end




>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>







 







|







 







>
>
>
>
>
>
>
>
>
>
>



>
>
|
|
<
<
<
|
|
|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<







 







|
<
<
<
<
<
<
<
<
<
<







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
...
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409



410
411
412
413
414
415
416
417
418
419
420
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
449


450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
...
483
484
485
486
487
488
489











490
491
492
493
494
495
496
...
571
572
573
574
575
576
577
578










579
580
581
582
583
584
585
...
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
-- vim: ft=terra
local util = lib.util
local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' }
local pstring = lib.mem.ptr(int8)
local mimetypes = {
	{'html', 'text/html'};
	{'json', 'application/json'};
	{'json', 'application/activity+json'};
	{'json', 'application/ld+json'};
	{'mkdown', 'text/markdown'};
	{'text', 'text/plain'};
	{'ansi', 'text/x-ansi'};
}

local struct srv
local struct cfgcache {
	secret: pstring
	pol_sec: secmode.t
	pol_reg: bool
	pol_autoherald: bool
	credmgd: bool
................................................................................

local usrdefs = {
	str = {
		['acl-follow'    ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'};
		['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'};
	};
}

terra convo:matchmime(mime: lib.http.mime.t): bool
	return self.reqtype == [lib.http.mime.none]
		or self.reqtype == mime
end

terra convo:usercfg_str(uid: uint64, setting: pstring): pstring
	var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting)
	if not set then
		[(function()
			local q = quote return pstring.null() end
			for key, dfl in pairs(usrdefs.str) do
................................................................................
	if not lockdown then lockhdr = "" end
	lib.net.mg_printf(self.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n%sX-Content-Options: nosniff\r\n\r\n", mime.ct, mime.ptr, data.ct + 2, lockhdr)
	lib.net.mg_send(self.con, data.ptr, data.ct)
	lib.net.mg_send(self.con, '\r\n', 2)
end

terra convo:json(data: pstring)
	self:bytestream_trusted(false, 'application/activity+json; charset=utf-8', data:blob())
end

terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8))
	-- TODO this is not a satisfactory solution; it's a bandaid on a gaping
	-- chest wound. ultimately we need to compile a whitelist of safe mime
	-- types as part of mimelib, but that is no small task. for now, this
	-- will keep the patient from immediately bleeding out
................................................................................
		p = p + lib.session.cookie_gen(self.srv.cfg.secret, aid, lib.osclock.time(nil), p)
		lib.dbg('sending cookie ',{&sesskey[0],15})
		p = lib.str.ncpy(p, '; Path=/', 9)
	end
	self:reroute_cookie(dest, &sesskey[0])
end
 
terra convo:stra(sz: intptr) -- convenience function
	var s: lib.str.acc
	s:pool(&self.srv.pool,sz)
	return s
end

convo.methods.qstr = macro(function(self, ...) -- convenience string builder
	local exp = {...}
	return `lib.str.acc{}:pcompose(&self.srv.pool, [exp]):finalize()
end)

terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
	if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end

	if self:matchmime(lib.http.mime.html) then
		var body = [convo.page] {
			title = self:qstr('error :: ', title);
			body = self:qstr('<div class="message"><img class="icon" src="/s/warn.svg"><h1>',title,'</h1><p>',msg,'</p></div>');



			class = 'error';
			cache = false;
		}

		self:statpage(code, body)
	else
		var pg = lib.http.page { respcode = code, body = pstring.null() }
		var ctt = lib.http.mime.none
		if self:matchmime(lib.http.mime.json) then ctt = lib.http.mime.json
			pg.body = ([lib.tpl.mk'{"_parsav_error":@$ekind, "_parsav_error_desc":@$edesc}']
				{ekind = title, edesc = msg}):poolstr(&self.srv.pool)
		elseif self:matchmime(lib.http.mime.text) then ctt = lib.http.mime.text
			pg.body = self:qstr('error: ',title,'\n',msg)
		elseif self:matchmime(lib.http.mime.mkdown) then ctt = lib.http.mime.mkdown
			pg.body = self:qstr('# error :: ',title,'\n\n',msg)
		elseif self:matchmime(lib.http.mime.ansi) then ctt = lib.http.mime.ansi
			pg.body = self:qstr('\27[1;31merror :: ',title,'\27[m\n',msg)
		end
		var cthdr = lib.http.header { 'Content-Type', 'text/plain' }
		if ctt == lib.http.mime.none then
			pg.headers.ct = 0
		else
			pg.headers = lib.typeof(pg.headers) { &cthdr, 1 }
			switch ctt do
				case [ctt.type](lib.http.mime.json) then
					cthdr.value = 'application/json'
				end
				escape
					for i,v in ipairs(mimetypes) do local key,mime = v[1],v[2]
						if key ~= 'json' then
							emit quote case [ctt.type](lib.http.mime.[key]) then cthdr.value = [mime] end end
						end
					end
				end
			end
		end
		pg:send(self.con)
	end
end



terra convo:fail(code: uint16)
	switch code do
		escape
			local stderrors = {
				{400, 'bad request', "the action you have attempted on this resource is not meaningful"};
				{401, 'unauthorized', "this resource is not available at your clearance level"};
				{403, 'forbidden', "we can neither confirm nor deny the existence of this resource"};
				{404, 'resource not found', "that resource is not extant on or known to this server"};
				{405, 'method not allowed', "the method you have attempted on this resource is not meaningful"};
				{406, 'not acceptable', "none of the suggested content types are a viable representation of this resource"};
				{500, 'internal server error', "parsav did a fucksy wucksy"};
			}

			for i,v in ipairs(stderrors) do
				emit quote case uint16([v[1]]) then
					self:complain([v])
				end end
			end
		end
		else self:complain(500,'unknown error','an unrecognized error was thrown. this is a bug')
	end
end

terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
	var conf = data.view.confirm {
		title = title;
		query = msg;
		cancel = cancel;
................................................................................
		class = 'query';
		body = body; cache = false;
	}
	self:stdpage(cf)
	--cf.title:free()
end












convo.methods.assertpow = macro(function(self, pow)
	return quote
		var ok = true
		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
			ok = false
			self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
		end
................................................................................
end
terra convo:pgetv(name: rawstring)
	var s,l = self:getv(name)
	return pstring { ptr = s, ct = l }
end

local route = {} -- these are defined in route.t, as they need access to renderers
terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8)} -> {}











local mimevar = symbol(lib.mem.ref(int8))
local mimeneg = `lib.http.mime.none

for i, t in ipairs(mimetypes) do
	local name, mime = t[1], t[2]
	mimeneg = quote
................................................................................
							end
							bsr:free()
							upmap:free()
						end
					end
				end

				route.dispatch_http(&co, uri)

				::fail::
				if co.uploads.run > 0 then
					for i=0,co.uploads.sz do
						co.uploads(i).filename:free()
						co.uploads(i).field:free()
					end

Modified str.t from [6c699847d5] to [072253bf19].

180
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
...
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
	end
	if self.buf ~= nil and self.space > 0 then
		lib.mem.heapf(self.buf)
	end
end;

terra m.acc:crush()
	--lib.dbg('crushing string accumulator')
	if self.pool ~= nil then return self end -- no point unless at end of buffer

	self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
	self.space = self.sz
	return self
end;

terra m.acc:finalize()
	--lib.dbg('finalizing string accumulator')
................................................................................
		var add, cont = disemvowel_codepoint(cur)
		if add:ref() then acc:ppush(add) end
		cur = cont
	end
	return acc:finalize()
end

terra m.qesc(pool: &lib.mem.pool, str: m.t): m.t
 -- escape double-quotes
	var a: m.acc a:pool(pool, str.ct + str.ct/2)
	a:lpush '"'
	for i=0, str.ct do
		if     str(i) == @'"'  then a:lpush '\\"'
		elseif str(i) == @'\\' then a:lpush '\\\\'
		elseif str(i) < 0x20 then -- for json
			var hex = lib.math.hexbyte(str(i))
			a:lpush('\\u00'):push(&hex[0], 2)
		else   a:push(str.ptr + i,1) end
	end
	a:lpush '"'
	return a:finalize()
end

return m







<

>







 







|


|








|




180
181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
...
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
	end
	if self.buf ~= nil and self.space > 0 then
		lib.mem.heapf(self.buf)
	end
end;

terra m.acc:crush()

	if self.pool ~= nil then return self end -- no point unless at end of buffer
	--lib.dbg('crushing string accumulator', &self.buf[0])
	self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
	self.space = self.sz
	return self
end;

terra m.acc:finalize()
	--lib.dbg('finalizing string accumulator')
................................................................................
		var add, cont = disemvowel_codepoint(cur)
		if add:ref() then acc:ppush(add) end
		cur = cont
	end
	return acc:finalize()
end

terra m.qesc(pool: &lib.mem.pool, str: m.t, wrap: bool): m.t
 -- escape double-quotes
	var a: m.acc a:pool(pool, str.ct + str.ct/2)
	if wrap then a:lpush '"' end
	for i=0, str.ct do
		if     str(i) == @'"'  then a:lpush '\\"'
		elseif str(i) == @'\\' then a:lpush '\\\\'
		elseif str(i) < 0x20 then -- for json
			var hex = lib.math.hexbyte(str(i))
			a:lpush('\\u00'):push(&hex[0], 2)
		else   a:push(str.ptr + i,1) end
	end
	if wrap then a:lpush '"' end
	return a:finalize()
end

return m

Modified tpl.t from [ce74a1083d] to [2f4ebceee0].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
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
...
102
103
104
105
106
107
108

109
110
111











112
113


114
115

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
	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, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!$]?)([-a-zA-Z0-9_]+)()') do
		if string.sub(str,start-1,start-1) ~= '\\' then
			segs[#segs+1] = string.sub(str,last,start-1)
			fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) }
			last = stop
		end
	end
	segs[#segs+1] = string.sub(str,last)
................................................................................
	local tallyup = {quote
		var [runningtally] = 1 + constlen
	end}
	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
	local symself = symbol(&rec)
	do local kfac = {}
		local sanmode = {}


		for afterseg,fld in ipairs(fields) do
			if not kfac[fld.key] then
				rec.entries[#rec.entries + 1] = {
					field = fld.key;
					type = lib.mem.ptr(int8);
				}

			end
			kfac[fld.key] = (kfac[fld.key] or 0) + 1
			sanmode[fld.key] = fld.mode == ':' and 6
				or fld.mode == '!' and 5


				or fld.mode == '$' and 2 or 1

		end
		for key, fac in pairs(kfac) do
			local sanfac = sanmode[key]
			

			tallyup[#tallyup + 1] = quote




				[runningtally] = [runningtally] + ([symself].[key].ct)*fac*sanfac

			end
		end
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
................................................................................
		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
		if fields[idx] and fields[idx].mode then
			local f = fields[idx]
			local fp = `symself.[f.key]
			local sanexp

			if f.mode == '$' then
				sanexp = `lib.str.qesc(pool, fp)
			else











				sanexp = `lib.html.sanitize(pool, fp, [f.mode == ':'])
			end


			copiers[#copiers+1] = quote 
				if fp.ct > 0 then

					var san = sanexp
					[cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct)
					--san:free()
				end
			end
			senders[#senders+1] = quote
				if fp.ct > 0 then
					var san = sanexp
					lib.net.mg_send([destcon], san.ptr, san.ct)
					--san:free()
				end
			end
			appenders[#appenders+1] = quote
				if fp.ct > 0 then
					var san = sanexp
					[accumulator]:ppush(san)
					--san:free()
				end
			end
		elseif fields[idx] then
			local f = fields[idx]







|







 







>
>




|

>




>
>
|
>



<
>
|
>
>
>
>
|
>







 







>
|
|
<
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>

<
>






|






|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
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
...
113
114
115
116
117
118
119
120
121
122

123
124
125
126
127
128
129
130
131
132
133
134

135
136
137

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
	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, mode, key, stop in string.gmatch(str,'()'..tplchar..'([+:!$#%^]?)([-a-zA-Z0-9_]+)()') do
		if string.sub(str,start-1,start-1) ~= '\\' then
			segs[#segs+1] = string.sub(str,last,start-1)
			fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) }
			last = stop
		end
	end
	segs[#segs+1] = string.sub(str,last)
................................................................................
	local tallyup = {quote
		var [runningtally] = 1 + constlen
	end}
	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
	local symself = symbol(&rec)
	do local kfac = {}
		local sanmode = {}
		local types = { ['^'] = uint64, ['#'] = uint64 }
		local recmap = {}
		for afterseg,fld in ipairs(fields) do
			if not kfac[fld.key] then
				rec.entries[#rec.entries + 1] = {
					field = fld.key;
					type = types[fld.mode] or pstr;
				}
				recmap[fld.key] = rec.entries[#rec.entries]
			end
			kfac[fld.key] = (kfac[fld.key] or 0) + 1
			sanmode[fld.key] = fld.mode == ':' and 6
				or fld.mode == '!' and 5
				or (fld.mode == '$' or fld.mode == '+') and 2
				or fld.mode == '^' and lib.math.shorthand.maxlen
				or fld.mode == '#' and 20
				or 1
		end
		for key, fac in pairs(kfac) do
			local sanfac = sanmode[key]

			if recmap[key].type ~= pstr then
				tallyup[#tallyup + 1] = quote
					[runningtally] = [runningtally] + fac*sanfac
				end
			else
				tallyup[#tallyup + 1] = quote
					[runningtally] = [runningtally] + ([symself].[key].ct)*fac*sanfac
				end
			end
		end
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
................................................................................
		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
		if fields[idx] and fields[idx].mode then
			local f = fields[idx]
			local fp = `symself.[f.key]
			local sanexp
			local nulexp
			if f.mode == '$' then sanexp = `lib.str.qesc(pool, fp, true)
			elseif f.mode == '+' then sanexp = `lib.str.qesc(pool, fp, false)

			elseif f.mode == '#' then
				sanexp = quote
					var ibuf: int8[21]
					var ptr = lib.math.decstr(fp, &ibuf[20])
				in pstr {ptr=ptr, ct=&ibuf[20] - ptr} end
			elseif f.mode == '^' then

				sanexp = quote
					var shbuf: lib.math.shorthand.t
					var len = lib.math.shorthand.gen(fp, &shbuf[0])
				in pstr {ptr=&shbuf[0],ct=len} end
			else sanexp = `lib.html.sanitize(pool, fp, [f.mode == ':']) end

			if f.mode == '^' or f.mode == '#' then nulexp = `true
			else nulexp = `fp.ct > 0 end
			copiers[#copiers+1] = quote 

				if [nulexp] then
					var san = sanexp
					[cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct)
					--san:free()
				end
			end
			senders[#senders+1] = quote
				if [nulexp] then
					var san = sanexp
					lib.net.mg_send([destcon], san.ptr, san.ct)
					--san:free()
				end
			end
			appenders[#appenders+1] = quote
				if [nulexp] then
					var san = sanexp
					[accumulator]:ppush(san)
					--san:free()
				end
			end
		elseif fields[idx] then
			local f = fields[idx]