parsav  Check-in [419d1a1ebe]

Overview
Comment:milestone
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 419d1a1ebe4ca565be0130e83bed00227fee120c08c3d46605168be664120074
User & Date: lexi on 2020-12-22 23:01:30
Other Links: manifest | tags
Context
2020-12-25
03:59
big ol iteration check-in: 5b3a03ad34 user: lexi tags: trunk
2020-12-22
23:01
milestone check-in: 419d1a1ebe user: lexi tags: trunk
2020-12-21
01:08
continued iteration check-in: 25e05466d5 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [2e402a5b93] to [17bd63f8c3].

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
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76










































77
78
79
80
81
82
83
..
85
86
87
88
89
90
91

92
93
94
95
96
97
98
99
100
101
102
103
104

105
106
107
108
109
110
111
...
176
177
178
179
180
181
182









183
184
185
186
187
188
189
...
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
...
345
346
347
348
349
350
351











352
353
354
355
356
357
358
...
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

411

412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446








447


























448
				bio, rank, quota, key
			from parsav_actors
				where id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {rawstring}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key, $1::text,



				coalesce(s.domain,
				        (select value from parsav_config
							where key='domain' limit 1)) as domain

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id

			where $1::text = (a.handle || '@' || domain) or
			      $1::text = ('@' || a.handle || '@' || domain) or


				  (a.origin is null and $1::text = ('@' || a.handle))
		]];
	};

	actor_enum_local = {
		params = {}, sql = [[
			select id, nym, handle, origin,
			       bio, rank, quota, key,
................................................................................
		]];
	};

	actor_enum = {
		params = {}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key,
				a.handle ||'@'||
				coalesce(s.domain,
				        (select value from parsav_config
							where key='domain' limit 1)) as xid
			from parsav_actors a
			left join parsav_servers s on s.id = a.origin
		]];
	};










































}

local struct pqr {
	sz: intptr
	res: &lib.pq.PGresult
}
terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
................................................................................
	return (lib.pq.PQgetisnull(self.res, row, col) == 1)
end
terra pqr:len(row: intptr, col: intptr)
	return lib.pq.PQgetlength(self.res, row, col)
end
terra pqr:cols() return lib.pq.PQnfields(self.res) end
terra pqr:string(row: intptr, col: intptr) -- not to be exported!!

	var v = lib.pq.PQgetvalue(self.res, row, col)
--	var r: lib.mem.ptr(int8)
--	r.ct = lib.str.sz(v)
--	r.ptr = v
	return v
end
terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
	return [lib.mem.ptr(uint8)] {
		ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
		ct = lib.pq.PQgetlength(self.res, row, col);
	}
end
terra pqr:String(row: intptr, col: intptr) -- suitable to be exported

	var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) }
	s.ct = lib.pq.PQgetlength(self.res, row, col)
	return s
end
terra pqr:bool(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	if @v == 0x01 then return true else return false end
................................................................................
	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
	for i, ty in ipairs(q.params) do
		args[i] = symbol(ty)
		ft[i] = `1
		if ty == rawstring then
			counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
			casts[i] = `[&int8]([args[i]])









		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			fixers[#fixers + 1] = quote
				--lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
................................................................................
			return pqr {ct, res}
		end
	end
end

local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)

	if r:cols() >= 8 then 
		a = [ lib.str.encapsulate(lib.store.actor, {
			nym = {`r:string(row, 1); `r:len(row,1) + 1};

			handle = {`r:string(row, 2); `r:len(row,2) + 1};
			bio = {`r:string(row, 4); `r:len(row,4) + 1};
			xid = {`r:string(row, 8); `r:len(row,8) + 1};
		}) ]
	else
		a = [ lib.str.encapsulate(lib.store.actor, {
			nym = {`r:string(row, 1); `r:len(row,1) + 1};
			handle = {`r:string(row, 2); `r:len(row,2) + 1};
			bio = {`r:string(row, 4); `r:len(row,4) + 1};
		}) ]
		a.ptr.xid = nil
	end
	a.ptr.id = r:int(uint64, row, 0);
	a.ptr.rights = lib.store.rights_default();
	a.ptr.rights.rank = r:int(uint16, row, 5);
	a.ptr.rights.quota = r:int(uint32, row, 6);
................................................................................
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.source = src
			return a
		end
	end];












	actor_enum = [terra(src: &lib.store.source)
		var r = queries.actor_enum.exec(src)
		if r.sz == 0 then
			return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var mem = lib.mem.heapa([&lib.store.actor], r.sz)
................................................................................
	end];

	actor_auth_how = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: rawstring
		)
		var authview = src:conf_get('auth-source') defer authview:free()
		var a: lib.str.acc defer a:free()
		a:compose('with mts as (select a.kind from ',authview,[' ' .. sqlsquash [[as a
			left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.netmask is null or a.netmask >> $2::inet) and
				blacklist = false)

			select
				(select count(*) from mts where kind like 'pw-%') > 0,
				(select count(*) from mts where kind like 'otp-%') > 0,
				(select count(*) from mts where kind like 'challenge-%') > 0,
				(select count(*) from mts where kind = 'trust') > 0 ]]]) -- cheat
		var cs: lib.store.credset cs:clear();
		var ipbuf: int8[20]
		;[pqt[lib.store.inet](false)](ip, [&uint8](&ipbuf))
		var ipbl: intptr if ip.pv == 4 then ipbl = 8 else ipbl = 20 end
		var params = arrayof(rawstring, username, [&int8](&ipbuf))
		var params_sz = arrayof(int, lib.str.sz(username), ipbl)
		var params_ft = arrayof(int, 1, 1)
		var res = lib.pq.PQexecParams([&lib.pq.PGconn](src.handle), a.buf, 2, nil,
			params, params_sz, params_ft, 1)
		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			if res == nil then
				lib.bail('grievous error occurred checking for auth methods')
			end
			lib.bail('could not get auth methods for user ',username,':\n',lib.pq.PQresultErrorMessage(res))
		end
		var r = pqr { res = res, sz = lib.pq.PQntuples(res) } 

		if r.sz == 0 then return cs end -- just in case

		(cs.pw << r:bool(0,0))
		(cs.otp << r:bool(0,1))
		(cs.challenge << r:bool(0,2))
		(cs.trust << r:bool(0,3))
		lib.pq.PQclear(res)
		return cs
	end];
	 
	actor_auth_pw = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: rawstring,
			cred: rawstring
		)
		var authview = src:conf_get('auth-source') defer authview:free()
		var a: lib.str.acc defer a:free()
		a:compose('select a.aid from ',authview,[' ' .. sqlsquash [[as a
			left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
				(a.netmask is null or a.netmask >> $4::inet)
			order by blacklist desc limit 1]]])

		[ checksha(`src.handle, `a.buf, 256, ip, username, cred) ] -- most common
		[ checksha(`src.handle, `a.buf, 512, ip, username, cred) ] -- most secure
		[ checksha(`src.handle, `a.buf, 384, ip, username, cred) ] -- weird
		[ checksha(`src.handle, `a.buf, 224, ip, username, cred) ] -- weirdest

		-- TODO: check pbkdf2-hmac
		-- TODO: check OTP
		return 0
	end];
}



































return b







|

|
>
>











>
>
|







 







|
|
<
<




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







 







>













>







 







>
>
>
>
>
>
>
>
>







 







>


|
>

<




|
|
|







 







>
>
>
>
>
>
>
>
>
>
>







 







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

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

>




<









|
<
<






|

|
|
|
|





|
>
>
>
>
>
>
>
>

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

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
66
67
68
69
70
71
72
73
74


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
...
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
...
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
...
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
...
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
...
440
441
442
443
444
445
446















447















448
449
450
451
452
453
454

455
456
457
458
459
460
461
462
463
464


465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
				bio, rank, quota, key
			from parsav_actors
				where id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {lib.mem.ptr(int8)}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key, 
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

				coalesce(s.domain,
				        (select value from parsav_config
							where key='domain' limit 1)) as domain

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id

			where $1::text = (a.handle || '@' || domain) or
			      $1::text = ('@' || a.handle || '@' || domain) or
				  (a.origin is null and
					  $1::text = a.handle or
					  $1::text = ('@' || a.handle))
		]];
	};

	actor_enum_local = {
		params = {}, sql = [[
			select id, nym, handle, origin,
			       bio, rank, quota, key,
................................................................................
		]];
	};

	actor_enum = {
		params = {}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid


			from parsav_actors a
			left join parsav_servers s on s.id = a.origin
		]];
	};

	actor_auth_how = {
		params = {rawstring, lib.store.inet}, sql = [[
		with mts as (select a.kind from parsav_auth as a
			left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.netmask is null or a.netmask >> $2::inet) and
				blacklist = false)

			select
				(select count(*) from mts where kind like 'pw-%') > 0,
				(select count(*) from mts where kind like 'otp-%') > 0,
				(select count(*) from mts where kind like 'challenge-%') > 0,
				(select count(*) from mts where kind = 'trust') > 0
		]]; -- cheat
	};

	actor_session_fetch = {
		params = {uint64, lib.store.inet}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

			       au.restrict,
						array['post'  ] <@ au.restrict as can_post,
						array['edit'  ] <@ au.restrict as can_edit,
						array['acct'  ] <@ au.restrict as can_acct,
						array['upload'] <@ au.restrict as can_upload,
						array['censor'] <@ au.restrict as can_censor,
						array['admin' ] <@ au.restrict as can_admin

			from      parsav_auth au
			left join parsav_actors a     on au.uid = a.id
			left join parsav_servers s    on a.origin = s.id

			where au.aid = $1::bigint and au.blacklist = false and
				(au.netmask is null or au.netmask >> $2::inet)
		]];
	};
}

local struct pqr {
	sz: intptr
	res: &lib.pq.PGresult
}
terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
................................................................................
	return (lib.pq.PQgetisnull(self.res, row, col) == 1)
end
terra pqr:len(row: intptr, col: intptr)
	return lib.pq.PQgetlength(self.res, row, col)
end
terra pqr:cols() return lib.pq.PQnfields(self.res) end
terra pqr:string(row: intptr, col: intptr) -- not to be exported!!
	if self:null(row,col) then return nil end
	var v = lib.pq.PQgetvalue(self.res, row, col)
--	var r: lib.mem.ptr(int8)
--	r.ct = lib.str.sz(v)
--	r.ptr = v
	return v
end
terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
	return [lib.mem.ptr(uint8)] {
		ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
		ct = lib.pq.PQgetlength(self.res, row, col);
	}
end
terra pqr:String(row: intptr, col: intptr) -- suitable to be exported
	if self:null(row,col) then return [lib.mem.ptr(int8)] {ptr=nil,ct=0} end
	var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) }
	s.ct = lib.pq.PQgetlength(self.res, row, col)
	return s
end
terra pqr:bool(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	if @v == 0x01 then return true else return false end
................................................................................
	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
	for i, ty in ipairs(q.params) do
		args[i] = symbol(ty)
		ft[i] = `1
		if ty == rawstring then
			counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
			casts[i] = `[&int8]([args[i]])
		elseif ty == lib.store.inet then -- assume not CIDR
			counters[i] = `lib.trn([args[i]].pv == 4,4,16)+4
			casts[i] = quote
				var ipbuf: int8[20]
				;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf))
			in &ipbuf[0] end
		elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then
			counters[i] = `[args[i]].ct
			casts[i] = `[&int8]([args[i]].ptr)
		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			fixers[#fixers + 1] = quote
				--lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
................................................................................
			return pqr {ct, res}
		end
	end
end

local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)

	if r:cols() >= 8 then 
		a = [ lib.str.encapsulate(lib.store.actor, {
			nym = {`r:string(row,1), `r:len(row,1)+1};
			bio = {`r:string(row,4), `r:len(row,4)+1};
			handle = {`r:string(row, 2); `r:len(row,2) + 1};

			xid = {`r:string(row, 8); `r:len(row,8) + 1};
		}) ]
	else
		a = [ lib.str.encapsulate(lib.store.actor, {
			nym = {`r:string(row,1), `r:len(row,1)+1};
			bio = {`r:string(row,4), `r:len(row,4)+1};
			handle = {`r:string(row, 2); `r:len(row,2) + 1};
		}) ]
		a.ptr.xid = nil
	end
	a.ptr.id = r:int(uint64, row, 0);
	a.ptr.rights = lib.store.rights_default();
	a.ptr.rights.rank = r:int(uint16, row, 5);
	a.ptr.rights.quota = r:int(uint32, row, 6);
................................................................................
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.source = src
			return a
		end
	end];

	actor_fetch_xid = [terra(src: &lib.store.source, xid: lib.mem.ptr(int8))
		var r = queries.actor_fetch_xid.exec(src, xid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.source = src
			return a
		end
	end];

	actor_enum = [terra(src: &lib.store.source)
		var r = queries.actor_enum.exec(src)
		if r.sz == 0 then
			return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var mem = lib.mem.heapa([&lib.store.actor], r.sz)
................................................................................
	end];

	actor_auth_how = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: rawstring
		)















		var cs: lib.store.credset cs:clear();















		var r = queries.actor_auth_how.exec(src, username, ip) 
		if r.sz == 0 then return cs end -- just in case
		defer r:free()
		(cs.pw << r:bool(0,0))
		(cs.otp << r:bool(0,1))
		(cs.challenge << r:bool(0,2))
		(cs.trust << r:bool(0,3))

		return cs
	end];
	 
	actor_auth_pw = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: rawstring,
			cred: rawstring
		)
		var q = [[select a.aid from parsav_auth as a


			left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
				(a.netmask is null or a.netmask >> $4::inet)
			order by blacklist desc limit 1]]

		[ checksha(`src.handle, q, 256, ip, username, cred) ] -- most common
		[ checksha(`src.handle, q, 512, ip, username, cred) ] -- most secure
		[ checksha(`src.handle, q, 384, ip, username, cred) ] -- weird
		[ checksha(`src.handle, q, 224, ip, username, cred) ] -- weirdest

		-- TODO: check pbkdf2-hmac
		-- TODO: check OTP
		return 0
	end];

	actor_session_fetch = [terra(
		src: &lib.store.source,
		aid: uint64,
		ip : lib.store.inet
	): { lib.stat(lib.store.auth), lib.mem.ptr(lib.store.actor) }
		var r = queries.actor_session_fetch.exec(src, aid, ip)
		if r.sz == 0 then goto fail end
		do defer r:free()

			if r:null(0,0) then goto fail end

			var a = row_to_actor(&r, 0)
			a.ptr.source = src

			var au = [lib.stat(lib.store.auth)] { ok = true }
			au.val.aid = aid
			au.val.uid = a.ptr.id
			if not r:null(0,10) then -- restricted?
				au.val.privs:clear()
				(au.val.privs.post   << r:bool(0,11)) 
				(au.val.privs.edit   << r:bool(0,12))
				(au.val.privs.acct   << r:bool(0,13))
				(au.val.privs.upload << r:bool(0,14))
				(au.val.privs.censor << r:bool(0,15))
				(au.val.privs.admin  << r:bool(0,16))
			else au.val.privs:fill() end

			return au, a
		end

		::fail:: return [lib.stat   (lib.store.auth) ] { ok = false        },
			            [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
	end];
}

return b

Modified cmdparse.t from [bad20dd1d0] to [50677a3c0c].

2
3
4
5
6
7
8
9
10
11
12



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

28
29








30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
return function(tbl)
	local options = terralib.types.newstruct('options') do
		local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
		local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
		options.entries = {
			{field = 'arglist', type = lib.mem.ptr(rawstring)}
		}
		local shortcases, longcases, init = {}, {}, {}
		local self = symbol(&options)
		local arg = symbol(rawstring)
		local idxo = symbol(uint)



		local skip = label()
		local sanitize = function(s) return s:gsub('_','-') end
		for o,desc in pairs(tbl) do
			local consume = desc[3] or 0
			options.entries[#options.entries + 1] = {
				field = o, type = (consume > 0) and uint or bool
			}
			helpstr = helpstr .. string.format('    -%s --%s: %s\n',
				desc[1], sanitize(o), desc[2])
		end
		for o,desc in pairs(tbl) do
			local flag = desc[1]
			local consume = desc[3] or 0
			init[#init + 1] = quote [self].[o] = [consume > 0 and 0 or false] end
			local ch if consume > 0 then ch = quote

				[self].[o] = idxo
				idxo = idxo + consume








			end else ch = quote
				[self].[o] = true
			end end
			shortcases[#shortcases + 1] = quote
				case [int8]([string.byte(flag)]) then [ch] end
			end
			longcases[#longcases + 1] = quote
				if lib.str.cmp([arg]+2, [sanitize(o)]) == 0 then [ch] goto [skip] end
			end
		end
		terra options:free() self.arglist:free() end
		options.methods.parse = terra([self], argc: int, argv: &rawstring)
			[init]
			var parseopts = true
			var [idxo] = 1
			self.arglist = lib.mem.heapa(rawstring, argc)
			var finalargc = 0
			for i=1,argc do
				var [arg] = argv[i]

				if arg[0] == ('-')[0] and parseopts then
					if arg[1] == ('-')[0] then -- long option
						if arg[2] == 0 then -- last option
							parseopts = false
						else [longcases] end
					else -- short options
						var j = 1
						while arg[j] ~= 0 do
							switch arg[j] do [shortcases] end
							j = j + 1
						end
					end
				else
					self.arglist.ptr[finalargc] = arg
					finalargc = finalargc + 1
				end
				::[skip]::
			end

			if finalargc == 0 then self.arglist:free()
							  else self.arglist:resize(finalargc) end
		end
		options.helptxt = helpstr
	end
	return options
end







|


|
>
>
>





|







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










|


|


|
|
>
|
|




<
|










>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
return function(tbl)
	local options = terralib.types.newstruct('options') do
		local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
		local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
		options.entries = {
			{field = 'arglist', type = lib.mem.ptr(rawstring)}
		}
		local shortcases, longcases, init, verifiers = {}, {}, {}, {}
		local self = symbol(&options)
		local arg = symbol(rawstring)
		local idx = symbol(uint)
		local argv = symbol(&rawstring)
		local argc = symbol(int)
		local optstack = symbol(intptr)
		local skip = label()
		local sanitize = function(s) return s:gsub('_','-') end
		for o,desc in pairs(tbl) do
			local consume = desc[3] or 0
			options.entries[#options.entries + 1] = {
				field = o, type = (consume > 0) and &rawstring or bool
			}
			helpstr = helpstr .. string.format('    -%s --%s: %s\n',
				desc[1], sanitize(o), desc[2])
		end
		for o,desc in pairs(tbl) do
			local flag = desc[1]
			local consume = desc[3] or 0
			init[#init + 1] = quote [self].[o] = [(consume > 0 and `nil) or false] end
			local ch if consume > 0 then
				ch = quote
					[self].[o] = argv+(idx+1+optstack)

					optstack = optstack + consume
				end
				verifiers[#verifiers+1] = quote
					var terminus = argv + argc
					if [self].[o] ~= nil and [self].[o] >= terminus then
						lib.bail(['missing argument for command line option ' .. sanitize(o)])
					end
				end
			else ch = quote
				[self].[o] = true
			end end
			shortcases[#shortcases + 1] = quote
				case [int8]([string.byte(flag)]) then [ch] end
			end
			longcases[#longcases + 1] = quote
				if lib.str.cmp([arg]+2, [sanitize(o)]) == 0 then [ch] goto [skip] end
			end
		end
		terra options:free() self.arglist:free() end
		options.methods.parse = terra([self], [argc], [argv])
			[init]
			var parseopts = true
			var [optstack] = 0
			self.arglist = lib.mem.heapa(rawstring, argc)
			var finalargc = 0
			for [idx]=1,argc do
				var [arg] = argv[idx]
				if optstack > 0 then optstack = optstack - 1 goto [skip] end
				if arg[0] == @'-' and parseopts then
					if arg[1] == @'-' then -- long option
						if arg[2] == 0 then -- last option
							parseopts = false
						else [longcases] end
					else -- short options

						var j = 1 while arg[j] ~= 0 do
							switch arg[j] do [shortcases] end
							j = j + 1
						end
					end
				else
					self.arglist.ptr[finalargc] = arg
					finalargc = finalargc + 1
				end
				::[skip]::
			end
			[verifiers]
			if finalargc == 0 then self.arglist:free()
							  else self.arglist:resize(finalargc) end
		end
		options.helptxt = helpstr
	end
	return options
end

Modified config.lua from [53779583db] to [7cb566b503].

36
37
38
39
40
41
42



43
44
45
46
47
48
49
		id = u.rndstr(6);
		release = u.ingest('release');
		when = os.date();
	};
	feat = {};
	backends = defaultlist('parsav_backends', 'pgsql');
	braingeniousmode = false;



}
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
	if u.ping '_FOSSIL_' then default_os = 'windows' end
	conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
	conf.build.checkout = (u.exec { 'fossil', 'sql',
		[[select value from localdb.vvar where name = 'checkout-hash']]
	}):gsub("^'(.*)'$", '%1')







>
>
>







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
		id = u.rndstr(6);
		release = u.ingest('release');
		when = os.date();
	};
	feat = {};
	backends = defaultlist('parsav_backends', 'pgsql');
	braingeniousmode = false;
	embeds = {
		{'style.css', 'text/css'};
	};
}
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
	if u.ping '_FOSSIL_' then default_os = 'windows' end
	conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
	conf.build.checkout = (u.exec { 'fossil', 'sql',
		[[select value from localdb.vvar where name = 'checkout-hash']]
	}):gsub("^'(.*)'$", '%1')

Modified crypt.t from [709e2a6426] to [48369b50e0].

13
14
15
16
17
18
19








20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to

local ctx = lib.pk.mbedtls_pk_context

local struct hashalg { id: uint8 bytes: intptr }
local m = {
	pemfile = uint8[const.maxpemsz];








	alg = {
		sha1 =   `hashalg {id = lib.md.MBEDTLS_MD_SHA1; bytes = 160/8};
		sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = 256/8};
		sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = 512/8};
		sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = 384/8};
		sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = 224/8};
		-- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!!
	};
}
local callbacks = {}
if config.feat.randomizer == 'kern' then
	local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
		return rnd(dest, sz, 0)
	end
elseif config.feat.randomizer == 'devfs' then







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







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
const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to

local ctx = lib.pk.mbedtls_pk_context

local struct hashalg { id: uint8 bytes: intptr }
local m = {
	pemfile = uint8[const.maxpemsz];
	algsz = {
		sha1 =   160/8;
		sha256 = 256/8;
		sha512 = 512/8;
		sha384 = 384/8;
		sha224 = 224/8;
	}
}
m.alg = {
	sha1 =   `hashalg {id = lib.md.MBEDTLS_MD_SHA1;   bytes = m.algsz.sha1};
	sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = m.algsz.sha256};
	sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = m.algsz.sha512};
	sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = m.algsz.sha384};
	sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = m.algsz.sha224};
	-- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!!
};

local callbacks = {}
if config.feat.randomizer == 'kern' then
	local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
		return rnd(dest, sz, 0)
	end
elseif config.feat.randomizer == 'devfs' then

Modified file.t from [fc7770c3f7] to [a196c3def2].

1
2
3
4
5

6
7
8
9
10
11
12
..
61
62
63
64
65
66
67
68





























69
-- vim: ft=terra
-- TODO: add support for windows IO calls
local handle_type = int
local posix = terralib.includec 'fcntl.h'
local unistd = terralib.includec 'unistd.h'


struct file {
	handle: handle_type
	read: bool
	write: bool
}

................................................................................
		elseif wh == [file.seek.eof] then
			whence = unistd.SEEK_END
		else lib.bail('invalid seek mode') end
	
		return unistd.lseek(self.handle, ofs, whence)
	end;
}






























return file





>







 








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

1
2
3
4
5
6
7
8
9
10
11
12
13
..
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
-- vim: ft=terra
-- TODO: add support for windows IO calls
local handle_type = int
local posix = terralib.includec 'fcntl.h'
local unistd = terralib.includec 'unistd.h'
local mm = terralib.includec 'sys/mman.h'

struct file {
	handle: handle_type
	read: bool
	write: bool
}

................................................................................
		elseif wh == [file.seek.eof] then
			whence = unistd.SEEK_END
		else lib.bail('invalid seek mode') end
	
		return unistd.lseek(self.handle, ofs, whence)
	end;
}

terra file:len(): intptr
	var cur = self:seek(0, [file.seek.ofs])
	var sz = self:seek(0, [file.seek.eof])
	self:seek(cur, [file.seek.abs])
	return sz
end

local struct mapping {
	addr: &opaque
	sz: intptr
}
terra mapping:unmap()
	lib.dbg('releasing file mapping')
	return mm.munmap(self.addr, self.sz)
end
-- provide for syncing mechanism?

terra file:mapin(ofs: intptr, sz: intptr)
	var prot = 0
	if self.read then prot = mm.PROT_READ end 
	if self.write then prot = prot or mm.PROT_WRITE end
	if sz == 0 then sz = self:len() end
	lib.dbg('mapping file into memory')
	return mapping {
		addr = mm.mmap(nil, sz, prot, mm.MAP_PRIVATE, self.handle, ofs);
		sz = sz;
	}
end

return file

Modified http.t from [6d6c40fc94] to [e5e590a634].

1
2
3
4




5
6
7
8
9
10
11
..
15
16
17
18
19
20
21
22
23

24



25
26
27
28
29
30
31
..
36
37
38
39
40
41
42



































43
44
45
46
47
48
49
-- vim: ft=terra
local m = {}
local util = dofile('common.lua')





struct m.header {
	key: rawstring
	value: rawstring
}
struct m.page {
	respcode: uint16
	body: lib.mem.ptr(int8)
................................................................................
local resps = {
	[200] = 'OK';
	[201] = 'Created';
	[301] = 'Moved Permanently';
	[302] = 'Found';
	[303] = 'See Other';
	[307] = 'Temporary Redirect';
	[404] = 'Not Found';
	[401] = 'Unauthorized';

	[403] = 'Forbidden';



	[418] = 'I\'m a teapot';
	[405] = 'Method Not Allowed';
	[500] = 'Internal Server Error';
}
local resptext = symbol(rawstring)
local resplen = symbol(intptr)
local respbranches = {}
................................................................................
	end
end
m.codestr = terra(code: uint16)
	var [resptext] var [resplen]
	switch code do [respbranches] end
	return resptext, resplen
end



































m.page.methods = {
	free = terra(self: &m.page)
		self.body:free()
		self.headers:free()
	end;
	send = terra(self: &m.page, con: &lib.net.mg_connection)
		var code: rawstring




>
>
>
>







 







|

>

>
>
>







 







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







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
-- vim: ft=terra
local m = {}
local util = dofile('common.lua')

m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' }

m.findheader = terralib.externfunction('mg_http_get_header', {&lib.net.mg_http_message, rawstring} -> &lib.mem.ref(int8)) -- unfortunately necessary to access this function, as its return type conflicts with a function name

struct m.header {
	key: rawstring
	value: rawstring
}
struct m.page {
	respcode: uint16
	body: lib.mem.ptr(int8)
................................................................................
local resps = {
	[200] = 'OK';
	[201] = 'Created';
	[301] = 'Moved Permanently';
	[302] = 'Found';
	[303] = 'See Other';
	[307] = 'Temporary Redirect';
	[400] = 'Bad Request';
	[401] = 'Unauthorized';
	[402] = 'Payment Required';
	[403] = 'Forbidden';
	[404] = 'Not Found';
	[405] = 'Method Not Allowed';
	[406] = 'Not Acceptable';
	[418] = 'I\'m a teapot';
	[405] = 'Method Not Allowed';
	[500] = 'Internal Server Error';
}
local resptext = symbol(rawstring)
local resplen = symbol(intptr)
local respbranches = {}
................................................................................
	end
end
m.codestr = terra(code: uint16)
	var [resptext] var [resplen]
	switch code do [respbranches] end
	return resptext, resplen
end

terra m.hier(uri: lib.mem.ptr(int8)): lib.mem.ptr(lib.mem.ref(int8))
	if uri.ct == 0 then return [lib.mem.ptr(lib.mem.ref(int8))] { ptr = nil, ct = 0 } end
	var sz = 1
	var start = 0 if uri.ptr[0] == @'/' then start = 1 end
	for i = start, uri.ct do if uri.ptr[i] == @'/' then sz = sz + 1 end end
	var lst = lib.mem.heapa([lib.mem.ref(int8)], sz)
	if sz == 0 then
		lst.ptr[0].ptr = uri.ptr
		lst.ptr[0].ct = uri.ct
		return lst
	end

	var idx: intptr = 0
	var len: intptr = 0
	lst.ptr[0].ptr = uri.ptr
	for i = 0, uri.ct do
		if uri.ptr[i] == @'/' then
			if len == 0 then
				lst.ptr[idx].ptr = lst.ptr[idx].ptr + 1
				goto skip
			end
			lst.ptr[idx].ct = len
			idx = idx + 1
			lst.ptr[idx].ptr = uri.ptr + i + 1
			len = 0
		else
			len = len + 1
		end
	::skip::end
	lst.ptr[idx].ct = len
	
	return lst
end

m.page.methods = {
	free = terra(self: &m.page)
		self.body:free()
		self.headers:free()
	end;
	send = terra(self: &m.page, con: &lib.net.mg_connection)
		var code: rawstring

Modified makefile from [40c8adaeb1] to [2d2acfe121].

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	mkdir $@
# generate a shim static library so mongoose cooperates
# with the build apparatus. note that parsav is designed
# to be fronted by a real web server like nginx if SSL
# is to be used, so we don't turn on SSL in mongoose
lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h
	$(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \
		-DMG_ENABLE_THREADS \
		-DMG_ENABLE_IPV6 \
		-DMG_ENABLE_HTTP_WEBDAV \
		-DMG_ENABLE_HTTP_WEBSOCKET=0
	ar rcs $@ lib/mongoose/*.o
	ranlib $@

lib/json-c/Makefile: lib/json-c lib/json-c/CMakeLists.txt
	cd lib/json-c && cmake .
lib/json-c/libjson-c.a: lib/json-c/Makefile







|
|
|







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	mkdir $@
# generate a shim static library so mongoose cooperates
# with the build apparatus. note that parsav is designed
# to be fronted by a real web server like nginx if SSL
# is to be used, so we don't turn on SSL in mongoose
lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h
	$(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \
		-DMG_ENABLE_THREADS=1 \
		-DMG_ENABLE_IPV6=1 \
		-DMG_ENABLE_HTTP_WEBDAV=1 \
		-DMG_ENABLE_HTTP_WEBSOCKET=0
	ar rcs $@ lib/mongoose/*.o
	ranlib $@

lib/json-c/Makefile: lib/json-c lib/json-c/CMakeLists.txt
	cd lib/json-c && cmake .
lib/json-c/libjson-c.a: lib/json-c/Makefile

Modified math.t from [f642fd73ba] to [f661c3d77e].

78
79
80
81
82
83
84






























85
86
87
88
89
90
91
	for i = 0, len do
		var v, ok = m.shorthand.cval(s[i])
		if ok == false then return 0, false end
		val = (val * 64) + v
	end
	return val, true
end































terra m.hexdigit(hb: uint8): int8
	var a = hb and 0x0F
	if a < 10 then return 0x30 + a
	else return 0x61 + (a-10) end
end








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







78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
	for i = 0, len do
		var v, ok = m.shorthand.cval(s[i])
		if ok == false then return 0, false end
		val = (val * 64) + v
	end
	return val, true
end

terra m.truncate64(val: &uint8, len: intptr): uint64
	var r: uint64 = 0
	for i=0, len do
		r = r << 3
		r = r + @val
		val = val + 1
	end
	return r
end

m.biggest = macro(function(a,b)
	local ty = a.tree.type
	if b.tree.type.bytes > ty.bytes then ty = b.tree.type end
	return quote
		var _a = [a]
		var _b = [b]
		var r: ty if _a > _b then r = _a else r = _b end
	in r end
end)

m.smallest = macro(function(a,b)
	local ty = a.tree.type
	if b.tree.type.bytes < ty.bytes then ty = b.tree.type end
	return quote
		var _a = [a]
		var _b = [b]
		var r: ty if _a < _b then r = _a else r = _b end
	in r end
end)

terra m.hexdigit(hb: uint8): int8
	var a = hb and 0x0F
	if a < 10 then return 0x30 + a
	else return 0x61 + (a-10) end
end

Modified mem.t from [07d2cb4a7e] to [e41dd1991a].

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68


























69
70



71
72
73
74
75
76
77
	local p = m.ptr(ty:astype())
	return `p {
		ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz));
		ct = sz;
	}
end)

m.ptr = terralib.memoize(function(ty)
	local t = terralib.types.newstruct(string.format('ptr<%s>', ty))
	t.entries = {
		{'ptr', &ty};
		{'ct', intptr};
	}
	t.ptr_basetype = ty
	local recurse = false
	if ty:isstruct() then
		if ty.methods.free then recurse = true end
	end
	t.metamethods.__not = macro(function(self)
		return `self.ptr
	end)

	t.methods = {
		free = terra(self: &t): bool
			[recurse and quote
				self.ptr:free()
			end or {}]
			if self.ct > 0 then
				m.heapf(self.ptr)
				self.ct = 0
				return true
			end
			return false
		end;
		init = terra(self: &t, newct: intptr): bool
			var nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
			if nv ~= nil then
				self.ptr = nv
				self.ct = newct
				return true
			else return false end
		end;
		resize = terra(self: &t, newct: intptr): bool
			var nv: &ty
			if self.ct > 0
				then nv = [&ty](m.heapr_raw(self.ptr, sizeof(ty) * newct))
				else nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
			end
			if nv ~= nil then
				self.ptr = nv
				self.ct = newct
				return true
			else return false end
		end;
	}


























	return t
end)




m.vec = terralib.memoize(function(ty)
	local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
	v.entries = {
		{field = 'storage', type = m.ptr(ty)};
		{field = 'sz', type = intptr};
		{field = 'run', type = intptr};







|
|












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

|
>
>
>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
	local p = m.ptr(ty:astype())
	return `p {
		ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz));
		ct = sz;
	}
end)

local function mkptr(ty, dyn)
	local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', ty))
	t.entries = {
		{'ptr', &ty};
		{'ct', intptr};
	}
	t.ptr_basetype = ty
	local recurse = false
	if ty:isstruct() then
		if ty.methods.free then recurse = true end
	end
	t.metamethods.__not = macro(function(self)
		return `self.ptr
	end)
	if dyn then
		t.methods = {
			free = terra(self: &t): bool
				[recurse and quote
					self.ptr:free()
				end or {}]
				if self.ct > 0 then
					m.heapf(self.ptr)
					self.ct = 0
					return true
				end
				return false
			end;
			init = terra(self: &t, newct: intptr): bool
				var nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
			resize = terra(self: &t, newct: intptr): bool
				var nv: &ty
				if self.ct > 0
					then nv = [&ty](m.heapr_raw(self.ptr, sizeof(ty) * newct))
					else nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				end
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
		}
		terra t:ensure(n: intptr)
			if self.ptr == nil then
				if not self:init(n) then return t {ptr=nil,ct=0} end
			end
			if self.ct >= n then return @self end
			self:resize(n)
			return @self
		end
	end
	terra t:advance(n: intptr)
		self.ptr = self.ptr + n
		self.ct = self.ct - n
		return self.ptr
	end
	if not ty:isstruct() then
		terra t:cmp_raw(other: &ty)
			for i=0, self.ct do
				if self.ptr[i] ~= other[i] then return false end
			end
			return true
		end
		terra t:cmp(other: t)
			if other.ct ~= self.ct then return false end
			return self:cmp_raw(other.ptr)
		end
	end
	return t
end

m.ptr = terralib.memoize(function(ty) return mkptr(ty, true) end)
m.ref = terralib.memoize(function(ty) return mkptr(ty, false) end)

m.vec = terralib.memoize(function(ty)
	local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
	v.entries = {
		{field = 'storage', type = m.ptr(ty)};
		{field = 'sz', type = intptr};
		{field = 'run', type = intptr};

Modified parsav.t from [fcc06cebed] to [41c6f93980].

4
5
6
7
8
9
10







11
12
13
14
15
16
17
18
..
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84


























85
86
87
88
89
90
91
..
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
130
131
132
133
134
135
136
137
138
139
...
159
160
161
162
163
164
165

166
167
168
169
170
171
172
...
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
...
280
281
282
283
284
285
286


287
288








































289
290
291
292
293
294
295
296
297
298
299
300

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')

lib = {
	init = {};
	load = function(lst)
		for _, l in pairs(lst) do







			lib[l] = terralib.loadfile(l .. '.t')()
		end
	end;
	loadlib = function(name,hdr)
		local p = config.pkg[name]
		-- for _,v in pairs(p.dylibs) do
		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
		-- end
................................................................................
		return macro(function(v,...)
			for ty,fn in pairs(tbl) do
				if v.tree.type == ty then return fn(v,...) end
			end
			return (tbl[false])(v,...)
		end)
	end;
	emit_unitary = function(fd,...)
		local code = {}
		for i,v in ipairs{...} do
			if type(v) == 'string' or type(v) == 'number' then
				local str = tostring(v)
				code[#code+1] = `lib.io.send(2, str, [#str])
			elseif type(v) == 'table' and #v == 2 then
				code[#code+1] = `lib.io.send(2, [v[1]], [v[2]])
................................................................................
				local str = tostring(v:asvalue())
				code[#code+1] = `lib.io.send(2, str, [#str])
			else
				code[#code+1] = quote var n = v in
					lib.io.send(2, n, lib.str.sz(n)) end
			end
		end
		code[#code+1] = `lib.io.send(fd, '\n', 1)
		return code
	end;
	emitv = function(fd,...)
		local vec = {}
		local defs = {}
		for i,v in ipairs{...} do
			local str, ct
			if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
				if v.tree.type.convertible == 'tuple' then
					str = `v._0
................................................................................
				else--if v.tree:is 'constant' then
					str = tostring(v:asvalue())
				end
				ct = ct or #str
			end
			vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
		end
		vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1}
		return quote
			[defs]
			var strs = array( [vec] )
		in lib.uio.writev(fd, strs, [#vec]) end
	end;
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end


























	end);
	proc = {
		exit = terralib.externfunction('exit', int -> {});
		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
	};
	io = {
		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
................................................................................
	str = { sz = terralib.externfunction('strlen', rawstring -> intptr) };
	copy = function(tbl)
		local new = {}
		for k,v in pairs(tbl) do new[k] = v end
		setmetatable(new, getmetatable(tbl))
		return new
	end;

}
if config.posix then
	lib.uio = terralib.includec 'sys/uio.h';
	lib.emit = lib.emitv -- use more efficient call where available
else lib.emit = lib.emit_unitary end



local noise = global(uint8,1)
local noise_header = function(code,txt,mod)
	if mod then
		return string.format('\27[%s;1m(parsav::%s %s)\27[m ', code,mod,txt)
	else
		return string.format('\27[%s;1m(parsav %s)\27[m ', code,txt)
	end
end
















local defrep = function(level,n,code)
	return macro(function(...)
		local q = lib.emit(2, noise_header(code,n), ...)
		return quote
			if noise >= level then [q] end
		end
	end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
	local q = lib.emit(2, noise_header('31','fatal'), ...)
	return quote
		[q]
		lib.proc.exit(1)
	end
end);
lib.stat = terralib.memoize(function(ty)
	local n = struct {
		ok: bool
		union {
................................................................................
lib.set = function(tbl)
	local bytes = math.ceil(#tbl / 8)
	local o = {}
	for i, name in ipairs(tbl) do o[name] = i end
	local struct set { _store: uint8[bytes] }
	local struct bit { _v: intptr _set: &set}
	terra set:clear() for i=0,bytes do self._store[i] = 0 end end

	set.members = tbl
	set.name = string.format('set<%s>', table.concat(tbl, '|'))
	set.metamethods.__entrymissing = macro(function(val, obj)
		if o[val] == nil then error('value ' .. val .. ' not in set') end
		return `bit { _v=[o[val] - 1], _set = &obj }
	end)
	set.methods.dump = macro(function(self)
................................................................................
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')

lib.load {
	'mem', 'str', 'file', 'math', 'crypt';
	'http', 'tpl', 'store';
}

local be = {}
for _, b in pairs(config.backends) do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))

lib.cmdparse = terralib.loadfile('cmdparse.t')()
lib.srv = terralib.loadfile('srv.t')()

do local collate = function(path,f, ...)
	return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
	view = collate('view','load');


} end






-- slightly silly -- because we're using plain lua to gather up
-- the template sources, they have to be actually turned into
-- templates in the terra code, so we "mutate" them here
for k,v in pairs(data.view) do
	local t = lib.tpl.mk { body = v, id = 'view/'..k }
	data.view[k] = t
end

local pemdump = macro(function(pub, kp)
	local msg = (pub:asvalue() and ' * emitting public key\n') or ' * emitting private key\n'
	return quote
		var buf: lib.crypt.pemfile
		lib.mem.zero(buf)
		lib.crypt.pem(pub, &kp, buf)
		lib.emit(msg, buf, '\n')
		--lib.io.send(1, msg, [#msg])
		--lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf)))
		--lib.io.send(1, '\n', 1)
	end
end)


do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
end

terra noise_init()


	var n = lib.proc.getenv('parsav_noise')
	if n ~= nil then
		if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
			noise = n[0] - 0x30
			return
		end
	end
................................................................................
end

local options = lib.cmdparse {
	version = {'V', 'display information about the binary build and exit'};
	quiet = {'q', 'do not print to standard out'};
	help = {'h', 'display this list'};
	backend_file = {'b', 'init from specified backend file', 1};


}









































terra entry(argc: int, argv: &rawstring): int
	if argc < 1 then lib.bail('bad invocation!') end

	noise_init()
	[lib.init]

	-- shut mongoose the fuck up
	lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
	var srv: lib.srv

	do var mode: options
		mode:parse(argc,argv) defer mode:free()

		if mode.version then version() return 0 end
		if mode.help then
			lib.io.send(1,  [options.helptxt], [#options.helptxt])
			return 0
		end
		var cnf: rawstring
		if mode.backend_file ~= 0
			then if mode.arglist.ct >= mode.backend_file
					then cnf = mode.arglist.ptr[mode.backend_file - 1]
					else lib.bail('bad invocation, backend file not specified') end
			else cnf = lib.proc.getenv('parsav_backend_file')
		end
		if cnf == nil then cnf = "backend.conf" end

		srv:start(cnf)
	end








>
>
>
>
>
>
>
|







 







|







 







|


|







 







|











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







 







>






>
>



|

|


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


|
<
|
<






|

|







 







>







 







|
|









<






>
>

>
>
>
>
>









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





>

>
>







 







>
>


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








|



>






|
|
<
<







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
..
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
...
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
181
182
183
184
185
186
187
188
189
...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315







316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
...
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405


406
407
408
409
410
411
412
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')

lib = {
	init = {};
	load = function(lst)
		for _, l in pairs(lst) do
			local path = {}
			for m in l:gmatch('([^:]+)') do path[#path+1]=m end
			local tgt = lib
			for i=1,#path-1 do
				if tgt[path[i]] == nil then tgt[path[i]] = {} end
				tgt = tgt[path[i]]
			end
			tgt[path[#path]] = terralib.loadfile(l:gsub(':','/') .. '.t')()
		end
	end;
	loadlib = function(name,hdr)
		local p = config.pkg[name]
		-- for _,v in pairs(p.dylibs) do
		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
		-- end
................................................................................
		return macro(function(v,...)
			for ty,fn in pairs(tbl) do
				if v.tree.type == ty then return fn(v,...) end
			end
			return (tbl[false])(v,...)
		end)
	end;
	emit_unitary = function(nl,fd,...)
		local code = {}
		for i,v in ipairs{...} do
			if type(v) == 'string' or type(v) == 'number' then
				local str = tostring(v)
				code[#code+1] = `lib.io.send(2, str, [#str])
			elseif type(v) == 'table' and #v == 2 then
				code[#code+1] = `lib.io.send(2, [v[1]], [v[2]])
................................................................................
				local str = tostring(v:asvalue())
				code[#code+1] = `lib.io.send(2, str, [#str])
			else
				code[#code+1] = quote var n = v in
					lib.io.send(2, n, lib.str.sz(n)) end
			end
		end
		if nl then code[#code+1] = `lib.io.send(fd, '\n', 1) end
		return code
	end;
	emitv = function(nl,fd,...)
		local vec = {}
		local defs = {}
		for i,v in ipairs{...} do
			local str, ct
			if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
				if v.tree.type.convertible == 'tuple' then
					str = `v._0
................................................................................
				else--if v.tree:is 'constant' then
					str = tostring(v:asvalue())
				end
				ct = ct or #str
			end
			vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
		end
		if nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} end
		return quote
			[defs]
			var strs = array( [vec] )
		in lib.uio.writev(fd, strs, [#vec]) end
	end;
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end
	end);
	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty if ty.type == 'integer'
			then empty = `0
			else empty = `nil
		end
		local exp = quote val = [empty] end

		for i=#args, 1, -1 do
			local v = args[i]
			exp = quote
				if [v] ~= [empty]
					then val = v
					else [exp]
				end
			end
		end

		local q = quote
			var [val]
			[exp]
		in val end
		return q
	end);
	proc = {
		exit = terralib.externfunction('exit', int -> {});
		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
	};
	io = {
		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
................................................................................
	str = { sz = terralib.externfunction('strlen', rawstring -> intptr) };
	copy = function(tbl)
		local new = {}
		for k,v in pairs(tbl) do new[k] = v end
		setmetatable(new, getmetatable(tbl))
		return new
	end;
	osclock = terralib.includec 'time.h';
}
if config.posix then
	lib.uio = terralib.includec 'sys/uio.h';
	lib.emit = lib.emitv -- use more efficient call where available
else lib.emit = lib.emit_unitary end

local starttime = global(lib.osclock.time_t)
local lastnoisetime = global(lib.osclock.time_t)
local noise = global(uint8,1)
local noise_header = function(code,txt,mod)
	if mod then
		return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt)
	else
		return string.format('\27[%s;1m(%s)\27[m ', code,txt)
	end
end

local terra timehdr()
	var now = lib.osclock.time(nil)
	var diff = now - lastnoisetime
	if diff > 30 then -- print cur time
		lastnoisetime = now
		var curtime: int8[26]
		lib.osclock.ctime_r(&now, &curtime[0])
		for i=0,26 do if curtime[i] == @'\n' then curtime[i] = 0 break end end -- :/
		[ lib.emit(false, 2, '\27[1m[', `&curtime[0], ']\27[;36m\n +00 ') ]
	else -- print time since last msg
		var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
		[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
	end
end

local defrep = function(level,n,code)
	return macro(function(...)
		local q = lib.emit(true, 2, noise_header(code,n), ...)

		return quote if noise >= level then timehdr(); [q] end end

	end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
	local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
	return quote
		timehdr(); [q]
		lib.proc.exit(1)
	end
end);
lib.stat = terralib.memoize(function(ty)
	local n = struct {
		ok: bool
		union {
................................................................................
lib.set = function(tbl)
	local bytes = math.ceil(#tbl / 8)
	local o = {}
	for i, name in ipairs(tbl) do o[name] = i end
	local struct set { _store: uint8[bytes] }
	local struct bit { _v: intptr _set: &set}
	terra set:clear() for i=0,bytes do self._store[i] = 0 end end
	terra set:fill() for i=0,bytes do self._store[i] = 0xFF end end
	set.members = tbl
	set.name = string.format('set<%s>', table.concat(tbl, '|'))
	set.metamethods.__entrymissing = macro(function(val, obj)
		if o[val] == nil then error('value ' .. val .. ' not in set') end
		return `bit { _v=[o[val] - 1], _set = &obj }
	end)
	set.methods.dump = macro(function(self)
................................................................................
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')

lib.load {
	'mem',  'math', 'str', 'file', 'crypt';
	'http', 'session', 'tpl', 'store';
}

local be = {}
for _, b in pairs(config.backends) do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))

lib.cmdparse = terralib.loadfile('cmdparse.t')()


do local collate = function(path,f, ...)
	return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
	view = collate('view','load');
	static = {};
	stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content
} end
for i,e in ipairs(config.embeds) do local v = e[1]
	local fh = io.open('static/' .. v,'r')
	if fh == nil then error('static file ' .. v .. ' missing') end
	data.static[v] = fh:read('*a') fh:close()
end

-- slightly silly -- because we're using plain lua to gather up
-- the template sources, they have to be actually turned into
-- templates in the terra code, so we "mutate" them here
for k,v in pairs(data.view) do
	local t = lib.tpl.mk { body = v, id = 'view/'..k }
	data.view[k] = t
end

lib.load {
	'srv';
	'render:profile';
	'render:userpage';
	'route';







}

do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
end

terra noise_init()
	starttime = lib.osclock.time(nil)
	lastnoisetime = 0
	var n = lib.proc.getenv('parsav_noise')
	if n ~= nil then
		if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
			noise = n[0] - 0x30
			return
		end
	end
................................................................................
end

local options = lib.cmdparse {
	version = {'V', 'display information about the binary build and exit'};
	quiet = {'q', 'do not print to standard out'};
	help = {'h', 'display this list'};
	backend_file = {'b', 'init from specified backend file', 1};
	static_dir = {'S', 'directory with overrides for static content', 1};
	builtin_data = {'B', 'do not load static content overrides at runtime under any circumstances'};
}


local static_setup = quote end
local mapin = quote end
local odir = symbol(rawstring)
local pathbuf = symbol(lib.str.acc)
for i,e in ipairs(config.embeds) do local v = e[1]
	local d = data.static[v]
	static_setup = quote [static_setup]
		([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] { ptr = [d], ct = [#d] })
	end
	mapin = quote [mapin]
		var osz = pathbuf.sz
		pathbuf:push([v],[#v])
		var f = lib.file.open(pathbuf.buf, [lib.file.mode.read])
		if f.ok then defer f.val:close()
			var map = f.val:mapin(0,0) -- currently maps are leaked, maybe do something more elegant in future
			lib.report('loading static override content from ', pathbuf.buf)
			([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] {
				ptr = [rawstring](map.addr);
				ct = map.sz;
			})
		end
		pathbuf.sz = osz
	end
end
local terra static_init(mode: &options)
	[static_setup]
	if mode.builtin_data then return end

	var [odir] = lib.proc.getenv('parsav_override_dir')
	if mode.static_dir ~= nil then
		odir=@mode.static_dir
	end
	if odir == nil then return end

	var [pathbuf] defer pathbuf:free()
	pathbuf:compose(odir,'/')
	[mapin]
end

terra entry(argc: int, argv: &rawstring): int
	if argc < 1 then lib.bail('bad invocation!') end

	noise_init()
	[lib.init]

	-- shut mongoose the fuck up
	lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
	var srv: lib.srv.overlord

	do var mode: options
		mode:parse(argc,argv) defer mode:free()
		static_init(&mode)
		if mode.version then version() return 0 end
		if mode.help then
			lib.io.send(1,  [options.helptxt], [#options.helptxt])
			return 0
		end
		var cnf: rawstring
		if mode.backend_file ~= nil
			then cnf = @mode.backend_file


			else cnf = lib.proc.getenv('parsav_backend_file')
		end
		if cnf == nil then cnf = "backend.conf" end

		srv:start(cnf)
	end

Added render/profile.t version [a405db9158].





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- vim: ft=terra
local terra 
render_profile(actor: &lib.store.actor)
	var profile = data.view.profile {
		nym = lib.coalesce(actor.nym, actor.handle);
		bio = lib.coalesce(actor.bio, "tall, dark, and mysterious");
		xid = actor.xid;
		avatar = "/no-avatars-yet.png";

		nposts = '0', nfollows = '0';
		nfollowers = '0', nmutuals = '0';
		tweetday = 'novembuary 67th';
	}

	return profile:tostr()
end

return render_profile

Added render/userpage.t version [052285d84c].



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- vim: ft=terra
local terra 
render_userpage(co: &lib.srv.convo, actor: &lib.store.actor)
	var ti: lib.str.acc defer ti:free()
	if co.aid ~= 0 and co.who.id == actor.id then
		ti:compose('my profile')
	else
		ti:compose('profile :: ', actor.handle)
	end
	var pftxt = lib.render.profile(actor) defer pftxt:free()

	var doc = data.view.docskel {
		instance = co.srv.cfg.instance.ptr;
		title = ti.buf;
		body = pftxt.ptr;
		class = 'profile';
	}

	var hdrs = array(
		lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' }
	)
	doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]})
end

return render_userpage

Added route.t version [70d0da6b8e].



































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method
local http = {}

terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 }
	for i=2,uri.ct do
		if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end
	end
	if handle.ct == 0 then
		handle.ct = uri.ct - 2
		uri:advance(uri.ct)
	else
		if handle.ct + 2 < uri.ct then
			uri:advance(handle.ct + 2)
			--uri.ptr = uri.ptr + (handle.ct + 2)
			--uri.ct = uri.ct - (handle.ct + 2)
		end
	end

	lib.dbg('looking up user by xid "', {handle.ptr,handle.ct} ,'", path: ', {uri.ptr,uri.ct})

	var path = lib.http.hier(uri) defer path:free()
	for i=0,path.ct do
		lib.dbg('got path component ', {path.ptr[i].ptr, path.ptr[i].ct})
	end

	var actor = co.srv:actor_fetch_xid(handle)
	if actor.ptr == nil then
		co:complain(404,'no such user','no such user known to this server')
		return
	end
	defer actor:free()

	lib.render.userpage(co, actor.ptr)
end

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

	var uid, ok = lib.math.shorthand.parse(path.ptr[1].ptr, path.ptr[1].ct)
	if not ok then
		co:complain(400, 'bad user ID', 'that user ID is not valid')
		return
	end

	var actor = co.srv:actor_fetch_uid(uid)
	if actor.ptr == nil then
		co:complain(404, 'no such user', 'no user by that ID is known to this instance')
		return
	end
	defer actor:free()

	lib.render.userpage(co, actor.ptr)
end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
	for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
		local d = data.static[id]
		branches = quote [branches];
			if lib.str.ncmp(filename, id, lib.math.biggest([#id], flen)) == 0 then
				page.headers.ptr[0].value = mime;
				page.body = [lib.mem.ptr(int8)] {
					ptr = storage[([i-1])].ptr;
					ct  = storage[([i-1])].ct;
				}
				goto [send]
			end
		end
	end
	terra http.static_content(co: &lib.srv.convo, [filename], [flen])
		var hdrs = array(lib.http.header{'Content-Type',nil})
		var [page] = lib.http.page {
			respcode = 200;
			headers = [lib.mem.ptr(lib.http.header)] {
				ptr = &hdrs[0], ct = 1
			}
		}
		[branches]
		do return false end
		::[send]:: page:send(co.con) return true
	end
end

http.static_content:printpretty()

-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	if uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
		if meth ~= method.get then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	else
		var path = lib.http.hier(uri) defer path:free()
		if path.ptr[0]:cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		else goto notfound end
	end

	::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this path') do return end
	::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end

Modified schema.sql from [6d12737279] to [636689e0dd].

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
\prompt 'domain name: ' domain

\prompt 'bind to socket: ' bind
\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.'
\prompt 'admin actor: ' admin
\qecho 'you will need to create an authentication view mapping your user database to something parsav can understand; see auth.sql for an example. enter the name of the view to use.'
\prompt 'auth view: ' auth

begin;

drop table if exists parsav_config;
create table if not exists parsav_config (
	key text primary key,
	value text
);

insert into parsav_config (key,value) values
	('bind',:'bind'),
	('domain',:'domain'),
	('auth-source',:'auth'),
	('administrator',:'admin'),
	('server-secret', encode(
			digest(int8send((2^63 * (random()*2 - 1))::bigint),
		'sha512'), 'base64'));

-- note that valid ids should always > 0, as 0 is reserved for null
-- on the client side, vastly simplifying code

>



|
<












|







1
2
3
4
5
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
\prompt 'domain name: ' domain
\prompt 'instance name: ' inst
\prompt 'bind to socket: ' bind
\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.'
\prompt 'admin actor: ' admin
\qecho 'you will need to create an authentication view named parsav_auth mapping your user database to something parsav can understand; see auth.sql for an example.'


begin;

drop table if exists parsav_config;
create table if not exists parsav_config (
	key text primary key,
	value text
);

insert into parsav_config (key,value) values
	('bind',:'bind'),
	('domain',:'domain'),
	('instance-name',:'inst'),
	('administrator',:'admin'),
	('server-secret', encode(
			digest(int8send((2^63 * (random()*2 - 1))::bigint),
		'sha512'), 'base64'));

-- note that valid ids should always > 0, as 0 is reserved for null
-- on the client side, vastly simplifying code

Added session.t version [58f0eab21d].











































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
-- vim: ft=terra
-- sessions are implemented so as to avoid any local data storage. they
-- are tracked by storing an encrypted cookie which contains an authid,
-- a login epoch time, and a truncated hmac code authenticating both, all
-- encoded using Shorthand. we need functions to generate and parse these

local m = {
	maxlen = lib.math.shorthand.maxlen*3 + 2;
	maxage = 2 * 60 * 60; -- 2 hours
}

terra m.cookie_gen(secret: lib.mem.ptr(int8), authid: uint64, time: uint64, out: &int8): intptr
	var ptr = out
	ptr = ptr + lib.math.shorthand.gen(authid, ptr)
	@ptr = @'.' ptr = ptr + 1
	ptr = ptr + lib.math.shorthand.gen(time, ptr)
	@ptr = @'.' ptr = ptr + 1
	var len = ptr - out
	var hash: uint8[lib.crypt.algsz.sha256]
	lib.crypt.hmac(lib.crypt.alg.sha256,
		[lib.mem.ptr(uint8)] {ptr = [&uint8](secret.ptr), ct = secret.ct},
		[lib.mem.ptr( int8)] {ptr = out, ct = len},
	&hash[0])
	ptr = ptr + lib.math.shorthand.gen(lib.math.truncate64(hash, [hash.type.N]), ptr)
	return ptr - out
end

terra m.cookie_interpret(secret: lib.mem.ptr(int8), c: lib.mem.ptr(int8), now: uint64): uint64 -- returns either 0 or a valid authid
	var authid_sz = lib.str.cspan(c.ptr, lib.str.lit '.', c.ct)
	if authid_sz == 0 then return 0 end
	if authid_sz + 1 > c.ct then return 0 end
	var time_sz = lib.str.cspan(c.ptr+authid_sz+1, lib.str.lit '.', c.ct - (authid_sz+1))
	if time_sz == 0 then return 0 end
	if (authid_sz + time_sz + 2) > c.ct then return 0 end
	var hash_sz = c.ct - (authid_sz + time_sz + 2)

	var knownhash: uint8[lib.crypt.algsz.sha256]
	lib.crypt.hmac(lib.crypt.alg.sha256,
		[lib.mem.ptr(uint8)] {ptr = [&uint8](secret.ptr), ct = secret.ct},
		[lib.mem.ptr( int8)] {ptr = c.ptr, ct = c.ct - hash_sz},
	&knownhash[0])

	var authid, authok = lib.math.shorthand.parse(c.ptr, authid_sz)
	var time, timeok = lib.math.shorthand.parse(c.ptr + authid_sz + 1, time_sz)
	var hash, hashok = lib.math.shorthand.parse(c.ptr + c.ct - hash_sz, hash_sz)
	if not (timeok and authok and hashok) then return 0 end
	if lib.math.truncate64(knownhash, [knownhash.type.N]) ~= hash then return 0 end
	if now - time > m.maxage then return 0 end

	return authid
end

return m

Modified srv.t from [aed7239c9c] to [ed3d5ec62e].

1
2







3
4
5
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...
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
...
189
190
191
192
193
194
195





196




-- vim: ft=terra
local util = dofile 'common.lua'







local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection

}

local handle = {
	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
		switch event do
			case lib.net.MG_EV_HTTP_MSG then
				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](p)

			end
		end
	end;
}
local char = macro(function(ch) return `[string.byte(ch:asvalue())] end)
local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)

	var fr = lib.file.open(befile, [lib.file.mode.read])
	if fr.ok == false then
		lib.bail('could not open configuration file ', befile)
	end

	var f = fr.val
	var c: lib.mem.vec(lib.store.source) c:init(8)
	var text: lib.str.acc text:init(64)
	do var buf: int8[64]
		while true do
			var ct = f:read(buf, [buf.type.N])
			if ct == 0 then break end
			text:push(buf, ct)
		end
	end
	f:close()

	var cur = text.buf
	var segs: tuple(&int8, &int8)[3] = array(
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)}
	)
	var segdup = [terra(s: {rawstring, rawstring})
		var sz = s._1 - s._0
		var str = s._0
		return [lib.mem.ptr(int8)] {
			ptr = lib.str.ndup(str, sz);
			ct = sz;
		}
	end]
	var fld = 0
	while (cur - text.buf) < text.sz do
		if segs[fld]._0 == nil then
			if not (@cur == char(' ') or @cur == char('\t') or @cur == char('\n')) then
				segs[fld] = {cur, nil}
			end
		else
			if fld < 2 and @cur == char(' ') or @cur == char('\t') then
				segs[fld]._1 = cur
				fld = fld + 1
				segs[fld] = {nil, nil}
			elseif @cur == char('\n') or cur == text.buf + (text.sz-1) then
				if fld < 2 then lib.bail('incomplete backend line in ', befile) else
					segs[fld]._1 = cur
					var src = c:new()
					src.id = segdup(segs[0])
					src.string = segdup(segs[2])
					src.backend = nil
					for i = 0,[lib.store.backends.type.N] do
						if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then
							src.backend = &lib.store.backends[i]
							break
						end
					end
					if src.backend == nil then
						lib.bail('unknown backend in ', befile)
					end
					src.handle = nil
					fld = 0
					segs[0] = {nil, nil}
				end
			end
		end
		cur = cur + 1
	end
	text:free()

	s.sources = c:crush()

end

--srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring)
--	self.sources.ptr[0]:conf_set(key, val)
--end

terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring)
	var cs: lib.store.credset cs:clear()
	for i=0,self.sources.ct do
		var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn)
		cs = cs + set
	end
	return cs
end
srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
		local ft = f.type or f[2]
................................................................................
					if [ok] then break
						else r = empty end
				end
			end
		in r end
	end
end)























































































































































































































































srv.methods.start = terra(self: &srv, befile: rawstring)
	cfg(self, befile)
	var success = false

	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
		src.handle = src.backend.open(src)
		if src.handle ~= nil then success = true end
	end
	if not success then
		lib.bail('could not connect to any data sources!')
	end



	var dbbind = self:conf_get('bind')
	var envbind = lib.proc.getenv('parsav_bind')
	var bind: rawstring
	if envbind ~= nil then
		bind = envbind
	elseif dbbind.ptr ~= nil then
		bind = dbbind.ptr
	else bind = '[::]:10917' end

	lib.report('binding to ', bind)
	lib.net.mg_mgr_init(&self.webmgr)
	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil)








	if dbbind.ptr ~= nil then dbbind:free() end
end

srv.methods.poll = terra(self: &srv)
	lib.net.mg_mgr_poll(&self.webmgr,1000)
end
................................................................................
	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






return srv






>
>
>
>
>
>
>




>


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


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







 








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



>








>
>












|

>
>
>
>
>
>







 







>
>
>
>
>
|
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

17















































































18


19
20
21












22
23
24
25
26
27
28
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
...
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
-- vim: ft=terra
local util = dofile 'common.lua'

local struct srv
local struct cfgcache {
	secret: lib.mem.ptr(int8)
	instance: lib.mem.ptr(int8)
	overlord: &srv
}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
}


terra cfgcache:free() -- :/















































































	self.secret:free()


	self.instance:free()
end













srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
		local ft = f.type or f[2]
................................................................................
					if [ok] then break
						else r = empty end
				end
			end
		in r end
	end
end)

local struct convo {
	srv: &srv
	con: &lib.net.mg_connection
	msg: &lib.net.mg_http_message
	aid: uint64 -- 0 if logged out
	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
}

-- this is unfortunately necessary to work around a terra bug
-- it can't seem to handle forward-declarations of structs in C

local getpeer
do local struct strucheader {
		next: &lib.net.mg_connection
		mgr: &lib.net.mg_mgr
		peer: lib.net.mg_addr
	}
	terra getpeer(con: &lib.net.mg_connection)
		return [&strucheader](con).peer
	end
end

terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
	var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' })

	var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free()
	var body = data.view.docskel {
		instance = self.srv.cfg.instance.ptr;
		title = ti.buf;
		body = msg;
		class = 'error';
	}

	if body.body == nil then
		body.body = "i'm sorry, dave. i can't let you do that"
	end

	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N]
	})
end

local urimatch = macro(function(uri, ptn)
	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
end)

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

local handle = {
	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
		var server = [&srv](ext)
		var mgpeer = getpeer(con)
		var peer = lib.store.inet { port = mgpeer.port; }
		if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
		if peer.pv == 6 then
			for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end
		else -- v4
			@[&uint32](&peer.v4) = mgpeer.ip
		end
		-- the peer property is currently broken and there is precious
		-- little i can do about this -- it always reports a peer v4 IP
		-- of 0.0.0.0, altho the port seems to come through correctly.
		-- for now i'm leaving it as is, but note that netmask restrictions
		-- WILL NOT WORK until upstream gets its shit together. FIXME

		switch event do
			case lib.net.MG_EV_HTTP_MSG then
				lib.dbg('routing HTTP request')
				var msg = [&lib.net.mg_http_message](p)
				var co = convo {
					con = con, srv = server, msg = msg;
					aid = 0, who = nil;
				}

				-- we need to check if there's any cookies sent with the request,
				-- and if so, whether they contain any credentials. this will be
				-- used to set the auth parameters in the http conversation
				var cookies_p = lib.http.findheader(msg, 'Cookie')
				if cookies_p ~= nil then
					var cookies = cookies_p.ptr
					var key = [lib.mem.ref(int8)] {ptr = cookies, ct = 0}
					var val = [lib.mem.ref(int8)] {ptr = nil, ct = 0}
					var i = 0 while i < cookies_p.ct    and
					                cookies[i] ~= 0     and
					                cookies[i] ~= @'\r' and
									cookies[i] ~= @'\n' do -- cover all the bases
						if val.ptr == nil then
							if cookies[i] == @'=' then
								key.ct = (cookies + i) - key.ptr
								val.ptr = cookies + i + 1
							end
							i = i + 1
						else
							if cookies[i] == @';' then
								val.ct = (cookies + i) - val.ptr
								if lib.str.ncmp(key.ptr, 'auth', key.ct) == 0 then
									goto foundcookie
								end

								i = i + 1
								i = lib.str.ffw(cookies + i, cookies_p.ct - i) - cookies
								key.ptr = cookies + i
								val.ptr = nil
							else i = i + 1 end
						end
					end
					if val.ptr == nil then goto nocookie end
					val.ct = (cookies + i) - val.ptr
					if lib.str.ncmp(key.ptr, 'auth', key.ct) ~= 0 then
						goto nocookie
					end
					::foundcookie:: do
						var aid = lib.session.cookie_interpret(server.cfg.secret,
							[lib.mem.ptr(int8)]{ptr=val.ptr,ct=val.ct},
							lib.osclock.time(nil))
						if aid ~= 0 then co.aid = aid end
					end ::nocookie::;
				end

				if co.aid ~= 0 then
					var sess, usr = co.srv:actor_session_fetch(co.aid, peer)
					if sess.ok == false then co.aid = 0 else co.who = usr.ptr end
				end

				var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free()
				var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1)

				var uri = uridec
				if urideclen == -1 then
					for i = 0,msg.uri.len do
						if msg.uri.ptr[i] == @'+'
							then uri.ptr[i] = @' '
							else uri.ptr[i] = msg.uri.ptr[i]
						end
					end
					uri.ct = msg.uri.len
				else uri.ct = urideclen end
				lib.dbg('routing URI ', {uri.ptr, uri.ct})
				
				if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
					route.dispatch_http(&co, uri, [lib.http.method.get])
				else
					co:complain(400,'unknown method','you have submitted an invalid http request')
				end

				if co.aid ~= 0 then lib.mem.heapf(co.who) end
			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)

	var fr = lib.file.open(befile, [lib.file.mode.read])
	if fr.ok == false then
		lib.bail('could not open configuration file ', befile)
	end

	var f = fr.val
	var c: lib.mem.vec(lib.store.source) c:init(8)
	var text: lib.str.acc text:init(64)
	do var buf: int8[64]
		while true do
			var ct = f:read(buf, [buf.type.N])
			if ct == 0 then break end
			text:push(buf, ct)
		end
	end
	f:close()

	var cur = text.buf
	var segs: tuple(&int8, &int8)[3] = array(
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)},
		{[&int8](0),[&int8](0)}
	)
	var segdup = [terra(s: {rawstring, rawstring})
		var sz = s._1 - s._0
		var str = s._0
		return [lib.mem.ptr(int8)] {
			ptr = lib.str.ndup(str, sz);
			ct = sz;
		}
	end]
	var fld = 0
	while (cur - text.buf) < text.sz do
		if segs[fld]._0 == nil then
			if not (@cur == @' ' or @cur == @'\t' or @cur == @'\n') then
				segs[fld] = {cur, nil}
			end
		else
			if fld < 2 and @cur == @' ' or @cur == @'\t' then
				segs[fld]._1 = cur
				fld = fld + 1
				segs[fld] = {nil, nil}
			elseif @cur == @'\n' or cur == text.buf + (text.sz-1) then
				if fld < 2 then lib.bail('incomplete backend line in ', befile) else
					segs[fld]._1 = cur
					var src = c:new()
					src.id = segdup(segs[0])
					src.string = segdup(segs[2])
					src.backend = nil
					for i = 0,[lib.store.backends.type.N] do
						if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then
							src.backend = &lib.store.backends[i]
							break
						end
					end
					if src.backend == nil then
						lib.bail('unknown backend in ', befile)
					end
					src.handle = nil
					fld = 0
					segs[0] = {nil, nil}
				end
			end
		end
		cur = cur + 1
	end
	text:free()

	if c.sz > 0 then
		s.sources = c:crush()
	else
		s.sources.ptr = nil
		s.sources.ct = 0
	end
end

terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring)
	var cs: lib.store.credset cs:clear()
	for i=0,self.sources.ct do
		var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn)
		cs = cs + set
	end
	return cs
end

terra cfgcache.methods.load :: {&cfgcache} -> {}
terra cfgcache:init(o: &srv)
	self.overlord = o
	self:load()
end

srv.methods.start = terra(self: &srv, befile: rawstring)
	cfg(self, befile)
	var success = false
	if self.sources.ct == 0 then lib.bail('no data sources specified') end
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
		src.handle = src.backend.open(src)
		if src.handle ~= nil then success = true end
	end
	if not success then
		lib.bail('could not connect to any data sources!')
	end

	self.cfg:init(self)

	var dbbind = self:conf_get('bind')
	var envbind = lib.proc.getenv('parsav_bind')
	var bind: rawstring
	if envbind ~= nil then
		bind = envbind
	elseif dbbind.ptr ~= nil then
		bind = dbbind.ptr
	else bind = '[::]:10917' end

	lib.report('binding to ', bind)
	lib.net.mg_mgr_init(&self.webmgr)
	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self)

	var buf: int8[lib.session.maxlen]
	var len = lib.session.cookie_gen(self.cfg.secret, 9139084444658983115ULL, lib.osclock.time(nil), &buf[0])
	buf[len] = 0
	
	var authid = lib.session.cookie_interpret(self.cfg.secret, [lib.mem.ptr(int8)] {ptr=buf, ct=len}, lib.osclock.time(nil))
	lib.io.fmt('generated cookie %s -- got authid %llu\n', buf, authid)

	if dbbind.ptr ~= nil then dbbind:free() end
end

srv.methods.poll = terra(self: &srv)
	lib.net.mg_mgr_poll(&self.webmgr,1000)
end
................................................................................
	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:load()
	self.instance = self.overlord:conf_get('instance-name')
	self.secret = self.overlord:conf_get('server-secret')
end

return {
	overlord = srv;
	convo = convo;
	route = route;
}

Added static/style.scss version [a7ffc6f8bf].

Modified store.t from [2c2e954f5c] to [213b3d2729].

10
11
12
13
14
15
16



17
18
19
20
21
22
23
..
28
29
30
31
32
33
34


35
36
37
38
39
40
41
...
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
...
183
184
185
186
187
188
189





190
191
192
193
194
195
196
	};
	relation = lib.enum {
		'follow', 'mute', 'block'
	};
	credset = lib.set {
		'pw', 'otp', 'challenge', 'trust'
	};



}

local str = rawstring --lib.mem.ptr(int8)

struct m.source

struct m.rights {
................................................................................
	-- user powers -- default on
	login: bool
	visible: bool
	post: bool
	shout: bool
	propagate: bool
	upload: bool



	-- admin powers -- default off
	ban: bool
	config: bool
	censor: bool
	suspend: bool
	rebrand: bool -- modify site's brand identity
................................................................................
end

struct m.auth {
	aid: uint64
	uid: uint64
	aname: str
	netmask: m.inet
	restrict: lib.mem.ptr(rawstring)
	blacklist: bool
}


-- backends only handle content on the local server
struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_save: {&m.source, m.actor} -> bool
	actor_create: {&m.source, m.actor} -> bool
	actor_fetch_xid: {&m.source, rawstring} -> lib.mem.ptr(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)

	actor_auth_how: {&m.source, m.inet, rawstring} -> m.credset
		-- returns a set of auth method categories that are available for a
................................................................................
			-- fingerprint: rawstring
	actor_auth_api:    {&m.source, m.inet, rawstring, rawstring} -> uint64
		-- handles API authentication
			-- origin: inet
			-- handle: rawstring
			-- key:    rawstring (X-API-Key)
	actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth)






	actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
	actor_conf_int: cnf(intptr, lib.stat(intptr))

	post_save: {&m.source, &m.post} -> bool
	post_create: {&m.source, &m.post} -> bool
	actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post)







>
>
>







 







>
>







 







|


<












|







 







>
>
>
>
>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...
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
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
	};
	relation = lib.enum {
		'follow', 'mute', 'block'
	};
	credset = lib.set {
		'pw', 'otp', 'challenge', 'trust'
	};
	privset = lib.set {
		'post', 'edit', 'acct', 'upload', 'censor', 'admin'
	}
}

local str = rawstring --lib.mem.ptr(int8)

struct m.source

struct m.rights {
................................................................................
	-- user powers -- default on
	login: bool
	visible: bool
	post: bool
	shout: bool
	propagate: bool
	upload: bool
	acct: bool
	edit: bool

	-- admin powers -- default off
	ban: bool
	config: bool
	censor: bool
	suspend: bool
	rebrand: bool -- modify site's brand identity
................................................................................
end

struct m.auth {
	aid: uint64
	uid: uint64
	aname: str
	netmask: m.inet
	privs: m.privset
	blacklist: bool
}


-- backends only handle content on the local server
struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_save: {&m.source, m.actor} -> bool
	actor_create: {&m.source, m.actor} -> bool
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)

	actor_auth_how: {&m.source, m.inet, rawstring} -> m.credset
		-- returns a set of auth method categories that are available for a
................................................................................
			-- fingerprint: rawstring
	actor_auth_api:    {&m.source, m.inet, rawstring, rawstring} -> uint64
		-- handles API authentication
			-- origin: inet
			-- handle: rawstring
			-- key:    rawstring (X-API-Key)
	actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth)
	actor_session_fetch: {&m.source, uint64, m.inet} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)}
		-- retrieves an auth record + actor combo suitable by AID suitable
		-- for determining session validity & caps
			-- aid:    uint64
			-- origin: inet

	actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
	actor_conf_int: cnf(intptr, lib.stat(intptr))

	post_save: {&m.source, &m.post} -> bool
	post_create: {&m.source, &m.post} -> bool
	actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post)

Modified str.t from [4b8724b0aa] to [c91733fef5].

1
2

3
4
5
6
7
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
..
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86





87
88
89
90
91
92
93
...
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
181
182

183



184
185
186
187
188
189
190
191
192
193



























194
-- vim: ft=terra
-- string.t: string classes


local m = {
	sz = terralib.externfunction('strlen', rawstring -> intptr);
	cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
	ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int);
	cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
	ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
	dup = terralib.externfunction('strdup',rawstring -> rawstring);
	ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
	fmt = terralib.externfunction('asprintf',
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));

}

(lib.mem.ptr(int8)).metamethods.__cast = function(from,to,e)
	if from == &int8 then
		return `[lib.mem.ptr(int8)]{ptr = e, ct = m.sz(e)}
	elseif to == &int8 then
		return e.ptr
................................................................................
}

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.buf = [rawstring](lib.mem.heapa_raw(run))
	self.run = run
	self.space = 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')
................................................................................
	pt.ct = self.sz
	self.buf = nil
	self.sz = 0
	return pt
end;

terra m.acc:push(str: rawstring, len: intptr)
	var llen = len
	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 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;





m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct)            return self end;
m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct) str:free() return self end;
m.acc.methods.compose = macro(function(self, ...)
	local minlen = 0
	local pstrs = {}
................................................................................
	local ptr = symbol(&int8)
	local box = symbol(&m.box(ty))
	local memreq_exp = `0
	local copiers = {}
	for k,v in pairs(vals) do
		local ty = (`box.obj.[k]).tree.type
		local kp

		if ty.ptr_basetype then

			kp = quote [box].obj.[k] = [ty] { [ptr] = [&ty.ptr_basetype]([ptr]) } ; end
		else
			kp = quote [box].obj.[k] = [ty]([ptr]) ; end

		end

		local cpy
		if type(v) ~= 'table' or #v ~= 2 then
			cpy = quote [kp] ; [ptr] = m.cpy(ptr, v) end

		end
		if type(v) == 'string' then
			memreq_const = memreq_const + #v + 1

		elseif type(v) == 'table' and v.tree and (v.tree.type.ptr_basetype == int8 or v.tree.type.ptr_basetype == uint8) then
			cpy = quote [kp]; [ptr] = [&int8](lib.mem.cpy([ptr], [v].ptr, [v].ct)) end
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = [v].ct end
			end

		elseif type(v) == 'table' and v.asvalue and type(v:asvalue()) == 'string' then
			local str = tostring(v:asvalue())
			memreq_const = memreq_const + #str + 1

		elseif type(v) == 'table' and #v == 2 then
			local str,sz = v[1],v[2]
			if type(sz) == 'number' then
				memreq_const = memreq_const + sz
			elseif type(sz:asvalue()) == 'number' then
				memreq_const = memreq_const + sz:asvalue()
			else memreq_exp = `[sz] + [memreq_exp] end

			cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = sz end
			end

		else
			memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL

			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = m.sz(v) end
			end
		end

		copiers[#copiers + 1] = cpy



	end

	return quote
		var sz: intptr = memreq_const + [memreq_exp]
		var [box] = [&m.box(ty)](lib.mem.heapa_raw(sz))
		var [ptr] = [box].storage
		[copiers]
	in [lib.mem.ptr(ty)] { ct = 1, ptr = &([box].obj) } end
end




























return m


>













>







 







|








|







 







|

|
|











>
>
>
>
>







 







>

>
|


>





>



>





>



>












>


>




>
|
>
>
>










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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
..
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
...
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
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
-- vim: ft=terra
-- string.t: string classes
local util = dofile('common.lua')

local m = {
	sz = terralib.externfunction('strlen', rawstring -> intptr);
	cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
	ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int);
	cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
	ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
	dup = terralib.externfunction('strdup',rawstring -> rawstring);
	ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
	fmt = terralib.externfunction('asprintf',
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));
	span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
}

(lib.mem.ptr(int8)).metamethods.__cast = function(from,to,e)
	if from == &int8 then
		return `[lib.mem.ptr(int8)]{ptr = e, ct = m.sz(e)}
	elseif to == &int8 then
		return e.ptr
................................................................................
}

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.buf = [rawstring](lib.mem.heapa_raw(run))
	self.run = run
	self.space = 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')
................................................................................
	pt.ct = self.sz
	self.buf = nil
	self.sz = 0
	return pt
end;

terra m.acc:push(str: rawstring, len: intptr)
	--var llen = len
	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 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;

m.lit = macro(function(str)
	return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]}
end)

m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct)            return self end;
m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
	self:push(str.ptr, str.ct) str:free() return self end;
m.acc.methods.compose = macro(function(self, ...)
	local minlen = 0
	local pstrs = {}
................................................................................
	local ptr = symbol(&int8)
	local box = symbol(&m.box(ty))
	local memreq_exp = `0
	local copiers = {}
	for k,v in pairs(vals) do
		local ty = (`box.obj.[k]).tree.type
		local kp
		local isnull, nullify
		if ty.ptr_basetype then
			kp = quote [box].obj.[k] = [ty] { ptr = [&ty.ptr_basetype]([ptr]) } ; end
			nullify = quote [box].obj.[k] = [ty] { ptr = nil, ct = 0 } end
		else
			kp = quote [box].obj.[k] = [ty]([ptr]) ; end
			nullify = quote [box].obj.[k] = nil end
		end

		local cpy
		if type(v) ~= 'table' or #v ~= 2 then
			cpy = quote [kp] ; [ptr] = m.cpy(ptr, v) end
			isnull = `v == nil
		end
		if type(v) == 'string' then
			memreq_const = memreq_const + #v + 1
			isnull = `false
		elseif type(v) == 'table' and v.tree and (v.tree.type.ptr_basetype == int8 or v.tree.type.ptr_basetype == uint8) then
			cpy = quote [kp]; [ptr] = [&int8](lib.mem.cpy([ptr], [v].ptr, [v].ct)) end
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = [v].ct end
			end
			isnull = `[v].ptr == nil
		elseif type(v) == 'table' and v.asvalue and type(v:asvalue()) == 'string' then
			local str = tostring(v:asvalue())
			memreq_const = memreq_const + #str + 1
			isnull = `false
		elseif type(v) == 'table' and #v == 2 then
			local str,sz = v[1],v[2]
			if type(sz) == 'number' then
				memreq_const = memreq_const + sz
			elseif type(sz:asvalue()) == 'number' then
				memreq_const = memreq_const + sz:asvalue()
			else memreq_exp = `[sz] + [memreq_exp] end

			cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = sz end
			end
			isnull = `[str] == nil
		else
			memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL
			isnull = `v == nil
			if ty.ptr_basetype then
				cpy = quote [cpy]; [box].obj.[k].ct = m.sz(v) end
			end
		end

		copiers[#copiers + 1] = quote
			if [isnull] then [nullify]
			            else [cpy] end
		end
	end

	return quote
		var sz: intptr = memreq_const + [memreq_exp]
		var [box] = [&m.box(ty)](lib.mem.heapa_raw(sz))
		var [ptr] = [box].storage
		[copiers]
	in [lib.mem.ptr(ty)] { ct = 1, ptr = &([box].obj) } end
end

terra m.cspan(str: lib.mem.ptr(int8), reject: lib.mem.ref(int8), maxlen: intptr)
	for i=0, lib.math.smallest(maxlen,str.ct) do
		if str.ptr[i] == 0 then return 0 end
		for j=0, reject.ct do
			if str.ptr[i] == reject.ptr[j] then return i end
		end
	end
	return maxlen
end

terra m.ffw(str: &int8, maxlen: intptr)
	while maxlen > 0 and @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1
		maxlen = maxlen - 1
	end
	return str
end

terra m.ffw_unsafe(str: &int8)
	while  @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1
	end
	return str
end

return m

Modified view/docskel.tpl from [1d38dac966] to [004398018e].

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
	<head>
		<title>@instance :: @title</title>
		<link rel="stylesheet" href="/style.css">
	</head>
	<body>
		<h1>@title</h1>
		@body
	</body>
</html>




|

|




1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
	<head>
		<title>@instance :: @title</title>
		<link rel="stylesheet" href="/s/style.css">
	</head>
	<body class="@class">
		<h1>@title</h1>
		@body
	</body>
</html>

Modified view/load.lua from [f2a65d7b61] to [53cfafaa7c].

2
3
4
5
6
7
8

9
10
11
12
13
14
15
-- file that indexes the templates manually, and
-- copies them into a data structure we can then
-- create templates from when we return to terra
local path = ...
local sources = {
	'docskel';
	'tweet';

}

local ingest = function(filename)
	local hnd = io.open(path..'/'..filename)
	local txt = hnd:read('*a')
	io.close(hnd)
	txt = txt:gsub('([^\\])!%b[]', '%1')







>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- file that indexes the templates manually, and
-- copies them into a data structure we can then
-- create templates from when we return to terra
local path = ...
local sources = {
	'docskel';
	'tweet';
	'profile';
}

local ingest = function(filename)
	local hnd = io.open(path..'/'..filename)
	local txt = hnd:read('*a')
	io.close(hnd)
	txt = txt:gsub('([^\\])!%b[]', '%1')

Added view/profile.tpl version [abc7153f4d].













































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="profile">
	<div class="banner">
		<img class="avatar" src="@avatar">
		<div class="id">@nym [@xid]</div>
		<div class="bio">
			@bio
		</div>
	</div>
	<table class="stats">
		<tr><th>posts</th> <td>@nposts</td></tr>
		<tr><th>following</th> <td>@nfollows</td></tr>
		<tr><th>followers</th> <td>@nfollowers</td></tr>
		<tr><th>mutuals</th> <td>@nmutuals</td></tr>
		<tr><th>account created</th> <td>@tweetday</td></tr>
	</table>
	<div class="menu">
		<a href="/\@@xid">posts</a>
		<a href="/\@@xid/media">media</a>
		<a href="/\@@xid/follows">follows</a>
		<a href="/\@@xid/chat">chat</a>
	</div>
</div>

Modified view/tweet.tpl from [59ae4e4ad9] to [4ad2e9eaa9].

1
2
3
4
5
6
7
8
9
10
11
12
<div class="post">
	<div class="detail">
		<a class="username" href="@link">
			<span class="nym">@nym</span> <span class="handle">[@handle]</span>
		</div>
		<div class="when">@when</div>
	</div>
	<div class="content">
		<img class="avatar" src="@avatar">
		<div class="text">@text</div>
	</div>
</div>



|








1
2
3
4
5
6
7
8
9
10
11
12
<div class="post">
	<div class="detail">
		<a class="username" href="@link">
			<span class="nym">@nym</span> <span class="handle">[@xid]</span>
		</div>
		<div class="when">@when</div>
	</div>
	<div class="content">
		<img class="avatar" src="@avatar">
		<div class="text">@text</div>
	</div>
</div>