Differences From
Artifact [9e0d1b7489]:
7 7 secret: lib.mem.ptr(int8)
8 8 pol_sec: secmode.t
9 9 pol_reg: bool
10 10 credmgd: bool
11 11 maxupsz: intptr
12 12 instance: lib.mem.ptr(int8)
13 13 overlord: &srv
14 + ui_hue: uint16
14 15 }
15 16 local struct srv {
16 17 sources: lib.mem.ptr(lib.store.source)
17 18 webmgr: lib.net.mg_mgr
18 19 webcon: &lib.net.mg_connection
19 20 cfg: cfgcache
20 21 id: rawstring
................................................................................
104 105 if [ok] then break
105 106 else r = empty end
106 107 end
107 108 end
108 109 in r end
109 110 end
110 111 end)
112 +
113 +terra lib.store.post:publish(s: &srv)
114 + self:comp()
115 + self.posted = lib.osclock.time(nil)
116 + self.discovered = self.posted
117 + self.chgcount = 0
118 + self.edited = 0
119 + self.id = s:post_create(self)
120 + return self.id
121 +end
111 122
112 123 local struct convo {
113 124 srv: &srv
114 125 con: &lib.net.mg_connection
115 126 msg: &lib.net.mg_http_message
116 127 aid: uint64 -- 0 if logged out
117 128 aid_issue: lib.store.timepoint
118 129 who: &lib.store.actor -- who we're logged in as, if aid ~= 0
119 130 peer: lib.store.inet
120 131 reqtype: lib.http.mime.t -- negotiated content type
132 + method: lib.http.method.t
133 + live_last: lib.store.timepoint
121 134 -- cache
135 + ui_hue: uint16
122 136 navbar: lib.mem.ptr(int8)
123 137 actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries
124 138 -- private
125 139 varbuf: lib.mem.ptr(int8)
126 140 vbofs: &int8
127 141 }
142 +
143 +struct convo.page {
144 + title: pstring
145 + body: pstring
146 + class: pstring
147 + cache: bool
148 +}
128 149
129 150 -- this is unfortunately necessary to work around a terra bug
130 151 -- it can't seem to handle forward-declarations of structs in C
131 152
132 153 local getpeer
133 154 do local struct strucheader {
134 155 next: &lib.net.mg_connection
................................................................................
136 157 peer: lib.net.mg_addr
137 158 }
138 159 terra getpeer(con: &lib.net.mg_connection)
139 160 return [&strucheader](con).peer
140 161 end
141 162 end
142 163
164 +terra convo:rawpage(code: uint16, pg: convo.page, hdrs: lib.mem.ptr(lib.http.header))
165 + var doc = data.view.docskel {
166 + instance = self.srv.cfg.instance;
167 + title = pg.title;
168 + body = pg.body;
169 + class = pg.class;
170 + navlinks = self.navbar;
171 + attr = '';
172 + }
173 + var attrbuf: int8[32]
174 + if self.aid ~= 0 and self.ui_hue ~= 323 then
175 + var hdecbuf: int8[21]
176 + var hdec = lib.math.decstr(self.ui_hue, &hdecbuf[20])
177 + lib.str.cpy(&attrbuf[0], ' style="--hue:')
178 + lib.str.cpy(&attrbuf[14], hdec)
179 + var len = &hdecbuf[20] - hdec
180 + lib.str.cpy(&attrbuf[14] + len, '"')
181 + doc.attr = &attrbuf[0]
182 + end
183 +
184 + if self.method == [lib.http.method.head]
185 + then doc:head(self.con,code,hdrs)
186 + else doc:send(self.con,code,hdrs)
187 + end
188 +end
189 +
190 +terra convo:statpage(code: uint16, pg: convo.page)
191 + var hdrs = array(
192 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
193 + lib.http.header { key = 'Cache-Control', value = 'no-store' }
194 + )
195 + self:rawpage(code,pg, [lib.mem.ptr(lib.http.header)] {
196 + ptr = &hdrs[0];
197 + ct = [hdrs.type.N] - lib.trn(pg.cache,1,0);
198 + })
199 +end
200 +
201 +terra convo:livepage(pg: convo.page, lastup: lib.store.timepoint)
202 + var nbuf: int8[21]
203 + var hdrs = array(
204 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
205 + lib.http.header { key = 'Cache-Control', value = 'no-store' },
206 + lib.http.header {
207 + key = 'X-Live-Newest-Artifact';
208 + value = lib.math.decstr(lastup, &nbuf[20]);
209 + },
210 + lib.http.header { key = 'Content-Length', value = '0' }
211 + )
212 + if self.live_last ~= 0 and self.live_last <= lastup then
213 + lib.net.mg_printf(self.con, 'HTTP/1.1 %s', lib.http.codestr(200))
214 + for i = 0, [hdrs.type.N] do
215 + lib.net.mg_printf(self.con, '%s: %s\r\n', hdrs[i].key, hdrs[i].value)
216 + end
217 + lib.net.mg_printf(self.con, '\r\n')
218 + else
219 + self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] {
220 + ptr = &hdrs[0], ct = 3
221 + })
222 + end
223 +end
224 +
225 +terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end
226 +
143 227 terra convo:reroute_cookie(dest: rawstring, cookie: rawstring)
144 228 var hdrs = array(
145 229 lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
146 230 lib.http.header { key = 'Location', value = dest },
147 231 lib.http.header { key = 'Set-Cookie', value = cookie }
148 232 )
149 233
150 234 var body = data.view.docskel {
151 235 instance = self.srv.cfg.instance.ptr;
152 236 title = 'rerouting';
153 237 body = 'you are being redirected';
154 238 class = 'error';
155 239 navlinks = '';
240 + attr = '';
156 241 }
157 242
158 243 body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] {
159 244 ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0)
160 245 })
161 246 end
162 247
................................................................................
170 255 lib.dbg('sending cookie ',{&sesskey[0],15})
171 256 p = lib.str.ncpy(p, '; Path=/', 9)
172 257 end
173 258 self:reroute_cookie(dest, &sesskey[0])
174 259 end
175 260
176 261 terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
177 - var hdrs = array(
178 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
179 - lib.http.header { key = 'Cache-Control', value = 'no-store' }
180 - )
262 + if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end
181 263
182 264 var ti: lib.str.acc ti:compose('error :: ', title)
183 265 var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>',title,'</h1><p>',msg,'</p></div>')
184 - var body = data.view.docskel {
185 - instance = self.srv.cfg.instance;
266 + var body = [convo.page] {
186 267 title = ti:finalize();
187 268 body = bo:finalize();
188 269 class = lib.str.plit 'error';
189 - navlinks = lib.coalesce(self.navbar, [lib.mem.ptr(int8)]{ptr='',ct=0});
270 + cache = false;
190 271 }
191 272
192 - if body.body.ptr == nil then
193 - body.body = lib.str.plit"i'm sorry, dave. i can't let you do that"
194 - end
195 -
196 - body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
197 - ptr = &hdrs[0], ct = [hdrs.type.N]
198 - })
273 + self:statpage(code, body)
199 274
200 275 body.title:free()
201 276 body.body:free()
202 277 end
203 278
204 279 convo.methods.assertpow = macro(function(self, pow)
205 280 return quote
................................................................................
207 282 if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
208 283 ok = false
209 284 self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
210 285 end
211 286 in ok end
212 287 end)
213 288
214 -struct convo.page {
215 - title: pstring
216 - body: pstring
217 - class: pstring
218 - cache: bool
219 -}
220 -
221 -terra convo:stdpage(pg: convo.page)
222 - var doc = data.view.docskel {
223 - instance = self.srv.cfg.instance;
224 - title = pg.title;
225 - body = pg.body;
226 - class = pg.class;
227 - navlinks = self.navbar;
228 - }
229 -
230 - var hdrs = array(
231 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
232 - lib.http.header { key = 'Cache-Control', value = 'no-store' }
233 - )
234 -
235 - doc:send(self.con,200,[lib.mem.ptr(lib.http.header)] {ct = [hdrs.type.N] - lib.trn(pg.cache,1,0), ptr = &hdrs[0]})
236 -end
237 -
238 289 -- CALL ONLY ONCE PER VAR
239 290 terra convo:postv(name: rawstring)
240 291 if self.varbuf.ptr == nil then
241 292 self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
242 293 self.vbofs = self.varbuf.ptr
243 294 end
244 295 var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
................................................................................
299 350 if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then
300 351 ret = [lib.http.mime[name]]
301 352 else ret = [mimeneg] end
302 353 in ret end
303 354 end
304 355
305 356 local handle = {
306 - http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
307 - var server = [&srv](ext)
357 + http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque)
358 + var server = [&srv](userdata)
308 359 var mgpeer = getpeer(con)
309 360 var peer = lib.store.inet { port = mgpeer.port; }
310 361 if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
311 362 if peer.pv == 6 then
312 363 for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end
313 364 else -- v4
314 365 @[&uint32](&peer.v4) = mgpeer.ip
................................................................................
319 370 -- for now i'm leaving it as is, but note that netmask restrictions
320 371 -- WILL NOT WORK until upstream gets its shit together. FIXME
321 372
322 373 -- needs to check for an X-Forwarded-For header from nginx and
323 374 -- use that instead of the peer iff peer is ::1/127.1 FIXME
324 375 -- maybe also haproxy support?
325 376
326 - switch event do
377 + switch event_kind do
327 378 case lib.net.MG_EV_HTTP_MSG then
328 379 lib.dbg('routing HTTP request')
329 - var msg = [&lib.net.mg_http_message](p)
380 + var msg = [&lib.net.mg_http_message](event)
330 381 var co = convo {
331 382 con = con, srv = server, msg = msg;
332 383 aid = 0, aid_issue = 0, who = nil;
333 384 reqtype = lib.http.mime.none;
334 - peer = peer;
385 + peer = peer, live_last = 0;
335 386 } co.varbuf.ptr = nil
336 387 co.navbar.ptr = nil
337 388 co.actorcache.top = 0
338 389 co.actorcache.cur = 0
390 + co.ui_hue = server.cfg.ui_hue
339 391
340 392 -- first, check for an accept header. if it's there, we need to
341 393 -- iterate over the values and pick the highest-priority one
342 394 do var acc = lib.http.findheader(msg, 'Accept')
343 395 -- TODO handle q-value
344 - if acc.ptr ~= nil then
396 + if acc ~= nil and acc.ptr ~= nil then
345 397 var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
346 398 var i = 0 while i < acc.ct do
347 399 if acc.ptr[i] == @',' or acc.ptr[i] == @';' then
348 400 mimevar.ct = (acc.ptr+i) - mimevar.ptr
349 401 var t = [mimeneg]
350 402 if t ~= lib.http.mime.none then
351 403 co.reqtype = t
................................................................................
377 429 else co.reqtype = lib.http.mime.html end
378 430 ::foundtype::end
379 431
380 432 -- we need to check if there's any cookies sent with the request,
381 433 -- and if so, whether they contain any credentials. this will be
382 434 -- used to set the auth parameters in the http conversation
383 435 var cookies_p = lib.http.findheader(msg, 'Cookie')
384 - if cookies_p ~= nil then
436 + if cookies_p ~= nil and cookies_p.ptr ~= nil then
385 437 var cookies = cookies_p.ptr
386 438 var key = [lib.mem.ref(int8)] {ptr = cookies, ct = 0}
387 439 var val = [lib.mem.ref(int8)] {ptr = nil, ct = 0}
388 440 var i = 0 while i < cookies_p.ct and
389 441 cookies[i] ~= 0 and
390 442 cookies[i] ~= @'\r' and
391 443 cookies[i] ~= @'\n' do -- cover all the bases
................................................................................
423 475 end
424 476
425 477 if co.aid ~= 0 then
426 478 var sess, usr = co.srv:actor_session_fetch(co.aid, peer, co.aid_issue)
427 479 if sess.ok == false then co.aid = 0 co.aid_issue = 0 else
428 480 co.who = usr.ptr
429 481 co.who.rights.powers = server:actor_powers_fetch(co.who.id)
482 + var userhue, hueok = server:actor_conf_int_get(co.who.id, 'ui-accent')
483 + if hueok then co.ui_hue = userhue end
430 484 end
431 485 end
486 +
487 + var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival')
488 + if livelast_p ~= nil and livelast_p.ptr ~= nil then
489 + var ll, ok = lib.math.decparse(pstring{ptr = livelast_p.ptr, ct = livelast_p.ct - 1})
490 + if ok then co.live_last = ll end
491 + end
492 +
432 493
433 494 var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free()
434 495 var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1)
435 496
436 497 var uri = uridec
437 498 if urideclen == -1 then
438 499 for i = 0,msg.uri.len do
................................................................................
442 503 end
443 504 end
444 505 uri.ct = msg.uri.len
445 506 else uri.ct = urideclen end
446 507 lib.dbg('routing URI ', {uri.ptr, uri.ct})
447 508
448 509 if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
510 + co.method = [lib.http.method.get]
449 511 route.dispatch_http(&co, uri, [lib.http.method.get])
450 512 elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then
513 + co.method = [lib.http.method.get]
451 514 route.dispatch_http(&co, uri, [lib.http.method.post])
452 515 elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then
516 + co.method = [lib.http.method.head]
453 517 route.dispatch_http(&co, uri, [lib.http.method.head])
454 518 elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then
519 + co.method = [lib.http.method.options]
455 520 route.dispatch_http(&co, uri, [lib.http.method.options])
456 521 else
457 522 co:complain(400,'unknown method','you have submitted an invalid http request')
458 523 end
459 524
460 525 if co.aid ~= 0 then lib.mem.heapf(co.who) end
461 526 if co.varbuf.ptr ~= nil then co.varbuf:free() end
................................................................................
670 735 do self.pol_reg = false
671 736 var sreg = self.overlord:conf_get('policy-self-register')
672 737 if sreg:ref() then
673 738 if lib.str.cmp(sreg.ptr, 'on') == 0
674 739 then self.pol_reg = true
675 740 else self.pol_reg = false
676 741 end
677 - end
678 - sreg:free() end
742 + sreg:free()
743 + end end
679 744
680 745 do self.credmgd = false
681 746 var sreg = self.overlord:conf_get('credential-store')
682 747 if sreg:ref() then
683 748 if lib.str.cmp(sreg.ptr, 'managed') == 0
684 749 then self.credmgd = true
685 750 else self.credmgd = false
686 751 end
687 - end
688 - sreg:free() end
752 + sreg:free()
753 + end end
689 754
690 755 do self.maxupsz = [1024 * 100] -- 100 kilobyte default
691 756 var sreg = self.overlord:conf_get('maximum-artifact-size')
692 757 if sreg:ref() then
693 758 var sz, ok = lib.math.fsz_parse(sreg)
694 759 if ok then self.maxupsz = sz else
695 760 lib.warn('invalid configuration value for maximum-artifact-size; keeping default 100K upload limit')
696 761 end
762 + sreg:free() end
697 763 end
698 - sreg:free() end
699 764
700 765 self.pol_sec = secmode.lockdown
701 766 var smode = self.overlord:conf_get('policy-security')
702 767 if smode.ptr ~= nil then
703 768 if lib.str.cmp(smode.ptr, 'public') == 0 then
704 769 self.pol_sec = secmode.public
705 770 elseif lib.str.cmp(smode.ptr, 'private') == 0 then
706 771 self.pol_sec = secmode.private
707 772 elseif lib.str.cmp(smode.ptr, 'lockdown') == 0 then
708 773 self.pol_sec = secmode.lockdown
709 774 elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then
710 775 self.pol_sec = secmode.isolate
711 776 end
777 + smode:free()
712 778 end
713 - smode:free()
779 +
780 + self.ui_hue = config.default_ui_accent
781 + var shue = self.overlord:conf_get('ui-accent')
782 + if shue.ptr ~= nil then
783 + var hue,ok = lib.math.decparse(shue)
784 + if ok then self.ui_hue = hue end
785 + shue:free()
786 + end
714 787 end
715 788
716 789 return {
717 790 overlord = srv;
718 791 convo = convo;
719 792 route = route;
720 793 secmode = secmode;
721 794 }