parsav  Check-in [7c8769bf96]

Overview
Comment:begin replacing inefficient memory management with a pool-based solution; fix memory leaks
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 7c8769bf968cda064fdda9ccd9fcd551ce183b0da38c153d85c4e14dc4bd1dd3
User & Date: lexi on 2021-01-10 08:19:56
Other Links: manifest | tags
Context
2021-01-10
11:17
add follow notices check-in: 00a6815988 user: lexi tags: trunk
08:19
begin replacing inefficient memory management with a pool-based solution; fix memory leaks check-in: 7c8769bf96 user: lexi tags: trunk
03:54
add memory pool impl, handle various little details, add beginnings of mimelib check-in: 8d35307a7f user: lexi tags: trunk
Changes

Modified html.t from [f6f8bc0dcc] to [06c69e3651].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- vim: ft=terra
local m={}
local pstr = lib.mem.ptr(int8)

terra m.sanitize(txt: pstr, quo: bool)
	if txt.ptr == nil then return pstr.null() end
	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
	var a: lib.str.acc a:init(txt.ct*1.3)
	for i=0,txt.ct do
		if txt(i) == @'<' then a:lpush('&lt;')
		elseif txt(i) == @'>' then a:lpush('&gt;')
		elseif txt(i) == @'&' then a:lpush('&amp;')
		elseif quo and txt(i) == @'"' then a:lpush('&quot;')
		else a:push(&txt(i),1) end
	end
................................................................................

terra m.hexbyte(i: uint8): int8[2]
	return arrayof(int8,
		m.hexdgt(i / 0x10),
		m.hexdgt(i % 0x10))
end

terra m.urlenc(txt: pstr, qparam: bool)
	if txt.ptr == nil then return pstr.null() end
	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
	var a: lib.str.acc a:init(txt.ct*1.3)
	for i=0,txt.ct do
		if txt(i) == @' ' then a:lpush('+')
		elseif txt(i) == @'&' and not qparam then a:lpush('&amp;')
		elseif (txt(i) < 0x2c or
			(txt(i) > @';'  and txt(i) < @'@') or
			(txt(i) > @'Z'  and txt(i) < @'a') or
			(txt(i) >= 0x7b and txt(i) <= 0x7f)) and




|


|







 







|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- vim: ft=terra
local m={}
local pstr = lib.mem.ptr(int8)

terra m.sanitize(pool: &lib.mem.pool, txt: pstr, quo: bool)
	if txt.ptr == nil then return pstr.null() end
	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
	var a: lib.str.acc a:pool(pool,txt.ct*1.3)
	for i=0,txt.ct do
		if txt(i) == @'<' then a:lpush('&lt;')
		elseif txt(i) == @'>' then a:lpush('&gt;')
		elseif txt(i) == @'&' then a:lpush('&amp;')
		elseif quo and txt(i) == @'"' then a:lpush('&quot;')
		else a:push(&txt(i),1) end
	end
................................................................................

terra m.hexbyte(i: uint8): int8[2]
	return arrayof(int8,
		m.hexdgt(i / 0x10),
		m.hexdgt(i % 0x10))
end

terra m.urlenc(pool: &lib.mem.pool, txt: pstr, qparam: bool)
	if txt.ptr == nil then return pstr.null() end
	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
	var a: lib.str.acc a:pool(pool,txt.ct*1.3)
	for i=0,txt.ct do
		if txt(i) == @' ' then a:lpush('+')
		elseif txt(i) == @'&' and not qparam then a:lpush('&amp;')
		elseif (txt(i) < 0x2c or
			(txt(i) > @';'  and txt(i) < @'@') or
			(txt(i) > @'Z'  and txt(i) < @'a') or
			(txt(i) >= 0x7b and txt(i) <= 0x7f)) and

Modified mem.t from [de24aef2de] to [7e2b478eaf].

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


230












231
232
233
234
235
236
237

238



239
240
241
242
243
end)

struct m.pool {
 -- implements growable memory pools. EVERY THREAD MUST HAVE ITS OWN
	storage: &opaque
	cursor: &opaque
	sz: intptr

}

terra m.pool:cue(sz: intptr)
	if self.storage == nil then
		self.storage = m.heapa_raw(sz)
		self.cursor = self.storage
		self.sz = sz
	else
		if self.sz >= sz then return self end
		var ofs = [&uint8](self.cursor) - [&uint8](self.storage)
		self.storage = m.heapr_raw(self.storage, sz)
		self.cursor = [&opaque]([&uint8](self.storage) + ofs)
		self.sz = sz
	end
	return self
end

terra m.pool:init(sz: intptr)





	self.storage = nil
	self:cue(sz)
	return self
end

terra m.pool:free()

	m.heapf(self.storage)


	self.storage = nil
	self.cursor = nil
	self.sz = 0

end

terra m.pool:clear()

	self.cursor = self.storage
	return self
end

terra m.pool:alloc_bytes(sz: intptr): &opaque
	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))



	if space < sz then self:cue(space + sz + 256) end
	var ptr = self.cursor
	self.cursor = [&opaque]([&uint8](self.cursor) + sz)
	return ptr
end

















m.pool.methods.alloc = macro(function(self,ty,sz)


	return `[ty](self:alloc_bytes(sizeof(ty) * sz))












end)

terra m.pool:frame() -- stack-style linear mgmt
	return self.cursor
end

terra m.pool:reset(frame: &opaque)

	self.cursor = frame



	return self
end


return m







>









|
|
|
<





>
>
>
>
>
|
<



|
>
|
>
>



>



>






>
>
>
|





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







>
|
>
>
>





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
230
231
232
233
234
235
236
237
238
239
240
241
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
278
279
280
281
282
283
284
285
286
287
288
289
end)

struct m.pool {
 -- implements growable memory pools. EVERY THREAD MUST HAVE ITS OWN
	storage: &opaque
	cursor: &opaque
	sz: intptr
	debris: &m.pool
}

terra m.pool:cue(sz: intptr)
	if self.storage == nil then
		self.storage = m.heapa_raw(sz)
		self.cursor = self.storage
		self.sz = sz
	else
		if self.sz >= sz then return self end
		var oldblock = @self
		self:init(sz)
		@self.debris = oldblock

	end
	return self
end

terra m.pool:init(sz: intptr)
	var b = m.heapa_raw(sz + sizeof(m.pool))
	self.storage = [&uint8](b) + sizeof(m.pool)
	self.cursor = self.storage
	self.sz = sz
	self.debris = [&m.pool](b)
	self.debris.storage = nil

	return self
end

terra m.pool:free(): {}
lib.io.fmt('DRAINING POOL %p\n',self.storage)
	if self.storage == nil then return end
	if self.debris.storage ~= nil then self.debris:free() end
	m.heapf(self.debris) -- storage + debris field allocated in one block
	self.storage = nil
	self.cursor = nil
	self.sz = 0
	self.debris = nil
end

terra m.pool:clear()
	if self.debris.storage ~= nil then self.debris:free() end
	self.cursor = self.storage
	return self
end

terra m.pool:alloc_bytes(sz: intptr): &opaque
	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
lib.io.fmt('%p / %p @ allocating %llu bytes in %llu of space\n',self.storage,self.cursor,sz,space)
	if space < sz then
lib.dbg('reserving more space')
		self:cue(space + sz + 256) end
	var ptr = self.cursor
	self.cursor = [&opaque]([&uint8](self.cursor) + sz)
	return ptr
end

terra m.pool:realloc_bytes(oldptr: &opaque, oldsz: intptr, newsz: intptr): &opaque
	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
	var cur = [&uint8](self.cursor)
	if cur - [&uint8](oldptr) == oldsz and newsz - oldsz < space then
		lib.dbg('moving pool cursor')
		cur = cur + (newsz - oldsz)
		self.cursor = [&opaque](cur)
		return oldptr
	else
		lib.dbg('copying pool object')
		var new = self:alloc_bytes(newsz)
		m.cpy(new, oldptr, oldsz)
		return new
	end
end

m.pool.methods.alloc = macro(function(self,typ,sz)
 	local ty = typ:astype()
	return `[m.ptr(ty)] {
		ptr = [&ty](self:alloc_bytes(sizeof([ty]) * [sz]));
		ct = [sz];
	}
end)

m.pool.methods.realloc = macro(function(self,ptr,oldsz,newsz)
	local ty = self.tree.type.type
	return `[m.ptr(ty)] {
		ptr = [&ty](self:realloc_bytes(ptr,
			sizeof(ty) * oldsz,
			sizeof(ty) * newsz));
		ct = sz;
	}
end)

terra m.pool:frame() -- stack-style linear mgmt
	return self.cursor
end

terra m.pool:reset(frame: &opaque)
	if frame >= self.storage and frame <= self.cursor then
		self.cursor = frame
	else -- trying to rewind into a previous block! not possible
		self.cursor = self.storage
	end
	return self
end


return m

Modified render/compose.t from [13509724e6] to [95dc7dcbc1].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
		form.acl = lib.trn(target == nil, 'all', 'mentioned') -- TODO default acl setting?
	else
		form.content = lib.coalesce(edit.body, '')
		form.acl = edit.acl
	end
	if acc ~= nil then form:append(acc) return end 

	var cotxt = form:tostr() defer cotxt:free()

	var doc = [lib.srv.convo.page] {
		title = lib.str.plit 'compose';
		body = cotxt;
		class = lib.str.plit 'compose';
		cache = true;
	}

	co:stdpage(doc)
end

return render_compose







|












12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
		form.acl = lib.trn(target == nil, 'all', 'mentioned') -- TODO default acl setting?
	else
		form.content = lib.coalesce(edit.body, '')
		form.acl = edit.acl
	end
	if acc ~= nil then form:append(acc) return end 

	var cotxt = form:poolstr(&co.srv.pool) -- defer cotxt:free()

	var doc = [lib.srv.convo.page] {
		title = lib.str.plit 'compose';
		body = cotxt;
		class = lib.str.plit 'compose';
		cache = true;
	}

	co:stdpage(doc)
end

return render_compose

Modified render/conf.t from [241a1c4277] to [cd79efba6f].

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
..
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
local invoker = quote co:complain(404,'not found','no such control panel is available in this version of parsav') end

for i, m in ipairs(mappings) do
	if lib.render.conf[m.render] then
		invoker = quote
			if path(1):cmp(lib.str.lit([m.url])) then
				var body = [lib.render.conf[m.render]] (co, path)
				var a: lib.str.acc a:init(body.ct+48)
				if not body then
					a:lpush(['<h1>' .. m.title .. ' :: error</h1>' ..
						'<p>the requested resource is not available.</p>'])
					panel = a:finalize()
				else
					a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body)
					panel = a:finalize()
					body:free()
				end
			else [invoker] end
		end
	end
end

local terra 
render_conf([co], [path], notify: pstr)
	var menu: lib.str.acc menu:init(64):lpush('<hr>') defer menu:free()


	-- build menu
	do var p = co.who.rights.powers
		if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end
		if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end
		if p.config() then menu:lpush([
			'<a href="/conf/srv">server &amp; policy</a>' ..
................................................................................
		menu = mptr;
		panel = panel;
	}

	var pgt: pstr
	if notify:ref() then
		var fnpg: lib.str.acc
		fnpg:compose('<div class="flashmsg">', notify, '</div>')
		pg:append(&fnpg)
		pgt = fnpg:finalize()
	else pgt = pg:tostr() end
	defer pgt:free()

	co:stdpage([lib.srv.convo.page] {
		title = 'configure'; body = pgt;
		class = lib.str.plit 'conf';
		cache = false;
	})

	if panel.ct ~= 0 then panel:free() end
end

return render_conf







|







|








|
>







 







|


|
|







|



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
..
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
local invoker = quote co:complain(404,'not found','no such control panel is available in this version of parsav') end

for i, m in ipairs(mappings) do
	if lib.render.conf[m.render] then
		invoker = quote
			if path(1):cmp(lib.str.lit([m.url])) then
				var body = [lib.render.conf[m.render]] (co, path)
				var a = co:stra(body.ct+48)
				if not body then
					a:lpush(['<h1>' .. m.title .. ' :: error</h1>' ..
						'<p>the requested resource is not available.</p>'])
					panel = a:finalize()
				else
					a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body)
					panel = a:finalize()
					--body:free()
				end
			else [invoker] end
		end
	end
end

local terra 
render_conf([co], [path], notify: pstr)
	var menu = co:stra(256)
	menu:lpush('<hr>') 

	-- build menu
	do var p = co.who.rights.powers
		if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end
		if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end
		if p.config() then menu:lpush([
			'<a href="/conf/srv">server &amp; policy</a>' ..
................................................................................
		menu = mptr;
		panel = panel;
	}

	var pgt: pstr
	if notify:ref() then
		var fnpg: lib.str.acc
		fnpg:pcompose(&co.srv.pool, '<div class="flashmsg">', notify, '</div>')
		pg:append(&fnpg)
		pgt = fnpg:finalize()
	else pgt = pg:poolstr(&co.srv.pool) end
	--defer pgt:free()

	co:stdpage([lib.srv.convo.page] {
		title = 'configure'; body = pgt;
		class = lib.str.plit 'conf';
		cache = false;
	})

	--if panel.ct ~= 0 then panel:free() end
end

return render_conf

Modified render/conf/profile.t from [864a63a85e] to [5b3736f2f4].

11
12
13
14
15
16
17
18
19
20
21
	var hue: int8[21]
	var c = data.view.conf_profile {
		handle = cs(co.who.handle);
		nym = cs(lib.coalesce(co.who.nym,''));
		bio = cs(lib.coalesce(co.who.bio,''));
		hue = lib.math.decstr(co.ui_hue, &hue[20]);
	}
	return c:tostr()
end

return render_conf_profile







|



11
12
13
14
15
16
17
18
19
20
21
	var hue: int8[21]
	var c = data.view.conf_profile {
		handle = cs(co.who.handle);
		nym = cs(lib.coalesce(co.who.nym,''));
		bio = cs(lib.coalesce(co.who.bio,''));
		hue = lib.math.decstr(co.ui_hue, &hue[20]);
	}
	return c:poolstr(&co.srv.pool)
end

return render_conf_profile

Modified render/conf/sec.t from [f6e2d18341] to [d16c1b9a13].

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
	lib.osclock.ctime_r(&time, &tstr[0])
	var body = data.view.conf_sec {
		lastreset = pstr {
			ptr = &tstr[0], ct = lib.str.sz(&tstr[0])
		}
	}
	
	var a: lib.str.acc a:init(768) defer a:free()

	if co.srv.cfg.credmgd then
		var new = co:pgetv('new')
		if not new then
			body:append(&a)
			var credmgr = data.view.conf_sec_credmg {
				credlist = pstr{'',0};
			}
			var creds = co.srv:auth_enum_uid(uid)
			if creds.ct > 0 then defer creds:free()
				var cl: lib.str.acc cl:init(256)
				for i=0, creds.ct do var c = creds(i).ptr
					if not c.blacklist then
						cl:lpush('<option value="'):shpush(c.aid):lpush('"> ['):push(c.kind,0):lpush('] '):push(c.comment,0)
						if c.netmask.pv ~= 0 then
							-- push string rep
						end
						cl:lpush('</option>')
					end
				end
				credmgr.credlist = cl:finalize()
			end
			credmgr:append(&a)
			if credmgr.credlist.ct > 0 then credmgr.credlist:free() end
		else
			if new:cmp(lib.str.plit'pw') then
				var d: data.view.conf_sec_pwnew
				var time = lib.osclock.time(nil)
				var timestr: int8[26] lib.osclock.ctime_r(&time, &timestr[0])
				var cmt: lib.str.acc
				cmt:init(48):lpush('enrolled over http on '):push(&timestr[0],0)
				d.comment = cmt:finalize()

				var st = d:tostr()
				d.comment:free()
				return st
			elseif new:cmp(lib.str.plit'challenge') then
			-- we're going to break the rules a bit and do database munging from
			-- the rendering code, because doing otherwise in this case would be
			-- genuinely nightmarish
			elseif new:cmp(lib.str.plit'otp') then
			elseif new:cmp(lib.str.plit'api') then







|










|












|





|
|


|
|







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
	lib.osclock.ctime_r(&time, &tstr[0])
	var body = data.view.conf_sec {
		lastreset = pstr {
			ptr = &tstr[0], ct = lib.str.sz(&tstr[0])
		}
	}
	
	var a = co:stra(768) -- defer a:free()

	if co.srv.cfg.credmgd then
		var new = co:pgetv('new')
		if not new then
			body:append(&a)
			var credmgr = data.view.conf_sec_credmg {
				credlist = pstr{'',0};
			}
			var creds = co.srv:auth_enum_uid(uid)
			if creds.ct > 0 then defer creds:free()
				var cl = co:stra(256)
				for i=0, creds.ct do var c = creds(i).ptr
					if not c.blacklist then
						cl:lpush('<option value="'):shpush(c.aid):lpush('"> ['):push(c.kind,0):lpush('] '):push(c.comment,0)
						if c.netmask.pv ~= 0 then
							-- push string rep
						end
						cl:lpush('</option>')
					end
				end
				credmgr.credlist = cl:finalize()
			end
			credmgr:append(&a)
			--if credmgr.credlist.ct > 0 then credmgr.credlist:free() end
		else
			if new:cmp(lib.str.plit'pw') then
				var d: data.view.conf_sec_pwnew
				var time = lib.osclock.time(nil)
				var timestr: int8[26] lib.osclock.ctime_r(&time, &timestr[0])
				var cmt = co:stra(48)
				cmt:lpush('enrolled over http on '):push(&timestr[0],0)
				d.comment = cmt:finalize()

				var st = d:poolstr(&co.srv.pool)
				--d.comment:free()
				return st
			elseif new:cmp(lib.str.plit'challenge') then
			-- we're going to break the rules a bit and do database munging from
			-- the rendering code, because doing otherwise in this case would be
			-- genuinely nightmarish
			elseif new:cmp(lib.str.plit'otp') then
			elseif new:cmp(lib.str.plit'api') then

Modified render/conf/users.t from [59cb9f4fac] to [7c5c858095].

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
...
234
235
236
237
238
239
240
241
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
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
		-- FIXME allow xids as well, for manual queries
		if not user then goto e404 end
		defer user:free()
		if not co.who:overpowers(user.ptr) then goto e403 end

		if path.ct == 4 then
			if path(3):cmp(lib.str.lit'cred') then
				var pg: lib.str.acc pg:init(1024)
				pg:lpush('<div class="context">editing credentials for user <a href="/conf/users/'):rpush(path(2)):lpush('">'):push(user(0).xid,0):lpush('</a></div>')
				var credmgr = lib.render.conf.sec(co, uid)
				pg:ppush(credmgr)
				credmgr:free()
				return pg:finalize()
			else goto e404 end
		elseif path.ct == 3 then
			var cinp: lib.str.acc cinp:init(256)
			cinp:lpush('<div class="elem-group">')
			if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then
				var max = co.who.rights.rank
				if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end
				var min = co.srv.cfg.nranks
				if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end

				push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id)
			end
			if co.who.rights.powers.herald() then
				var sanitized: pstr
				if user.ptr.epithet == nil
					then sanitized = pstr {ptr='', ct=0}
					else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true)
				end
				cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>')
				if user.ptr.epithet ~= nil then sanitized:free() end
			end
			if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then
				var min: uint32 = 0
				if not (co.who.rights.powers.discipline() or
					co.who.rights.powers.demote() and co.who.rights.powers.invite())
						then min = user.ptr.rights.invites end
				var max: uint32 = co.srv.cfg.maxinvites
................................................................................
				var map = array([lib.store.powmap])
				cinp:lpush('<details><summary>powers</summary><div class="pick-list">')
					for i=0, [map.type.N] do
						if (co.who.rights.powers and map[i].val):sz() > 0 then
							var on = (user.ptr.rights.powers and map[i].val):sz() > 0
							var enabled = (     on  and co.who.rights.powers.demote() ) or
										  ((not on) and co.who.rights.powers.elevate())
							var namea: lib.str.acc namea:compose('power-', map[i].name)
							var name = namea:finalize()
							push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null())
							name:free()
						end
					end
				cinp:lpush('</div></details>')
			end

			if co.who.id ~= uid and co.who.rights.powers.purge() then
				var purgeconf: lib.str.acc purgeconf:init(48)
				var purgestrs = array(
					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
					'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory',
					'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy'
				)
				for i=0,3 do
					purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0)
					if i ~= 2 then purgeconf:lpush('-') end
				end
				cinp:lpush('<details><summary>purge account</summary><p>you have the authority to destroy this account and all its associated content irreversibly and irretrievably. if you really wish to apply such an extreme sanction, enter the confirmation string <strong style="user-select:none">'):push(purgeconf.buf,purgeconf.sz):lpush('</strong> below and press the “alter” button to begin the process.</p><div class="elem"><label for="purge">purge confirmation string</label><input type="text" id="purge" name="purgekey"></div><input type="hidden" name="purgestr" value="'):push(purgeconf.buf,purgeconf.sz):lpush('"></details>')
				purgeconf:free()
			end

			-- TODO black mark system? e.g. resolution option for badthink reports
			-- adds a black mark to the offending user; they can be automatically banned
			-- or brought up for review after a certain number of offenses; possibly lower
			-- set of default privs for marked users

			var cinpp = cinp:finalize() defer cinpp:free()
			var unym: lib.str.acc unym:init(64)
			unym:lpush('<a href="/')
			if user(0).origin ~= 0 then unym:lpush('@') end
			do var sanxid = lib.html.sanitize(user(0).xid, true)
				unym:ppush(sanxid)
				sanxid:free() end

			unym:lpush('" class="id">')
			lib.render.nym(user.ptr,0,&unym,false)
			unym:lpush('</a>')
			var ctlbox = data.view.conf_user_ctl {
				name = unym:finalize();
				inputcontent = cinpp;
				btns = pstr{'',0};
			}
			if co.who.id ~= uid and co.who.rights.powers.cred() then
				ctlbox.btns = lib.str.acc{}:compose('<a class="button" href="/conf/users/',path(2),'/cred">security &amp; credentials</a>'):finalize()
			end
			var pg: lib.str.acc pg:init(512)
			ctlbox:append(&pg)
			ctlbox.name:free()
			if ctlbox.btns.ct > 0 then ctlbox.btns:free() end

			return pg:finalize()
		end
	else
		var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all')
		var idbuf: int8[lib.math.shorthand.maxlen]
		var ulst: lib.str.acc ulst:init(256)
		var mode: uint8 = mode_local
		var modestr = co:pgetv('show')
		ulst:lpush('<div style="text-align: right"><em>showing ')
		for i=0,[modes.type.N] do
			if modestr:ref() and modes[i]:cmp(modestr) then mode = i end
		end
		for i=0,[modes.type.N] do







|



|



|













|


|







 







|


|






|










|







|
|


|

|
>









|

|

|
|






|







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
...
234
235
236
237
238
239
240
241
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
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
		-- FIXME allow xids as well, for manual queries
		if not user then goto e404 end
		defer user:free()
		if not co.who:overpowers(user.ptr) then goto e403 end

		if path.ct == 4 then
			if path(3):cmp(lib.str.lit'cred') then
				var pg = co:stra(1024)
				pg:lpush('<div class="context">editing credentials for user <a href="/conf/users/'):rpush(path(2)):lpush('">'):push(user(0).xid,0):lpush('</a></div>')
				var credmgr = lib.render.conf.sec(co, uid)
				pg:ppush(credmgr)
				--credmgr:free()
				return pg:finalize()
			else goto e404 end
		elseif path.ct == 3 then
			var cinp = co:stra(256)
			cinp:lpush('<div class="elem-group">')
			if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then
				var max = co.who.rights.rank
				if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end
				var min = co.srv.cfg.nranks
				if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end

				push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id)
			end
			if co.who.rights.powers.herald() then
				var sanitized: pstr
				if user.ptr.epithet == nil
					then sanitized = pstr {ptr='', ct=0}
					else sanitized = lib.html.sanitize(&co.srv.pool,cs(user.ptr.epithet),true)
				end
				cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>')
				--if user.ptr.epithet ~= nil then sanitized:free() end
			end
			if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then
				var min: uint32 = 0
				if not (co.who.rights.powers.discipline() or
					co.who.rights.powers.demote() and co.who.rights.powers.invite())
						then min = user.ptr.rights.invites end
				var max: uint32 = co.srv.cfg.maxinvites
................................................................................
				var map = array([lib.store.powmap])
				cinp:lpush('<details><summary>powers</summary><div class="pick-list">')
					for i=0, [map.type.N] do
						if (co.who.rights.powers and map[i].val):sz() > 0 then
							var on = (user.ptr.rights.powers and map[i].val):sz() > 0
							var enabled = (     on  and co.who.rights.powers.demote() ) or
										  ((not on) and co.who.rights.powers.elevate())
							var namea: lib.str.acc namea:pcompose(&co.srv.pool,'power-', map[i].name)
							var name = namea:finalize()
							push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null())
							--name:free()
						end
					end
				cinp:lpush('</div></details>')
			end

			if co.who.id ~= uid and co.who.rights.powers.purge() then
				var purgeconf = co:stra(48)
				var purgestrs = array(
					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
					'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory',
					'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy'
				)
				for i=0,3 do
					purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0)
					if i ~= 2 then purgeconf:lpush('-') end
				end
				cinp:lpush('<details><summary>purge account</summary><p>you have the authority to destroy this account and all its associated content irreversibly and irretrievably. if you really wish to apply such an extreme sanction, enter the confirmation string <strong style="user-select:none">'):push(purgeconf.buf,purgeconf.sz):lpush('</strong> below and press the “alter” button to begin the process.</p><div class="elem"><label for="purge">purge confirmation string</label><input type="text" id="purge" name="purgekey"></div><input type="hidden" name="purgestr" value="'):push(purgeconf.buf,purgeconf.sz):lpush('"></details>')
				--purgeconf:free()
			end

			-- TODO black mark system? e.g. resolution option for badthink reports
			-- adds a black mark to the offending user; they can be automatically banned
			-- or brought up for review after a certain number of offenses; possibly lower
			-- set of default privs for marked users

			var cinpp = cinp:finalize() --defer cinpp:free()
			var unym = co:stra(64)
			unym:lpush('<a href="/')
			if user(0).origin ~= 0 then unym:lpush('@') end
			do var sanxid = lib.html.sanitize(&co.srv.pool,user(0).xid, true)
				unym:ppush(sanxid)
				--sanxid:free()
			end
			unym:lpush('" class="id">')
			lib.render.nym(user.ptr,0,&unym,false)
			unym:lpush('</a>')
			var ctlbox = data.view.conf_user_ctl {
				name = unym:finalize();
				inputcontent = cinpp;
				btns = pstr{'',0};
			}
			if co.who.id ~= uid and co.who.rights.powers.cred() then
				ctlbox.btns = lib.str.acc{}:pcompose(&co.srv.pool,'<a class="button" href="/conf/users/',path(2),'/cred">security &amp; credentials</a>'):finalize()
			end
			var pg = co:stra(512)
			ctlbox:append(&pg)
			--ctlbox.name:free()
			--if ctlbox.btns.ct > 0 then ctlbox.btns:free() end

			return pg:finalize()
		end
	else
		var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all')
		var idbuf: int8[lib.math.shorthand.maxlen]
		var ulst = co:stra(256)
		var mode: uint8 = mode_local
		var modestr = co:pgetv('show')
		ulst:lpush('<div style="text-align: right"><em>showing ')
		for i=0,[modes.type.N] do
			if modestr:ref() and modes[i]:cmp(modestr) then mode = i end
		end
		for i=0,[modes.type.N] do

Modified render/media-gallery.t from [da27a31f83] to [5c4e9df122].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
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
...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
201
202
	var owner = false
	if co.aid ~= 0 and co.who.id == uid then owner = true end
	var ou = co.srv:actor_fetch_uid(uid)
	if not ou then goto e404 end
	do defer ou:free()
		var pfx = pstr.null()
		if not owner then
			var pa: lib.str.acc pa:init(32)
			pa:lpush('/')
			if ou(0).origin ~= 0 then pa:lpush('@') end
			pa:push(ou(0).xid,0)
			pfx = pa:finalize()
		end

		if path.ct >= 3 and path(1):cmp(lib.str.lit'a') then
................................................................................
			var art = co.srv:artifact_fetch(uid, id)
			if not art then goto e404 end
			if path.ct == 3 then
			-- sniff out the artifact type and display the appropriate viewer
				var artid = cs(art(0).url)
				var btns: lib.str.acc
				if owner then
					btns:compose('<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>')
				else
					btns:compose('<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>')
				end
				var btntxt = btns:finalize() defer btntxt:free()
				var desc = lib.smackdown.html(pstr{art(0).desc,0}, true) defer desc:free()
				var viewerprops = {
					pfx = pfx, desc = desc;
					id = artid; btns = btntxt;
				}
				if lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then
					var view = data.view.media_image(viewerprops)
					var pg = view:tostr()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: image';
						class = lib.str.plit'media viewer img';
						cache = false, body = pg;
					})
					pg:free()
				elseif lib.str.cmp(art(0).mime, 'text/markdown') == 0 then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					view.text = lib.smackdown.html(pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					var pg = view:tostr()
					view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
						class = lib.str.plit'media viewer text';
						cache = false, body = pg;
					})
					pg:free()
				elseif
					lib.str.ncmp(art(0).mime, 'text/', 5) == 0          or
					lib.str.cmp(art(0).mime, 'application/x-perl') == 0 or
					lib.str.cmp(art(0).mime, 'application/sql') == 0
					-- and so on (we need a mimelib at some point) --
				then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					var san = lib.html.sanitize(pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					view.text = lib.str.acc{}:compose('<pre>',san,'</pre>'):finalize()
					san:free()
					var pg = view:tostr()
					view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
................................................................................
				folders = pstr{'',0};
				directory = pstr{'',0};
				images = pstr{'',0};
				pfx = pfx;
			}

			if folders.ct > 0 then
				var fa: lib.str.acc fa:init(128)
				var fldr = co:pgetv('folder')
				for i=0,folders.ct do
					var ule = lib.html.urlenc(folders(i), true) defer ule:free()
					var san = lib.html.sanitize(folders(i), true) defer san:free()
					fa:lpush('<a href="'):ppush(pfx):lpush('/media?folder='):ppush(ule)
						:lpush('">'):ppush(san):lpush('</a>')
					lib.dbg('checking folder ',{fldr.ptr,fldr.ct},' against ',{folders(i).ptr,folders(i).ct})
					if fldr:ref() and folders(i):cmp(fldr)
						then folder = folders(i) lib.dbg('folder match ',{fldr.ptr,fldr.ct})
						else folders(i):free()
					end
................................................................................
			end

			if owner then
				view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>'
			end

			var md = co.srv:artifact_enum_uid(uid, folder)
			var gallery: lib.str.acc gallery:init(256)
			var files: lib.str.acc files:init(256) 
			for i=0,md.ct do
				var desc = lib.smackdown.html(pstr{md(i)(0).desc,0}, true) defer desc:free()
				if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
					gallery:lpush('<a class="thumb" href="'):ppush(pfx):lpush('/media/a/')
						:push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0)
						:lpush('"><div class="caption">'):ppush(desc)
						:lpush('</div></a>')
				else
					var mime = lib.html.sanitize(pstr{md(i)(0).mime,0}, true) defer mime:free() --just in case
					files:lpush('<a class="file" href="'):ppush(pfx):lpush('/media/a/')
						:push(md(i)(0).url,0):lpush('"><span class="label">'):ppush(desc)
						:lpush('</span> <span class="mime">'):ppush(mime)
						:lpush('</span></a>')
				end
				md(i):free()
			end
................................................................................

			view.images = gallery:finalize()
			view.directory = files:finalize()

			if acc ~= nil then
				view:append(acc)
			else
			lib.dbg('emitting page')
				var pg = view:tostr() defer pg:free()
			lib.dbg('compiled page')
				co:stdpage([lib.srv.convo.page] {
					title = P'media';
					class = P'media manager';
					cache = false;
					body = pg;
				})
			lib.dbg('sent page')
			end

			view.images:free()
			view.directory:free()
			if view.folders.ct > 0 then view.folders:free() end
			if folder.ct > 0 then folder:free() end
			if md:ref() then md:free() end
		end
		if not owner then pfx:free() end
	return end

	::e404:: co:complain(404,'media not found','no such media exists on this server')
end

return render_media_gallery







|







 







|

|

|
|






|





|



|

|
|





|








|







 







|


|
|







 







|
|

|






|







 







|
<
<






<


|
|
|



|






14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
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
...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
	var owner = false
	if co.aid ~= 0 and co.who.id == uid then owner = true end
	var ou = co.srv:actor_fetch_uid(uid)
	if not ou then goto e404 end
	do defer ou:free()
		var pfx = pstr.null()
		if not owner then
			var pa = co:stra(32)
			pa:lpush('/')
			if ou(0).origin ~= 0 then pa:lpush('@') end
			pa:push(ou(0).xid,0)
			pfx = pa:finalize()
		end

		if path.ct >= 3 and path(1):cmp(lib.str.lit'a') then
................................................................................
			var art = co.srv:artifact_fetch(uid, id)
			if not art then goto e404 end
			if path.ct == 3 then
			-- sniff out the artifact type and display the appropriate viewer
				var artid = cs(art(0).url)
				var btns: lib.str.acc
				if owner then
					btns:pcompose(&co.srv.pool,'<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>')
				else
					btns:pcompose(&co.srv.pool,'<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>')
				end
				var btntxt = btns:finalize() -- defer btntxt:free()
				var desc = lib.smackdown.html(&co.srv.pool, pstr{art(0).desc,0}, true) -- defer desc:free()
				var viewerprops = {
					pfx = pfx, desc = desc;
					id = artid; btns = btntxt;
				}
				if lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then
					var view = data.view.media_image(viewerprops)
					var pg = view:poolstr(&co.srv.pool)
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: image';
						class = lib.str.plit'media viewer img';
						cache = false, body = pg;
					})
					--pg:free()
				elseif lib.str.cmp(art(0).mime, 'text/markdown') == 0 then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					view.text = lib.smackdown.html(&co.srv.pool, pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					var pg = view:poolstr(&co.srv.pool)
					--view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
						class = lib.str.plit'media viewer text';
						cache = false, body = pg;
					})
					--pg:free()
				elseif
					lib.str.ncmp(art(0).mime, 'text/', 5) == 0          or
					lib.str.cmp(art(0).mime, 'application/x-perl') == 0 or
					lib.str.cmp(art(0).mime, 'application/sql') == 0
					-- and so on (we need a mimelib at some point) --
				then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					var san = lib.html.sanitize(&co.srv.pool,pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					view.text = lib.str.acc{}:compose('<pre>',san,'</pre>'):finalize()
					san:free()
					var pg = view:tostr()
					view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
................................................................................
				folders = pstr{'',0};
				directory = pstr{'',0};
				images = pstr{'',0};
				pfx = pfx;
			}

			if folders.ct > 0 then
				var fa: lib.str.acc fa:pool(&co.srv.pool,128)
				var fldr = co:pgetv('folder')
				for i=0,folders.ct do
					var ule = lib.html.urlenc(&co.srv.pool,folders(i), true) -- defer ule:free()
					var san = lib.html.sanitize(&co.srv.pool,folders(i), true) -- defer san:free()
					fa:lpush('<a href="'):ppush(pfx):lpush('/media?folder='):ppush(ule)
						:lpush('">'):ppush(san):lpush('</a>')
					lib.dbg('checking folder ',{fldr.ptr,fldr.ct},' against ',{folders(i).ptr,folders(i).ct})
					if fldr:ref() and folders(i):cmp(fldr)
						then folder = folders(i) lib.dbg('folder match ',{fldr.ptr,fldr.ct})
						else folders(i):free()
					end
................................................................................
			end

			if owner then
				view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>'
			end

			var md = co.srv:artifact_enum_uid(uid, folder)
			var gallery: lib.str.acc gallery:pool(&co.srv.pool,256)
			var files: lib.str.acc files:pool(&co.srv.pool,256) 
			for i=0,md.ct do
				var desc = lib.smackdown.html(&co.srv.pool,pstr{md(i)(0).desc,0}, true) --defer desc:free()
				if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
					gallery:lpush('<a class="thumb" href="'):ppush(pfx):lpush('/media/a/')
						:push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0)
						:lpush('"><div class="caption">'):ppush(desc)
						:lpush('</div></a>')
				else
					var mime = lib.html.sanitize(&co.srv.pool,pstr{md(i)(0).mime,0}, true) --defer mime:free() --just in case
					files:lpush('<a class="file" href="'):ppush(pfx):lpush('/media/a/')
						:push(md(i)(0).url,0):lpush('"><span class="label">'):ppush(desc)
						:lpush('</span> <span class="mime">'):ppush(mime)
						:lpush('</span></a>')
				end
				md(i):free()
			end
................................................................................

			view.images = gallery:finalize()
			view.directory = files:finalize()

			if acc ~= nil then
				view:append(acc)
			else
				var pg = view:poolstr(&co.srv.pool) -- defer pg:free()


				co:stdpage([lib.srv.convo.page] {
					title = P'media';
					class = P'media manager';
					cache = false;
					body = pg;
				})

			end

			--view.images:free()
			--view.directory:free()
			--if view.folders.ct > 0 then view.folders:free() end
			if folder.ct > 0 then folder:free() end
			if md:ref() then md:free() end
		end
		--if not owner then pfx:free() end
	return end

	::e404:: co:complain(404,'media not found','no such media exists on this server')
end

return render_media_gallery

Modified render/nav.t from [0fd87c81ae] to [9f2c55cf5b].

1
2
3
4
5
6
7
8
9
10
11
-- vim: ft=terra
local terra 
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 accesskey="t" href="/">timeline</a>')
	end
	if co.who ~= nil then
		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
		t:push(co.who.handle,0)



|







1
2
3
4
5
6
7
8
9
10
11
-- vim: ft=terra
local terra 
render_nav(co: &lib.srv.convo)
	var t = co:stra(64)
	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
		t:lpush(' <a accesskey="t" href="/">timeline</a>')
	end
	if co.who ~= nil then
		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
		t:push(co.who.handle,0)

Modified render/notices.t from [99b348e685] to [745afb2f25].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
..
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
	
	if notes.ct == 0 then
		co:complain(200,'no news is good news',"you don't have any notices to review")
		return
	end
	defer notes:free()

	var pg: lib.str.acc pg:init(512) defer pg:free()
	var pflink: lib.str.acc pflink:init(64)
	var body: lib.str.acc body:init(256)
	var latest: lib.store.timepoint = 0
	for i=0,notes.ct do
		if notes(i).when > latest then latest = notes(i).when end
		var who = co.srv:actor_fetch_uid(notes(i).who) defer who:free()
		if not who then lib.bail('schema integrity violation: nonexistent actor referenced in notification, this is almost certainly an SQL error or bug in the backend implementation') end
		pflink:cue(lib.str.sz(who(0).xid) + 4)
		if who(0).origin == 0 then pflink:lpush('/')
................................................................................
				n.kind = P'reply'
				n.act = P'replied to your post'
				notweet = false
			end
		else goto skip end
		do var idbuf: int8[lib.math.shorthand.maxlen]
			var idlen = lib.math.shorthand.gen(notes(i).what, idbuf)
			var b = lib.smackdown.html(pstr {ptr=what(0).body,ct=0},true) defer b:free()
			body:lpush(' <a class="quote" href="/post/'):push(&idbuf[0],idlen):lpush('">'):ppush(b):lpush('</a>')
		end
		if not notweet then
			var reply = co.srv:post_fetch(notes(i).reply)
				lib.render.tweet(co,reply.ptr,&body)
			reply:free()
		end
................................................................................
		n.ref = pstr {ptr = body.buf, ct = body.sz}

		n:append(&pg)
		::skip:: n.nym:free()
		         pflink:reset()
				 body:reset()
	end
	pflink:free()
	pg:lpush('<form method="post"><button name="act" value="clear">clear all notices</button></form>')
	co:livepage([lib.srv.convo.page] {
		title = P'notices', class = P'notices';
		body = pstr {ptr = pg.buf, ct = pg.sz};
		cache = false;
	}, latest)
end

return render_notices







|
|
|







 







|







 







|









13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
..
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
	
	if notes.ct == 0 then
		co:complain(200,'no news is good news',"you don't have any notices to review")
		return
	end
	defer notes:free()

	var pg = co:stra(512) -- defer pg:free()
	var pflink = co:stra(64)
	var body = co:stra(256)
	var latest: lib.store.timepoint = 0
	for i=0,notes.ct do
		if notes(i).when > latest then latest = notes(i).when end
		var who = co.srv:actor_fetch_uid(notes(i).who) defer who:free()
		if not who then lib.bail('schema integrity violation: nonexistent actor referenced in notification, this is almost certainly an SQL error or bug in the backend implementation') end
		pflink:cue(lib.str.sz(who(0).xid) + 4)
		if who(0).origin == 0 then pflink:lpush('/')
................................................................................
				n.kind = P'reply'
				n.act = P'replied to your post'
				notweet = false
			end
		else goto skip end
		do var idbuf: int8[lib.math.shorthand.maxlen]
			var idlen = lib.math.shorthand.gen(notes(i).what, idbuf)
			var b = lib.smackdown.html(&co.srv.pool, pstr {ptr=what(0).body,ct=0},true) --defer b:free()
			body:lpush(' <a class="quote" href="/post/'):push(&idbuf[0],idlen):lpush('">'):ppush(b):lpush('</a>')
		end
		if not notweet then
			var reply = co.srv:post_fetch(notes(i).reply)
				lib.render.tweet(co,reply.ptr,&body)
			reply:free()
		end
................................................................................
		n.ref = pstr {ptr = body.buf, ct = body.sz}

		n:append(&pg)
		::skip:: n.nym:free()
		         pflink:reset()
				 body:reset()
	end
	--pflink:free()
	pg:lpush('<form method="post"><button name="act" value="clear">clear all notices</button></form>')
	co:livepage([lib.srv.convo.page] {
		title = P'notices', class = P'notices';
		body = pstr {ptr = pg.buf, ct = pg.sz};
		cache = false;
	}, latest)
end

return render_notices

Modified render/nym.t from [ea921b8ffe] to [c2ab22760a].

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
render_nym(who: &lib.store.actor, scope: uint64, tgt: &lib.str.acc, minimal: bool)
	var acc: lib.str.acc
	var n: &lib.str.acc
	if tgt ~= nil then n = tgt else
		n = &acc
		n:init(128)
	end










	var xidsan = lib.html.sanitize(cs(who.xid),false)
	if who.nym ~= nil and who.nym[0] ~= 0 then
		var nymsan = lib.html.sanitize(cs(who.nym),false)
		n:lpush('<span class="nym">'):ppush(nymsan)
			:lpush('</span> [<span class="handle">'):ppush(xidsan)
			:lpush('</span>]')
		nymsan:free()
	else n:lpush('<span class="handle">'):ppush(xidsan):lpush('</span>') end
	xidsan:free()

	if not minimal then
		if who.epithet ~= nil then
			var episan = lib.html.sanitize(cs(who.epithet),false)
			n:lpush('<span class="epithet">'):ppush(episan):lpush('</span>')
			episan:free()
		end
	end
	

	-- TODO: if scope == chat room then lookup titles in room member db
	if tgt == nil then
		return n:finalize()
	else return pstr.null() end
end

return render_nym







>
>
>
>
>
>
>
>
>
>
|

|



|

|



|

|



>







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
render_nym(who: &lib.store.actor, scope: uint64, tgt: &lib.str.acc, minimal: bool)
	var acc: lib.str.acc
	var n: &lib.str.acc
	if tgt ~= nil then n = tgt else
		n = &acc
		n:init(128)
	end

	var pool_obj: lib.mem.pool
	var pool: &lib.mem.pool
	if tgt ~= nil and tgt.pool ~= nil then
		pool = tgt.pool
	else
		pool_obj:init(128)
		pool = &pool_obj
	end

	var xidsan = lib.html.sanitize(pool,cs(who.xid),false)
	if who.nym ~= nil and who.nym[0] ~= 0 then
		var nymsan = lib.html.sanitize(pool,cs(who.nym),false)
		n:lpush('<span class="nym">'):ppush(nymsan)
			:lpush('</span> [<span class="handle">'):ppush(xidsan)
			:lpush('</span>]')
		--nymsan:free()
	else n:lpush('<span class="handle">'):ppush(xidsan):lpush('</span>') end
	--xidsan:free()

	if not minimal then
		if who.epithet ~= nil then
			var episan = lib.html.sanitize(pool,cs(who.epithet),false)
			n:lpush('<span class="epithet">'):ppush(episan):lpush('</span>')
			--episan:free()
		end
	end
	
	if pool == &pool_obj then pool:free() end
	-- TODO: if scope == chat room then lookup titles in room member db
	if tgt == nil then
		return n:finalize()
	else return pstr.null() end
end

return render_nym

Modified render/profile.t from [3525ca58bc] to [a033243372].

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
..
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
	co: &lib.srv.convo,
	actor: &lib.store.actor,
	relationship: &lib.store.relationship
): pstr
	var aux: lib.str.acc
	var followed = false -- FIXME
	if co.aid ~= 0 and co.who.id == actor.id then
		aux:compose('<a accesskey="a" class="button" href="/conf/profile?go=/@',actor.handle,'">alter</a>')
	elseif co.aid ~= 0 then
		if not relationship.rel.follow() then
			aux:compose('<button accesskey="f" method="post" class="pos" name="act" value="follow">follow</button>')
		elseif relationship.rel.follow() then
			aux:compose('<button accesskey="f" method="post" class="neg" name="act" value="unfollow">unfollow</button>')
		end
		aux:lpush('<a accesskey="h" class="button" href="/'):push(actor.xid,0):lpush('/chat">chat</a>')
		if co.who.rights.powers:affect_users() and co.who:overpowers(actor) then
			aux:lpush('<a accesskey="n" class="button" href="/'):push(actor.xid,0):lpush('/ctl">control</a>')
		end
	else
		aux:compose('<a accesskey="f" class="button" href="/', actor.xid, '/follow">remote follow</a>')
	end
	var auxp = aux:finalize()
	var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, &timestr[0])

	var strfbuf: int8[28*4]
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
	var bio = lib.str.plit '<em style="opacity:0.6">tall, dark, and mysterious</em>'
	if actor.bio ~= nil then
		bio = lib.smackdown.html(cs(actor.bio),false)
	end
	var fullname = lib.render.nym(actor,0,nil,false) defer fullname:free()
	var comments: lib.str.acc comments:init(64)

	if co.srv.cfg.master == actor.id then
		var foundertxt = lib.str.plit 'founder'
		if co.srv.cfg.ui_cue_founder:ref() then
			if co.srv.cfg.ui_cue_founder.ct == 0 -- empty string, suppress field
				then foundertxt = pstr.null()
				else foundertxt = co.srv.cfg.ui_cue_founder
................................................................................

		remarks = '';

		auxbtn = auxp;
	}
	if comments.sz > 0 then profile.remarks = comments:finalize() end

	var ret = profile:tostr()
	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







|


|

|






|












|


|







 







|
|
|





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
..
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
	co: &lib.srv.convo,
	actor: &lib.store.actor,
	relationship: &lib.store.relationship
): pstr
	var aux: lib.str.acc
	var followed = false -- FIXME
	if co.aid ~= 0 and co.who.id == actor.id then
		aux:pcompose(&co.srv.pool,'<a accesskey="a" class="button" href="/conf/profile?go=/@',actor.handle,'">alter</a>')
	elseif co.aid ~= 0 then
		if not relationship.rel.follow() then
			aux:pcompose(&co.srv.pool,'<button accesskey="f" method="post" class="pos" name="act" value="follow">follow</button>')
		elseif relationship.rel.follow() then
			aux:pcompose(&co.srv.pool,'<button accesskey="f" method="post" class="neg" name="act" value="unfollow">unfollow</button>')
		end
		aux:lpush('<a accesskey="h" class="button" href="/'):push(actor.xid,0):lpush('/chat">chat</a>')
		if co.who.rights.powers:affect_users() and co.who:overpowers(actor) then
			aux:lpush('<a accesskey="n" class="button" href="/'):push(actor.xid,0):lpush('/ctl">control</a>')
		end
	else
		aux:pcompose(&co.srv.pool,'<a accesskey="f" class="button" href="/', actor.xid, '/follow">remote follow</a>')
	end
	var auxp = aux:finalize()
	var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, &timestr[0])

	var strfbuf: int8[28*4]
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
	var bio = lib.str.plit '<em style="opacity:0.6">tall, dark, and mysterious</em>'
	if actor.bio ~= nil then
		bio = lib.smackdown.html(&co.srv.pool,cs(actor.bio),false)
	end
	var fullname = lib.render.nym(actor,0,nil,false) defer fullname:free()
	var comments: lib.str.acc comments:pool(&co.srv.pool,64)

	if co.srv.cfg.master == actor.id then
		var foundertxt = lib.str.plit 'founder'
		if co.srv.cfg.ui_cue_founder:ref() then
			if co.srv.cfg.ui_cue_founder.ct == 0 -- empty string, suppress field
				then foundertxt = pstr.null()
				else foundertxt = co.srv.cfg.ui_cue_founder
................................................................................

		remarks = '';

		auxbtn = auxp;
	}
	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 render/timeline.t from [ab5808172b] to [7375f87c90].

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
..
41
42
43
44
45
46
47
48
49
50
			from_time = stoptime;
			to_idx = 64;
		})
	elseif mode == modes.fediglobal then
	elseif mode == modes.circle then
	end

	var acc: lib.str.acc acc:init(1024)
	acc:lpush('<div id="tl" data-live="10">')
	var newest: lib.store.timepoint = 0
	for i = 0, posts.sz do
		lib.render.tweet(co, posts(i).ptr, &acc)
		var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
		if t > newest then newest = t end
		posts(i):free()
................................................................................
	var doc = [lib.srv.convo.page] {
		title = lib.str.plit'timeline';
		body = acc:finalize();
		class = lib.str.plit'timeline';
		cache = false;
	}
	co:livepage(doc,newest)
	doc.body:free()
end
return render_timeline







|







 







|


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
..
41
42
43
44
45
46
47
48
49
50
			from_time = stoptime;
			to_idx = 64;
		})
	elseif mode == modes.fediglobal then
	elseif mode == modes.circle then
	end

	var acc: lib.str.acc acc:pool(&co.srv.pool,1024)
	acc:lpush('<div id="tl" data-live="10">')
	var newest: lib.store.timepoint = 0
	for i = 0, posts.sz do
		lib.render.tweet(co, posts(i).ptr, &acc)
		var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
		if t > newest then newest = t end
		posts(i):free()
................................................................................
	var doc = [lib.srv.convo.page] {
		title = lib.str.plit'timeline';
		body = acc:finalize();
		class = lib.str.plit'timeline';
		cache = false;
	}
	co:livepage(doc,newest)
	--doc.body:free()
end
return render_timeline

Modified render/tweet-page.t from [c0b864229b] to [304c6c3aff].

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
..
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
render_tweet_page(
	co: &lib.srv.convo,
	path: lib.mem.ptr(pref),
	p: &lib.store.post
): {}
	var livetime = co.srv:thread_latest_arrival_calc(p.id)

	var pg: lib.str.acc pg:init(256)
	pg:lpush('<div data-live="10">') -- make the OP refresh too
	lib.render.tweet(co, p, &pg)
	pg:lpush('</div>')

	if co.aid ~= 0 then
		pg:lpush('<form class="action-bar" method="post">')
		if not co.srv:post_liked_uid(co.who.id, p.id)
................................................................................
	render_tweet_replies(co, &pg, p.id)
	pg:lpush('</div>')

	if co.aid ~= 0 and co.who.rights.powers.post() then
		lib.render.compose(co, nil, &pg)
	end

	var ppg = pg:finalize() defer ppg:free()
	co:livepage([lib.srv.convo.page] {
		title = lib.str.plit 'post'; cache = false;
		class = lib.str.plit 'post'; body = ppg;
	}, livetime)

	-- TODO display conversation
	-- perhaps display descendant nodes here, and have a link to the top of the whole tree?
end

return render_tweet_page







|







 







|










26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
..
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
render_tweet_page(
	co: &lib.srv.convo,
	path: lib.mem.ptr(pref),
	p: &lib.store.post
): {}
	var livetime = co.srv:thread_latest_arrival_calc(p.id)

	var pg = co:stra(256)
	pg:lpush('<div data-live="10">') -- make the OP refresh too
	lib.render.tweet(co, p, &pg)
	pg:lpush('</div>')

	if co.aid ~= 0 then
		pg:lpush('<form class="action-bar" method="post">')
		if not co.srv:post_liked_uid(co.who.id, p.id)
................................................................................
	render_tweet_replies(co, &pg, p.id)
	pg:lpush('</div>')

	if co.aid ~= 0 and co.who.rights.powers.post() then
		lib.render.compose(co, nil, &pg)
	end

	var ppg = pg:finalize() --defer ppg:free()
	co:livepage([lib.srv.convo.page] {
		title = lib.str.plit 'post'; cache = false;
		class = lib.str.plit 'post'; body = ppg;
	}, livetime)

	-- TODO display conversation
	-- perhaps display descendant nodes here, and have a link to the top of the whole tree?
end

return render_tweet_page

Modified render/tweet.t from [83917dbbe9] to [57f4f6bb5e].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
..
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
120
121
		retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr
	end

	::foundauth::
	var timestr: int8[26] lib.osclock.ctime_r(&p.posted, &timestr[0])
	for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄

	var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
	defer bhtml:free()

	var idbuf: int8[lib.math.shorthand.maxlen]
	var idlen = lib.math.shorthand.gen(p.id, idbuf)
	var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
	var fullname = lib.render.nym(author,0,nil, false) defer fullname:free()
	var tpl = data.view.tweet {
		text = bhtml;
		subject = cs(lib.coalesce(p.subject,''));
		nym = fullname;
		when = cs(&timestr[0]);
		avatar = cs(author.avatar);
................................................................................
		var parent = co.srv:post_fetch(p.parent) defer parent:free()
		if not parent then
			lib.bail('schema integrity violation - could not match post to parent')
		end
		var pauth = co.srv:actor_fetch_uid(parent(0).author) defer pauth:free()
		var pidbuf: int8[lib.math.shorthand.maxlen]
		var pidlen = lib.math.shorthand.gen(p.parent, pidbuf)
		var pa: lib.str.acc pa:init(128)
		pa:lpush('<small>in reply to <a class="username" href="/post/'):push(&pidbuf[0],pidlen):lpush('">')
		lib.render.nym(pauth.ptr,0,&pa,true)
		pa:lpush('</a></small>')
		tpl.extra = pa:finalize()
	end
	if p.rts + p.likes > 0 then
		var s: lib.str.acc s:init(128)
		s:lpush('<div class="stats">')
		if p.rts   > 0 then s:lpush('<div class="rt">'  ):dpush(p.rts  ):lpush('</div>') end
		if p.likes > 0 then s:lpush('<div class="like">'):dpush(p.likes):lpush('</div>') end
		s:lpush('</div>')
		tpl.stats = s:finalize()
	end

................................................................................
		attrcur = lib.str.cpy(attrcur, '"')
	end
	if co.aid ~= 0 and p.author == co.who.id then attrcur = lib.str.cpy(attrcur, ' data-own') end
	if retweeter ~= nil then attrcur = lib.str.cpy(attrcur, ' data-rt') end

	if attrcur ~= &attrbuf[0] then tpl.attr = &attrbuf[0] end

	defer tpl.permalink:free()
	if acc ~= nil then
		if retweeter ~= nil then push_promo_header(co, acc, retweeter, p.rtact) end
		tpl:append(acc)
		if retweeter ~= nil then acc:lpush('</div>') end
		if p.rts + p.likes > 0 then tpl.stats:free() end
		if tpl.extra.ct > 0 then tpl.extra:free() end
		return [lib.mem.ptr(int8)]{ptr=nil,ct=0}
	end

	if retweeter ~= nil then
		var rta: lib.str.acc rta:init(512)
		push_promo_header(co, &rta, retweeter, p.rtact)
		tpl:append(&rta) rta:lpush('</div>')
		if tpl.extra.ct > 0 then tpl.extra:free() end
		return rta:finalize()
	else
		var txt = tpl:tostr()
		if tpl.extra.ct > 0 then tpl.extra:free() end
		if p.rts + p.likes > 0 then tpl.stats:free() end
		return txt
	end
end
return render_tweet







|
|



|







 







|






|







 







|




|
|




|


|


|
|
|




34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
..
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
120
121
		retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr
	end

	::foundauth::
	var timestr: int8[26] lib.osclock.ctime_r(&p.posted, &timestr[0])
	for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄

	var bhtml = lib.smackdown.html(&co.srv.pool, [lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
	--defer bhtml:free()

	var idbuf: int8[lib.math.shorthand.maxlen]
	var idlen = lib.math.shorthand.gen(p.id, idbuf)
	var permalink: lib.str.acc permalink:pool(&co.srv.pool, 7+idlen):lpush('/post/'):push(idbuf,idlen)
	var fullname = lib.render.nym(author,0,nil, false) defer fullname:free()
	var tpl = data.view.tweet {
		text = bhtml;
		subject = cs(lib.coalesce(p.subject,''));
		nym = fullname;
		when = cs(&timestr[0]);
		avatar = cs(author.avatar);
................................................................................
		var parent = co.srv:post_fetch(p.parent) defer parent:free()
		if not parent then
			lib.bail('schema integrity violation - could not match post to parent')
		end
		var pauth = co.srv:actor_fetch_uid(parent(0).author) defer pauth:free()
		var pidbuf: int8[lib.math.shorthand.maxlen]
		var pidlen = lib.math.shorthand.gen(p.parent, pidbuf)
		var pa: lib.str.acc pa:pool(&co.srv.pool, 128)
		pa:lpush('<small>in reply to <a class="username" href="/post/'):push(&pidbuf[0],pidlen):lpush('">')
		lib.render.nym(pauth.ptr,0,&pa,true)
		pa:lpush('</a></small>')
		tpl.extra = pa:finalize()
	end
	if p.rts + p.likes > 0 then
		var s: lib.str.acc s:pool(&co.srv.pool,128)
		s:lpush('<div class="stats">')
		if p.rts   > 0 then s:lpush('<div class="rt">'  ):dpush(p.rts  ):lpush('</div>') end
		if p.likes > 0 then s:lpush('<div class="like">'):dpush(p.likes):lpush('</div>') end
		s:lpush('</div>')
		tpl.stats = s:finalize()
	end

................................................................................
		attrcur = lib.str.cpy(attrcur, '"')
	end
	if co.aid ~= 0 and p.author == co.who.id then attrcur = lib.str.cpy(attrcur, ' data-own') end
	if retweeter ~= nil then attrcur = lib.str.cpy(attrcur, ' data-rt') end

	if attrcur ~= &attrbuf[0] then tpl.attr = &attrbuf[0] end

	--defer tpl.permalink:free()
	if acc ~= nil then
		if retweeter ~= nil then push_promo_header(co, acc, retweeter, p.rtact) end
		tpl:append(acc)
		if retweeter ~= nil then acc:lpush('</div>') end
		--if p.rts + p.likes > 0 then tpl.stats:free() end
		--if tpl.extra.ct > 0 then tpl.extra:free() end
		return [lib.mem.ptr(int8)]{ptr=nil,ct=0}
	end

	if retweeter ~= nil then
		var rta: lib.str.acc rta:pool(&co.srv.pool,512)
		push_promo_header(co, &rta, retweeter, p.rtact)
		tpl:append(&rta) rta:lpush('</div>')
		--if tpl.extra.ct > 0 then tpl.extra:free() end
		return rta:finalize()
	else
		var txt = tpl:poolstr(&co.srv.pool)
		--if tpl.extra.ct > 0 then tpl.extra:free() end
		--if p.rts + p.likes > 0 then tpl.stats:free() end
		return txt
	end
end
return render_tweet

Modified render/user-page.t from [e4691ec838] to [be37fdb666].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
39
40
41
42
43
44
45
46
47
48
49
	if co.aid ~= 0 and co.who.id == actor.id then
		ti:compose('my profile')
	else
		ti:compose('profile :: ', actor.handle)
	end
	var tiptr = ti:finalize()

	var acc: lib.str.acc acc:init(1024)
	var pftxt = lib.render.profile(co,actor,relationship) defer pftxt:free()
	acc:ppush(pftxt)

	var stoptime = lib.osclock.time(nil)
	var posts = co.srv:post_enum_author_uid(actor.id, lib.store.range {
		mode = 1; -- T->I
		from_time = stoptime;
		to_idx = 64;
................................................................................
	co:livepage([lib.srv.convo.page] {
		title = tiptr; body = bdf;
		class = lib.str.plit 'profile';
		cache = false;
	}, newest)

	tiptr:free()
	bdf:free()
end

return render_userpage







|
|







 







|



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
39
40
41
42
43
44
45
46
47
48
49
	if co.aid ~= 0 and co.who.id == actor.id then
		ti:compose('my profile')
	else
		ti:compose('profile :: ', actor.handle)
	end
	var tiptr = ti:finalize()

	var acc: lib.str.acc acc:pool(&co.srv.pool, 1024)
	var pftxt = lib.render.profile(co,actor,relationship) --defer pftxt:free()
	acc:ppush(pftxt)

	var stoptime = lib.osclock.time(nil)
	var posts = co.srv:post_enum_author_uid(actor.id, lib.store.range {
		mode = 1; -- T->I
		from_time = stoptime;
		to_idx = 64;
................................................................................
	co:livepage([lib.srv.convo.page] {
		title = tiptr; body = bdf;
		class = lib.str.plit 'profile';
		cache = false;
	}, newest)

	tiptr:free()
	--bdf:free()
end

return render_userpage

Modified route.t from [2b1fe64d41] to [a232000d86].

248
249
250
251
252
253
254
255

256
257
258
259
260

261
262
263
264
265
266
267
...
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
				else
					conf = data.view.confirm {
						title = lib.str.plit 'cancel retweet';
						query = lib.str.plit 'are you sure you want to undo this retweet?';
						cancel = lib.str.plit'/';
					}
				end
				var body = conf:tostr() defer body:free()

				co:stdpage([lib.srv.convo.page] {
					title = lib.str.plit 'post :: delete';
					class = lib.str.plit 'query';
					body = body; cache = false;
				})

				return
			elseif meth == method.post then
				var act = co:ppostv('act')
				if act:cmp(lib.str.plit 'confirm') then
					if post:ref() then
						post(0).source:post_destroy(post(0).id)
					elseif rt.kind ~= 0 then
................................................................................

terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t, uid: uint64)
	if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
		if meth == method.get then
			var view = data.view.media_upload {
				folders = ''
			}
			var pg = view:tostr() defer pg:free()
			co:stdpage([lib.srv.convo.page] {
				title = lib.str.plit'media :: upload';
				class = lib.str.plit'media upload';
				cache = false; body = pg;
			})
		elseif meth == method.post_file then
			var desc = pstring.null()







|
>





>







 







|







248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
...
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
				else
					conf = data.view.confirm {
						title = lib.str.plit 'cancel retweet';
						query = lib.str.plit 'are you sure you want to undo this retweet?';
						cancel = lib.str.plit'/';
					}
				end
				var fr = co.srv.pool:frame()
				var body = conf:poolstr(&co.srv.pool) --defer body:free()
				co:stdpage([lib.srv.convo.page] {
					title = lib.str.plit 'post :: delete';
					class = lib.str.plit 'query';
					body = body; cache = false;
				})
				co.srv.pool:reset(fr)
				return
			elseif meth == method.post then
				var act = co:ppostv('act')
				if act:cmp(lib.str.plit 'confirm') then
					if post:ref() then
						post(0).source:post_destroy(post(0).id)
					elseif rt.kind ~= 0 then
................................................................................

terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t, uid: uint64)
	if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
		if meth == method.get then
			var view = data.view.media_upload {
				folders = ''
			}
			var pg = view:poolstr(&co.srv.pool) -- defer pg:free()
			co:stdpage([lib.srv.convo.page] {
				title = lib.str.plit'media :: upload';
				class = lib.str.plit'media upload';
				cache = false; body = pg;
			})
		elseif meth == method.post_file then
			var desc = pstring.null()

Modified smackdown.t from [e99ea3e622] to [ad4d039b1c].

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
...
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
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or not isws(@(sl-1)) then return sl-nc end
	return nil
end

terra m.html(input: pstr, firstline: bool)
	if input.ptr == nil then return pstr.null() end
	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end

	var md = lib.html.sanitize(input,false)

	var styled: lib.str.acc styled:init(md.ct)

	do var i = 0 while i < md.ct do
		--var wordstart = (i == 0 or isws(md.ptr[i-1]))
		--var wordend = (i == md.ct - 1 or isws(md.ptr[i+1]))
		var wordstart = (i + 1 < md.ct and not isws(md.ptr[i+1]))
		var wordend = (i == md.ct - 1 or not isws(md.ptr[i-1]))

................................................................................
				goto skip
			end
		end

		::fallback::styled:push(here,1) -- :/
		i = i + 1
	::skip::end end
	md:free()

	-- we make two passes: the first detects and transforms inline elements,
	-- the second carries out block-level organization

	var html: lib.str.acc html:init(styled.sz)
	var s = state {
		segt = segt.none;
		bqlvl = 0;
		curpos = md.ptr;
		blockstart = nil;
	}
	while s.curpos < md.ptr + md.ct do
		s.curpos = s.curpos + 1
	end 

		html:free() -- JUST FOR NOW
	return styled:finalize()
end

return m







|



|

|







 







|




|










|




52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
...
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
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or not isws(@(sl-1)) then return sl-nc end
	return nil
end

terra m.html(pool: &lib.mem.pool, input: pstr, firstline: bool)
	if input.ptr == nil then return pstr.null() end
	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end

	var md = lib.html.sanitize(pool,input,false)

	var styled: lib.str.acc styled:pool(pool,md.ct)

	do var i = 0 while i < md.ct do
		--var wordstart = (i == 0 or isws(md.ptr[i-1]))
		--var wordend = (i == md.ct - 1 or isws(md.ptr[i+1]))
		var wordstart = (i + 1 < md.ct and not isws(md.ptr[i+1]))
		var wordend = (i == md.ct - 1 or not isws(md.ptr[i-1]))

................................................................................
				goto skip
			end
		end

		::fallback::styled:push(here,1) -- :/
		i = i + 1
	::skip::end end
	--md:free()

	-- we make two passes: the first detects and transforms inline elements,
	-- the second carries out block-level organization

	var html: lib.str.acc html:pool(pool,styled.sz)
	var s = state {
		segt = segt.none;
		bqlvl = 0;
		curpos = md.ptr;
		blockstart = nil;
	}
	while s.curpos < md.ptr + md.ct do
		s.curpos = s.curpos + 1
	end 

		--html:free() -- JUST FOR NOW
	return styled:finalize()
end

return m

Modified srv.t from [80adbc5ad3] to [854410a8ca].

5
6
7
8
9
10
11

12
13
14
15
16
17
18
..
20
21
22
23
24
25
26

27
28
29
30
31
32
33
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321






322
323
324
325
326
327
328
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
...
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
...
677
678
679
680
681
682
683


684
685
686
687
688
689
690
691
692
693
694
695
696

697
698
699
700
701
702
703
...
853
854
855
856
857
858
859

860
861
862
863
864
865
866
...
890
891
892
893
894
895
896

897
898
899
900
901
902
903
904
905
906












907
908
909
910
911
912
913
...
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
...
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
local struct srv
local struct cfgcache {
	secret: pstring
	pol_sec: secmode.t
	pol_reg: bool
	credmgd: bool
	maxupsz: intptr

	instance: pstring
	overlord: &srv
	ui_cue_staff: pstring
	ui_cue_founder: pstring
	ui_hue: uint16
	nranks: uint16
	maxinvites: uint16
................................................................................
}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
	id: rawstring

}

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

terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
	var conf = data.view.confirm {
		title = title;
		query = msg;
		cancel = cancel;
	}
	var ti: lib.str.acc ti:compose('confirm :: ', title)
	var body = conf:tostr() defer body:free()
	var cf = [convo.page] {
		title = ti:finalize();
		class = lib.str.plit '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
................................................................................
		end
	in ok end
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
	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
................................................................................
terra convo:ppostv(name: rawstring)
	var s,l = self:postv(name)
	return pstring { ptr = s, ct = l }
end

terra convo:getv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
................................................................................
				var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival')
				if livelast_p ~= nil and livelast_p.ptr ~= nil then
					var ll, ok = lib.math.decparse(pstring{ptr = livelast_p.ptr, ct = livelast_p.ct - 1})
					if ok then co.live_last = ll 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
					for i = 0,msg.uri.len do
						if msg.uri.ptr[i] == @'+'
							then uri.ptr[i] = @' '
................................................................................
							bsr:free()
							upmap:free()
						end
					end
				end

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


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

				::fail::
				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end
				co.actorcache:free()

			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)
................................................................................
		lib.bail('could not connect to any data sources!')
	end
end

terra srv:start(iname: rawstring)
	self:conprep(lib.store.prepmode.full)
	self.cfg:init(self)

	var dbbind = self:conf_get('bind')
	if iname == nil then iname = lib.proc.getenv('parsav_instance') end
	if iname == nil then
		self.id = self.cfg.instance.ptr;
		-- let this leak -- it'll be needed for the lifetime of the process anyway
	else self.id = iname end 

................................................................................
terra srv:shutdown()
	lib.net.mg_mgr_free(&self.webmgr)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
		src:close()
	end
	self.sources:free()

end

terra cfgcache:cfint(name: rawstring, default: intptr)
	var str = self.overlord:conf_get(name)
	if str.ptr ~= nil then
		var i,ok = lib.math.decparse(str)
		if ok then default = i else
			lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected integer; using default value instead')
		end
		str:free()












	end
	return default
end

terra cfgcache:cfbool(name: rawstring, default: bool)
	var str = self.overlord:conf_get(name)
	if str.ptr ~= nil then
................................................................................
		if lib.str.cmp(sreg.ptr, 'managed') == 0
			then self.credmgd = true
			else self.credmgd = false
		end
		sreg:free()
	end end

	do self.maxupsz = [1024 * 100] -- 100 kilobyte default
	var sreg = self.overlord:conf_get('maximum-artifact-size')
	if sreg:ref() then
		var sz, ok = lib.math.fsz_parse(sreg)
		if ok then self.maxupsz = sz else
			lib.warn('invalid configuration value for maximum-artifact-size; keeping default 100K upload limit')
		end
		sreg:free() end
	end
	
	self.pol_sec = secmode.lockdown
	var smode = self.overlord:conf_get('policy-security')
	if smode.ptr ~= nil then
		if lib.str.cmp(smode.ptr, 'public') == 0 then
			self.pol_sec = secmode.public
		elseif lib.str.cmp(smode.ptr, 'private') == 0 then
................................................................................
	self.nranks = self:cfint('user-ranks',10)
	self.maxinvites = self:cfint('max-invites',64)
	
	var webmaster = self.overlord:conf_get('master')
	if webmaster:ref() then defer webmaster:free()
		var wma = self.overlord:actor_fetch_xid(webmaster)
		if not wma then
			lib.warn('the webmaster specified in the configuration store does not seem to exist or is not known to this instance; preceding as if no master defined. if the master is a remote user, you can rectify this with the `actor ',{webmaster.ptr,webmaster.ct},' instantiate` and `conf refresh` commands')
		else
			self.master = wma(0).id
			wma:free()
		end
	end

	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')







>







 







>







 







|
|






|
>
>
>
>
>
>







 







|







 







|







 







|







 







>
>








<

|
|

>







 







>







 







>










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







 







|
|
<
<
<
<
<
<
<







 







|







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
...
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
...
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
...
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701

702
703
704
705
706
707
708
709
710
711
712
713
...
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
...
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
...
961
962
963
964
965
966
967
968
969







970
971
972
973
974
975
976
...
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
local struct srv
local struct cfgcache {
	secret: pstring
	pol_sec: secmode.t
	pol_reg: 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
................................................................................
}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	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()
................................................................................

terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
	var conf = data.view.confirm {
		title = title;
		query = msg;
		cancel = cancel;
	}
	var ti: lib.str.acc ti:pcompose(&self.srv.pool,'confirm :: ', title)
	var body = conf:poolstr(&self.srv.pool) -- defer body:free()
	var cf = [convo.page] {
		title = ti:finalize();
		class = lib.str.plit '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.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
................................................................................
		end
	in ok end
end)

-- CALL ONLY ONCE PER VAR
terra convo:postv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = self.srv.pool:alloc(int8, self.msg.body.len + self.msg.query.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
................................................................................
terra convo:ppostv(name: rawstring)
	var s,l = self:postv(name)
	return pstring { ptr = s, ct = l }
end

terra convo:getv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = self.srv.pool:alloc(int8, self.msg.query.len + self.msg.body.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o + 1
		@(self.vbofs - 1) = 0
................................................................................
				var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival')
				if livelast_p ~= nil and livelast_p.ptr ~= nil then
					var ll, ok = lib.math.decparse(pstring{ptr = livelast_p.ptr, ct = livelast_p.ct - 1})
					if ok then co.live_last = ll end
				end


				var uridec = server.pool:alloc(int8, msg.uri.len)
				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
					for i = 0,msg.uri.len do
						if msg.uri.ptr[i] == @'+'
							then uri.ptr[i] = @' '
................................................................................
							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
					co.uploads:free()
				end


				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				-- if co.varbuf.ptr ~= nil then co.varbuf:free() end
				-- if co.navbar.ptr ~= nil then co.navbar:free() end
				co.actorcache:free()
				server.pool:clear()
			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)
................................................................................
		lib.bail('could not connect to any data sources!')
	end
end

terra srv:start(iname: rawstring)
	self:conprep(lib.store.prepmode.full)
	self.cfg:init(self)
	self.pool:init(self.cfg.poolinitsz)
	var dbbind = self:conf_get('bind')
	if iname == nil then iname = lib.proc.getenv('parsav_instance') end
	if iname == nil then
		self.id = self.cfg.instance.ptr;
		-- let this leak -- it'll be needed for the lifetime of the process anyway
	else self.id = iname end 

................................................................................
terra srv:shutdown()
	lib.net.mg_mgr_free(&self.webmgr)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
		src:close()
	end
	self.sources:free()
	self.pool:free()
end

terra cfgcache:cfint(name: rawstring, default: intptr)
	var str = self.overlord:conf_get(name)
	if str.ptr ~= nil then
		var i,ok = lib.math.decparse(str)
		if ok then default = i else
			lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected integer; using default value instead')
		end
		str:free()
	end
	return default
end

terra cfgcache:cffsz(name: rawstring, default: intptr)
	var str = self.overlord:conf_get(name)
	if str:ref() then
		var sz, ok = lib.math.fsz_parse(str)
		if ok then default = sz else
			lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected byte length; using default value instead')
		end
		str:free()
	end
	return default
end

terra cfgcache:cfbool(name: rawstring, default: bool)
	var str = self.overlord:conf_get(name)
	if str.ptr ~= nil then
................................................................................
		if lib.str.cmp(sreg.ptr, 'managed') == 0
			then self.credmgd = true
			else self.credmgd = false
		end
		sreg:free()
	end end

	self.maxupsz = self:cffsz('maximum-artifact-size', [1024 * 100]) -- 100 kilobyte default
	self.poolinitsz = self:cffsz('server-pool-size-initial', [1024 * 10]) -- 10 kilobyte default







	
	self.pol_sec = secmode.lockdown
	var smode = self.overlord:conf_get('policy-security')
	if smode.ptr ~= nil then
		if lib.str.cmp(smode.ptr, 'public') == 0 then
			self.pol_sec = secmode.public
		elseif lib.str.cmp(smode.ptr, 'private') == 0 then
................................................................................
	self.nranks = self:cfint('user-ranks',10)
	self.maxinvites = self:cfint('max-invites',64)
	
	var webmaster = self.overlord:conf_get('master')
	if webmaster:ref() then defer webmaster:free()
		var wma = self.overlord:actor_fetch_xid(webmaster)
		if not wma then
			lib.warn('the webmaster specified in the configuration store does not seem to exist or is not known to this instance; preceding as if no master defined. if the master is a remote user, you can rectify this with the `actor "',{webmaster.ptr,webmaster.ct},'" instantiate` and `conf refresh` commands')
		else
			self.master = wma(0).id
			wma:free()
		end
	end

	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')

Modified str.t from [d98a573fe7] to [89921fa130].

107
108
109
110
111
112
113

114
115
116
117
118
119
120
...
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
160
161
162
163
164
...
169
170
171
172
173
174
175

176
177
178



179

180
181
182
183
184
185
186
...
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
204
...
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
...
263
264
265
266
267
268
269
270
271
272
273
274
275







276
277
278
279
280
281
282
end

struct m.acc {
	buf: rawstring
	sz: intptr
	run: intptr
	space: intptr

}

terra m.cdowncase(c: int8)
	if c >= @'A' and c <= @'Z' then
		return c + (@'a' - @'A')
	else return c end
end
................................................................................

local terra biggest(a: intptr, b: intptr)
	if a > b then return a else return b end
end

terra m.acc:init(run: intptr)
	--lib.dbg('initializing string accumulator')

	if run == 0 then
		lib.warn('attempted to allocate zero-length string accumulator')
		self.buf = nil
	else
		self.buf = [rawstring](lib.mem.heapa_raw(run))
		if self.buf == nil then
			lib.warn('string buffer allocation failed, very little memory availble')
................................................................................
		end
	end
	self.run = lib.trn(self.buf == nil, 0, run)
	self.space = self.run
	self.sz = 0
	return self
end;










terra m.acc:free()
	--lib.dbg('freeing string accumulator')




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

	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')
................................................................................
	self.buf = nil
	self.sz = 0
	return pt
end;

terra m.acc:cue(sz: intptr)
	if sz <= self.run then return end

	self.run = sz
	if self.space - self.sz < self.run then
		self.space = self.sz + self.run



		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))

	end
end

terra m.acc:reset() -- semantic convenience function
	self.sz = 0
end

................................................................................
	if str == nil then return self end
	--if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output
	-- lib.dbg('pushing "',{str,llen},'" onto accumulator')
	if self.buf == nil then self:init(self.run) end
	if self.buf == nil then lib.warn('attempted to push string onto unallocated accumulator') return self end
	if len == 0 then len = m.sz(str) end
	if len >= self.space - self.sz then

		self.space = self.space + biggest(self.run,len + 1)
		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
	end
	lib.mem.cpy(self.buf + self.sz, str, len)
	self.sz = self.sz + len
	self.buf[self.sz] = 0
	return self
end;

................................................................................
	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
		if type(v) == 'table' then
			local gl = 16 -- guess wildly
			if v.tree and v.tree.type.convertible == 'tuple' then
				pstrs[#pstrs+1] = {str = `v._0, len = `v._1}
................................................................................
			else pstrs[#pstrs+1] = {str = v, len = 0} end
			minlen = minlen + gl
		elseif type(v) == 'string' then 
			pstrs[#pstrs+1] = {str = v, len = #v}
			minlen = minlen + #v + 1
		else error('invalid type in compose expression') end
	end
	local call = `self:init(minlen)
	for i,v in ipairs(pstrs) do
		call = `[call]:push([v.str],[v.len])
	end
	return call
end)







m.acc.metamethods.__lshift = terralib.overloadedfunction('(<<)', {
	terra(self: &m.acc, str: rawstring)         return self: push(str,0) end;
	terra(self: &m.acc, str: lib.mem.ptr(int8)) return self:ppush(str  ) end;
})

m.box = terralib.memoize(function(ty)
	local b = struct {







>







 







>







 







>
>
>
>
>
>
>
>
>



>
>
>
>







>







 







>



>
>
>
|
>







 







>
|
|







 







|







 







|




|
>
>
>
>
>
>
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
...
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
...
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
end

struct m.acc {
	buf: rawstring
	sz: intptr
	run: intptr
	space: intptr
	pool: &lib.mem.pool
}

terra m.cdowncase(c: int8)
	if c >= @'A' and c <= @'Z' then
		return c + (@'a' - @'A')
	else return c end
end
................................................................................

local terra biggest(a: intptr, b: intptr)
	if a > b then return a else return b end
end

terra m.acc:init(run: intptr)
	--lib.dbg('initializing string accumulator')
	self.pool = nil
	if run == 0 then
		lib.warn('attempted to allocate zero-length string accumulator')
		self.buf = nil
	else
		self.buf = [rawstring](lib.mem.heapa_raw(run))
		if self.buf == nil then
			lib.warn('string buffer allocation failed, very little memory availble')
................................................................................
		end
	end
	self.run = lib.trn(self.buf == nil, 0, run)
	self.space = self.run
	self.sz = 0
	return self
end;

terra m.acc:pool(pool: &lib.mem.pool, run: intptr)
	self.buf = [&int8](pool:alloc_bytes(run))
	self.pool = pool
	self.run = run
	self.space = self.run
	self.sz = 0
	return self
end

terra m.acc:free()
	--lib.dbg('freeing string accumulator')
	if self.pool ~= nil then
		lib.dbg('attempted to free pooled string accumulator; use frame-reset instead')
		return
	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')
................................................................................
	self.buf = nil
	self.sz = 0
	return pt
end;

terra m.acc:cue(sz: intptr)
	if sz <= self.run then return end
	var curspace = self.space
	self.run = sz
	if self.space - self.sz < self.run then
		self.space = self.sz + self.run
		if self.pool ~= nil then
			self.buf = [&int8](self.pool:realloc_bytes(self.buf, curspace, self.space))
		else
			self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
		end
	end
end

terra m.acc:reset() -- semantic convenience function
	self.sz = 0
end

................................................................................
	if str == nil then return self end
	--if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output
	-- lib.dbg('pushing "',{str,llen},'" onto accumulator')
	if self.buf == nil then self:init(self.run) end
	if self.buf == nil then lib.warn('attempted to push string onto unallocated accumulator') return self end
	if len == 0 then len = m.sz(str) end
	if len >= self.space - self.sz then
		self:cue(self.space + biggest(self.run,len + 1))
		--self.space = self.space + biggest(self.run,len + 1)
		--self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
	end
	lib.mem.cpy(self.buf + self.sz, str, len)
	self.sz = self.sz + len
	self.buf[self.sz] = 0
	return self
end;

................................................................................
	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;
local composefn = function(call, ...)
	local minlen = 0
	local pstrs = {}
	for i,v in ipairs{...} do
		if type(v) == 'table' then
			local gl = 16 -- guess wildly
			if v.tree and v.tree.type.convertible == 'tuple' then
				pstrs[#pstrs+1] = {str = `v._0, len = `v._1}
................................................................................
			else pstrs[#pstrs+1] = {str = v, len = 0} end
			minlen = minlen + gl
		elseif type(v) == 'string' then 
			pstrs[#pstrs+1] = {str = v, len = #v}
			minlen = minlen + #v + 1
		else error('invalid type in compose expression') end
	end
	call = call(minlen) --`self:init(minlen)
	for i,v in ipairs(pstrs) do
		call = `[call]:push([v.str],[v.len])
	end
	return call
end
m.acc.methods.compose = macro(function(self, ...)
	return composefn(function(minlen) return `self:init(minlen) end, ...)
end)
m.acc.methods.pcompose = macro(function(self, pool, ...)
	return composefn(function(minlen) return `self:pool(pool,minlen) end, ...)
end)

m.acc.metamethods.__lshift = terralib.overloadedfunction('(<<)', {
	terra(self: &m.acc, str: rawstring)         return self: push(str,0) end;
	terra(self: &m.acc, str: lib.mem.ptr(int8)) return self:ppush(str  ) end;
})

m.box = terralib.memoize(function(ty)
	local b = struct {

Modified tpl.t from [9f68e11c52] to [db48a650e7].

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
120
121
122
123
124
125
126
127
128
...
139
140
141
142
143
144
145










146
147
148
149
150


151
152
153

154
155
156






157
158
159
160

161
162
163


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

	local copiers = {}
	local senders = {}
	local appenders = {}
	local symtxt = symbol(lib.mem.ptr(int8))
	local cpypos = symbol(&opaque)

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

	local tid = tplspec.id or '<anonymous>'










	rec.methods.tostr = terra([symself])
		lib.dbg(['compiling template ' .. tid])
		[tallyup]
		var [symtxt] = lib.mem.heapa(int8, [runningtally])
		var [cpypos] = [&opaque](symtxt.ptr)


		[copiers]
		@[&int8](cpypos) = 0
		symtxt.ct = [&int8](cpypos) - symtxt.ptr

		return symtxt
	end
	rec.methods.append = terra([symself], [accumulator])






		lib.dbg(['appending template ' .. tid])
		[tallyup]
		accumulator:cue([runningtally])
		[appenders]

		return accumulator
	end
	rec.methods.head = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))


		lib.dbg(['transmitting template headers ' .. tid])
		[tallyup]
		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
		for i = 0, hd.ct do
			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
		end
		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)

	end
	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))


		lib.dbg(['transmitting template ' .. tid])

		symself:head(destcon,code,hd)

		[senders]
		lib.net.mg_send([destcon],'\r\n',2)

	end
	rec.methods.sz = terra([symself])
		lib.dbg(['tallying template ' .. tid])
		[tallyup]
		return [runningtally] + 1
	end

	return rec
end

return m







>











|






|






|







 







>
>
>
>
>
>
>
>
>
>





>
>



>



>
>
>
>
>
>




>



>
>







>


>
>






>











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
120
121
122
123
124
125
126
127
128
129
...
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
	end

	local copiers = {}
	local senders = {}
	local appenders = {}
	local symtxt = symbol(lib.mem.ptr(int8))
	local cpypos = symbol(&opaque)
	local pool = symbol(&lib.mem.pool)
	local accumulator = symbol(&lib.str.acc)
	local destcon = symbol(&lib.net.mg_connection)
	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]
................................................................................
			appenders[#appenders+1] = quote
				if fp.ct > 0 then [accumulator]:ppush(fp) end
			end
		end
	end

	local tid = tplspec.id or '<anonymous>'
	rec.methods.poolstr = terra([symself],[pool])
		lib.dbg(['pooling template ' .. tid])
		[tallyup]
		var [symtxt] = pool:alloc(int8, [runningtally])
		var [cpypos] = [&opaque](symtxt.ptr)
		[copiers]
		@[&int8](cpypos) = 0
		symtxt.ct = [&int8](cpypos) - symtxt.ptr
		return symtxt
	end
	rec.methods.tostr = terra([symself])
		lib.dbg(['compiling template ' .. tid])
		[tallyup]
		var [symtxt] = lib.mem.heapa(int8, [runningtally])
		var [cpypos] = [&opaque](symtxt.ptr)
		var p: lib.mem.pool p:init(128)
		var [pool] = &p
		[copiers]
		@[&int8](cpypos) = 0
		symtxt.ct = [&int8](cpypos) - symtxt.ptr
		pool:free()
		return symtxt
	end
	rec.methods.append = terra([symself], [accumulator])
		var [pool]
		var p: lib.mem.pool
		if [accumulator].pool == nil then
			p:init(128)
			pool = &p
		else pool = [accumulator].pool end
		lib.dbg(['appending template ' .. tid])
		[tallyup]
		accumulator:cue([runningtally])
		[appenders]
		if [accumulator].pool == nil then p:free() end
		return accumulator
	end
	rec.methods.head = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
		var p: lib.mem.pool p:init(128) -- FIXME
		var [pool] = &p
		lib.dbg(['transmitting template headers ' .. tid])
		[tallyup]
		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
		for i = 0, hd.ct do
			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
		end
		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)
		p:free()
	end
	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
		var p: lib.mem.pool p:init(128) -- FIXME
		var [pool] = &p
		lib.dbg(['transmitting template ' .. tid])

		symself:head(destcon,code,hd)

		[senders]
		lib.net.mg_send([destcon],'\r\n',2)
		p:free()
	end
	rec.methods.sz = terra([symself])
		lib.dbg(['tallying template ' .. tid])
		[tallyup]
		return [runningtally] + 1
	end

	return rec
end

return m