parsav  Check-in [64ae6724c2]

Overview
Comment:enable webfinger
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 64ae6724c26d73c349c71d534d955bb90ac06c4813e562d990a07a853df8bc91
User & Date: lexi on 2021-01-24 23:18:29
Other Links: manifest | tags
Context
2021-01-25
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
2021-01-22
17:22
improve compose icon check-in: db0e155b9d user: lexi tags: trunk
Changes

Modified common.lua from [c868f8cd22] to [31fe76fcd7].

20
21
22
23
24
25
26











































27
28
29
30
31
32
33
34
..
37
38
39
40
41
42
43







44

45
46
47
48

49
50
51
52
53
54
55
..
68
69
70
71
72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87
..
94
95
96
97
98
99
100
101
102



103
104
105
106
107
108
109
110
111
	if val then return os.execute(cmd) else
		local fd = io.popen(cmd,'r')
		local t = fd:read('*a')
		return chomp(t), fd:close()
	end
end












































local function dump(v,pfx,cyc)
	pfx = pfx or ''
	cyc = cyc or {}
	local np = pfx .. '  '

	if type(v) == 'table' then
		if cyc[v] then return '<...>' else cyc[v] = true end
	end
................................................................................
		return string.format('%q', v)
	elseif type(v) == 'table' then
		local str = ''
		for k,v in pairs(v) do
			local tkey, tval = dump(k,np,cyc), dump(v,np,cyc)
			str = str .. string.format('%s[%s] = %s\n', np, tkey,tval)
		end







		return '{\n' .. str .. pfx .. '}\n'

	else
		return string.format('%s', v)
	end
end

local ping = function(path)
	local f = io.open(path)
	if f then f:close() return true end
	return false
end
local tobool = function(s)
	if s == true then return true
................................................................................
	local seed = 1 for i = 1, 8 do
		seed = seed * string.byte(string.sub(ent,i,i))
	end
	math.randomseed(seed)
else math.randomseed(os.time()) end

return {
	exec = exec;
	dump = dump;
	ping = ping;
	chomp = chomp;
	map = map;

	tobool = tobool;
	find = function(lst,pred)
		for k,v in pairs(lst) do
			local test = pred(v,k) 
			if test then return test end
		end
		return nil
	end;
................................................................................
			s = s .. string.char(ofs) 
		end
		return s
	end;
	append = function(a,b)
		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;







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







 







>
>
>
>
>
>
>
|
>




>







 







<

|
<
<
>
|







 







|
|
>
>
>

|







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
..
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
...
120
121
122
123
124
125
126

127
128


129
130
131
132
133
134
135
136
137
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
	if val then return os.execute(cmd) else
		local fd = io.popen(cmd,'r')
		local t = fd:read('*a')
		return chomp(t), fd:close()
	end
end

local function copy(a)
	local new = {}
	for k,v in pairs(a) do new[k] = v end
	return new
end

local function cat(a,b)
	a = copy(a)
	local ofs = #a
	for k,v in pairs(b) do
		if type(k) == 'number' then
			a[k+ofs] = v
		else a[k] = v end
	end
	return a
end

local function search(tbl,pred,lst,path)
	lst = lst or {} path = path or {}
	if type(pred) ~= 'function' then
		local val = pred
		pred = function(a,k)
			if type(a) == 'table' and a ~= val then return end
			return a == val
		end
	end
	for k,v in pairs(tbl) do
		local res = pred(v,k)
		local np = cat(path, {tbl})
		if res == true then
			table.insert(lst, {
				key = k;
				value = v;
				parent = tbl;
				path = np;
			})
		elseif res == nil then
			search(v,pred,lst,np)
		end
	end
	return lst
end

local function dump(v,pfx,cyc,ismeta)
	pfx = pfx or ''
	cyc = cyc or {}
	local np = pfx .. '  '

	if type(v) == 'table' then
		if cyc[v] then return '<...>' else cyc[v] = true end
	end
................................................................................
		return string.format('%q', v)
	elseif type(v) == 'table' then
		local str = ''
		for k,v in pairs(v) do
			local tkey, tval = dump(k,np,cyc), dump(v,np,cyc)
			str = str .. string.format('%s[%s] = %s\n', np, tkey,tval)
		end
		local meta = ''
		if getmetatable(v) then
			meta = dump(getmetatable(v),pfx,cyc,true) .. '::'
		end
		if ismeta then
			return string.format('%s<|\n%s%s|>',meta,str,pfx)
		else
			return meta..'{\n' .. str .. pfx .. '}\n'
		end
	else
		return string.format('%s', v)
	end
end

local ping = function(path)
	local f = io.open(path)
	if f then f:close() return true end
	return false
end
local tobool = function(s)
	if s == true then return true
................................................................................
	local seed = 1 for i = 1, 8 do
		seed = seed * string.byte(string.sub(ent,i,i))
	end
	math.randomseed(seed)
else math.randomseed(os.time()) end

return {

	dump = dump;
	exec = exec, ping = ping;


	map = map, copy = copy, cat = cat, search = search;
	chomp = chomp, tobool = tobool;
	find = function(lst,pred)
		for k,v in pairs(lst) do
			local test = pred(v,k) 
			if test then return test end
		end
		return nil
	end;
................................................................................
			s = s .. string.char(ofs) 
		end
		return s
	end;
	append = function(a,b)
		for _, v in pairs(b) do a[#a+1] = v end
	end;
	has = function(haystack,pred)
		if type(pred) ~= 'function' then
			local val = pred
			pred = function(a) return a == val end
		end
		for k,v in pairs(haystack) do
			if pred(v,k) then return k,v end
		end
	end;
	keys = function(ary)
		local kt = {}
		for k,v in pairs(ary) do kt[#kt+1] = k end
		return kt
	end;

Modified mem.t from [8c252399a5] to [d10508934e].

106
107
108
109
110
111
112



113
114
115
116
117
118
119
		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 or 0] ] end)



	t.metamethods.__update = macro(function(self,idx,rhs)
		return quote self.ptr[idx] = rhs end end)
	t.metamethods.__cast = function(from,to,exp)
		if to == t then
			if from == niltype then return `t.null()
			elseif from == &ty then return `t {ptr = exp, ct = 1}
			elseif from == ty then return `t {ptr = &exp, ct = 1}







>
>
>







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
		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 or 0] ] end)
	t.metamethods.__add = terra(self: &t, sz: intptr): t
		var n = @self n:advance(sz) return n
	end
	t.metamethods.__update = macro(function(self,idx,rhs)
		return quote self.ptr[idx] = rhs end end)
	t.metamethods.__cast = function(from,to,exp)
		if to == t then
			if from == niltype then return `t.null()
			elseif from == &ty then return `t {ptr = exp, ct = 1}
			elseif from == ty then return `t {ptr = &exp, ct = 1}

Modified route.t from [8f0d6bf9b0] to [325df84c8e].

923
924
925
926
927
928
929






930


931





















932
933
934
935
936
937
938
	return end

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

local json = {}







terra json.webfinger(co: &lib.srv.convo)


	





















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







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







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
	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

Modified srv.t from [c8974a3f52] to [557555fd0b].

8
9
10
11
12
13
14

15
16
17
18
19
20
21
..
28
29
30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
...
507
508
509
510
511
512
513


514
515
516
517
518
519
520
...
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
....
1110
1111
1112
1113
1114
1115
1116

1117
1118
1119
1120
1121
1122
1123
	pol_sec: secmode.t
	pol_reg: bool
	pol_autoherald: bool
	credmgd: bool
	maxupsz: intptr
	poolinitsz: intptr
	instance: pstring

	overlord: &srv
	ui_cue_staff: pstring
	ui_cue_founder: pstring
	ui_hue: uint16
	nranks: uint16
	maxinvites: uint16
	master: uint64
................................................................................
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
	id: rawstring
	pool: lib.mem.pool
}

terra cfgcache:free() -- :/
	self.secret:free()
	self.instance:free()

	self.ui_cue_staff:free()
	self.ui_cue_founder:free()
	self.usrdef_pol_follow:free()
	self.usrdef_pol_follow_req:free()
end

terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
................................................................................

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'};


	{'mkdown', 'text/markdown'};
	{'text', 'text/plain'};
	{'ansi', 'text/x-ansi'};
}

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

local handle = {
	http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque)
		var server = [&srv](userdata)
		var mgpeer = getpeer(con)
		var peer = lib.store.inet { port = mgpeer.port; }
		if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
		if peer.pv == 6 then
			for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end
		else -- v4
			@[&uint32](&peer.v4) = mgpeer.ip
		end

		-- the peer property is currently broken and there is precious
		-- little i can do about this -- it always reports a peer v4 IP
		-- of 0.0.0.0, altho the port seems to come through correctly.
		-- for now i'm leaving it as is, but note that netmask restrictions

		-- WILL NOT WORK until upstream gets its shit together. FIXME

		-- needs to check for an X-Forwarded-For header from nginx and
		-- use that instead of the peer iff peer is ::1/127.1 FIXME
		-- maybe also haproxy support?

		switch event_kind do
			case lib.net.MG_EV_HTTP_MSG then










				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](event)
				var co = convo {
					con = con, srv = server, msg = msg;
					aid = 0, aid_issue = 0, who = nil;
					reqtype = lib.http.mime.none;
					peer = peer, live_last = 0;
................................................................................
		str:free()
	end
	return default
end

terra cfgcache:load()
	self.instance = self.overlord:conf_get('instance-name')

	self.secret = self.overlord:conf_get('server-secret')

	self.pol_reg = self:cfbool('policy-self-register', false)
	self.pol_autoherald = self:cfbool('policy-self-herald', true)

	do self.credmgd = false
	var sreg = self.overlord:conf_get('credential-store')







>







 







|


>







 







>
>







 







|
<
<
<
<
<
<
>


|
|
>
|







>
>
>
>
>
>
>
>
>
>







 







>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
...
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
569
570
571
....
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
	pol_sec: secmode.t
	pol_reg: bool
	pol_autoherald: bool
	credmgd: bool
	maxupsz: intptr
	poolinitsz: intptr
	instance: pstring
	domain: pstring
	overlord: &srv
	ui_cue_staff: pstring
	ui_cue_founder: pstring
	ui_hue: uint16
	nranks: uint16
	maxinvites: uint16
	master: uint64
................................................................................
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
	id: rawstring
	pool: lib.mem.pool
}

terra cfgcache:free() -- :/ TODO replace with pool
	self.secret:free()
	self.instance:free()
	self.domain:free()
	self.ui_cue_staff:free()
	self.ui_cue_founder:free()
	self.usrdef_pol_follow:free()
	self.usrdef_pol_follow_req:free()
end

terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
................................................................................

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
................................................................................
	in ret end
end

local handle = {
	http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque)
		var server = [&srv](userdata)
		var mgpeer = getpeer(con)
		-- var pbuf: int8[128]







		-- the peer property is currently broken and there is precious
		-- little i can do about this -- it always reports a peer v4 IP
		-- of 0.0.0.0 for v6 connections, altho the port seems to come
		-- through correctly.  -- for now i'm leaving it as is, but note
		-- that netmask restrictions WILL NOT WORK until upstream gets
		-- its shit together. FIXME

		-- needs to check for an X-Forwarded-For header from nginx and
		-- use that instead of the peer iff peer is ::1/127.1 FIXME
		-- maybe also haproxy support?

		switch event_kind do
			case lib.net.MG_EV_HTTP_MSG then
				-- lib.net.mg_ntoa(&mgpeer,&pbuf[0],127)
				-- lib.dbg('got connection from client ',&pbuf[0])
				var peer = lib.store.inet { port = mgpeer.port; }
				if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
				if peer.pv == 6 then
					for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end
				else -- v4
					@[&uint32](&peer.v4) = mgpeer.ip
				end

				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](event)
				var co = convo {
					con = con, srv = server, msg = msg;
					aid = 0, aid_issue = 0, who = nil;
					reqtype = lib.http.mime.none;
					peer = peer, live_last = 0;
................................................................................
		str:free()
	end
	return default
end

terra cfgcache:load()
	self.instance = self.overlord:conf_get('instance-name')
	self.domain = self.overlord:conf_get('domain')
	self.secret = self.overlord:conf_get('server-secret')

	self.pol_reg = self:cfbool('policy-self-register', false)
	self.pol_autoherald = self:cfbool('policy-self-herald', true)

	do self.credmgd = false
	var sreg = self.overlord:conf_get('credential-store')

Modified str.t from [0c9ba1d780] to [6c699847d5].

59
60
61
62
63
64
65







66
67
68
69
70
71
72
...
559
560
561
562
563
564
565
566
















567

			var sz = lib.math.biggest(self.ct, other.ct)
			for i = 0, sz do
				if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
				if self.ptr[i] ~= other.ptr[i] then return false end
			end
			return true







		end
		terra ty:ffw()
			var newp = m.ffw(self.ptr,self.ct)
			var newct = self.ct - (newp - self.ptr)
			return ty { ptr = newp, ct = newct }
		end
		terra ty:blob()
................................................................................
	var cur = str while cur.ct > 0 do
		var add, cont = disemvowel_codepoint(cur)
		if add:ref() then acc:ppush(add) end
		cur = cont
	end
	return acc:finalize()
end

















return m







>
>
>
>
>
>
>







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590

			var sz = lib.math.biggest(self.ct, other.ct)
			for i = 0, sz do
				if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
				if self.ptr[i] ~= other.ptr[i] then return false end
			end
			return true
		end
		terra ty:startswith(other: ty)
			for i=0, other.ct do
				if other(i) == 0 then return true end
				if other(i) ~= self(i) then return false end
			end
			return true
		end
		terra ty:ffw()
			var newp = m.ffw(self.ptr,self.ct)
			var newct = self.ct - (newp - self.ptr)
			return ty { ptr = newp, ct = newct }
		end
		terra ty:blob()
................................................................................
	var cur = str while cur.ct > 0 do
		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

Modified tpl.t from [7216746946] to [ce74a1083d].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
73
74
75
76
77
78
79
80


81
82
83
84
85
86
87
..
99
100
101
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
	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)
................................................................................
			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 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
................................................................................
	for idx, seg in ipairs(segs) do
		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]






			copiers[#copiers+1] = quote 
				if fp.ct > 0 then
					var san = lib.html.sanitize(pool, fp, [f.mode == ':'])

					[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 = lib.html.sanitize(pool, fp, [f.mode == ':'])
					lib.net.mg_send([destcon], san.ptr, san.ct)
					--san:free()
				end
			end
			appenders[#appenders+1] = quote
				if fp.ct > 0 then
					var san = lib.html.sanitize(pool, fp, [f.mode == ':'])
					[accumulator]:ppush(san)
					--san:free()
				end
			end
		elseif fields[idx] then
			local f = fields[idx]
			local fp = `symself.[f.key]







|







 







|
>
>







 







>
>
>
>
>
>


<
>






|






|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
...
101
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
137
	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)
................................................................................
			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
................................................................................
	for idx, seg in ipairs(segs) do
		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]
			local fp = `symself.[f.key]