2 -- Copyright (C) 2009-2012 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 tostring = tostring;
13 local xmlns_compression_feature = "http://jabber.org/features/compress"
14 local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
15 local xmlns_stream = "http://etherx.jabber.org/streams";
16 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
17 local add_filter = require "util.filters".add_filter;
19 local compression_level = module:get_option_number("compression_level", 7);
21 if not compression_level or compression_level < 1 or compression_level > 9 then
22 module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
23 module:log("warn", "Module loading aborted. Compression won't be available.");
27 module:hook("stream-features", function(event)
28 local origin, features = event.origin, event.features;
29 if not origin.compressed and origin.type == "c2s" then
30 -- FIXME only advertise compression support when TLS layer has no compression enabled
31 features:add_child(compression_stream_feature);
35 module:hook("s2s-stream-features", function(event)
36 local origin, features = event.origin, event.features;
37 -- FIXME only advertise compression support when TLS layer has no compression enabled
38 if not origin.compressed and origin.type == "s2sin" then
39 features:add_child(compression_stream_feature);
43 -- Hook to activate compression if remote server supports it.
44 module:hook_stanza(xmlns_stream, "features",
45 function (session, stanza)
46 if not session.compressed and session.type == "s2sout" then
47 -- does remote server support compression?
48 local comp_st = stanza:get_child("compression", xmlns_compression_feature);
50 -- do we support the mechanism
51 for a in comp_st:childtags("method") do
52 local algorithm = a:get_text();
53 if algorithm == "zlib" then
54 session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
55 session.log("debug", "Enabled compression using zlib.")
59 session.log("debug", "Remote server supports no compression algorithm we support.")
66 -- returns either nil or a fully functional ready to use inflate stream
67 local function get_deflate_stream(session)
68 local status, deflate_stream = pcall(zlib.deflate, compression_level);
69 if status == false then
70 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
71 (session.sends2s or session.send)(error_st);
72 session.log("error", "Failed to create zlib.deflate filter.");
73 module:log("error", "%s", tostring(deflate_stream));
79 -- returns either nil or a fully functional ready to use inflate stream
80 local function get_inflate_stream(session)
81 local status, inflate_stream = pcall(zlib.inflate);
82 if status == false then
83 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
84 (session.sends2s or session.send)(error_st);
85 session.log("error", "Failed to create zlib.inflate filter.");
86 module:log("error", "%s", tostring(inflate_stream));
92 -- setup compression for a stream
93 local function setup_compression(session, deflate_stream)
94 add_filter(session, "bytes/out", function(t)
95 local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
96 if status == false then
97 module:log("warn", "%s", tostring(compressed));
99 condition = "undefined-condition";
101 extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
109 -- setup decompression for a stream
110 local function setup_decompression(session, inflate_stream)
111 add_filter(session, "bytes/in", function(data)
112 local status, decompressed, eof = pcall(inflate_stream, data);
113 if status == false then
114 module:log("warn", "%s", tostring(decompressed));
116 condition = "undefined-condition";
118 extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
126 module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event)
127 local session = event.origin;
129 if session.type == "s2sout" then
130 session.log("debug", "Activating compression...")
131 -- create deflate and inflate streams
132 local deflate_stream = get_deflate_stream(session);
133 if not deflate_stream then return true; end
135 local inflate_stream = get_inflate_stream(session);
136 if not inflate_stream then return true; end
138 -- setup compression for session.w
139 setup_compression(session, deflate_stream);
141 -- setup decompression for session.data
142 setup_decompression(session, inflate_stream);
143 session:reset_stream();
144 session:open_stream(session.from_host, session.to_host);
145 session.compressed = true;
150 module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event)
151 local session, stanza = event.origin, event.stanza;
153 if session.type == "c2s" or session.type == "s2sin" then
154 -- fail if we are already compressed
155 if session.compressed then
156 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
157 (session.sends2s or session.send)(error_st);
158 session.log("debug", "Client tried to establish another compression layer.");
162 -- checking if the compression method is supported
163 local method = stanza:get_child_text("method");
164 if method == "zlib" then
165 session.log("debug", "zlib compression enabled.");
167 -- create deflate and inflate streams
168 local deflate_stream = get_deflate_stream(session);
169 if not deflate_stream then return true; end
171 local inflate_stream = get_inflate_stream(session);
172 if not inflate_stream then return true; end
174 (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
175 session:reset_stream();
177 -- setup compression for session.w
178 setup_compression(session, deflate_stream);
180 -- setup decompression for session.data
181 setup_decompression(session, inflate_stream);
183 session.compressed = true;
185 session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
186 local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
187 (session.sends2s or session.send)(error_st);
189 (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"));