2 -- Copyright (C) 2009 Tobias Markmann
4 -- This project is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information.
8 local st = require "util.stanza";
9 local zlib = require "zlib";
11 local xmlns_compression_feature = "http://jabber.org/features/compress"
12 local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
13 local xmlns_stream = "http://etherx.jabber.org/streams";
14 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
16 local compression_level = module:get_option("compression_level");
17 -- if not defined assume admin wants best compression
18 if compression_level == nil then compression_level = 9 end;
21 compression_level = tonumber(compression_level);
22 if not compression_level or compression_level < 1 or compression_level > 9 then
23 module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
24 module:log("warn", "Module loading aborted. Compression won't be available.");
28 module:hook("stream-features", function(event)
29 local origin, features = event.origin, event.features;
30 if not origin.compressed then
31 -- FIXME only advertise compression support when TLS layer has no compression enabled
32 features:add_child(compression_stream_feature);
36 module:hook("s2s-stream-features", function(event)
37 local origin, features = event.origin, event.features;
38 -- FIXME only advertise compression support when TLS layer has no compression enabled
39 if not origin.compressed then
40 features:add_child(compression_stream_feature);
44 -- Hook to activate compression if remote server supports it.
45 module:hook_stanza(xmlns_stream, "features",
46 function (session, stanza)
47 if not session.compressed then
48 -- does remote server support compression?
49 local comp_st = stanza:child_with_name("compression");
51 -- do we support the mechanism
52 for a in comp_st:children() do
53 local algorithm = a[1]
54 if algorithm == "zlib" then
55 session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
56 session.log("info", "Enabled compression using zlib.")
60 session.log("debug", "Remote server supports no compression algorithm we support.")
67 -- returns either nil or a fully functional ready to use inflate stream
68 local function get_deflate_stream(session)
69 local status, deflate_stream = pcall(zlib.deflate, compression_level);
70 if status == false then
71 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
72 (session.sends2s or session.send)(error_st);
73 session.log("error", "Failed to create zlib.deflate filter.");
74 module:log("error", deflate_stream);
80 -- returns either nil or a fully functional ready to use inflate stream
81 local function get_inflate_stream(session)
82 local status, inflate_stream = pcall(zlib.inflate);
83 if status == false then
84 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
85 (session.sends2s or session.send)(error_st);
86 session.log("error", "Failed to create zlib.deflate filter.");
87 module:log("error", inflate_stream);
93 -- setup compression for a stream
94 local function setup_compression(session, deflate_stream)
95 local old_send = (session.sends2s or session.send);
97 local new_send = function(t)
98 --TODO: Better code injection in the sending process
100 local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
101 if status == false then
103 condition = "undefined-condition";
105 extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
107 module:log("warn", compressed);
110 session.conn:write(compressed);
113 if session.sends2s then session.sends2s = new_send
114 elseif session.send then session.send = new_send end
117 -- setup decompression for a stream
118 local function setup_decompression(session, inflate_stream)
119 local old_data = session.data
120 session.data = function(conn, data)
121 local status, decompressed, eof = pcall(inflate_stream, data);
122 if status == false then
124 condition = "undefined-condition";
126 extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
128 module:log("warn", decompressed);
131 old_data(conn, decompressed);
135 module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol,
136 function(session ,stanza)
137 session.log("debug", "Activating compression...")
138 -- create deflate and inflate streams
139 local deflate_stream = get_deflate_stream(session);
140 if not deflate_stream then return end
142 local inflate_stream = get_inflate_stream(session);
143 if not inflate_stream then return end
145 -- setup compression for session.w
146 setup_compression(session, deflate_stream);
148 -- setup decompression for session.data
149 setup_decompression(session, inflate_stream);
150 local session_reset_stream = session.reset_stream;
151 session.reset_stream = function(session)
152 session_reset_stream(session);
153 setup_decompression(session, inflate_stream);
156 session:reset_stream();
157 local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
158 ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
159 session.sends2s("<?xml version='1.0'?>");
160 session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
161 session.compressed = true;
165 module:add_handler({"c2s_unauthed", "c2s", "s2sin_unauthed", "s2sin"}, "compress", xmlns_compression_protocol,
166 function(session, stanza)
167 -- fail if we are already compressed
168 if session.compressed then
169 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
170 (session.sends2s or session.send)(error_st);
171 session.log("warn", "Tried to establish another compression layer.");
174 -- checking if the compression method is supported
175 local method = stanza:child_with_name("method")[1];
176 if method == "zlib" then
177 session.log("debug", method.." compression selected.");
179 -- create deflate and inflate streams
180 local deflate_stream = get_deflate_stream(session);
181 if not deflate_stream then return end
183 local inflate_stream = get_inflate_stream(session);
184 if not inflate_stream then return end
186 (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
187 session:reset_stream();
189 -- setup compression for session.w
190 setup_compression(session, deflate_stream);
192 -- setup decompression for session.data
193 setup_decompression(session, inflate_stream);
195 local session_reset_stream = session.reset_stream;
196 session.reset_stream = function(session)
197 session_reset_stream(session);
198 setup_decompression(session, inflate_stream);
201 session.compressed = true;
203 session.log("warn", method.." compression selected. But we don't support it.");
204 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
205 (session.sends2s or session.send)(error_st);