parsav  Check-in [af5ed65b68]

Overview
Comment:further iteration on media
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: af5ed65b68e96856432eb7477e8852b2a716281a993d114188c5d6695c014dd2
User & Date: lexi on 2021-01-07 09:39:29
Other Links: manifest | tags
Context
2021-01-07
20:39
media uploads work now, some types can be viewed check-in: 93aea04a05 user: lexi tags: trunk
09:39
further iteration on media check-in: af5ed65b68 user: lexi tags: trunk
07:35
tentative beginnings of upload + media management system check-in: f4c6e72a22 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [5d4cebec04] to [fc1c52be55].

1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
















1607
1608
1609
1610
1611
1612
1613
			for i=0,res.sz do
				var id = res:int(uint64,i,0)
				var idbuf: int8[lib.math.shorthand.maxlen]
				var idlen = lib.math.shorthand.gen(id, &idbuf[0])
				var desc = res:_string(i,2)
				var folder = res:_string(i,3)
				var mime = res:_string(i,4)
				var url = lib.str.acc{}:init(48):lpush('/media/a/'):push(&idbuf[0],idlen):finalize() defer url:free()
				m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, {
					desc =  {`desc.ptr, `desc.ct + 1};
					folder = {`folder.ptr, `folder.ct + 1};
					mime = {`mime.ptr, `mime.ct + 1};
					url = {`url.ptr, `url.ct + 1};
				}) ]
				m(i).ptr.rid = id
				m(i).ptr.owner = uid
			end
			return m
		else return [lib.mem.lstptr(lib.store.artifact)].null() end
	end];

















	post_attach_ctl = [terra(
		src: &lib.store.source,
		post: uint64,
		artifact: uint64,
		detach: bool
	): {}







<




|







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







1587
1588
1589
1590
1591
1592
1593

1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
			for i=0,res.sz do
				var id = res:int(uint64,i,0)
				var idbuf: int8[lib.math.shorthand.maxlen]
				var idlen = lib.math.shorthand.gen(id, &idbuf[0])
				var desc = res:_string(i,2)
				var folder = res:_string(i,3)
				var mime = res:_string(i,4)

				m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, {
					desc =  {`desc.ptr, `desc.ct + 1};
					folder = {`folder.ptr, `folder.ct + 1};
					mime = {`mime.ptr, `mime.ct + 1};
					url = {`&idbuf[0], `idlen + 1};
				}) ]
				m(i).ptr.rid = id
				m(i).ptr.owner = uid
			end
			return m
		else return [lib.mem.lstptr(lib.store.artifact)].null() end
	end];

	artifact_load = [terra(
		src: &lib.store.source,
		rid: uint64
	): {binblob, pstring}
		var r = queries.artifact_load.exec(src,rid)
		if r.sz == 0 then return binblob.null(), pstring.null() end

		var mime = r:String(0,1)
		var mbin = r:bin(0,0)
		var bin = lib.mem.heapa(uint8,mbin.ct)
		lib.mem.cpy(bin.ptr, mbin.ptr, bin.ct)

		r:free()
		return bin, mime
	end];

	post_attach_ctl = [terra(
		src: &lib.store.source,
		post: uint64,
		artifact: uint64,
		detach: bool
	): {}

Modified math.t from [1d1d177061] to [60110bc615].

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
	var sz: intptr = 0
	for i = 0, f.ct do
		if f(i) == @',' then goto skip end
		if f(i) >= 0x30 and f(i) <= 0x39 then
			sz = sz * 10
			sz = sz + f(i) - 0x30
		else
			if i+1 == f.ct or f(i) == 0 then return sz, true end
			if i+2 == f.ct or f(i+1) == 0 then
				if f(i) == @'b' then return sz/8, true end -- bits
			else
				var s: intptr = 0
				if i+3 == f.ct or f(i+2) == 0 then 
					s = i + 1
				elseif (i+4 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then
				-- grudgingly tolerate ~mebibits~ and its ilk, without
				-- affecting the result in any way
					s = i + 2
				else return 0, false end

				if f(s) == @'b' then sz = sz/8 -- bits
				elseif f(s) ~= @'B' then return 0, false end -- wth
			end
			var c = f(i)
			if c >= @'A' and c <= @'Z' then c = c - 0x20 end
			switch c do -- normal char literal syntax doesn't work here, leads to llvm error (!!)
				case [uint8]([string.byte('k')]) then return sz * [1024ULL ^ 1], true end
				case [uint8]([string.byte('m')]) then return sz * [1024ULL ^ 2], true end
				case [uint8]([string.byte('g')]) then return sz * [1024ULL ^ 3], true end
				case [uint8]([string.byte('t')]) then return sz * [1024ULL ^ 4], true end

				case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 5], true end

				case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 6], true end
				else return sz, true
			end
		end
	::skip::end
	return sz, true
end

return m







|
|



|

|









|





>
|
>
|
|







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
	var sz: intptr = 0
	for i = 0, f.ct do
		if f(i) == @',' then goto skip end
		if f(i) >= 0x30 and f(i) <= 0x39 then
			sz = sz * 10
			sz = sz + f(i) - 0x30
		else
			if i+0 == f.ct or f(i) == 0 then return sz, true end
			if i+1 == f.ct or f(i+1) == 0 then
				if f(i) == @'b' then return sz/8, true end -- bits
			else
				var s: intptr = 0
				if i+2 == f.ct or f(i+2) == 0 then 
					s = i + 1
				elseif (i+3 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then
				-- grudgingly tolerate ~mebibits~ and its ilk, without
				-- affecting the result in any way
					s = i + 2
				else return 0, false end

				if f(s) == @'b' then sz = sz/8 -- bits
				elseif f(s) ~= @'B' then return 0, false end -- wth
			end
			var c = f(i)
			if c >= @'A' and c <= @'Z' then c = c + 0x20 end
			switch c do -- normal char literal syntax doesn't work here, leads to llvm error (!!)
				case [uint8]([string.byte('k')]) then return sz * [1024ULL ^ 1], true end
				case [uint8]([string.byte('m')]) then return sz * [1024ULL ^ 2], true end
				case [uint8]([string.byte('g')]) then return sz * [1024ULL ^ 3], true end
				case [uint8]([string.byte('t')]) then return sz * [1024ULL ^ 4], true end
				case [uint8]([string.byte('p')]) then return sz * [1024ULL ^ 5], true end
				case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 6], true end
				case [uint8]([string.byte('z')]) then return sz * [1024ULL ^ 7], true end
				case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 8], true end
				else return 0, false
			end
		end
	::skip::end
	return sz, true
end

return m

Modified render/media-gallery.t from [79b3557b2e] to [7a98efa9ff].

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
-- vim: ft=terra
local pstr = lib.str.t 
local P = lib.str.plit
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local show_all,show_new,show_files,show_vid,show_img=1,2,3,4,5

local terra 
render_media_gallery(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), uid: uint64, acc: &lib.str.acc)
 -- note that when calling this function, path must be adjusted so that path(0)
 -- eq "media"
	var owner = false
	if co.aid ~= 0 and co.who.id == uid then owner = true end
	var ou = co.srv:actor_fetch_uid(uid)
	if not ou then goto e404 end





















	var view = data.view.media_gallery {
		menu = pstr{'',0};
		folders = pstr{'',0};
		directory = pstr{'',0};
		images = pstr{'',0};

	}








	if owner then
		view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>'
	end
	var mode: uint8 = show_new
	var folder: pstr
	if mode == show_new then
		folder = lib.str.plit''
	elseif mode == show_all then
		folder = pstr.null()
	-- else get folder from query str
	end

	var md = co.srv:artifact_enum_uid(uid, folder)
	var gallery: lib.str.acc gallery:init(256)
	var files: lib.str.acc files:init(256) 
	for i=0,md.ct do
		if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
			gallery:lpush('<a class="thumb" href="')
			if not owner then
				gallery:lpush('/')
				if ou(0).origin ~= 0 then gallery:lpush('@') end
				gallery:push(ou(0).xid,0):lpush('/')
			end
			gallery:push(md(i)(0).url,0)
				:lpush('"><img src="') :push(md(i)(0).url,0)
				:lpush('/raw"><div class="caption">') :push(md(i)(0).desc,0)
				:lpush('</div></a>')
		else
			files:lpush('<a class="file" href="')
			if not owner then
				gallery:lpush('/')
				if ou(0).origin ~= 0 then gallery:lpush('@') end
				gallery:push(ou(0).xid,0):lpush('/')
			end
			files:push(md(i)(0).url,0)
				:lpush('"><span class="label">'):push(md(i)(0).desc,0)
				:lpush('</span> <span class="mime">'):push(md(i)(0).mime,0)
				:lpush('</span></a>')
		end
		md(i):free()
	end

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

	if acc ~= nil then
		view:append(acc)
	else

		var pg = view:tostr() defer pg:free()

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

	end

	view.images:free()
	view.directory:free()

	if md:ref() then md:free() end
	do return end

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

return render_media_gallery







|









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






>

>
>
>
>
>
>
>




<
<
<
<
<
<
<
<






|
<
<
<
<
<
|
<
|


|
<
<
<
<
<
<
|












>

>






>




>







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
-- vim: ft=terra
local pstr = lib.str.t 
local P = lib.str.plit
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local show_all,show_new,show_unfiled,show_files,show_vid,show_img=1,2,3,4,5,6

local terra 
render_media_gallery(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), uid: uint64, acc: &lib.str.acc)
 -- note that when calling this function, path must be adjusted so that path(0)
 -- eq "media"
	var owner = false
	if co.aid ~= 0 and co.who.id == uid then owner = true end
	var ou = co.srv:actor_fetch_uid(uid)
	if not ou then goto e404 end

	var mode: uint8 = show_new
	var folder: pstr
	if path.ct == 2 then
		if path(1):cmp(lib.str.lit'unfiled') then
			mode=show_unfiled
		elseif path(1):cmp(lib.str.lit'all') then
			mode=show_all
		else goto e404 end
	elseif path.ct == 3 and path(1):cmp(lib.str.lit'kind') then
	end

	if mode == show_new then
		folder = lib.str.plit''
	elseif mode == show_all then
		folder = pstr.null()
	elseif mode == show_unfiled then
		folder = lib.str.plit'' -- TODO
	-- else get folder from query str
	end

	var view = data.view.media_gallery {
		menu = pstr{'',0};
		folders = pstr{'',0};
		directory = pstr{'',0};
		images = pstr{'',0};
		pfx = pstr{'',0};
	}
	if not owner then
		var pa: lib.str.acc pa:init(32)
		pa:lpush('/')
		if ou(0).origin ~= 0 then pa:lpush('@') end
		pa:push(ou(0).xid,0)
		view.pfx = pa:finalize()
	end

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









	var md = co.srv:artifact_enum_uid(uid, folder)
	var gallery: lib.str.acc gallery:init(256)
	var files: lib.str.acc files:init(256) 
	for i=0,md.ct do
		if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
			gallery:lpush('<a class="thumb" href="'):ppush(view.pfx):lpush('/media/a/')





				:push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0)

				:lpush('"><div class="caption">'):push(md(i)(0).desc,0)
				:lpush('</div></a>')
		else
			files:lpush('<a class="file" href="'):ppush(view.pfx):lpush('/media/a/')






				:push(md(i)(0).url,0):lpush('"><span class="label">'):push(md(i)(0).desc,0)
				:lpush('</span> <span class="mime">'):push(md(i)(0).mime,0)
				:lpush('</span></a>')
		end
		md(i):free()
	end

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

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

	view.images:free()
	view.directory:free()
	if not owner then view.pfx:free() end
	if md:ref() then md:free() end
	do return end

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

return render_media_gallery

Modified route.t from [af3d41e739] to [8605ea50bd].

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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463



464
465
466
467
468
469
470
471
472
...
505
506
507
508
509
510
511














512
513
514
515
516
517
518
...
526
527
528
529
530
531
532


533
534
535
536
537
538
539
...
551
552
553
554
555
556
557

558
559
560
561
562
563
564
end

terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t)
	if meth == method.post then
		goto badop
	end

	if path.ct == 1 or (path.ct >= 3 and path(1):cmp(lib.str.lit'a')) then
		if meth == method.post then goto badop end
		lib.render.media_gallery(co,path,co.who.id,nil)
	elseif path.ct == 2 then
		if path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
			if meth == method.get then
				var view = data.view.media_upload {
					folders = ''
				}
				var pg = view:tostr() defer pg:free()
				co:stdpage([lib.srv.convo.page] {
					title = lib.str.plit'media :: upload';
					class = lib.str.plit'media upload';
					cache = false; body = pg;
				})
			elseif meth == method.post_file then
				var desc = pstring.null()
				var folder = pstring.null()
				var mime = pstring.null()
				var name = pstring.null()
				var body = binblob.null()
				for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i
					if up.body.ct > 0 then
						if up.field:cmp(lib.str.plit'desc') then
							desc = up.body
						elseif up.field:cmp(lib.str.plit'folder') then
							folder = up.body
						elseif up.field:cmp(lib.str.plit'file') then
							mime = up.ctype
							body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct}
							name = up.filename
						end
					end
				end
				if not body then goto badop end
				if body.ct > co.srv.cfg.maxupsz then
					co:complain(403, 'file too long', "the file you have attempted to upload exceeds the maximum length permitted by this server's upload policy. if it is an image or video, try compressing it at a lower quality setting or resolution")
					return
				end
				var id = co.srv:artifact_instantiate(body,mime)
				if id == 0 then
					co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server')
					return
				end
				co.srv:artifact_expropriate(co.who.id,id,desc,folder)

				var idbuf: int8[lib.math.shorthand.maxlen]
				var idlen = lib.math.shorthand.gen(id,&idbuf[0])

				var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
				co:reroute(url.ptr)
				url:free()
			else goto badop end



		end
	else goto e404 end
	do return end

	::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end
end

do local branches = quote end
................................................................................
end


terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8))
	-- TODO retrieve user avatars
	co:reroute('/s/default-avatar.webp')
end















-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
	co.navbar = lib.render.nav(co)
	-- some routes are non-hierarchical, and can be resolved with a simple strcmp
	-- we run through those first before giving up and parsing the URI
................................................................................
	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 not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})


	elseif uri:cmp(lib.str.plit '/notices') then
		if co.aid == 0 then co:reroute('/login') return end
		http.user_notices(co,meth)
	elseif uri:cmp(lib.str.plit '/compose') then
		if co.aid == 0 then co:reroute('/login') return end
		http.post_compose(co,meth)
	elseif uri:cmp(lib.str.plit '/login') then
................................................................................
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp(lib.str.lit('tl')) then
			http.timeline(co, path)
		elseif path(0):cmp(lib.str.lit('media')) then

			http.media_manager(co, path, meth)
		elseif path(0):cmp(lib.str.lit('doc')) then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp(lib.str.lit('conf')) then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)







<
<
<
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|

|
|
|
|
>
>
>
|
<







 







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







 







>
>







 







>







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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463

464
465
466
467
468
469
470
...
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
...
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
...
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
end

terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t)
	if meth == method.post then
		goto badop
	end





	if path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
		if meth == method.get then
			var view = data.view.media_upload {
				folders = ''
			}
			var pg = view:tostr() defer pg:free()
			co:stdpage([lib.srv.convo.page] {
				title = lib.str.plit'media :: upload';
				class = lib.str.plit'media upload';
				cache = false; body = pg;
			})
		elseif meth == method.post_file then
			var desc = pstring.null()
			var folder = pstring.null()
			var mime = pstring.null()
			var name = pstring.null()
			var body = binblob.null()
			for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i
				if up.body.ct > 0 then
					if up.field:cmp(lib.str.plit'desc') then
						desc = up.body
					elseif up.field:cmp(lib.str.plit'folder') then
						folder = up.body
					elseif up.field:cmp(lib.str.plit'file') then
						mime = up.ctype
						body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct}
						name = up.filename
					end
				end
			end
			if not body then goto badop end
			if body.ct > co.srv.cfg.maxupsz then
				co:complain(403, 'file too long', "the file you have attempted to upload exceeds the maximum length permitted by this server's upload policy. if it is an image or video, try compressing it at a lower quality setting or resolution")
				return
			end
			var id = co.srv:artifact_instantiate(body,mime)
			if id == 0 then
				co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server')
				return
			end
			co.srv:artifact_expropriate(co.who.id,id,desc,folder)

			var idbuf: int8[lib.math.shorthand.maxlen]
			var idlen = lib.math.shorthand.gen(id,&idbuf[0])

			var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
			co:reroute(url.ptr)
			url:free()
		else goto badop end
	else
		if meth == method.post then goto badop end
		lib.render.media_gallery(co,path,co.who.id,nil)
	end

	do return end

	::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end
end

do local branches = quote end
................................................................................
end


terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8))
	-- TODO retrieve user avatars
	co:reroute('/s/default-avatar.webp')
end

terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8))
	var id, idok = lib.math.shorthand.parse(id.ptr, id.ct)
	if not idok then goto e404 end
	var data, mime = co.srv:artifact_load(id)
	if not data then goto e404 end
	do defer data:free() defer mime:free()
		lib.net.mg_printf(co.con, 'HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n\r\n', mime.ct, mime.ptr, data.ct + 2)
		lib.net.mg_send(co.con, data.ptr, data.ct)
		lib.net.mg_send(co.con, '\r\n', 2)
	return end

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

-- entry points
terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t)
	lib.dbg('handling URI of form ', {uri.ptr,uri.ct})
	co.navbar = lib.render.nav(co)
	-- some routes are non-hierarchical, and can be resolved with a simple strcmp
	-- we run through those first before giving up and parsing the URI
................................................................................
	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 not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
	elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
	elseif uri:cmp(lib.str.plit '/notices') then
		if co.aid == 0 then co:reroute('/login') return end
		http.user_notices(co,meth)
	elseif uri:cmp(lib.str.plit '/compose') then
		if co.aid == 0 then co:reroute('/login') return end
		http.post_compose(co,meth)
	elseif uri:cmp(lib.str.plit '/login') then
................................................................................
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp(lib.str.lit('tl')) then
			http.timeline(co, path)
		elseif path(0):cmp(lib.str.lit('media')) then
			if co.aid == 0 then goto unauth end
			http.media_manager(co, path, meth)
		elseif path(0):cmp(lib.str.lit('doc')) then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp(lib.str.lit('conf')) then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)

Modified srv.t from [a1d0408148] to [31acbbce83].

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
...
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
...
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
...
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
				end
				-- check for a content-type header, and see if it's a multipart/
				-- form-data encoded POST request so we can handle file uploads
				co.uploads.sz = 0 co.uploads.run = 0
				if co.method == [lib.http.method.post] then
					var ctt = lib.http.findheader(msg, 'Content-Type')
					if ctt ~= nil then
						lib.dbg('found content type', {ctt.ptr,ctt.ct})
						if lib.str.ncmp(ctt.ptr,'multipart/form-data;',20) == 0 then
							var p = lib.str.ffw(ctt.ptr + 20,ctt.ct-20)
							if lib.str.ncmp(p,'boundary=',9) ~= 0 then
								co:complain(400,'bad request','unrecognized content-type')
								goto fail
							end
							var boundary = pstring {ptr=p+9,ct=ctt.ct - ((p - ctt.ptr) + 9)}
							lib.dbg('got boundary ',{boundary.ptr,boundary.ct})
							co.method = lib.http.method.post_file
							co.uploads:init(8)

							var bsr = (lib.str.acc{}):compose('\r\n--',boundary,'\r\n'):finalize()

							var upmap = lib.str.splitmap(co.body,bsr,8)
							-- first entry may not be preceded by header-break
................................................................................
									lsent.ct = halt.ptr - lsent.ptr
								end
								lsr:free() end

							for i=0,upmap.ct do
								var hdrbrk = lib.str.find(upmap(i), lib.str.plit'\r\n\r\n')
								if hdrbrk:ref() then
									lib.dbg('got new entry')
									var hdrtxt = pstring {upmap(i).ptr,upmap(i).ct - hdrbrk.ct}
									var hdrs = lib.str.splitmap(hdrtxt, '\r\n',6)
									var ctt = pstring.null()
									var ctd = pstring.null()
									for j=0, hdrs.ct do
										var brk = lib.str.find(hdrs(j),lib.str.plit':')
										if brk:ref() then
................................................................................
												ctd = val
											end
										end
									end
									if ctd:ref() then
										var ctdvals = lib.str.splitmap(ctd, ';', 4) defer ctdvals:free()
										if ctdvals(0):cmp(lib.str.plit'form-data') and ctdvals.ct > 1 then
											lib.dbg('found form data')
											var fld = pstring.null()
											var file = pstring.null()
											for j=1, ctdvals.ct do var v = ctdvals(j):ffw()
												var x = lib.str.find(v,lib.str.plit'=')
												if x:ref() then
													var key = pstring{v.ptr, v.ct - x.ct}
													var val = pstring{x.ptr + 1, x.ct - 1}
................................................................................
			end
		end
	end

	return 0
end

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

terra srv:setup(befile: rawstring)







<







<







 







<







 







<







 







<







526
527
528
529
530
531
532

533
534
535
536
537
538
539

540
541
542
543
544
545
546
...
559
560
561
562
563
564
565

566
567
568
569
570
571
572
...
578
579
580
581
582
583
584

585
586
587
588
589
590
591
...
772
773
774
775
776
777
778

779
780
781
782
783
784
785
				end
				-- check for a content-type header, and see if it's a multipart/
				-- form-data encoded POST request so we can handle file uploads
				co.uploads.sz = 0 co.uploads.run = 0
				if co.method == [lib.http.method.post] then
					var ctt = lib.http.findheader(msg, 'Content-Type')
					if ctt ~= nil then

						if lib.str.ncmp(ctt.ptr,'multipart/form-data;',20) == 0 then
							var p = lib.str.ffw(ctt.ptr + 20,ctt.ct-20)
							if lib.str.ncmp(p,'boundary=',9) ~= 0 then
								co:complain(400,'bad request','unrecognized content-type')
								goto fail
							end
							var boundary = pstring {ptr=p+9,ct=ctt.ct - ((p - ctt.ptr) + 9)}

							co.method = lib.http.method.post_file
							co.uploads:init(8)

							var bsr = (lib.str.acc{}):compose('\r\n--',boundary,'\r\n'):finalize()

							var upmap = lib.str.splitmap(co.body,bsr,8)
							-- first entry may not be preceded by header-break
................................................................................
									lsent.ct = halt.ptr - lsent.ptr
								end
								lsr:free() end

							for i=0,upmap.ct do
								var hdrbrk = lib.str.find(upmap(i), lib.str.plit'\r\n\r\n')
								if hdrbrk:ref() then

									var hdrtxt = pstring {upmap(i).ptr,upmap(i).ct - hdrbrk.ct}
									var hdrs = lib.str.splitmap(hdrtxt, '\r\n',6)
									var ctt = pstring.null()
									var ctd = pstring.null()
									for j=0, hdrs.ct do
										var brk = lib.str.find(hdrs(j),lib.str.plit':')
										if brk:ref() then
................................................................................
												ctd = val
											end
										end
									end
									if ctd:ref() then
										var ctdvals = lib.str.splitmap(ctd, ';', 4) defer ctdvals:free()
										if ctdvals(0):cmp(lib.str.plit'form-data') and ctdvals.ct > 1 then

											var fld = pstring.null()
											var file = pstring.null()
											for j=1, ctdvals.ct do var v = ctdvals(j):ffw()
												var x = lib.str.find(v,lib.str.plit'=')
												if x:ref() then
													var key = pstring{v.ptr, v.ct - x.ct}
													var val = pstring{x.ptr + 1, x.ct - 1}
................................................................................
			end
		end
	end

	return 0
end


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

terra srv:setup(befile: rawstring)

Modified static/live.js from [45ade869ea] to [76c21b64a1].

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
			if (ert != null) { lede = post; post = ert; }
			let purl = posturl(post);
			let url = null;
			if (lede == null) {url = purl;} else {
				url = lede.querySelector('a[href].del').
					attributes.getNamedItem('href').value;
			}
			console.log('post',post,'lede',lede,url);

			if (last == null) { newmap.first = url; } else {
				newmap.map.get(last).next = url;
			}
			newmap.map.set(url, {me: post, go: purl, prev: last, next: null})
			last = url;
			if (window._liveTweetMap &&







<







117
118
119
120
121
122
123

124
125
126
127
128
129
130
			if (ert != null) { lede = post; post = ert; }
			let purl = posturl(post);
			let url = null;
			if (lede == null) {url = purl;} else {
				url = lede.querySelector('a[href].del').
					attributes.getNamedItem('href').value;
			}


			if (last == null) { newmap.first = url; } else {
				newmap.map.get(last).next = url;
			}
			newmap.map.set(url, {me: post, go: purl, prev: last, next: null})
			last = url;
			if (window._liveTweetMap &&

Modified static/style.scss from [7f55f7d5d1] to [f5bd822625].

1018
1019
1020
1021
1022
1023
1024
1025
1026
1027

1028



1029
1030
1031
1032

1033
1034



1035
1036
1037
1038

1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
		background: tone(-55%,-0.5);
		border: 1px solid tone(-60%);
		padding: 0.2in;
		display: flex;
		flex-wrap: wrap;
	}
	.gallery {
		grid-row: 1/2; grid-column: 2/3;
		margin-left: 0.1in;
		flex-flow: row;

		> a[href].thumb {



			display: block;
			width: 1.5in;
			padding: 0.1in;
			height: max-content;

			> img {
				width: 1.5in; height: 1.5in;



			}
			> .caption {
				text-align: center;
				font-size: 80%;

			}
		}
	}
	.dir {
		grid-row: 2/3; grid-column: 1/3;
		margin-top: 0.1in;
		flex-flow: column;
		flex-grow: 1;
		> a[href].file {
			padding: 0.1in 0.15in;
			text-decoration: none;
			height: max-content;
			background-image: url(/s/file.webp); //TODO different icons for different mime types







|
|

>

>
>
>




>


>
>
>




>




|
|







1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
		background: tone(-55%,-0.5);
		border: 1px solid tone(-60%);
		padding: 0.2in;
		display: flex;
		flex-wrap: wrap;
	}
	.gallery {
		grid-row: 2/3; grid-column: 1/3;
		margin-top: 0.1in;
		flex-flow: row;
		padding: 0 0.1in;
		> a[href].thumb {
			background: linear-gradient(to top, tone(10%,-0.8), tone(10%,-0.94) 30%, transparent);
			border-radius: 4px;
			border-bottom: 1px solid tone(15%, -0.5);
			display: block;
			width: 1.5in;
			padding: 0.1in;
			height: max-content;
			margin: 0.1in;
			> img {
				width: 1.5in; height: 1.5in;
				object-fit: contain;
				object-position: center;
				outline: none;
			}
			> .caption {
				text-align: center;
				font-size: 80%;
				text-shadow: 1px 1px 0 black;
			}
		}
	}
	.dir {
		grid-row: 1/2; grid-column: 2/3;
		margin-left: 0.1in;
		flex-flow: column;
		flex-grow: 1;
		> a[href].file {
			padding: 0.1in 0.15in;
			text-decoration: none;
			height: max-content;
			background-image: url(/s/file.webp); //TODO different icons for different mime types

Modified view/media-gallery.tpl from [d752c55f41] to [18862037ee].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<menu>@menu
	<a href="/media">new uploads</a>
	<a href="/media/unfiled">unfiled</a>
	<hr>
	@folders
	<a href="/media/all">all uploads</a>
	<a href="/media/kind/img">all images</a>
	<a href="/media/kind/vid">all videos</a>
	<a href="/media/kind/txt">all text files</a>
	<a href="/media/king/misc">all others</a>
</menu>

<div class="dir">
	@directory
</div>

<div class="gallery">
	@images
</div>

|
|


|
|
|
|
|









1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<menu>@menu
	<a href="@pfx/media">new uploads</a>
	<a href="@pfx/media/unfiled">unfiled</a>
	<hr>
	@folders
	<a href="@pfx/media/all">all uploads</a>
	<a href="@pfx/media/kind/img">all images</a>
	<a href="@pfx/media/kind/vid">all videos</a>
	<a href="@pfx/media/kind/txt">all text files</a>
	<a href="@pfx/media/kind/misc">all others</a>
</menu>

<div class="dir">
	@directory
</div>

<div class="gallery">
	@images
</div>