parsav  Diff

Differences From Artifact [ed3d5ec62e]:

To Artifact [afa0417e30]:


1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
..
68
69
70
71
72
73
74







75
76
77
78
79
80
81
..
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
...
128
129
130
131
132
133
134




135
136
137
138
139
140
141



142







































143
144
145
146
147
148
149
...
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
...
204
205
206
207
208
209
210






211
212
213
214
215


216
217
218
219
220
221
222
...
292
293
294
295
296
297
298















299
300
301

302
303

304

305

306
307
308
309
310
311
312
313
...
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
...
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
}
................................................................................

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 {
................................................................................
		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
................................................................................
		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
................................................................................
								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
					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)
................................................................................
	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
................................................................................
		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

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

}


|





>
>







 







>
>
>
>
>
>
>







 







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




>



|

>










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







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







 







>
>
>
>






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







 







|












|







 







>
>
>
>
>
>





>
>







 







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



>

|
>
|
>
|
>
|







 







<
<
<
<
<
<
<







 







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






>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
..
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
...
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
...
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
...
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
356
357
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
...
490
491
492
493
494
495
496







497
498
499
500
501
502
503
...
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
-- vim: ft=terra
local util = dofile 'common.lua'
local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' }
local struct srv
local struct cfgcache {
	secret: lib.mem.ptr(int8)
	instance: lib.mem.ptr(int8)
	overlord: &srv
	pol_sec: secmode.t
	pol_reg: bool
}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
}
................................................................................

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
	peer: lib.store.inet
	reqtype: lib.http.mime.t -- negotiated content type
-- cache
	navbar: lib.mem.ptr(int8)
-- private
	varbuf: lib.mem.ptr(int8)
	vbofs: &int8
}

-- 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 {
................................................................................
		peer: lib.net.mg_addr
	}
	terra getpeer(con: &lib.net.mg_connection)
		return [&strucheader](con).peer
	end
end

terra convo:reroute_cookie(dest: rawstring, cookie: rawstring)
	var hdrs = array(
		lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
		lib.http.header { key = 'Location',     value = dest },
		lib.http.header { key = 'Set-Cookie',   value = cookie }
	)

	var body = data.view.docskel {
		instance = self.srv.cfg.instance.ptr;
		title = 'rerouting';
		body = 'you are being redirected';
		class = 'error';
		navlinks = '';
	}

	body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0)
	})
end

terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) 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 bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>error</h1><p>',msg,'</p></div>') defer bo:free()
	var body = data.view.docskel {
		instance = self.srv.cfg.instance.ptr;
		title = ti.buf;
		body = bo.buf;
		class = 'error';
		navlinks = lib.coalesce(self.navbar.ptr, '');
	}

	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

-- CALL ONLY ONCE PER VAR
terra convo:postv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o
		return r, o
	else return nil, 0 end
end

terra convo:getv(name: rawstring)
	if self.varbuf.ptr == nil then
		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
		self.vbofs = self.varbuf.ptr
	end
	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
	if o > 0 then
		var r = self.vbofs
		self.vbofs = self.vbofs + o
		return r, o
	else return nil, 0 end
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 mimetypes = {
	{'html', 'text/html'};
	{'json', 'application/json'};
	{'mkdown', 'text/markdown'};
	{'text', 'text/plain'};
	{'ansi', 'text/x-ansi'};
}

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

for i, t in ipairs(mimetypes) do
	local name, mime = t[1], t[2]
	mimeneg = quote
		var ret: lib.http.mime.t
		if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then
			ret = [lib.http.mime[name]]
		else ret = [mimeneg] end
	in ret end
end

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

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

		switch event 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, peer = peer;
					reqtype = lib.http.mime.none;
				} co.varbuf.ptr = nil
				  co.navbar.ptr = nil

				-- first, check for an accept header. if it's there, we need to
				-- iterate over the values and pick the highest-priority one
				do var acc = lib.http.findheader(msg, 'Accept')
					-- TODO handle q-value
					if acc.ptr ~= nil then
						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
						var i = 0 while i < acc.ct do
							if acc.ptr[i] == @',' or acc.ptr[i] == @';' then
								mimevar.ct = (acc.ptr+i) - mimevar.ptr
								var t = [mimeneg]
								if t ~= lib.http.mime.none then
									co.reqtype = t
									goto foundtype
								end

								if acc.ptr[i] == @';' then -- fast-forward over q
									for j=i+1,acc.ct do i=j
										if acc.ptr[j] == @',' then break end
									end
								end
								
								while i < acc.ct and -- fast-forward over ws
									acc.ptr[i+1] == @' ' or
									acc.ptr[i+1] == @'\t'
								do i=i+1 end

								mimevar.ptr = acc.ptr + i + 1
							end
							i=i+1
						end
						if co.reqtype == lib.http.mime.none then
							mimevar.ct = acc.ct - (mimevar.ptr - acc.ptr)
							co.reqtype = [mimeneg]
							if co.reqtype == lib.http.mime.none then
								co.reqtype = lib.http.mime.html
							end
						end
					else co.reqtype = lib.http.mime.html end
				::foundtype::end

				-- 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
................................................................................
								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, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename],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, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename], 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
					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])
				elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then
					route.dispatch_http(&co, uri, [lib.http.method.post])
				elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then
					route.dispatch_http(&co, uri, [lib.http.method.head])
				elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then
					route.dispatch_http(&co, uri, [lib.http.method.options])
				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
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end
			end
		end
	end;
}

local terra cfg(s: &srv, befile: rawstring)
	lib.report('configuring backends from ', befile)
................................................................................
	if c.sz > 0 then
		s.sources = c:crush()
	else
		s.sources.ptr = nil
		s.sources.ct = 0
	end
end

terra srv:actor_stats(uid: uint64)
	var stats = lib.store.actor_stats {
		posts = 0, mutuals = 0;
		follows = 0, followers = 0;
	}
	for i=0,self.sources.ct do
		var s = self.sources.ptr[i]:actor_stats(uid)
		stats.posts     = stats.posts     + s.posts
		stats.mutuals   = stats.mutuals   + s.mutuals
		stats.followers = stats.followers + s.followers
		stats.follows   = stats.follows   + s.follows
	end
	return stats
end

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

terra cfgcache.methods.load :: {&cfgcache} -> {}
terra cfgcache:init(o: &srv)
	self.overlord = o
	self:load()
end
................................................................................
		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)








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

srv.methods.poll = terra(self: &srv)
	lib.net.mg_mgr_poll(&self.webmgr,1000)
end

................................................................................
	end
	self.sources:free()
end

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

	self.pol_reg = false
	var sreg = self.overlord:conf_get('policy-self-register')
	if sreg.ptr ~= nil then
		if lib.str.cmp(sreg.ptr, 'on') == 0
			then self.pol_reg = true
			else self.pol_reg = false
		end
	end
	sreg:free()
	
	self.pol_sec = secmode.lockdown
	var smode = self.overlord:conf_get('policy-security')
	if smode.ptr ~= nil then
		if lib.str.cmp(smode.ptr, 'public') == 0 then
			self.pol_sec = secmode.public
		elseif lib.str.cmp(smode.ptr, 'private') == 0 then
			self.pol_sec = secmode.private
		elseif lib.str.cmp(smode.ptr, 'lockdown') == 0 then
			self.pol_sec = secmode.lockdown
		elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then
			self.pol_sec = secmode.isolate
		end
	end
	smode:free()
end

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