mirror of https://github.com/sipwise/prosody.git
- mod_carbons - mod_csi - mod_man - mod_websocket * debian/control: we want prosody 0.11 version remove ngcp-system-tools from dependences, not called since removal of postinst remove modules merged upstream from description Change-Id: I83f5fd48ba17fe4c91763177fed7dee69ca02db1changes/52/34952/8
parent
34e3345f53
commit
4fb063dd25
@ -1,54 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Fri, 7 Apr 2017 11:12:23 +0200
|
||||
Subject: TT#14278 mam: add all supported versions to disco
|
||||
|
||||
* sipwise app needs 'urn:xmpp:mam:1' at disco result
|
||||
* upstream did implement supporting urn:xmpp_mam:[0-2] slightly different
|
||||
looking through the code y noticed that if the disco has no 'to' it shows
|
||||
the versions of mam supported
|
||||
|
||||
<!-- Out Fri 07 Apr 2017 10:53:38 AM CEST -->
|
||||
<iq type='get'
|
||||
id='info1'>
|
||||
<query xmlns='http://jabber.org/protocol/disco#info'/>
|
||||
</iq>
|
||||
|
||||
<!-- In Fri 07 Apr 2017 10:53:38 AM CEST -->
|
||||
<iq id='info1' type='result' to='vseva@sipwise.com/Gajim' from='vseva@sipwise.com'>
|
||||
<query xmlns='http://jabber.org/protocol/disco#info';>
|
||||
<feature var='urn:xmpp:mam:0'/>
|
||||
<feature var='urn:xmpp:mam:1'/>
|
||||
<feature var='urn:xmpp:mam:2'/>
|
||||
<feature var='urn:xmpp:sid:0'/>
|
||||
<identity type='pep' category='pubsub'/>
|
||||
<feature var='http://jabber.org/protocol/pubsub#publish'/>
|
||||
</query>
|
||||
</iq>
|
||||
|
||||
That is not what the XEP 0313 says:
|
||||
<quote>
|
||||
7. Determining support
|
||||
If a server or other entity hosts archives and supports MAM queries, it MUST
|
||||
advertise the 'urn:xmpp:mam:2' feature in response to Service Discovery
|
||||
(XEP-0030) [15] requests made to archiving JIDs
|
||||
(i.e. JIDs hosting an archive, such as users' bare JIDs):
|
||||
</quote>
|
||||
|
||||
Change-Id: Ib79b6f2f60f71a80030bbd12b2eb893b60ac025d
|
||||
---
|
||||
plugins/mod_mam/mod_mam.lua | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
|
||||
index 61b4388..f8a2321 100644
|
||||
--- a/plugins/mod_mam/mod_mam.lua
|
||||
+++ b/plugins/mod_mam/mod_mam.lua
|
||||
@@ -423,6 +423,8 @@ module:hook("message/bare", message_handler, priority);
|
||||
module:hook("message/full", message_handler, priority);
|
||||
|
||||
module:add_feature(xmlns_mam0); -- COMPAT with XEP-0313 v 0.1
|
||||
+module:add_feature(xmlns_mam1); -- COMPAT with XEP-0313 v 0.5
|
||||
+module:add_feature(xmlns_mam2); -- COMPAT with XEP-0313 v 0.6
|
||||
|
||||
module:hook("account-disco-info", function(event)
|
||||
(event.reply or event.stanza):tag("feature", {var=xmlns_mam0}):up();
|
@ -1,211 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Tue, 3 Jan 2017 17:14:49 +0100
|
||||
Subject: [PATCH] TT#8297 mam: implement archive to DB
|
||||
|
||||
Change-Id: If9644ac66fdd257a430cfb7fa185230c77cd783e
|
||||
|
||||
Change-Id: I587debda7ed78cc51088bd4a5ba96c878db45e6f
|
||||
---
|
||||
plugins/mod_mam/mod_mam.lua | 16 +---
|
||||
plugins/mod_mam/sipwise_archive.lib.lua | 165 ++++++++++++++++++++++++++++++++
|
||||
2 files changed, 166 insertions(+), 15 deletions(-)
|
||||
create mode 100644 plugins/mod_mam/sipwise_archive.lib.lua
|
||||
|
||||
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
|
||||
index f8a2321..faff0dd 100644
|
||||
--- a/plugins/mod_mam/mod_mam.lua
|
||||
+++ b/plugins/mod_mam/mod_mam.lua
|
||||
@@ -40,21 +40,7 @@ if global_default_policy ~= "roster" then
|
||||
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
|
||||
end
|
||||
|
||||
-local archive_store = module:get_option_string("archive_store", "archive2");
|
||||
-local archive = module:open_store(archive_store, "archive");
|
||||
-
|
||||
-if archive.name == "null" or not archive.find then
|
||||
- -- luacheck: ignore 631
|
||||
- if not archive.find then
|
||||
- module:log("debug", "Attempt to open archive storage returned a valid driver but it does not seem to implement the storage API");
|
||||
- module:log("debug", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or "<unknown>");
|
||||
- else
|
||||
- module:log("debug", "Attempt to open archive storage returned null driver");
|
||||
- end
|
||||
- module:log("debug", "See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information");
|
||||
- module:log("info", "Using in-memory fallback archive driver");
|
||||
- archive = module:require "fallback_archive";
|
||||
-end
|
||||
+local archive = module:require "sipwise_archive";
|
||||
|
||||
local use_total = true;
|
||||
|
||||
diff --git a/plugins/mod_mam/sipwise_archive.lib.lua b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
new file mode 100644
|
||||
index 0000000..f772ea6
|
||||
--- /dev/null
|
||||
+++ b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
@@ -0,0 +1,165 @@
|
||||
+-- luacheck: ignore 212/self
|
||||
+local uuid = require "util.uuid".generate;
|
||||
+local archive_store = { _provided_by = "mam"; name = "fallback"; };
|
||||
+
|
||||
+local serialize = require "util.serialization".serialize;
|
||||
+local deserialize = require "util.serialization".deserialize;
|
||||
+local st = require "util.stanza";
|
||||
+
|
||||
+local mod_sql = module:require("sql");
|
||||
+local params = module:get_option("sql", {});
|
||||
+local engine = mod_sql:create_engine(params);
|
||||
+engine:execute("SET NAMES 'utf8' COLLATE 'utf8_bin';");
|
||||
+local log = require "util.logger".init("sipwise_archive");
|
||||
+local ut_tostring = require "util.table".table.tostring;
|
||||
+
|
||||
+local store_query=[[
|
||||
+INSERT INTO `sipwise_mam` (`username`, `key`, `stanza`, `epoch`, `with`)
|
||||
+VALUES (?,UuidToBin(?),?,?,?);
|
||||
+]]
|
||||
+
|
||||
+local delete_query=[[
|
||||
+DELETE FROM `sipwise_mam`
|
||||
+WHERE `username` = ?;
|
||||
+]]
|
||||
+
|
||||
+local delete_query_extra=[[
|
||||
+DELETE FROM `sipwise_mam`
|
||||
+WHERE `username` = ? AND `epoch` <= ?;
|
||||
+]]
|
||||
+
|
||||
+local select_key_query=[[
|
||||
+SELECT id FROM `sipwise_mam`
|
||||
+WHERE `key` = UuidToBin(?)
|
||||
+]]
|
||||
+
|
||||
+local select_query_base=[[
|
||||
+SELECT UuidFromBin(`key`),`stanza`,`epoch`,`with` FROM `sipwise_mam`
|
||||
+WHERE `username` = ?
|
||||
+]]
|
||||
+
|
||||
+-- Reconnect to DB if necessary
|
||||
+local function reconect_check()
|
||||
+ if not engine.conn:ping() then
|
||||
+ engine.conn = nil;
|
||||
+ log("debug", "DDBB reconecting");
|
||||
+ engine:connect();
|
||||
+ end
|
||||
+end
|
||||
+
|
||||
+local function load_db(query, _params)
|
||||
+ local res;
|
||||
+ reconect_check();
|
||||
+ log("debug", "query[%s]", query);
|
||||
+ log("debug", "_params[%s]", ut_tostring(_params));
|
||||
+ res = engine:select(query, unpack(_params));
|
||||
+ local out = {};
|
||||
+ for row in res do
|
||||
+ table.insert(out, {row[1], row[2], row[3], row[4]});
|
||||
+ end
|
||||
+ return out;
|
||||
+end
|
||||
+
|
||||
+local function key_get_id(key)
|
||||
+ local res;
|
||||
+ reconect_check();
|
||||
+ res = engine:select(select_key_query, key);
|
||||
+ local out = {};
|
||||
+ for row in res do
|
||||
+ table.insert(out, row[1]);
|
||||
+ end
|
||||
+ return out[1];
|
||||
+end
|
||||
+
|
||||
+local function key_in_db(key)
|
||||
+ local res = key_get_id(key);
|
||||
+ if res then
|
||||
+ return true;
|
||||
+ else
|
||||
+ return false;
|
||||
+ end
|
||||
+end
|
||||
+
|
||||
+function archive_store:append(username, key, value, when, with)
|
||||
+ reconect_check();
|
||||
+ if not key or key_in_db(key) then
|
||||
+ key = uuid();
|
||||
+ end
|
||||
+ engine:insert(store_query, username, key, serialize(st.preserialize(value)),
|
||||
+ when, with);
|
||||
+ engine.conn:commit();
|
||||
+end
|
||||
+
|
||||
+function archive_store:find(username, query)
|
||||
+ local qstart, qend, qwith = -math.huge, math.huge;
|
||||
+ local qlimit, qid;
|
||||
+ local db_query = select_query_base;
|
||||
+ local _params = { username, };
|
||||
+ local i, values = 0;
|
||||
+
|
||||
+ if query then
|
||||
+ if query.reverse then
|
||||
+ if query.before then
|
||||
+ qid = key_get_id(query.before);
|
||||
+ end
|
||||
+ elseif query.after then
|
||||
+ qid = key_get_id(query.after);
|
||||
+ end
|
||||
+ qwith = query.with;
|
||||
+ qlimit = query.limit;
|
||||
+ qstart = query.start or qstart;
|
||||
+ qend = query["end"] or qend;
|
||||
+ end
|
||||
+
|
||||
+ if qwith then
|
||||
+ db_query = db_query.." AND `with` = ?";
|
||||
+ table.insert(_params, qwith);
|
||||
+ end
|
||||
+ if qid then
|
||||
+ if query.reverse then
|
||||
+ db_query = db_query.." AND `id` < ?";
|
||||
+ else
|
||||
+ db_query = db_query.." AND `id` > ?";
|
||||
+ end
|
||||
+ table.insert(_params, qid);
|
||||
+ end
|
||||
+ db_query = db_query.." AND (`epoch` >= ? AND `epoch` <= ?)";
|
||||
+ table.insert(_params, qstart);
|
||||
+ table.insert(_params, qend);
|
||||
+ db_query = db_query.." ORDER BY `epoch`";
|
||||
+ if query.reverse then
|
||||
+ db_query = db_query.." DESC";
|
||||
+ end
|
||||
+ if qlimit then
|
||||
+ db_query = db_query.." LIMIT ?";
|
||||
+ table.insert(_params, qlimit);
|
||||
+ end
|
||||
+ db_query = db_query..";"
|
||||
+ values = load_db(db_query, _params);
|
||||
+
|
||||
+ return function ()
|
||||
+ i = i + 1;
|
||||
+ if values[i] then
|
||||
+ return values[i][1], deserialize(values[i][2]), values[i][3], values[i][4];
|
||||
+ end
|
||||
+ end
|
||||
+end
|
||||
+
|
||||
+function archive_store:delete(username, query)
|
||||
+ if not query or next(query) == nil then
|
||||
+ -- no specifics, delete everything
|
||||
+ reconect_check();
|
||||
+ engine:delete(delete_query, username);
|
||||
+ engine.conn:commit();
|
||||
+ return true;
|
||||
+ end
|
||||
+
|
||||
+ local qend = query["end"] or math.huge;
|
||||
+
|
||||
+ reconect_check();
|
||||
+ engine:delete(delete_query_extra, username, qend);
|
||||
+ engine.conn:commit();
|
||||
+ return true;
|
||||
+end
|
||||
+
|
||||
+return archive_store;
|
@ -1,39 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Thu, 19 Jan 2017 16:25:24 +0100
|
||||
Subject: [PATCH] TT#9303 mod_mam: sipwise_archive.lib fix crash at find()
|
||||
|
||||
> mod_c2s: Traceback[c2s]: /usr/lib/prosody/util/sql.lua:189:
|
||||
> Error executing statement parameters: Data too long for column '_uuid' at row 1
|
||||
|
||||
Change-Id: I9e7d6a310ee5e5e8f83cbc97cb2f7ebcf4f534b5
|
||||
---
|
||||
plugins/mod_mam/sipwise_archive.lib.lua | 9 +++++++--
|
||||
1 file changed, 7 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/plugins/mod_mam/sipwise_archive.lib.lua b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
index f772ea6..087c306 100644
|
||||
--- a/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
+++ b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
@@ -29,8 +29,8 @@ WHERE `username` = ? AND `epoch` <= ?;
|
||||
]]
|
||||
|
||||
local select_key_query=[[
|
||||
-SELECT id FROM `sipwise_mam`
|
||||
-WHERE `key` = UuidToBin(?)
|
||||
+SELECT `id` FROM `sipwise_mam`
|
||||
+WHERE `key` = UuidToBin(?);
|
||||
]]
|
||||
|
||||
local select_query_base=[[
|
||||
@@ -63,6 +63,11 @@ end
|
||||
local function key_get_id(key)
|
||||
local res;
|
||||
reconect_check();
|
||||
+ -- key is an uuid
|
||||
+ if string.len(key) ~= 36 then
|
||||
+ log("warn", "key[%s] is not a proper uuid");
|
||||
+ return nil;
|
||||
+ end
|
||||
res = engine:select(select_key_query, key);
|
||||
local out = {};
|
||||
for row in res do
|
@ -1,63 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Thu, 19 Jan 2017 16:38:26 +0100
|
||||
Subject: [PATCH] TT#9311 mod_mam/sipwise_archive.lib: full username
|
||||
|
||||
* domain was not included
|
||||
|
||||
Change-Id: Id2bbc496545ba7c167cd4dacb24153708e826ca2
|
||||
---
|
||||
plugins/mod_mam/sipwise_archive.lib.lua | 10 ++++++----
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/plugins/mod_mam/sipwise_archive.lib.lua b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
index 087c306..46166f8 100644
|
||||
--- a/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
+++ b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
@@ -12,6 +12,7 @@ local engine = mod_sql:create_engine(params);
|
||||
engine:execute("SET NAMES 'utf8' COLLATE 'utf8_bin';");
|
||||
local log = require "util.logger".init("sipwise_archive");
|
||||
local ut_tostring = require "util.table".table.tostring;
|
||||
+local host = module.host;
|
||||
|
||||
local store_query=[[
|
||||
INSERT INTO `sipwise_mam` (`username`, `key`, `stanza`, `epoch`, `with`)
|
||||
@@ -90,7 +91,7 @@ function archive_store:append(username, key, value, when, with)
|
||||
if not key or key_in_db(key) then
|
||||
key = uuid();
|
||||
end
|
||||
- engine:insert(store_query, username, key, serialize(st.preserialize(value)),
|
||||
+ engine:insert(store_query, username..'@'..host, key, serialize(st.preserialize(value)),
|
||||
when, with);
|
||||
engine.conn:commit();
|
||||
end
|
||||
@@ -99,7 +100,7 @@ function archive_store:find(username, query)
|
||||
local qstart, qend, qwith = -math.huge, math.huge;
|
||||
local qlimit, qid;
|
||||
local db_query = select_query_base;
|
||||
- local _params = { username, };
|
||||
+ local _params = { username..'@'..host, };
|
||||
local i, values = 0;
|
||||
|
||||
if query then
|
||||
@@ -151,10 +152,11 @@ function archive_store:find(username, query)
|
||||
end
|
||||
|
||||
function archive_store:delete(username, query)
|
||||
+ local jid = username..'@'..host
|
||||
if not query or next(query) == nil then
|
||||
-- no specifics, delete everything
|
||||
reconect_check();
|
||||
- engine:delete(delete_query, username);
|
||||
+ engine:delete(delete_query, jid);
|
||||
engine.conn:commit();
|
||||
return true;
|
||||
end
|
||||
@@ -162,7 +164,7 @@ function archive_store:delete(username, query)
|
||||
local qend = query["end"] or math.huge;
|
||||
|
||||
reconect_check();
|
||||
- engine:delete(delete_query_extra, username, qend);
|
||||
+ engine:delete(delete_query_extra, jid, qend);
|
||||
engine.conn:commit();
|
||||
return true;
|
||||
end
|
@ -1,57 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Thu, 19 Jan 2017 17:13:12 +0100
|
||||
Subject: [PATCH] TT#9317 mod_mam: don't store bodyless chat messages
|
||||
|
||||
* implement store hints
|
||||
https://prosody.im/issues/issue/750
|
||||
mod_mam doesn't honor forced storage hint.
|
||||
When a message without a body is received mod_mam always ignores the forced
|
||||
storage hint, even if <store xmlns='urn:xmpp:hints'/> is present.
|
||||
This can cause issue with OMEMO, which sends message elements without a body
|
||||
|
||||
Change-Id: I590b1fb9bd95afdce6a117778052d11d8102f718
|
||||
---
|
||||
plugins/mod_mam/mod_mam.lua | 23 +++++++++++++++++------
|
||||
1 file changed, 17 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
|
||||
index faff0dd..759a5e2 100644
|
||||
--- a/plugins/mod_mam/mod_mam.lua
|
||||
+++ b/plugins/mod_mam/mod_mam.lua
|
||||
@@ -285,19 +285,30 @@ local function message_handler(event, c2s)
|
||||
event.stanza = stanza;
|
||||
end
|
||||
|
||||
- -- We store chat messages or normal messages that have a body
|
||||
- if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then
|
||||
- log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
|
||||
- return;
|
||||
- end
|
||||
-
|
||||
-- or if hints suggest we shouldn't
|
||||
if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store
|
||||
+ -- We store chat messages or normal messages that have a body
|
||||
+ local body = stanza:get_child("body");
|
||||
+ if not(orig_type == "chat" or (orig_type == "normal" and body)) then
|
||||
+ log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
|
||||
+ return;
|
||||
+ elseif (orig_type == 'chat' and not body) then
|
||||
+ log("debug", "Not archiving stanza: %s (type), has no body",
|
||||
+ stanza:top_tag());
|
||||
+ return;
|
||||
+ end
|
||||
+
|
||||
if stanza:get_child("no-permanent-store", "urn:xmpp:hints")
|
||||
or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store
|
||||
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
|
||||
return;
|
||||
end
|
||||
+ else
|
||||
+ log("debug", "store hint detected");
|
||||
+ if orig_type == 'error' then
|
||||
+ log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
|
||||
+ return;
|
||||
+ end
|
||||
end
|
||||
|
||||
-- Check with the users preferences
|
@ -1,34 +0,0 @@
|
||||
From: Victor Seva <vseva@sipwise.com>
|
||||
Date: Tue, 24 Jan 2017 11:14:40 +0100
|
||||
Subject: TT#9314 mod_mam/sipwise_archive.lib: support emojis
|
||||
|
||||
* we do need utf8mb4 charset at sipwise_mam table
|
||||
|
||||
Change-Id: I44b17058f1bb0839de835151e6885a64e203fac1
|
||||
---
|
||||
plugins/mod_mam/sipwise_archive.lib.lua | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/plugins/mod_mam/sipwise_archive.lib.lua b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
index 46166f8..e58f593 100644
|
||||
--- a/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
+++ b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
@@ -9,7 +9,7 @@ local st = require "util.stanza";
|
||||
local mod_sql = module:require("sql");
|
||||
local params = module:get_option("sql", {});
|
||||
local engine = mod_sql:create_engine(params);
|
||||
-engine:execute("SET NAMES 'utf8' COLLATE 'utf8_bin';");
|
||||
+engine:execute("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';");
|
||||
local log = require "util.logger".init("sipwise_archive");
|
||||
local ut_tostring = require "util.table".table.tostring;
|
||||
local host = module.host;
|
||||
@@ -43,8 +43,8 @@ WHERE `username` = ?
|
||||
local function reconect_check()
|
||||
if not engine.conn:ping() then
|
||||
engine.conn = nil;
|
||||
- log("debug", "DDBB reconecting");
|
||||
engine:connect();
|
||||
+ engine:execute("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';");
|
||||
end
|
||||
end
|
||||
|
@ -1,21 +0,0 @@
|
||||
From: Sipwise Development Team <support@sipwise.com>
|
||||
Date: Wed, 11 Dec 2019 11:15:14 +0100
|
||||
Subject: TT-27653-mod_mam-sipwise_archive.lib-fix-uuid-trailing-garbage
|
||||
|
||||
---
|
||||
plugins/mod_mam/sipwise_archive.lib.lua | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/plugins/mod_mam/sipwise_archive.lib.lua b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
index e58f593..15acb33 100644
|
||||
--- a/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
+++ b/plugins/mod_mam/sipwise_archive.lib.lua
|
||||
@@ -56,7 +56,7 @@ local function load_db(query, _params)
|
||||
res = engine:select(query, unpack(_params));
|
||||
local out = {};
|
||||
for row in res do
|
||||
- table.insert(out, {row[1], row[2], row[3], row[4]});
|
||||
+ table.insert(out, {string.sub(row[1], 0, 36), row[2], row[3], row[4]});
|
||||
end
|
||||
return out;
|
||||
end
|
@ -1,9 +1,2 @@
|
||||
mod_auth_sql.path
|
||||
0001-mod_mam_disco.path
|
||||
0002-TT-8297-mam-implement-archive-to-DB.patch
|
||||
0003-TT-9303-mod_mam-sipwise_archive.lib-fix-crash-at-fin.patch
|
||||
0004-TT-9311-mod_mam-sipwise_archive.lib-full-username.patch
|
||||
0005-TT-9317-mod_mam-don-t-store-bodyless-chat-messages.patch
|
||||
0006-TT-9314-mod_mam-sipwise_archive.lib-support-emojis.patch
|
||||
0007-TT-27653-mod_mam-sipwise_archive.lib-fix-uuid-trailing-garbage.patch
|
||||
0009-TT-35604-mod_sipwise_pushd.lib-add-silent-push
|
||||
|
@ -1,49 +0,0 @@
|
||||
---
|
||||
labels:
|
||||
- 'Stage-Merged'
|
||||
summary: Message Carbons
|
||||
...
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This module implements [XEP-0280: Message
|
||||
Carbons](http://xmpp.org/extensions/xep-0280.html), allowing users to
|
||||
maintain a shared and synchronized view of all conversations across all
|
||||
their online clients and devices.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
As with all modules, you enable it by adding it to the modules\_enabled
|
||||
list.
|
||||
|
||||
modules_enabled = {
|
||||
...
|
||||
"carbons";
|
||||
...
|
||||
}
|
||||
|
||||
The module has no further configuration.
|
||||
|
||||
Clients
|
||||
=======
|
||||
|
||||
Clients that support XEP-0280:
|
||||
|
||||
- [Gajim](http://gajim.org/) (Desktop)
|
||||
- [Adium (1.6)](http://adium.im/) (Desktop - OS X)
|
||||
- [Yaxim](http://yaxim.org/) (Mobile - Android)
|
||||
- [Conversations](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
|
||||
(Mobile - Android)
|
||||
- [poezio](http://poezio.eu/en/) (Console)
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
------- -----------------------
|
||||
0.8 Works
|
||||
0.9 Works
|
||||
0.10 Included with prosody
|
||||
trunk Included with prosody
|
||||
------- -----------------------
|
@ -1,136 +0,0 @@
|
||||
-- XEP-0280: Message Carbons implementation for Prosody
|
||||
-- Copyright (C) 2011-2016 Kim Alvefur
|
||||
--
|
||||
-- This file is MIT/X11 licensed.
|
||||
|
||||
local st = require "util.stanza";
|
||||
local jid_bare = require "util.jid".bare;
|
||||
local xmlns_carbons = "urn:xmpp:carbons:2";
|
||||
local xmlns_carbons_old = "urn:xmpp:carbons:1";
|
||||
local xmlns_carbons_really_old = "urn:xmpp:carbons:0";
|
||||
local xmlns_forward = "urn:xmpp:forward:0";
|
||||
local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions;
|
||||
|
||||
local function toggle_carbons(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local state = stanza.tags[1].attr.mode or stanza.tags[1].name;
|
||||
module:log("debug", "%s %sd carbons", origin.full_jid, state);
|
||||
origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns;
|
||||
origin.send(st.reply(stanza));
|
||||
return true;
|
||||
end
|
||||
module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons);
|
||||
module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons);
|
||||
|
||||
-- COMPAT
|
||||
module:hook("iq-set/self/"..xmlns_carbons_old..":disable", toggle_carbons);
|
||||
module:hook("iq-set/self/"..xmlns_carbons_old..":enable", toggle_carbons);
|
||||
module:hook("iq-set/self/"..xmlns_carbons_really_old..":carbons", toggle_carbons);
|
||||
|
||||
local function message_handler(event, c2s)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local orig_type = stanza.attr.type or "normal";
|
||||
local orig_from = stanza.attr.from;
|
||||
local orig_to = stanza.attr.to;
|
||||
|
||||
if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body"))) then
|
||||
return -- Only chat type messages
|
||||
end
|
||||
|
||||
-- Stanza sent by a local client
|
||||
local bare_jid = jid_bare(orig_from);
|
||||
local target_session = origin;
|
||||
local top_priority = false;
|
||||
local user_sessions = bare_sessions[bare_jid];
|
||||
|
||||
-- Stanza about to be delivered to a local client
|
||||
if not c2s then
|
||||
bare_jid = jid_bare(orig_to);
|
||||
target_session = full_sessions[orig_to];
|
||||
user_sessions = bare_sessions[bare_jid];
|
||||
if not target_session and user_sessions then
|
||||
-- The top resources will already receive this message per normal routing rules,
|
||||
-- so we are going to skip them in order to avoid sending duplicated messages.
|
||||
local top_resources = user_sessions.top_resources;
|
||||
top_priority = top_resources and top_resources[1].priority
|
||||
end
|
||||
end
|
||||
|
||||
if not user_sessions then
|
||||
module:log("debug", "Skip carbons for offline user");
|
||||
return -- No use in sending carbons to an offline user
|
||||
end
|
||||
|
||||
if stanza:get_child("private", xmlns_carbons) then
|
||||
if not c2s then
|
||||
stanza:maptags(function(tag)
|
||||
if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
|
||||
return tag;
|
||||
end
|
||||
end);
|
||||
end
|
||||
module:log("debug", "Message tagged private, ignoring");
|
||||
return
|
||||
elseif stanza:get_child("no-copy", "urn:xmpp:hints") then
|
||||
module:log("debug", "Message has no-copy hint, ignoring");
|
||||
return
|
||||
elseif not c2s and bare_jid == orig_from and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then
|
||||
module:log("debug", "MUC PM, ignoring");
|
||||
return
|
||||
end
|
||||
|
||||
-- Create the carbon copy and wrap it as per the Stanza Forwarding XEP
|
||||
local copy = st.clone(stanza);
|
||||
copy.attr.xmlns = "jabber:client";
|
||||
local carbon = st.message{ from = bare_jid, type = orig_type, }
|
||||
:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
|
||||
:tag("forwarded", { xmlns = xmlns_forward })
|
||||
:add_child(copy):reset();
|
||||
|
||||
-- COMPAT
|
||||
local carbon_old = st.message{ from = bare_jid, type = orig_type, }
|
||||
:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_old }):up()
|
||||
:tag("forwarded", { xmlns = xmlns_forward })
|
||||
:add_child(copy):reset();
|
||||
|
||||
-- COMPAT
|
||||
local carbon_really_old = st.clone(stanza)
|
||||
:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_really_old }):up()
|
||||
|
||||
user_sessions = user_sessions and user_sessions.sessions;
|
||||
for _, session in pairs(user_sessions) do
|
||||
-- Carbons are sent to resources that have enabled it
|
||||
if session.want_carbons
|
||||
-- but not the resource that sent the message, or the one that it's directed to
|
||||
and session ~= target_session
|
||||
-- and isn't among the top resources that would receive the message per standard routing rules
|
||||
and (c2s or session.priority ~= top_priority)
|
||||
-- don't send v0 carbons (or copies) for c2s
|
||||
and (not c2s or session.want_carbons ~= xmlns_carbons_really_old) then
|
||||
carbon.attr.to = session.full_jid;
|
||||
module:log("debug", "Sending carbon to %s", session.full_jid);
|
||||
local carbon = session.want_carbons == xmlns_carbons_old and carbon_old -- COMPAT
|
||||
or session.want_carbons == xmlns_carbons_really_old and carbon_really_old -- COMPAT
|
||||
or carbon;
|
||||
session.send(carbon);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function c2s_message_handler(event)
|
||||
return message_handler(event, true)
|
||||
end
|
||||
|
||||
-- Stanzas sent by local clients
|
||||
module:hook("pre-message/host", c2s_message_handler, 0.05); -- priority between mod_message (0 in 0.9) and mod_firewall (0.1)
|
||||
module:hook("pre-message/bare", c2s_message_handler, 0.05);
|
||||
module:hook("pre-message/full", c2s_message_handler, 0.05);
|
||||
-- Stanzas to local clients
|
||||
module:hook("message/bare", message_handler, 0.05);
|
||||
module:hook("message/full", message_handler, 0.05);
|
||||
|
||||
module:add_feature(xmlns_carbons);
|
||||
module:add_feature(xmlns_carbons_old);
|
||||
if module:get_option_boolean("carbons_v0") then
|
||||
module:add_feature(xmlns_carbons_really_old);
|
||||
end
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
summary: Client State Indication support
|
||||
labels:
|
||||
- 'Stage-Merged'
|
||||
...
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This module implements [XEP-0352: Client State
|
||||
Indication](http://xmpp.org/extensions/xep-0352.html), a way for mobile
|
||||
clients to tell the server that they are sitting in someones pocket and
|
||||
would rather not get some less urgent things pushed to it.
|
||||
|
||||
This module has been merged into Prosody 0.11. Please see the
|
||||
[mod_csi documentation](https://prosody.im/doc/modules/mod_csi) for more
|
||||
information about how it is used.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
There is no configuration for this module, just add it to
|
||||
modules\_enabled as normal.
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
----- -------
|
||||
0.9 Works
|
||||
----- -------
|
||||
0.10 Works
|
||||
----- -------
|
||||
0.11 Works (included)
|
||||
----- -------
|
@ -1,23 +0,0 @@
|
||||
local st = require "util.stanza";
|
||||
local xmlns_csi = "urn:xmpp:csi:0";
|
||||
local csi_feature = st.stanza("csi", { xmlns = xmlns_csi });
|
||||
|
||||
module:hook("stream-features", function (event)
|
||||
if event.origin.username then
|
||||
event.features:add_child(csi_feature);
|
||||
end
|
||||
end);
|
||||
|
||||
function refire_event(name)
|
||||
return function (event)
|
||||
if event.origin.username then
|
||||
event.origin.state = event.stanza.name;
|
||||
module:fire_event(name, event);
|
||||
return true;
|
||||
end
|
||||
end;
|
||||
end
|
||||
|
||||
module:hook("stanza/"..xmlns_csi..":active", refire_event("csi-client-active"));
|
||||
module:hook("stanza/"..xmlns_csi..":inactive", refire_event("csi-client-inactive"));
|
||||
|
@ -1,129 +0,0 @@
|
||||
---
|
||||
labels:
|
||||
- 'Stage-Merged'
|
||||
summary: 'XEP-0313: Message Archive Management'
|
||||
...
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Implementation of [XEP-0313: Message Archive Management].
|
||||
|
||||
Details
|
||||
=======
|
||||
|
||||
This module will archive all messages that match the simple rules setup
|
||||
by the user, and allow the user to access this archive.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
First copy the module to the prosody plugins directory.
|
||||
|
||||
Then add "mam" to your modules\_enabled list:
|
||||
|
||||
``` {.lua}
|
||||
modules_enabled = {
|
||||
-- ...
|
||||
"mam",
|
||||
-- ...
|
||||
}
|
||||
```
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Option summary
|
||||
--------------
|
||||
|
||||
option type default
|
||||
------------------------------ ----------------------- -----------
|
||||
max\_archive\_query\_results number `50`
|
||||
default\_archive\_policy boolean or `"roster"` `true`
|
||||
archive\_expires\_after string `"1w"`
|
||||
archive\_cleanup\_interval number `4*60*60`
|
||||
|
||||
|
||||
Storage backend
|
||||
---------------
|
||||
|
||||
mod\_mam uses the store "archive2"[^1]. See [Prosodys data storage
|
||||
documentation][doc:storage] for information on how to configure storage.
|
||||
|
||||
For example, to use mod\_storage\_sql (requires Prosody 0.10 or later):
|
||||
|
||||
``` {.lua}
|
||||
storage = {
|
||||
archive2 = "sql";
|
||||
}
|
||||
```
|
||||
|
||||
If no archive-capable storage backend can be opened then an in-memory
|
||||
one will be used as fallback.
|
||||
|
||||
Query size limits
|
||||
-----------------
|
||||
|
||||
max_archive_query_results = 20;
|
||||
|
||||
This is the largest number of messages that are allowed to be retrieved
|
||||
in one request *page*. A query that does not fit in one page will
|
||||
include a reference to the next page, letting clients page through the
|
||||
result set. Setting large number is not recommended, as Prosody will be
|
||||
blocked while processing the request and will not be able to do anything
|
||||
else.
|
||||
|
||||
Archive expiry
|
||||
--------------
|
||||
|
||||
Messages in the archive will expire after some time, by default one
|
||||
week. This can be changed by setting `archive_expires_after`:
|
||||
|
||||
``` {.lua}
|
||||
archive_expires_after = "1d" -- one day
|
||||
|
||||
archive_expires_after = "1w" -- one week, the default
|
||||
|
||||
archive_expires_after = "2m" -- two months
|
||||
|
||||
archive_expires_after = "1y" -- one year
|
||||
|
||||
archive_expires_after = 60 * 60 -- one hour
|
||||
|
||||
archive_expires_after = "never" -- forever
|
||||
```
|
||||
|
||||
The format is an integer number of seconds or a multiple of a period
|
||||
given by a suffix that can be one of `d` (day), `w` (week), `m` (month)
|
||||
or `y` (year). No multiplier means seconds.
|
||||
|
||||
Message matching policy
|
||||
-----------------------
|
||||
|
||||
The MAM protocol includes a way for clients to control what messages
|
||||
should be stored. This allows users to enable or disable archiving by
|
||||
default or for specific contacts.
|
||||
|
||||
``` {.lua}
|
||||
default_archive_policy = true
|
||||
```
|
||||
|
||||
`default_archive_policy =` Meaning
|
||||
---------------------------- ------------------------------------------------------
|
||||
`false` Store no messages.
|
||||
`"roster"` Store messages to/from contacts in the users roster.
|
||||
`true` Store all messages. This is the default.
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
------- -----------------------
|
||||
trunk Included with Prosody
|
||||
0.10 Included with Prosody
|
||||
0.9 Works
|
||||
0.8 Does not work
|
||||
------- -----------------------
|
||||
|
||||
|
||||
[^1]: Might be changed to "mam" at some point
|
||||
|
@ -1,83 +0,0 @@
|
||||
-- luacheck: ignore 212/self
|
||||
|
||||
local uuid = require "util.uuid".generate;
|
||||
local store = module:shared("archive");
|
||||
local archive_store = { _provided_by = "mam"; name = "fallback"; };
|
||||
|
||||
function archive_store:append(username, key, value, when, with)
|
||||
local archive = store[username];
|
||||
if not archive then
|
||||
archive = { [0] = 0 };
|
||||
store[username] = archive;
|
||||
end
|
||||
local index = (archive[0] or #archive)+1;
|
||||
local item = { key = key, when = when, with = with, value = value };
|
||||
if not key or archive[key] then
|
||||
key = uuid();
|
||||
item.key = key;
|
||||
end
|
||||
archive[index] = item;
|
||||
archive[key] = index;
|
||||
archive[0] = index;
|
||||
return key;
|
||||
end
|
||||
|
||||
function archive_store:find(username, query)
|
||||
local archive = store[username] or {};
|
||||
local start, stop, step = 1, archive[0] or #archive, 1;
|
||||
local qstart, qend, qwith = -math.huge, math.huge;
|
||||
local limit;
|
||||
|
||||
if query then
|
||||
if query.reverse then
|
||||
start, stop, step = stop, start, -1;
|
||||
if query.before and archive[query.before] then
|
||||
start = archive[query.before] - 1;
|
||||
end
|
||||
elseif query.after and archive[query.after] then
|
||||
start = archive[query.after] + 1;
|
||||
end
|
||||
qwith = query.with;
|
||||
limit = query.limit;
|
||||
qstart = query.start or qstart;
|
||||
qend = query["end"] or qend;
|
||||
end
|
||||
|
||||
return function ()
|
||||
if limit and limit <= 0 then return end
|
||||
for i = start, stop, step do
|
||||
local item = archive[i];
|
||||
if (not qwith or qwith == item.with) and item.when >= qstart and item.when <= qend then
|
||||
if limit then limit = limit - 1; end
|
||||
start = i + step; -- Start on next item
|
||||
return item.key, item.value, item.when, item.with;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function archive_store:delete(username, query)
|
||||
if not query or next(query) == nil then
|
||||
-- no specifics, delete everything
|
||||
store[username] = nil;
|
||||
return true;
|
||||
end
|
||||
local archive = store[username];
|
||||
if not archive then return true; end -- no messages, nothing to delete
|
||||
|
||||
local qstart = query.start or -math.huge;
|
||||
local qend = query["end"] or math.huge;
|
||||
local qwith = query.with;
|
||||
store[username] = nil;
|
||||
for i = 1, #archive do
|
||||
local item = archive[i];
|
||||
local when, with = item.when, item.when;
|
||||
-- Add things that don't match the query
|
||||
if not ((not qwith or qwith == item.with) and item.when >= qstart and item.when <= qend) then
|
||||
self:append(username, item.key, item.value, when, with);
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
return archive_store;
|
@ -1,46 +0,0 @@
|
||||
-- XEP-0313: Message Archive Management for Prosody
|
||||
-- Copyright (C) 2011-2013 Kim Alvefur
|
||||
--
|
||||
-- This file is MIT/X11 licensed.
|
||||
-- luacheck: ignore 122/prosody
|
||||
|
||||
local global_default_policy = module:get_option("default_archive_policy", true);
|
||||
|
||||
do
|
||||
-- luacheck: ignore 211/prefs_format
|
||||
local prefs_format = {
|
||||
[false] = "roster",
|
||||
-- default ::= true | false | "roster"
|
||||
-- true = always, false = never, nil = global default
|
||||
["romeo@montague.net"] = true, -- always
|
||||
["montague@montague.net"] = false, -- newer
|
||||
};
|
||||
end
|
||||
|
||||
local sessions = prosody.hosts[module.host].sessions;
|
||||
local archive_store = module:get_option_string("archive_store", "archive");
|
||||
local prefs = module:open_store(archive_store .. "_prefs");
|
||||
|
||||
local function get_prefs(user)
|
||||
local user_sessions = sessions[user];
|
||||
local user_prefs = user_sessions and user_sessions.archive_prefs
|
||||
if not user_prefs then
|
||||
user_prefs = prefs:get(user);
|
||||
if user_sessions then
|
||||
user_sessions.archive_prefs = user_prefs;
|
||||
end
|
||||
end
|
||||
return user_prefs or { [false] = global_default_policy };
|
||||
end
|
||||
local function set_prefs(user, user_prefs)
|
||||
local user_sessions = sessions[user];
|
||||
if user_sessions then
|
||||
user_sessions.archive_prefs = user_prefs;
|
||||
end
|
||||
return prefs:set(user, user_prefs);
|
||||
end
|
||||
|
||||
return {
|
||||
get = get_prefs,
|
||||
set = set_prefs,
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
-- XEP-0313: Message Archive Management for Prosody
|
||||
-- Copyright (C) 2011-2013 Kim Alvefur
|
||||
--
|
||||
-- This file is MIT/X11 licensed.
|
||||
|
||||
local st = require"util.stanza";
|
||||
|
||||
local default_attrs = {
|
||||
always = true, [true] = "always",
|
||||
never = false, [false] = "never",
|
||||
roster = "roster",
|
||||
}
|
||||
|
||||
local function tostanza(prefs, xmlns_mam)
|
||||
local default = prefs[false];
|
||||
default = default_attrs[default];
|
||||
local prefstanza = st.stanza("prefs", { xmlns = xmlns_mam, default = default });
|
||||
local always = st.stanza("always");
|
||||
local never = st.stanza("never");
|
||||
for jid, choice in pairs(prefs) do
|
||||
if jid then
|
||||
(choice and always or never):tag("jid"):text(jid):up();
|
||||
end
|
||||
end
|
||||
prefstanza:add_child(always):add_child(never);
|
||||
return prefstanza;
|
||||
end
|
||||
local function fromstanza(prefstanza)
|
||||
local prefs = {};
|
||||
local default = prefstanza.attr.default;
|
||||
if default then
|
||||
prefs[false] = default_attrs[default];
|
||||
end
|
||||
|
||||
local always = prefstanza:get_child("always");
|
||||
if always then
|
||||
for rule in always:childtags("jid") do
|
||||
local jid = rule:get_text();
|
||||
prefs[jid] = true;
|
||||
end
|
||||
end
|
||||
|
||||
local never = prefstanza:get_child("never");
|
||||
if never then
|
||||
for rule in never:childtags("jid") do
|
||||
local jid = rule:get_text();
|
||||
prefs[jid] = false;
|
||||
end
|
||||
end
|
||||
|
||||
return prefs;
|
||||
end
|
||||
|
||||
return {
|
||||
tostanza = tostanza;
|
||||
fromstanza = fromstanza;
|
||||
}
|
@ -1,433 +0,0 @@
|
||||
-- XEP-0313: Message Archive Management for Prosody
|
||||
-- Copyright (C) 2011-2016 Kim Alvefur
|
||||
--
|
||||
-- This file is MIT/X11 licensed.
|
||||
|
||||
local xmlns_mam0 = "urn:xmpp:mam:0";
|
||||
local xmlns_mam1 = "urn:xmpp:mam:1";
|
||||
local xmlns_mam2 = "urn:xmpp:mam:2";
|
||||
local xmlns_delay = "urn:xmpp:delay";
|
||||
local xmlns_forward = "urn:xmpp:forward:0";
|
||||
local xmlns_st_id = "urn:xmpp:sid:0";
|
||||
|
||||
local um = require "core.usermanager";
|
||||
local st = require "util.stanza";
|
||||
local rsm = module:require "rsm";
|
||||
local get_prefs = module:require"mamprefs".get;
|
||||
local set_prefs = module:require"mamprefs".set;
|
||||
local prefs_to_stanza = module:require"mamprefsxml".tostanza;
|
||||
local prefs_from_stanza = module:require"mamprefsxml".fromstanza;
|
||||
local jid_bare = require "util.jid".bare;
|
||||
local jid_split = require "util.jid".split;
|
||||
local jid_prepped_split = require "util.jid".prepped_split;
|
||||
local dataform = require "util.dataforms".new;
|
||||
local host = module.host;
|
||||
|
||||
local rm_load_roster = require "core.rostermanager".load_roster;
|
||||
|
||||
local getmetatable = getmetatable;
|
||||
local function is_stanza(x)
|
||||
return getmetatable(x) == st.stanza_mt;
|
||||
end
|
||||
|
||||
local tostring = tostring;
|
||||
local time_now = os.time;
|
||||
local m_min = math.min;
|
||||
local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
|
||||
local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
|
||||
local global_default_policy = module:get_option("default_archive_policy", true);
|
||||
if global_default_policy ~= "roster" then
|
||||
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
|
||||
end
|
||||
|
||||
local archive_store = module:get_option_string("archive_store", "archive2");
|
||||
local archive = module:open_store(archive_store, "archive");
|
||||
|
||||
if archive.name == "null" or not archive.find then
|
||||
-- luacheck: ignore 631
|
||||
if not archive.find then
|
||||
module:log("debug", "Attempt to open archive storage returned a valid driver but it does not seem to implement the storage API");
|
||||
module:log("debug", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or "<unknown>");
|
||||
else
|
||||
module:log("debug", "Attempt to open archive storage returned null driver");
|
||||
end
|
||||
module:log("debug", "See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information");
|
||||
module:log("info", "Using in-memory fallback archive driver");
|
||||
archive = module:require "fallback_archive";
|
||||
end
|
||||
|
||||
local use_total = true;
|
||||
|
||||
local cleanup;
|
||||
|
||||
local function schedule_cleanup(username)
|
||||
if cleanup and not cleanup[username] then
|
||||
table.insert(cleanup, username);
|
||||
cleanup[username] = true;
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle prefs.
|
||||
local function handle_prefs(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local xmlns_mam = stanza.tags[1].attr.xmlns;
|
||||
local user = origin.username;
|
||||
if stanza.attr.type == "get" then
|
||||
local prefs = prefs_to_stanza(get_prefs(user), xmlns_mam);
|
||||
local reply = st.reply(stanza):add_child(prefs);
|
||||
origin.send(reply);
|
||||
else -- type == "set"
|
||||
local new_prefs = stanza:get_child("prefs", xmlns_mam);
|
||||
local prefs = prefs_from_stanza(new_prefs);
|
||||
local ok, err = set_prefs(user, prefs);
|
||||
if not ok then
|
||||
origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err)));
|
||||
else
|
||||
origin.send(st.reply(stanza));
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
module:hook("iq/self/"..xmlns_mam0..":prefs", handle_prefs);
|
||||
module:hook("iq/self/"..xmlns_mam1..":prefs", handle_prefs);
|
||||
module:hook("iq/self/"..xmlns_mam2..":prefs", handle_prefs);
|
||||
|
||||
local query_form = dataform {
|
||||
{ name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam0; };
|
||||
{ name = "with"; type = "jid-single"; };
|
||||
{ name = "start"; type = "text-single" };
|
||||
{ name = "end"; type = "text-single"; };
|
||||
};
|
||||
|
||||
-- Serve form
|
||||
local function handle_get_form(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local xmlns_mam = stanza.tags[1].attr.xmlns;
|
||||
query_form[1].value = xmlns_mam;
|
||||
origin.send(st.reply(stanza):query(xmlns_mam):add_child(query_form:form()));
|
||||
return true;
|
||||
end
|
||||
|
||||
module:hook("iq-get/self/"..xmlns_mam0..":query", handle_get_form);
|
||||
module:hook("iq-get/self/"..xmlns_mam1..":query", handle_get_form);
|
||||
module:hook("iq-get/self/"..xmlns_mam2..":query", handle_get_form);
|
||||
|
||||
-- Handle archive queries
|
||||
local function handle_mam_query(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local xmlns_mam = stanza.tags[1].attr.xmlns;
|
||||
local query = stanza.tags[1];
|
||||
local qid = query.attr.queryid;
|
||||
|
||||
origin.mam_requested = true;
|
||||
|
||||
schedule_cleanup(origin.username);
|
||||
|
||||
-- Search query parameters
|
||||
local qwith, qstart, qend;
|
||||
local form = query:get_child("x", "jabber:x:data");
|
||||
if form then
|
||||
local err;
|
||||
query_form[1].value = xmlns_mam;
|
||||
form, err = query_form:data(form);
|
||||
if err then
|
||||
origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
|
||||
return true;
|
||||
end
|
||||
qwith, qstart, qend = form["with"], form["start"], form["end"];
|
||||
qwith = qwith and jid_bare(qwith); -- dataforms does jidprep
|
||||
end
|
||||
|
||||
if qstart or qend then -- Validate timestamps
|
||||
local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend));
|
||||
if (qstart and not vstart) or (qend and not vend) then
|
||||
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"))
|
||||
return true;
|
||||
end
|
||||
qstart, qend = vstart, vend;
|
||||
end
|
||||
|
||||
module:log("debug", "Archive query, id %s with %s from %s until %s)",
|
||||
tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now");
|
||||
|
||||
-- RSM stuff
|
||||
local qset = rsm.get(query);
|
||||
local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
|
||||
local reverse = qset and qset.before or false;
|
||||
local before, after = qset and qset.before, qset and qset.after;
|
||||
if type(before) ~= "string" then before = nil; end
|
||||
|
||||
-- Load all the data!
|
||||
local data, err = archive:find(origin.username, {
|
||||
start = qstart; ["end"] = qend; -- Time range
|
||||
with = qwith;
|
||||
limit = qmax + 1;
|
||||
before = before; after = after;
|
||||
reverse = reverse;
|
||||
total = use_total;
|
||||
});
|
||||
|
||||
if not data then
|
||||
origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
|
||||
return true;
|
||||
end
|
||||
local total = tonumber(err);
|
||||
|
||||
if xmlns_mam == xmlns_mam0 then
|
||||
origin.send(st.reply(stanza));
|
||||
end
|
||||
local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
|
||||
|
||||
local results = {};
|
||||
|
||||
-- Wrap it in stuff and deliver
|
||||
local first, last;
|
||||
local count = 0;
|
||||
local complete = "true";
|
||||
for id, item, when in data do
|
||||
count = count + 1;
|
||||
if count > qmax then
|
||||
complete = nil;
|
||||
break;
|
||||
end
|
||||
local fwd_st = st.message(msg_reply_attr)
|
||||
:tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
|
||||
:tag("forwarded", { xmlns = xmlns_forward })
|
||||
:tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
|
||||
|
||||
if not is_stanza(item) then
|
||||
item = st.deserialize(item);
|
||||
end
|
||||
item.attr.xmlns = "jabber:client";
|
||||
fwd_st:add_child(item);
|
||||
|
||||
if not first then first = id; end
|
||||
last = id;
|
||||
|
||||
if reverse then
|
||||
results[count] = fwd_st;
|
||||
else
|
||||
origin.send(fwd_st);
|
||||
end
|
||||
end
|
||||
|
||||
if reverse then
|
||||
for i = #results, 1, -1 do
|
||||
origin.send(results[i]);
|
||||
end
|
||||
first, last = last, first;
|
||||
end
|
||||
|
||||
-- That's all folks!
|
||||
module:log("debug", "Archive query %s completed", tostring(qid));
|
||||
|
||||
local fin;
|
||||
if xmlns_mam == xmlns_mam0 then
|
||||
fin = st.message(msg_reply_attr);
|
||||
else
|
||||
fin = st.reply(stanza);
|
||||
end
|
||||
do
|
||||
fin:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
|
||||
:add_child(rsm.generate {
|
||||
first = first, last = last, count = total })
|
||||
end
|
||||
origin.send(fin);
|
||||
return true;
|
||||
end
|
||||
module:hook("iq-set/self/"..xmlns_mam0..":query", handle_mam_query);
|
||||
module:hook("iq-set/self/"..xmlns_mam1..":query", handle_mam_query);
|
||||
module:hook("iq-set/self/"..xmlns_mam2..":query", handle_mam_query);
|
||||
|
||||
local function has_in_roster(user, who)
|
||||
local roster = rm_load_roster(user, host);
|
||||
module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no");
|
||||
return roster[who];
|
||||
end
|
||||
|
||||
local function shall_store(user, who)
|
||||
-- TODO Cache this?
|
||||
if not um.user_exists(user, host) then
|
||||
return false;
|
||||
end
|
||||
local prefs = get_prefs(user);
|
||||
local rule = prefs[who];
|
||||
module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule));
|
||||
if rule ~= nil then
|
||||
return rule;
|
||||
end
|
||||
-- Below could be done by a metatable
|
||||
local default = prefs[false];
|
||||
module:log("debug", "%s's default rule is %s", user, tostring(default));
|
||||
if default == nil then
|
||||
default = global_default_policy;
|
||||
module:log("debug", "Using global default rule, %s", tostring(default));
|
||||
end
|
||||
if default == "roster" then
|
||||
return has_in_roster(user, who);
|
||||
end
|
||||
return default;
|
||||
end
|
||||
|
||||
-- Handle messages
|
||||
local function message_handler(event, c2s)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local log = c2s and origin.log or module._log;
|
||||
local orig_type = stanza.attr.type or "normal";
|
||||
local orig_from = stanza.attr.from;
|
||||
local orig_to = stanza.attr.to or orig_from;
|
||||
-- Stanza without 'to' are treated as if it was to their own bare jid
|
||||
|
||||
-- Whos storage do we put it in?
|
||||
local store_user = c2s and origin.username or jid_split(orig_to);
|
||||
-- And who are they chatting with?
|
||||
local with = jid_bare(c2s and orig_to or orig_from);
|
||||
|
||||
-- Filter out <stanza-id> that claim to be from us
|
||||
if stanza:get_child("stanza-id", xmlns_st_id) then
|
||||
stanza = st.clone(stanza);
|
||||
stanza:maptags(function (tag)
|
||||
if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id then
|
||||
local by_user, by_host, res = jid_prepped_split(tag.attr.by);
|
||||
if not res and by_host == module.host and by_user == store_user then
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
return tag;
|
||||
end);
|
||||
event.stanza = stanza;
|
||||
end
|
||||
|
||||
-- We store chat messages or normal messages that have a body
|
||||
if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then
|
||||
log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
|
||||
return;
|
||||
end
|
||||
|
||||
-- or if hints suggest we shouldn't
|
||||
if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store
|
||||
if stanza:get_child("no-permanent-store", "urn:xmpp:hints")
|
||||
or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store
|
||||
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
-- Check with the users preferences
|
||||
if shall_store(store_user, with) then
|
||||
log("debug", "Archiving stanza: %s", stanza:top_tag());
|
||||
|
||||
-- And stash it
|
||||
local ok = archive:append(store_user, nil, stanza, time_now(), with);
|
||||
if ok then
|
||||
local clone_for_other_handlers = st.clone(stanza);
|
||||
local id = ok;
|
||||
clone_for_other_handlers:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up();
|
||||
event.stanza = clone_for_other_handlers;
|
||||
schedule_cleanup(store_user);
|
||||
module:fire_event("archive-message-added", { origin = origin, stanza = stanza, for_user = store_user, id = id });
|
||||
end
|
||||
else
|
||||
log("debug", "Not archiving stanza: %s (prefs)", stanza:top_tag());
|
||||
end
|
||||
end
|
||||
|
||||
local function c2s_message_handler(event)
|
||||
return message_handler(event, true);
|
||||
end
|
||||
|
||||
local function strip_stanza_id(event)
|
||||
local strip_by = jid_bare(event.origin.full_jid);
|
||||
event.stanza = st.clone(event.stanza);
|
||||
event.stanza:maptags(function(tag)
|
||||
if not ( tag.attr.xmlns == xmlns_st_id and tag.attr.by == strip_by ) then
|
||||
return tag;
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
module:hook("pre-message/bare", strip_stanza_id, 0.01);
|
||||
module:hook("pre-message/full", strip_stanza_id, 0.01);
|
||||
|
||||
local cleanup_after = module:get_option_string("archive_expires_after", "1w");
|
||||
local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
|
||||
if cleanup_after ~= "never" then
|
||||
local day = 86400;
|
||||
local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day };
|
||||
local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)");
|
||||
if not n then
|
||||
module:log("error", "Could not parse archive_expires_after string %q", cleanup_after);
|
||||
return false;
|
||||
end
|
||||
|
||||
cleanup_after = tonumber(n) * ( multipliers[m] or 1 );
|
||||
|
||||
module:log("debug", "archive_expires_after = %d -- in seconds", cleanup_after);
|
||||
|
||||
if not archive.delete then
|
||||
module:log("error", "archive_expires_after set but mod_%s does not support deleting", archive._provided_by);
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Set of known users to do message expiry for
|
||||
-- Populated either below or when new messages are added
|
||||
cleanup = {};
|
||||
|
||||
-- Iterating over users is not supported by all authentication modules
|
||||
-- Catch and ignore error if not supported
|
||||
pcall(function ()
|
||||
-- If this works, then we schedule cleanup for all known users on startup
|
||||
for user in um.users(module.host) do
|
||||
schedule_cleanup(user);
|
||||
end
|
||||
end);
|
||||
|
||||
-- At odd intervals, delete old messages for one user
|
||||
module:add_timer(math.random(10, 60), function()
|
||||
local user = table.remove(cleanup, 1);
|
||||
if user then
|
||||
module:log("debug", "Removing old messages for user %q", user);
|
||||
local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; })
|
||||
if not ok then
|
||||
module:log("warn", "Could not expire archives for user %s: %s", user, err);
|
||||
else
|
||||
-- :affected() is a recent addition for eg SQLite3 in LuaDBI
|
||||
pcall(function(stmt)
|
||||
module:log("debug", "Removed %d messages", stmt:affected());
|
||||
end, err);
|
||||
end
|
||||
cleanup[user] = nil;
|
||||
end
|
||||
return math.random(cleanup_interval, cleanup_interval * 2);
|
||||
end);
|
||||
else
|
||||
-- Don't ask the backend to count the potentially unbounded number of items,
|
||||
-- it'll get slow.
|
||||
use_total = false;
|
||||
end
|
||||
|
||||
-- Stanzas sent by local clients
|
||||
local priority = 0.075
|
||||
assert(priority < 0.1, "priority must be after mod_firewall");
|
||||
assert(priority > 0.05, "priority must be before mod_carbons");
|
||||
assert(priority > 0.01, "priority must be before strip_stanza_id");
|
||||
module:hook("pre-message/bare", c2s_message_handler, priority);
|
||||
module:hook("pre-message/full", c2s_message_handler, priority);
|
||||
-- Stanszas to local clients
|
||||
priority = 0.075
|
||||
assert(priority > 0, "priority must be before mod_message");
|
||||
assert(priority < 0.1, "priority must be after mod_firewall");
|
||||
assert(priority > 0.05, "priority must be before mod_carbons");
|
||||
module:hook("message/bare", message_handler, priority);
|
||||
module:hook("message/full", message_handler, priority);
|
||||
|
||||
module:add_feature(xmlns_mam0); -- COMPAT with XEP-0313 v 0.1
|
||||
|
||||
module:hook("account-disco-info", function(event)
|
||||
(event.reply or event.stanza):tag("feature", {var=xmlns_mam0}):up();
|
||||
(event.reply or event.stanza):tag("feature", {var=xmlns_mam1}):up();
|
||||
(event.reply or event.stanza):tag("feature", {var=xmlns_mam2}):up();
|
||||
(event.reply or event.stanza):tag("feature", {var=xmlns_st_id}):up();
|
||||
end);
|
||||
|
@ -1,87 +0,0 @@
|
||||
local stanza = require"util.stanza".stanza;
|
||||
local tostring, tonumber = tostring, tonumber;
|
||||
local type = type;
|
||||
local pairs = pairs;
|
||||
|
||||
local xmlns_rsm = 'http://jabber.org/protocol/rsm';
|
||||
|
||||
local element_parsers = {};
|
||||
|
||||
do
|
||||
local parsers = element_parsers;
|
||||
local function xs_int(st)
|
||||
return tonumber((st:get_text()));
|
||||
end
|
||||
local function xs_string(st)
|
||||
return st:get_text();
|
||||
end
|
||||
|
||||
parsers.after = xs_string;
|
||||
parsers.before = function(st)
|
||||
local text = st:get_text();
|
||||
return text == "" or text;
|
||||
end;
|
||||
parsers.max = xs_int;
|
||||
parsers.index = xs_int;
|
||||
|
||||
parsers.first = function(st)
|
||||
return { index = tonumber(st.attr.index); st:get_text() };
|
||||
end;
|
||||
parsers.last = xs_string;
|
||||
parsers.count = xs_int;
|
||||
end
|
||||
|
||||
local element_generators = setmetatable({
|
||||
first = function(st, data)
|
||||
if type(data) == "table" then
|
||||
st:tag("first", { index = data.index }):text(data[1]):up();
|
||||
else
|
||||
st:tag("first"):text(tostring(data)):up();
|
||||
end
|
||||
end;
|
||||
before = function(st, data)
|
||||
if data == true then
|
||||
st:tag("before"):up();
|
||||
else
|
||||
st:tag("before"):text(tostring(data)):up();
|
||||
end
|
||||
end
|
||||
}, {
|
||||
__index = function(_, name)
|
||||
return function(st, data)
|
||||
st:tag(name):text(tostring(data)):up();
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
|
||||
local function parse(set)
|
||||
local rs = {};
|
||||
for tag in set:childtags() do
|
||||
local name = tag.name;
|
||||
local parser = name and element_parsers[name];
|
||||
if parser then
|
||||
rs[name] = parser(tag);
|
||||
end
|
||||
end
|
||||
return rs;
|
||||
end
|
||||
|
||||
local function generate(t)
|
||||
local st = stanza("set", { xmlns = xmlns_rsm });
|
||||
for k,v in pairs(t) do
|
||||
if element_parsers[k] then
|
||||
element_generators[k](st, v);
|
||||
end
|
||||
end
|
||||
return st;
|
||||
end
|
||||
|
||||
local function get(st)
|
||||
local set = st:get_child("set", xmlns_rsm);
|
||||
if set and #set.tags > 0 then
|
||||
return parse(set);
|
||||
end
|
||||
end
|
||||
|
||||
return { parse = parse, generate = generate, get = get };
|
@ -1,256 +0,0 @@
|
||||
-- Prosody IM
|
||||
-- Copyright (C) 2012 Florian Zeitz
|
||||
--
|
||||
-- This project is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
--
|
||||
|
||||
module:set_global();
|
||||
|
||||
local add_filter = require "util.filters".add_filter;
|
||||
local sha1 = require "util.hashes".sha1;
|
||||
local base64 = require "util.encodings".base64.encode;
|
||||
local softreq = require "util.dependencies".softreq;
|
||||
local portmanager = require "core.portmanager";
|
||||
|
||||
local bit;
|
||||
pcall(function() bit = require"bit"; end);
|
||||
bit = bit or softreq"bit32"
|
||||
if not bit then module:log("error", "No bit module found. Either LuaJIT 2, lua-bitop or Lua 5.2 is required"); end
|
||||
local band = bit.band;
|
||||
local bxor = bit.bxor;
|
||||
local rshift = bit.rshift;
|
||||
|
||||
local cross_domain = module:get_option("cross_domain_websocket");
|
||||
if cross_domain then
|
||||
if cross_domain == true then
|
||||
cross_domain = "*";
|
||||
elseif type(cross_domain) == "table" then
|
||||
cross_domain = table.concat(cross_domain, ", ");
|
||||
end
|
||||
if type(cross_domain) ~= "string" then
|
||||
cross_domain = nil;
|
||||
end
|
||||
end
|
||||
|
||||
module:depends("c2s")
|
||||
local sessions = module:shared("c2s/sessions");
|
||||
local c2s_listener = portmanager.get_service("c2s").listener;
|
||||
|
||||
-- Websocket helpers
|
||||
local function parse_frame(frame)
|
||||
local result = {};
|
||||
local pos = 1;
|
||||
local length_bytes = 0;
|
||||
local counter = 0;
|
||||
local tmp_byte;
|
||||
|
||||
if #frame < 2 then return; end
|
||||
|
||||
tmp_byte = string.byte(frame, pos);
|
||||
result.FIN = band(tmp_byte, 0x80) > 0;
|
||||
result.RSV1 = band(tmp_byte, 0x40) > 0;
|
||||
result.RSV2 = band(tmp_byte, 0x20) > 0;
|
||||
result.RSV3 = band(tmp_byte, 0x10) > 0;
|
||||
result.opcode = band(tmp_byte, 0x0F);
|
||||
|
||||
pos = pos + 1;
|
||||
tmp_byte = string.byte(frame, pos);
|
||||
result.MASK = band(tmp_byte, 0x80) > 0;
|
||||
result.length = band(tmp_byte, 0x7F);
|
||||
|
||||
if result.length == 126 then
|
||||
length_bytes = 2;
|
||||
result.length = 0;
|
||||
elseif result.length == 127 then
|
||||
length_bytes = 8;
|
||||
result.length = 0;
|
||||
end
|
||||
|
||||
if #frame < (2 + length_bytes) then return; end
|
||||
|
||||
for i = 1, length_bytes do
|
||||
pos = pos + 1;
|
||||
result.length = result.length * 256 + string.byte(frame, pos);
|
||||
end
|
||||
|
||||
if #frame < (2 + length_bytes + (result.MASK and 4 or 0) + result.length) then return; end
|
||||
|
||||
if result.MASK then
|
||||
result.key = {string.byte(frame, pos+1), string.byte(frame, pos+2),
|
||||
string.byte(frame, pos+3), string.byte(frame, pos+4)}
|
||||
|
||||
pos = pos + 5;
|
||||
result.data = "";
|
||||
for i = pos, pos + result.length - 1 do
|
||||
result.data = result.data .. string.char(bxor(result.key[counter+1], string.byte(frame, i)));
|
||||
counter = (counter + 1) % 4;
|
||||
end
|
||||
else
|
||||
result.data = frame:sub(pos + 1, pos + result.length);
|
||||
end
|
||||
|
||||
return result, 2 + length_bytes + (result.MASK and 4 or 0) + result.length;
|
||||
end
|
||||
|
||||
local function build_frame(desc)
|
||||
local length;
|
||||
local result = "";
|
||||
local data = desc.data or "";
|
||||
|
||||
result = result .. string.char(0x80 * (desc.FIN and 1 or 0) + desc.opcode);
|
||||
|
||||
length = #data;
|
||||
if length <= 125 then -- 7-bit length
|
||||
result = result .. string.char(length);
|
||||
elseif length <= 0xFFFF then -- 2-byte length
|
||||
result = result .. string.char(126);
|
||||
result = result .. string.char(rshift(length, 8)) .. string.char(length%0x100);
|
||||
else -- 8-byte length
|
||||
result = result .. string.char(127);
|
||||
for i = 7, 0, -1 do
|
||||
result = result .. string.char(rshift(length, 8*i) % 0x100);
|
||||
end
|
||||
end
|
||||
|
||||
result = result .. data;
|
||||
|
||||
return result;
|
||||
end
|
||||
|
||||
--- Filter stuff
|
||||
function handle_request(event, path)
|
||||
local request, response = event.request, event.response;
|
||||
local conn = response.conn;
|
||||
|
||||
if not request.headers.sec_websocket_key then
|
||||
response.headers.content_type = "text/html";
|
||||
return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
|
||||
<p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
|
||||
</body></html>]];
|
||||
end
|
||||
|
||||
local wants_xmpp = false;
|
||||
(request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
|
||||
if proto == "xmpp" then wants_xmpp = true; end
|
||||
end);
|
||||
|
||||
if not wants_xmpp then
|
||||
return 501;
|
||||
end
|
||||
|
||||
local function websocket_close(code, message)
|
||||
local data = string.char(rshift(code, 8)) .. string.char(code%0x100) .. message;
|
||||
conn:write(build_frame({opcode = 0x8, FIN = true, data = data}));
|
||||
conn:close();
|
||||
end
|
||||
|
||||
local dataBuffer;
|
||||
local function handle_frame(frame)
|
||||
module:log("debug", "Websocket received: %s (%i bytes)", frame.data, #frame.data);
|
||||
|
||||
-- Error cases
|
||||
if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero
|
||||
websocket_close(1002, "Reserved bits not zero");
|
||||
return false;
|
||||
end
|
||||
|
||||
if frame.opcode >= 0x8 and frame.length > 125 then -- Control frame with too much payload
|
||||
websocket_close(1002, "Payload too large");
|
||||
return false;
|
||||
end
|
||||
|
||||
if frame.opcode >= 0x8 and not frame.FIN then -- Fragmented control frame
|
||||
websocket_close(1002, "Fragmented control frame");
|
||||
return false;
|
||||
end
|
||||
|
||||
if (frame.opcode > 0x2 and frame.opcode < 0x8) or (frame.opcode > 0xA) then
|
||||
websocket_close(1002, "Reserved opcode");
|
||||
return false;
|
||||
end
|
||||
|
||||
if frame.opcode == 0x0 and not dataBuffer then
|
||||
websocket_close(1002, "Unexpected continuation frame");
|
||||
return false;
|
||||
end
|
||||
|
||||
if (frame.opcode == 0x1 or frame.opcode == 0x2) and dataBuffer then
|
||||
websocket_close(1002, "Continuation frame expected");
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Valid cases
|
||||
if frame.opcode == 0x0 then -- Continuation frame
|
||||
dataBuffer = dataBuffer .. frame.data;
|
||||
elseif frame.opcode == 0x1 then -- Text frame
|
||||
dataBuffer = frame.data;
|
||||
elseif frame.opcode == 0x2 then -- Binary frame
|
||||
websocket_close(1003, "Only text frames are supported");
|
||||
return;
|
||||
elseif frame.opcode == 0x8 then -- Close request
|
||||
websocket_close(1000, "Goodbye");
|
||||
return;
|
||||
elseif frame.opcode == 0x9 then -- Ping frame
|
||||
frame.opcode = 0xA;
|
||||
conn:write(build_frame(frame));
|
||||
return "";
|
||||
else
|
||||
log("warn", "Received frame with unsupported opcode %i", frame.opcode);
|
||||
return "";
|
||||
end
|
||||
|
||||
if frame.FIN then
|
||||
data = dataBuffer;
|
||||
dataBuffer = nil;
|
||||
|
||||
return data;
|
||||
end
|
||||
return "";
|
||||
end
|
||||
|
||||
conn:setlistener(c2s_listener);
|
||||
c2s_listener.onconnect(conn);
|
||||
|
||||
local frameBuffer = "";
|
||||
add_filter(sessions[conn], "bytes/in", function(data)
|
||||
local cache = "";
|
||||
frameBuffer = frameBuffer .. data;
|
||||
local frame, length = parse_frame(frameBuffer);
|
||||
|
||||
while frame do
|
||||
frameBuffer = frameBuffer:sub(length + 1);
|
||||
local result = handle_frame(frame);
|
||||
if not result then return; end
|
||||
cache = cache .. result;
|
||||
frame, length = parse_frame(frameBuffer);
|
||||
end
|
||||
return cache;
|
||||
|
||||
end);
|
||||
|
||||
add_filter(sessions[conn], "bytes/out", function(data)
|
||||
return build_frame({ FIN = true, opcode = 0x01, data = tostring(data)});
|
||||
end);
|
||||
|
||||
response.status_code = 101;
|
||||
response.headers.upgrade = "websocket";
|
||||
response.headers.connection = "Upgrade";
|
||||
response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
||||
response.headers.sec_webSocket_protocol = "xmpp";
|
||||
response.headers.access_control_allow_origin = cross_domain;
|
||||
|
||||
return "";
|
||||
end
|
||||
|
||||
function module.add_host(module)
|
||||
module:depends("http");
|
||||
module:provides("http", {
|
||||
name = "websocket";
|
||||
default_path = "xmpp-websocket";
|
||||
route = {
|
||||
["GET"] = handle_request;
|
||||
["GET /"] = handle_request;
|
||||
};
|
||||
});
|
||||
end
|
Loading…
Reference in new issue