Mercurial > prosody-modules
view mod_muc_log_http/mod_muc_log_http.lua @ 99:2d03350613c4
mod_srvinjection: Added support for resolving "localhost" and "127.0.0.1".
author | Waqas Hussain <waqas20@gmail.com> |
---|---|
date | Thu, 26 Nov 2009 20:23:20 +0500 |
parents | 941fd7d8b9b2 |
children | e3c09996cf7b |
line wrap: on
line source
-- Copyright (C) 2009 Thilo Cestonaro -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local prosody = prosody; local tabSort = table.sort; local tonumber = _G.tonumber; local tostring = _G.tostring; local strformat = string.format; local splitJid = require "util.jid".split; local config_get = require "core.configmanager".get; local httpserver = require "net.httpserver"; local datamanager = require "util.datamanager"; local data_load, data_getpath = datamanager.load, datamanager.getpath; local datastore = "muc_log"; local muc_hosts = {}; local config = nil; local lom = require "lxp.lom"; --[[ LuaFileSystem * URL: http://www.keplerproject.org/luafilesystem/index.html * Install: luarocks install luafilesystem * ]] local lfs = require "lfs"; --[[ * Default templates for the html output. ]]-- local html = {}; html.doc = [[<html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" > <head> <title>muc_log</title> </head> <script type="text/javascript"><!-- function showHide(name) { var eles = document.getElementsByName(name); for (var i = 0; i < eles.length; i++) { eles[i].style.display = eles[i].style.display != "none" ? "none" : ""; } } --></script> <style type="text/css"> <!-- .timestuff {color: #AAAAAA; text-decoration: none;} .muc_join {color: #009900; font-style: italic;} .muc_leave {color: #009900; font-style: italic;} .muc_statusChange {color: #009900; font-style: italic;} .muc_title {color: #BBBBBB; font-size: 32px;} .muc_titleChange {color: #009900; font-style: italic;} .muc_kick {color: #009900; font-style: italic;} .muc_bann {color: #009900; font-style: italic;} .muc_msg_nick {color: #0000AA;} .muc_msg_me {color: #0000AA;} .join_link {font-height: 9px;} //--> </style> <body> ###BODY_STUFF### </body> </html>]]; html.components = {}; html.components.bit = [[<a href="###COMPONENT###/">###COMPONENT###</a><br />]] html.components.body = [[<h2>MUC hosts available on this server:</h2><hr /><p> ###COMPONENTS_STUFF### </p><hr />]]; html.rooms = {}; html.rooms.bit = [[<a href="###ROOM###/">###ROOM###</a><br />]] html.rooms.body = [[<h2>Rooms hosted on MUC host: ###COMPONENT###</h2><hr /><p> ###ROOMS_STUFF### </p><hr />]]; html.days = {}; html.days.bit = [[<a href="###BARE_DAY###/">20###YEAR###/###MONTH###/###DAY###</a><br />]]; html.days.body = [[<h2>available logged days of room: ###JID###</h2><hr /><p> ###DAYS_STUFF### </p><hr />]]; html.day = {}; html.day.title = [[Subject: <font class="muc_title">###TITLE###</font>]]; html.day.time = [[<a name="###TIME###" href="####TIME###" class="timestuff">[###TIME###]</a> ]]; -- the one ####TIME### need to stay! it will evaluate to e.g. #09:10:56 which is an anker then html.day.presence = {}; html.day.presence.join = [[<div name="joinLeave" style="display: ###SHOWHIDE###;">###TIME_STUFF###<font class="muc_join"> *** ###NICK### joins the room</font><br /></div>]]; html.day.presence.leave = [[<div name="joinLeave" style="display: ###SHOWHIDE###;">###TIME_STUFF###<font class="muc_leave"> *** ###NICK### leaves the room</font><br /></div>]]; html.day.presence.statusText = [[ and his status message is "###STATUS###"]]; html.day.presence.statusChange = [[<div name="status" style="display: ###SHOWHIDE###;">###TIME_STUFF###<font class="muc_statusChange"> *** ###NICK### shows now as "###SHOW###"###STATUS_STUFF###</font><br /></div>]]; html.day.message = [[###TIME_STUFF###<font class="muc_msg_nick"><###NICK###></font> ###MSG###<br />]]; html.day.message_me = [[###TIME_STUFF###<font class="muc_msg_me">*###NICK### ###MSG###</font><br />]]; html.day.titleChange = [[###TIME_STUFF###<font class="muc_titleChange"> *** ###NICK### changed the title to "###TITLE###"</font><br />]]; html.day.reason = [[, the reason was "###REASON###"]] html.day.kick = [[###TIME_STUFF###<font class="muc_kick"> *** ###VICTIM### got kicked###REASON_STUFF###</font><br />]]; html.day.bann = [[###TIME_STUFF###<font class="muc_bann"> *** ###VICTIM### got banned###REASON_STUFF###</font><br />]]; html.day.day_link = [[<a href="../###DAY###/">###TEXT###</a>]] html.day.body = [[<h2>Logs of room ###JID### of 20###YEAR###/###MONTH###/###DAY###</h2> <p>###TITLE_STUFF###</p> <font class="join_link"><a href="http://speeqe.com/room/###JID###/" target="_blank">Join room now via speeqe.com!</a></font><br /> ###PREVIOUS_LINK### ###NEXT_LINK###<br /> <input type="checkbox" onclick="showHide('joinLeave')" ###JOIN_CHECKED###/>show/hide joins and Leaves</button> <input type="checkbox" onclick="showHide('status')" ###STATUS_CHECKED###/>show/hide status changes</button> <hr /><div id="main" style="overflow: auto;"> ###DAY_STUFF### </div><hr /> <script><!-- window.captureEvents(Event.RESIZE | Event.LOAD); window.onresize = resize; window.onload = resize; function resize(e) { var ele = document.getElementById("main"); ele.style.height = window.innerHeight - ele.offsetTop - 25; } --></script>]]; local function checkDatastorePathExists(node, host, today, create) create = create or false; local path = data_getpath(node, host, datastore, "dat", true); path = path:gsub("/[^/]*$", ""); -- check existance local attributes, err = lfs.attributes(path); if attributes == nil or attributes.mode ~= "directory" then module:log("warn", "muc_log folder isn't a folder: %s", path); return false; end attributes, err = lfs.attributes(path .. "/" .. today); if attributes == nil then if create then return lfs.mkdir(path .. "/" .. today); else return false; end elseif attributes.mode == "directory" then return true; end return false; end function createDoc(body) if body then return html.doc:gsub("###BODY_STUFF###", body); end end local function htmlEscape(t) t = t:gsub("<", "<"); t = t:gsub(">", ">"); t = t:gsub("(http://[%a%d@%.:/&%?=%-_#]+)", [[<a href="%1">%1</a>]]); t = t:gsub("\n", "<br />"); return t; end function splitUrl(url) local tmp = url:sub(string.len("/muc_log/") + 1); local day = nil; local room = nil; local component = nil; local at = nil; local slash = nil; local slash2 = nil; slash = tmp:find("/"); if slash then component = tmp:sub(1, slash - 1); if tmp:len() > slash then room = tmp:sub(slash + 1); slash = room:find("/"); if slash then tmp = room; room = tmp:sub(1, slash - 1); if tmp:len() > slash then day = tmp:sub(slash + 1); slash = day:find("/"); if slash then day = day:sub(1, slash - 1); end end end end end return room, component, day; end local function generateComponentListSiteContent() local components = ""; for component,muc_host in pairs(muc_hosts) do components = components .. html.components.bit:gsub("###COMPONENT###", component); end if components ~= "" then return html.components.body:gsub("###COMPONENTS_STUFF###", components); end end local function generateRoomListSiteContent(component) local rooms = ""; if prosody.hosts[component] and prosody.hosts[component].muc ~= nil then for jid, room in pairs(prosody.hosts[component].muc.rooms) do local node = splitJid(jid); if not room._data.hidden and node then rooms = rooms .. html.rooms.bit:gsub("###ROOM###", node):gsub("###COMPONENT###", component); end end if rooms ~= "" then return html.rooms.body:gsub("###ROOMS_STUFF###", rooms):gsub("###COMPONENT###", component); end end end local function generateDayListSiteContentByRoom(bareRoomJid) local days = ""; local arrDays = {}; local tmp; local node, host, resource = splitJid(bareRoomJid); local path = data_getpath(node, host, datastore); local room = nil; local attributes = nil; path = path:gsub("/[^/]*$", ""); attributes = lfs.attributes(path); if muc_hosts[host] and prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bareRoomJid] ~= nil then room = prosody.hosts[host].muc.rooms[bareRoomJid]; if room._data.hidden then room = nil end end if attributes ~= nil and room ~= nil then for file in lfs.dir(path) do local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)"); if year ~= nil and month ~= nil and day ~= nil and year ~= "" and month ~= "" and day ~= "" then arrDays[#arrDays + 1] = {bare=file, year=year, month=month, day=day}; end end tabSort(arrDays, function(a,b) return a.bare < b.bare; end); for _, date in pairs(arrDays) do tmp = html.days.bit; tmp = tmp:gsub("###ROOM###", node):gsub("###COMPONENT###", host); tmp = tmp:gsub("###BARE_DAY###", date.bare); tmp = tmp:gsub("###YEAR###", date.year):gsub("###MONTH###", date.month):gsub("###DAY###", date.day); days = tmp .. days; end end if days ~= "" then tmp = html.days.body:gsub("###DAYS_STUFF###", days); return tmp:gsub("###JID###", bareRoomJid); end end local function parseIqStanza(stanza, timeStuff, nick) local text = nil; local victim = nil; if(stanza.attr.type == "set") then for _,tag in ipairs(stanza) do if tag.tag == "query" then for _,item in ipairs(tag) do if item.tag == "item" and item.attr.nick ~= nil and tostring(item.attr.role) == 'none' then victim = item.attr.nick; for _,reason in ipairs(item) do if reason.tag == "reason" then text = reason[1]; break; end end break; end end break; end end if victim ~= nil then if text ~= nil then text = html.day.reason:gsub("###REASON###", htmlEscape(text)); else text = ""; end return html.day.kick:gsub("###TIME_STUFF###", timeStuff):gsub("###VICTIM###", victim):gsub("###REASON_STUFF###", text); end end return; end local function parsePresenceStanza(stanza, timeStuff, nick) local ret = ""; local showJoin = "block" if config and not config.showJoin then showJoin = "none"; end if stanza.attr.type == nil then local showStatus = "block" if config and not config.showStatus then showStatus = "none"; end local show, status = nil, ""; local alreadyJoined = false; for _, tag in ipairs(stanza) do if tag.tag == "alreadyJoined" then alreadyJoined = true; elseif tag.tag == "show" then show = tag[1]; elseif tag.tag == "status" then status = tag[1]; end end if alreadyJoined == true then if show == nil then show = "online"; end ret = html.day.presence.statusChange:gsub("###TIME_STUFF###", timeStuff); if status ~= "" then status = html.day.presence.statusText:gsub("###STATUS###", htmlEscape(status)); end ret = ret:gsub("###SHOW###", show):gsub("###NICK###", nick):gsub("###SHOWHIDE###", showStatus):gsub("###STATUS_STUFF###", status); else ret = html.day.presence.join:gsub("###TIME_STUFF###", timeStuff):gsub("###SHOWHIDE###", showJoin):gsub("###NICK###", nick); end elseif stanza.attr.type ~= nil and stanza.attr.type == "unavailable" then ret = html.day.presence.leave:gsub("###TIME_STUFF###", timeStuff):gsub("###SHOWHIDE###", showJoin):gsub("###NICK###", nick); end return ret; end local function parseMessageStanza(stanza, timeStuff, nick) local body, title, ret = nil, nil, ""; for _,tag in ipairs(stanza) do if tag.tag == "body" then body = tag[1]; if nick ~= nil then break; end elseif tag.tag == "nick" and nick == nil then nick = htmlEscape(tag[1]); if body ~= nil or title ~= nil then break; end elseif tag.tag == "subject" then title = tag[1]; if nick ~= nil then break; end end end if nick ~= nil and body ~= nil then body = htmlEscape(body); local me = body:find("^/me"); local template = ""; if not me then template = html.day.message; else template = html.day.message_me; body = body:gsub("^/me ", ""); end ret = template:gsub("###TIME_STUFF###", timeStuff):gsub("###NICK###", nick):gsub("###MSG###", body); elseif nick ~= nil and title ~= nil then title = htmlEscape(title); ret = html.day.titleChange:gsub("###TIME_STUFF###", timeStuff):gsub("###NICK###", nick):gsub("###TITLE###", title); end return ret; end local function incrementDay(bare_day) local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)"); local leapyear = false; module:log("debug", tostring(day).."/"..tostring(month).."/"..tostring(year)) day = tonumber(day); month = tonumber(month); year = tonumber(year); if year%4 == 0 and year%100 == 0 then if year%400 == 0 then leapyear = true; else leapyear = false; -- turn of the century but not a leapyear end elseif year%4 == 0 then leapyear = true; end if (month == 2 and leapyear and day + 1 > 29) or (month == 2 and not leapyear and day + 1 > 28) or (month < 8 and month%2 == 1 and day + 1 > 31) or (month < 8 and month%2 == 0 and day + 1 > 30) or (month >= 8 and month%2 == 0 and day + 1 > 31) or (month >= 8 and month%2 == 1 and day + 1 > 30) then if month + 1 > 12 then year = year + 1; else month = month + 1; end else day = day + 1; end return strformat("%.02d%.02d%.02d", year, month, day); end local function findNextDay(bareRoomJid, bare_day) local node, host, resource = splitJid(bareRoomJid); local day = incrementDay(bare_day); local max_trys = 7; module:log("debug", day); while(not checkDatastorePathExists(node, host, day, false)) do max_trys = max_trys - 1; if max_trys == 0 then break; end day = incrementDay(day); end if max_trys == 0 then return nil; else return day; end end local function decrementDay(bare_day) local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)"); module:log("debug", tostring(day).."/"..tostring(month).."/"..tostring(year)) day = tonumber(day); month = tonumber(month); year = tonumber(year); if day - 1 == 0 then if month - 1 == 0 then year = year - 1; else month = month - 1; end else day = day - 1; end return strformat("%.02d%.02d%.02d", year, month, day); end local function findPreviousDay(bareRoomJid, bare_day) local node, host, resource = splitJid(bareRoomJid); local day = decrementDay(bare_day); local max_trys = 7; module:log("debug", day); while(not checkDatastorePathExists(node, host, day, false)) do max_trys = max_trys - 1; if max_trys == 0 then break; end day = decrementDay(day); end if max_trys == 0 then return nil; else return day; end end local function parseDay(bareRoomJid, roomSubject, bare_day) local ret = ""; local year; local month; local day; local tmp; local node, host, resource = splitJid(bareRoomJid); local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)"); local previousDay = findPreviousDay(bareRoomJid, bare_day); local nextDay = findNextDay(bareRoomJid, bare_day); if bare_day ~= nil then local data = data_load(node, host, datastore .. "/" .. bare_day); if data ~= nil then for i=1, #data, 1 do local stanza = lom.parse(data[i]); if stanza ~= nil and stanza.attr ~= nil and stanza.attr.time ~= nil then local timeStuff = html.day.time:gsub("###TIME###", stanza.attr.time); if stanza[1] ~= nil then local nick; local tmp; -- grep nick from "from" resource if stanza[1].attr.from ~= nil then -- presence and messages nick = htmlEscape(stanza[1].attr.from:match("/(.+)$")); elseif stanza[1].attr.to ~= nil then -- iq nick = htmlEscape(stanza[1].attr.to:match("/(.+)$")); end if stanza[1].tag == "presence" and nick ~= nil then tmp = parsePresenceStanza(stanza[1], timeStuff, nick); elseif stanza[1].tag == "message" then tmp = parseMessageStanza(stanza[1], timeStuff, nick); elseif stanza[1].tag == "iq" then tmp = parseIqStanza(stanza[1], timeStuff, nick); else module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day); end if tmp ~= nil then ret = ret .. tmp tmp = nil; end end end end end if ret ~= "" then if nextDay then nextDay = html.day.day_link:gsub("###DAY###", nextDay):gsub("###TEXT###", "next day >>") end if previousDay then previousDay = html.day.day_link:gsub("###DAY###", previousDay):gsub("###TEXT###", "<< previous day"); end tmp = html.day.body:gsub("###DAY_STUFF###", ret):gsub("###JID###", bareRoomJid); tmp = tmp:gsub("###YEAR###", year):gsub("###MONTH###", month):gsub("###DAY###", day); tmp = tmp:gsub("###TITLE_STUFF###", html.day.title:gsub("###TITLE###", roomSubject)); tmp = tmp:gsub("###STATUS_CHECKED###", config.showStatus and "checked='checked'" or ""); tmp = tmp:gsub("###JOIN_CHECKED###", config.showJoin and "checked='checked'" or ""); tmp = tmp:gsub("###NEXT_LINK###", nextDay or ""); tmp = tmp:gsub("###PREVIOUS_LINK###", previousDay or ""); return tmp; end end end function handle_request(method, body, request) local node, host, day = splitUrl(request.url.path); if node ~= nil and host ~= nil then local bare = node .. "@" .. host; if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil then if prosody.hosts[host].muc.rooms[bare] ~= nil then local room = prosody.hosts[host].muc.rooms[bare]; if day == nil then return createDoc(generateDayListSiteContentByRoom(bare)); else local subject = "" if room._data ~= nil and room._data.subject ~= nil then subject = room._data.subject; end return createDoc(parseDay(bare, subject, day)); end else return createDoc(generateRoomListSiteContent(host)); end else return createDoc(generateComponentListSiteContent()); end elseif host ~= nil then return createDoc(generateRoomListSiteContent(host)); else return createDoc(generateComponentListSiteContent()); end return; end function module.load() config = config_get("*", "core", "muc_log_http") or {}; if config.showStatus == nil then config.showStatus = true; end if config.showJoin == nil then config.showJoin = true; end httpserver.new_from_config({ config.http_port or true }, handle_request, { base = "muc_log" }); for jid, host in pairs(prosody.hosts) do if host.muc then local enabledModules = config_get(jid, "core", "modules_enabled"); if enabledModules then for _,mod in ipairs(enabledModules) do if(mod == "muc_log") then module:log("debug", "component: %s", tostring(jid)); muc_hosts[jid] = true; break; end end end end end end function module.unload() muc_hosts = nil; end module:add_event_hook("component-activated", function(component, config) if config.core and config.core.modules_enabled then for _,mod in ipairs(config.core.modules_enabled) do if(mod == "muc_log") then module:log("debug", "component: %s", tostring(component)); muc_hosts[component] = true; break; end end end end);