parsav  Check-in [93aea04a05]

Overview
Comment:media uploads work now, some types can be viewed
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 93aea04a05d7e6e0ea44a8a308c0f7d33fd4bbcd8422e7f95f95b176851885d3
User & Date: lexi on 2021-01-07 20:39:57
Other Links: manifest | tags
Context
2021-01-08
05:58
enable passwords check-in: d6024624c6 user: lexi tags: trunk
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
Changes

Modified backend/pgsql.t from [fc1c52be55] to [cb3e1743a5].

550
551
552
553
554
555
556







557
558
559
560
561
562
563
...
826
827
828
829
830
831
832

















833
834
835
836
837
838
839
....
1581
1582
1583
1584
1585
1586
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
....
1615
1616
1617
1618
1619
1620
1621












1622
1623
1624
1625
1626
1627
1628
			from parsav_artifact_claims as a where uid = $1::bigint and rid = $2::bigint
		]];
	};
	artifact_load = {
		params = {uint64}, sql = [[
			select content, mime from parsav_artifacts where id = $1::bigint
		]];







	};
	post_attach_ctl_ins = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = artifacts || $2::bigint
			where id = $1::bigint and not
				artifacts @> array[$2::bigint] -- prevent duplication
................................................................................
			lib.pq.PQclear(res)
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end


















local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post)
	var subj: rawstring, sblen: intptr
	var cvhu: rawstring, cvhlen: intptr
	if r:null(row,3)
		then subj = nil sblen = 0
		else subj = r:string(row,3) sblen = r:len(row,3)+1
................................................................................
		uid: uint64,
		folder: pstring
	)
		var res = queries.artifact_enum_uid.exec(src,uid,folder)
		if res.sz > 0 then
			var m = lib.mem.heapa([lib.mem.ptr(lib.store.artifact)], res.sz)
			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 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
	): {}







>
>
>
>
>
>
>







 







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







 







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





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







 







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







550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
...
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
....
1605
1606
1607
1608
1609
1610
1611
1612












1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
....
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
			from parsav_artifact_claims as a where uid = $1::bigint and rid = $2::bigint
		]];
	};
	artifact_load = {
		params = {uint64}, sql = [[
			select content, mime from parsav_artifacts where id = $1::bigint
		]];
	};
	artifact_folder_enum = {
		params = {uint64}, sql = [[
			select distinct folder from parsav_artifact_claims where
				uid = $1::bigint and folder is not null
				order by folder
		]];
	};
	post_attach_ctl_ins = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = artifacts || $2::bigint
			where id = $1::bigint and not
				artifacts @> array[$2::bigint] -- prevent duplication
................................................................................
			lib.pq.PQclear(res)
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end

local terra row_to_artifact(res: &pqr, i: intptr): lib.mem.ptr(lib.store.artifact)
	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 m = [ 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.ptr.rid = id
	return m
end

local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post)
	var subj: rawstring, sblen: intptr
	var cvhu: rawstring, cvhlen: intptr
	if r:null(row,3)
		then subj = nil sblen = 0
		else subj = r:string(row,3) sblen = r:len(row,3)+1
................................................................................
		uid: uint64,
		folder: pstring
	)
		var res = queries.artifact_enum_uid.exec(src,uid,folder)
		if res.sz > 0 then
			var m = lib.mem.heapa([lib.mem.ptr(lib.store.artifact)], res.sz)
			for i=0,res.sz do
				m.ptr[i] = row_to_artifact(&res, i)












				m(i).ptr.owner = uid
			end
			return m
		else return [lib.mem.lstptr(lib.store.artifact)].null() end
	end];

	artifact_fetch = [terra(
		src: &lib.store.source,
		uid: uint64,
		rid: uint64
	)
		var res = queries.artifact_fetch.exec(src,uid,rid)
		if res.sz > 0 then
			var a = row_to_artifact(&res, 0)
			a.ptr.owner = uid
			res:free()
			return a
		end
		return [lib.mem.ptr(lib.store.artifact)].null()
	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 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];

	artifact_folder_enum = [terra(
		src: &lib.store.source,
		uid: uint64
	)
		var r = queries.artifact_folder_enum.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(pstring)].null() end
		defer r:free()
		var lst = lib.mem.heapa(pstring, r.sz)
		for i=0,r.sz do lst.ptr[i] = r:String(i,0) end
		return lst
	end];

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

Modified html.t from [ee4d50abb4] to [f6f8bc0dcc].

1
2
3
4
5


6
7
8
9
10
11
12
13
14
15
16































17
-- vim: ft=terra
local m={}
local pstr = lib.mem.ptr(int8)

terra m.sanitize(txt: pstr, quo: bool)


	var a: lib.str.acc a:init(txt.ct*1.3)
	for i=0,txt.ct do
		if txt(i) == @'<' then a:lpush('&lt;')
		elseif txt(i) == @'>' then a:lpush('&gt;')
		elseif txt(i) == @'&' then a:lpush('&amp;')
		elseif quo and txt(i) == @'"' then a:lpush('&quot;')
		else a:push(&txt(i),1) end
	end
	return a:finalize()
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
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
-- vim: ft=terra
local m={}
local pstr = lib.mem.ptr(int8)

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

terra m.hexdgt(i: uint8)
	if i >= 10 then
		return @'A' + (i - 10)
	else return 0x30 + i end
end

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

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

return m

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

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







>
>
>
>
>
>
>
>
>

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

>
>
|
|
|
|
<
<
<
|

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

|
|
|

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

|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
<
>
>
|
>
>
|





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
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
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
	do defer ou:free()
		var pfx = pstr.null()
		if not owner then
			var pa: lib.str.acc pa:init(32)
			pa:lpush('/')
			if ou(0).origin ~= 0 then pa:lpush('@') end
			pa:push(ou(0).xid,0)
			pfx = pa:finalize()
		end

		if path.ct >= 3 and path(1):cmp(lib.str.lit'a') then
			var id, idok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
			if not idok then goto e404 end
			var art = co.srv:artifact_fetch(uid, id)
			if not art then goto e404 end
			if path.ct == 3 then
			-- sniff out the artifact type and display the appropriate viewer
				var artid = cs(art(0).url)
				var btns: lib.str.acc
				if owner then
					btns:compose('<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>')
				else
					btns:compose('<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>')
				end
				var btntxt = btns:finalize() defer btntxt:free()
				var desc = lib.smackdown.html(pstr{art(0).desc,0}, true) defer desc:free()
				var viewerprops = {
					pfx = pfx, desc = desc;
					id = artid; btns = btntxt;
				}
				if lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then
					var view = data.view.media_image(viewerprops)
					var pg = view:tostr()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: image';
						class = lib.str.plit'media viewer img';
						cache = false, body = pg;
					})
					pg:free()
				elseif lib.str.cmp(art(0).mime, 'text/markdown') == 0 then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					view.text = lib.smackdown.html(pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					var pg = view:tostr()
					view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
						class = lib.str.plit'media viewer text';
						cache = false, body = pg;
					})
					pg:free()
				elseif
					lib.str.ncmp(art(0).mime, 'text/', 5) == 0          or
					lib.str.cmp(art(0).mime, 'application/x-perl') == 0 or
					lib.str.cmp(art(0).mime, 'application/sql') == 0
					-- and so on (we need a mimelib at some point) --
				then
					var view = data.view.media_text(viewerprops)
					var text, mime = co.srv:artifact_load(id) mime:free()
					var san = lib.html.sanitize(pstr{[rawstring](text.ptr),text.ct}, false)
					text:free()
					view.text = lib.str.acc{}:compose('<pre>',san,'</pre>'):finalize()
					san:free()
					var pg = view:tostr()
					view.text:free()
					co:stdpage([lib.srv.convo.page] {
						title = lib.str.plit'media :: text';
						class = lib.str.plit'media viewer text';
						cache = false, body = pg;
					})
					pg:free()
				else co:complain(500,'bad file type','this file type is not supported') end
			elseif path.ct == 4 then
				var act = path(3)
				var curl = lib.str.acc{}:compose(pfx, '/media/a/', path(2)):finalize()
				defer curl:free()
				if act:cmp(lib.str.lit'avi') and lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then
					co:confirm('set avatar', 'are you sure you want this image to be your new avatar?',curl)
				elseif act:cmp(lib.str.lit'del') then
					co:confirm('delete', 'are you sure you want to permanently delete this artifact?',curl)
				else goto e404 end
			end
		else
			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

			var folders = co.srv:artifact_folder_enum(uid)

			if mode == show_new then
				folder = lib.str.plit''
			elseif mode == show_all or mode == show_unfiled then
				folder = pstr.null()



			end

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


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


				view.folders = fa:finalize()
				folders:free()
			end

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

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

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

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

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

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

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

return render_media_gallery

Modified route.t from [8605ea50bd] to [881176d2e1].

510
511
512
513
514
515
516
517














518
519
520
521
522
523
524

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








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







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

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()
		var safemime = mime
		-- TODO this is not a satisfactory solution; it's a bandaid on a gaping
		-- chest wound. ultimately we need to compile a whitelist of safe mime
		-- types as part of mimelib, but that is no small task. for now, this
		-- will keep the patient from immediately bleeding out
		if mime:cmp(lib.str.plit'text/html') or
			mime:cmp(lib.str.plit'text/xml') or
			mime:cmp(lib.str.plit'application/xhtml+xml') or
			mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml')
		then -- danger will robinson
			safemime = lib.str.plit'text/plain'
		elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then
			safemime = lib.str.plit'application/octet-stream'
		end
		lib.net.mg_printf(co.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\nContent-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\nX-Content-Options: nosniff\r\n\r\n", safemime.ct, safemime.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

Modified smackdown.t from [926ec15b69] to [e99ea3e622].

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
		do return l+i end
	::nexti::end
end

local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or isws(@sl) then return sl-nc end
	return nil
end

terra m.html(input: pstr, firstline: bool)

	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end

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

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

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



		var here = md.ptr + i
		var rem = md.ct - i
		if @here == @'[' then
			var sep = scanline(here,rem, '](', 2)
			var term = scanline(sep+2,rem - ((sep+2)-here), ')', 1)
			if sep ~= nil and term ~= nil then







|




>







|
|
>
>







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
		do return l+i end
	::nexti::end
end

local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or not isws(@(sl-1)) then return sl-nc end
	return nil
end

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

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

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

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

		var here = md.ptr + i
		var rem = md.ct - i
		if @here == @'[' then
			var sep = scanline(here,rem, '](', 2)
			var term = scanline(sep+2,rem - ((sep+2)-here), ')', 1)
			if sep ~= nil and term ~= nil then

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

280
281
282
283
284
285
286

















287
288
289
290
291
292
293
	}

	self:statpage(code, body)

	body.title:free()
	body.body:free()
end


















convo.methods.assertpow = macro(function(self, pow)
	return quote
		var ok = true
		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
			ok = false
			self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])







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







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
	}

	self:statpage(code, body)

	body.title:free()
	body.body:free()
end

terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
	var conf = data.view.confirm {
		title = title;
		query = msg;
		cancel = cancel;
	}
	var ti: lib.str.acc ti:compose('confirm :: ', title)
	var body = conf:tostr() defer body:free()
	var cf = [convo.page] {
		title = ti:finalize();
		class = lib.str.plit 'query';
		body = body; cache = false;
	}
	self:stdpage(cf)
	cf.title:free()
end

convo.methods.assertpow = macro(function(self, pow)
	return quote
		var ok = true
		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
			ok = false
			self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])

Modified static/style.scss from [f5bd822625] to [fb57063208].

281
282
283
284
285
286
287

288
289
290
291
292
293
294
...
487
488
489
490
491
492
493


494
495
496
497
498
499
500
....
1059
1060
1061
1062
1063
1064
1065





1066
1067
1068
1069
1070
1071
1072
....
1073
1074
1075
1076
1077
1078
1079

































		grid-row: 1 / 2;
		display: grid;
		grid-template-columns: 1.1in 1fr;
		grid-template-rows: max-content 1fr;
		> .avatar {
			display: block;
			width: 1in; height: 1in;

			grid-column: 1 / 2;
			grid-row: 1 / 3;
			border: 1px solid black;
		}
		> .id {
			grid-column: 2 / 3;
			grid-row: 1 / 2;
................................................................................
	font-size: 1.5ex !important;
	letter-spacing: 1.3px;
	padding-bottom: 3px;
	border-radius: 2px;
	vertical-align: baseline;
	box-shadow: 1px 1px 1px black;
}



div.thread {
	margin-left: 0.3in;
	& + article.post { margin-top: 0.3in; }
}

a[href].username {
................................................................................
			height: max-content;
			background-image: url(/s/file.webp); //TODO different icons for different mime types
			background-repeat: no-repeat;
			background-position: left;
			padding-left: 0.4in;
			> .label {
				text-decoration: underline;





			}
			> .mime {
				font-style: italic;
				opacity: 60%;
				margin-left: 0.5ex;
			}
		}
................................................................................
	}
}

.media.upload form {
	padding: 0.1in 0.2in;
	@extend %box;
}








































>







 







>
>







 







>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
...
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
....
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
....
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
		grid-row: 1 / 2;
		display: grid;
		grid-template-columns: 1.1in 1fr;
		grid-template-rows: max-content 1fr;
		> .avatar {
			display: block;
			width: 1in; height: 1in;
			object-fit: contain;
			grid-column: 1 / 2;
			grid-row: 1 / 3;
			border: 1px solid black;
		}
		> .id {
			grid-column: 2 / 3;
			grid-row: 1 / 2;
................................................................................
	font-size: 1.5ex !important;
	letter-spacing: 1.3px;
	padding-bottom: 3px;
	border-radius: 2px;
	vertical-align: baseline;
	box-shadow: 1px 1px 1px black;
}

pre { @extend %teletype; white-space: pre-wrap; }

div.thread {
	margin-left: 0.3in;
	& + article.post { margin-top: 0.3in; }
}

a[href].username {
................................................................................
			height: max-content;
			background-image: url(/s/file.webp); //TODO different icons for different mime types
			background-repeat: no-repeat;
			background-position: left;
			padding-left: 0.4in;
			> .label {
				text-decoration: underline;
				text-decoration-width: 1px;
				text-underline-offset: 0.1em;
				text-decoration-color: tone(10%,-0.5);
			}
			&:hover > .label {
			}
			> .mime {
				font-style: italic;
				opacity: 60%;
				margin-left: 0.5ex;
			}
		}
................................................................................
	}
}

.media.upload form {
	padding: 0.1in 0.2in;
	@extend %box;
}

body.media div.viewer {
	@extend %box;
	padding: 0.2in;
	margin-bottom: 0.2in;
	&.img {
		> img {
			display: block;
			max-width: 100%;
			margin: auto;
		}
		.caption {
			margin-top: 0.2in;
			text-align: center;
			&:empty {margin: 0;}
		}
	}
	&.text {
		> .desc {
			border-bottom: 1px solid tone(-5%);
			box-shadow: 0 2px 0 black;
			margin-bottom: 0.1in;
			padding-bottom: 0.1in;
		}
		> article {
			font-size: 90%;
			padding: 0 0.2in;
			max-height: calc(100vh - 3in);
			overflow-y: scroll;
			text-align: justify;
		}
	}
}

Modified store.t from [fdc1c1d9e2] to [eca94a58d5].

488
489
490
491
492
493
494


495
496
497
498
499
500
501
		-- restricted by folder (empty string = new only)
	artifact_fetch: {&m.source, uint64, uint64} -> lib.mem.ptr(m.artifact)
		-- fetch a user's view of an artifact
			-- uid: uint64
			-- rid: uint64
	artifact_load: {&m.source, uint64} -> {lib.mem.ptr(uint8),lib.str.t}
		-- load the body of an artifact into memory (also returns mime)



	nkvd_report_issue: {&m.source, &m.kompromat} -> {}
		-- an incidence of Badthink has been detected. report it immediately
		-- to the Supreme Soviet
	nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat)
		-- search through the Archives
			-- proto: kompromat (null for all records, or a prototype describing the records to return)







>
>







488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
		-- restricted by folder (empty string = new only)
	artifact_fetch: {&m.source, uint64, uint64} -> lib.mem.ptr(m.artifact)
		-- fetch a user's view of an artifact
			-- uid: uint64
			-- rid: uint64
	artifact_load: {&m.source, uint64} -> {lib.mem.ptr(uint8),lib.str.t}
		-- load the body of an artifact into memory (also returns mime)
	artifact_folder_enum: {&m.source, uint64} -> lib.mem.ptr(lib.str.t)
		-- enumerate all of a user's folders

	nkvd_report_issue: {&m.source, &m.kompromat} -> {}
		-- an incidence of Badthink has been detected. report it immediately
		-- to the Supreme Soviet
	nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat)
		-- search through the Archives
			-- proto: kompromat (null for all records, or a prototype describing the records to return)

Modified view/load.lua from [fbf23a2927] to [9f4f065de0].

9
10
11
12
13
14
15


16
17
18
19
20
21
22
	'tweet';
	'profile';
	'compose';
	'notice';

	'media-gallery';
	'media-upload';



	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';







>
>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	'tweet';
	'profile';
	'compose';
	'notice';

	'media-gallery';
	'media-upload';
	'media-image';
	'media-text';

	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';

Added view/media-image.tpl version [df3a1820c7].





















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
<div class="viewer img">
	<img src="/file/@id">
	<div class="caption">@desc</div>
</div>
<menu class="choice horizontal">
	<a class="button" href="@pfx/media">index</a>
	<a class="button" href="@pfx/media/a/@id/avi">set as avatar</a>
	@btns
	<a class="button" href="/file/@id" download>download</a>
</menu>

Added view/media-text.tpl version [73c12d23b4].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
<div class="viewer text">
	<div class="desc">@desc</div>
	<article>@text</article>
</div>
<menu class="choice horizontal">
	<a class="button" href="@pfx/media">index</a>
	@btns
	<a class="button" href="/file/@id" download>download</a>
</menu>