changeset 6417:dcc8d4af8d4f

mod_muc_cache_media: move to storage that matches RFC 6920
author Stephen Paul Weber <singpolyma@singpolyma.net>
date Tue, 24 Feb 2026 11:57:09 -0500
parents 2511bf07b8d2
children 98e06fc5680f
files mod_muc_cache_media/mod_muc_cache_media.lua
diffstat 1 files changed, 35 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- a/mod_muc_cache_media/mod_muc_cache_media.lua	Tue Feb 24 16:39:04 2026 +0100
+++ b/mod_muc_cache_media/mod_muc_cache_media.lua	Tue Feb 24 11:57:09 2026 -0500
@@ -10,31 +10,12 @@
 local jid = require "util.jid"
 
 local storage_path = module:get_option_string("muc_media_store_path", "/var/lib/prosody/muc-media")
-local public_base_url = module:get_option_string("muc_media_public_base", "https://cache.example.com")
-local files_have_extensions = module:get_option_boolean("muc_media_files_have_extensions", true)
+local public_base_url = module:get_option_string("muc_media_public_base")
 local max_size = module:get_option_number("muc_media_max_size", 10 * 1024 * 1024)
 
 lfs.mkdir(storage_path)
-
-local mime_extensions = {
-	["image/jpeg"] = ".jpg",
-	["image/jpg"]  = ".jpg",
-	["image/png"]  = ".png",
-	["image/gif"]  = ".gif",
-	["image/webp"] = ".webp",
-	["image/avif"] = ".avif",
-	["video/mp4"]  = ".mp4",
-	["video/webm"] = ".webm",
-	["audio/mpeg"] = ".mp3",
-	["audio/ogg"]  = ".ogg",
-}
-
-local function extension_from_content_type(content_type)
-	if not content_type then return ".bin" end
-	local clean = content_type:match("^[^;]+")
-	clean = clean and clean:lower()
-	return mime_extensions[clean] or ".bin"
-end
+lfs.mkdir(storage_path.."/sha-256")
+lfs.mkdir(storage_path.."/sha3-256")
 
 local function ipv4_to_number(ip)
 	local a,b,c,d = ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
@@ -160,20 +141,20 @@
 			local sha3 = sha3_256_file(tmp_path)
 			local size = file_size(tmp_path)
 			local name = filename_from_url(original_url)
-			if sha2 then
-				local ext = extension_from_content_type(content_type)
-				local file_name = sha2:gsub("+","-"):gsub("/","_"):gsub("=","")
-				local url_name = file_name..ext
-				if files_have_extensions then
-					file_name = file_name..ext
-				end
-				local final_path = storage_path.."/"..file_name
+			if sha2 and sha3 then
+				local final_name = "sha3-256/"..sha3:gsub("+","-"):gsub("/","_"):gsub("=","")
+				local final_path = storage_path.."/"..final_name
 				if not lfs.attributes(final_path) then
 					os.rename(tmp_path, final_path)
 				else
+					lfs.touch(final_path)
 					os.remove(tmp_path)
 				end
-				url_map[original_url] = { local_url = public_base_url.."/"..url_name, path=final_path, sha2=sha2, sha3=sha3, size=size, name=name, content_type=content_type }
+				local sha2_path = storage_path.."/sha-256/"..sha2:gsub("+","-"):gsub("/","_"):gsub("=","")
+				if not lfs.attributes(sha2_path) then
+					lfs.link(final_path, sha2_path, true)
+				end
+				url_map[original_url] = { local_url = public_base_url..final_name.."?ct="..content_type, path=final_path, sha2=sha2, sha3=sha3, size=size, name=name, content_type=content_type }
 			else
 				os.remove(tmp_path)
 			end
@@ -247,20 +228,18 @@
 								-- Fill SHA256 hash
 								local sha2_tag = nil
 								for htag in file_tag:childtags("hash","urn:xmpp:hashes:2") do
-									if htag.attr.algo=="sha2-256" then sha2_tag = htag end
+									if htag.attr.algo=="sha-256" then sha2_tag = htag end
 								end
 								if not sha2_tag then
-									file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha2-256" }):text(data.sha2))
+									file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha-256" }):text(data.sha2))
 								end
-								if data.sha3 then
-									-- Fill SHA3-256 hash
-									local sha3_tag = nil
-									for htag in file_tag:childtags("hash","urn:xmpp:hashes:2") do
-										if htag.attr.algo=="sha3-256" then sha3_tag = htag end
-									end
-									if not sha3_tag then
-										file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha3-256" }):text(data.sha3))
-									end
+								-- Fill SHA3-256 hash
+								local sha3_tag = nil
+								for htag in file_tag:childtags("hash","urn:xmpp:hashes:2") do
+									if htag.attr.algo=="sha3-256" then sha3_tag = htag end
+								end
+								if not sha3_tag then
+									file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha3-256" }):text(data.sha3))
 								end
 							end
 						end
@@ -275,8 +254,8 @@
 				file_tag:add_child(st.stanza("media-type"):text(data.content_type or "application/octet-stream"))
 				file_tag:add_child(st.stanza("name"):text(data.name))
 				file_tag:add_child(st.stanza("size"):text(tostring(data.size)))
-				file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha2-256" }):text(data.sha2))
-				if data.sha3 then file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha3-256" }):text(data.sha3)) end
+				file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha-256" }):text(data.sha2))
+				file_tag:add_child(st.stanza("hash",{ xmlns="urn:xmpp:hashes:2", algo="sha3-256" }):text(data.sha3))
 				ms:add_child(file_tag)
 				local sources = st.stanza("sources")
 				sources:add_child(st.stanza("reference",{ xmlns="urn:xmpp:reference:0", type="data", uri=data.local_url }))
@@ -310,36 +289,19 @@
 		-- Extract SIMS hashes
 		for ref in stanza:childtags("reference", "urn:xmpp:reference:0") do
 			for ms in ref:childtags("media-sharing", "urn:xmpp:sims:1") do
-				local file_tag = ms:get_child("file", "urn:xmpp:jingle:apps:file-transfer:5")
-				if file_tag then
+				for file_tag in ms:childtags("file", "urn:xmpp:jingle:apps:file-transfer:5") do
 					for hash_tag in file_tag:childtags("hash", "urn:xmpp:hashes:2") do
-						if hash_tag.attr.algo == "sha2-256" then
-							local sha2 = hash_tag:get_text()
-							if sha2 then
-								local prefix = sha2:gsub("+","-"):gsub("/","_"):gsub("=","")
-								if files_have_extensions then
-									-- Files are named: hash + extension
-									for file in lfs.dir(storage_path) do
-										if file:match("^" .. prefix) then
-											local path = storage_path .. "/" .. file
-											if lfs.attributes(path) then
-												os.remove(path)
-												module:log("debug",
-													"Deleted cached media %s for moderated message %s",
-													file, stanza_id
-												)
-											end
-										end
-									end
-								else
-									local path = storage_path .. "/" .. prefix
-									if lfs.attributes(path) then
-										os.remove(path)
-										module:log("debug",
-											"Deleted cached media %s for moderated message %s",
-											file, stanza_id
-										)
-									end
+						if hash_tag.attr.algo == "sha3-256" then
+							local sha3 = hash_tag:get_text()
+							if sha3 then
+								local file = sha3:gsub("+","-"):gsub("/","_"):gsub("=","")
+								local path = storage_path .. "/sha3-256/" .. file
+								if lfs.attributes(path) then
+									os.remove(path)
+									module:log("debug",
+										"Deleted cached media %s for moderated message %s",
+										file, stanza_id
+									)
 								end
 							end
 						end