plugins/compression.lua

1
-- Copyright (C) 2009-2010 Matthew Wild
2
-- Copyright (C) 2009-2010 Tobias Markmann
3
--
4
-- This project is MIT/X11 licensed. Please see the
5
-- COPYING file in the source package for more information.
6
--
7
 
8
local verse = require "verse";
9
local zlib = require "zlib";
10
 
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
 
15
local compression_level = 9;
16
 
17
-- returns either nil or a fully functional ready to use inflate stream
18
local function get_deflate_stream(session)
19
	local status, deflate_stream = pcall(zlib.deflate, compression_level);
20
	if status == false then
21
		local error_st = verse.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
22
		session:send(error_st);
23
		session:error("Failed to create zlib.deflate filter: %s", tostring(deflate_stream));
24
		return
25
	end
26
	return deflate_stream
27
end
28
 
29
-- returns either nil or a fully functional ready to use inflate stream
30
local function get_inflate_stream(session)
31
	local status, inflate_stream = pcall(zlib.inflate);
32
	if status == false then
33
		local error_st = verse.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
34
		session:send(error_st);
35
		session:error("Failed to create zlib.inflate filter: %s", tostring(inflate_stream));
36
		return
37
	end
38
	return inflate_stream
39
end
40
 
41
-- setup compression for a stream
42
local function setup_compression(session, deflate_stream)
43
	function session:send(t)
44
			--TODO: Better code injection in the sending process
45
			local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
46
			if status == false then
47
				session:close({
48
					condition = "undefined-condition";
49
					text = compressed;
50
					extra = verse.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("processing-failed");
51
				});
52
				session:warn("Compressed send failed: %s", tostring(compressed));
53
				return;
54
			end
55
			session.conn:write(compressed);
56
		end;
57
end
58
 
59
-- setup decompression for a stream
60
local function setup_decompression(session, inflate_stream)
61
	local old_data = session.data
62
	session.data = function(conn, data)
63
			session:debug("Decompressing data...");
64
			local status, decompressed, eof = pcall(inflate_stream, data);
65
			if status == false then
66
				session:close({
67
					condition = "undefined-condition";
68
					text = decompressed;
69
					extra = verse.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("processing-failed");
70
				});
71
				stream:warn("%s", tostring(decompressed));
72
				return;
73
			end
74
			return old_data(conn, decompressed);
75
		end;
76
end
77
 
78
function verse.plugins.compression(stream)
79
	local function handle_features(features)
80
		if not stream.compressed then
81
			-- does remote server support compression?
82
			local comp_st = features:child_with_name("compression");
83
			if comp_st then
84
				-- do we support the mechanism
85
				for a in comp_st:children() do
86
					local algorithm = a[1]
87
					if algorithm == "zlib" then
88
						stream:send(verse.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
89
						stream:debug("Enabled compression using zlib.")
90
						return true;
91
					end
92
				end
93
				session:debug("Remote server supports no compression algorithm we support.")
94
			end
95
		end
96
	end
97
	local function handle_compressed(stanza)
98
		if stanza.name == "compressed" then
99
			stream:debug("Activating compression...")
100
 
101
			-- create deflate and inflate streams
102
			local deflate_stream = get_deflate_stream(stream);
103
			if not deflate_stream then return end
104
 
105
			local inflate_stream = get_inflate_stream(stream);
106
			if not inflate_stream then return end
107
 
108
			-- setup compression for stream.w
109
			setup_compression(stream, deflate_stream);
110
 
111
			-- setup decompression for stream.data
112
			setup_decompression(stream, inflate_stream);
113
 
114
			stream.compressed = true;
115
			stream:reopen();
116
		elseif stanza.name == "failure" then
117
			stream:warn("Failed to establish compression");
118
		end
119
	end
120
	stream:hook("stream-features", handle_features, 250);
121
	stream:hook("stream/"..xmlns_compression_protocol, handle_compressed);
122
end