comparison mod_websocket/mod_websocket.lua @ 677:eeb41cd5e9f3

mod_websocket: Move frame handling into a separate function
author Florian Zeitz <florob@babelmonkeys.de>
date Sat, 26 May 2012 03:09:09 +0200
parents 54fa9d6d7809
children d141375ece4b
comparison
equal deleted inserted replaced
676:54fa9d6d7809 677:eeb41cd5e9f3
16 local st = require "util.stanza"; 16 local st = require "util.stanza";
17 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; 17 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
18 local uuid_generate = require "util.uuid".generate; 18 local uuid_generate = require "util.uuid".generate;
19 local sha1 = require "util.hashes".sha1; 19 local sha1 = require "util.hashes".sha1;
20 local base64 = require "util.encodings".base64.encode; 20 local base64 = require "util.encodings".base64.encode;
21 local band = require "bit".band;
21 local bxor = require "bit".bxor; 22 local bxor = require "bit".bxor;
22 local tohex = require "bit".tohex; 23 local tohex = require "bit".tohex;
23 24
24 module:depends("http") 25 module:depends("http")
25 26
36 37
37 local sessions = module:shared("sessions"); 38 local sessions = module:shared("sessions");
38 39
39 local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza }; 40 local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
40 local listener = {}; 41 local listener = {};
42
43 -- Websocket helpers
44 local function parse_frame(frame)
45 local result = {};
46 local pos = 1;
47 local length_bytes = 0;
48 local counter = 0;
49 local tmp_byte;
50
51 tmp_byte = string.byte(frame, pos);
52 result.FIN = band(tmp_byte, 0x80) > 0;
53 result.RSV1 = band(tmp_byte, 0x40) > 0;
54 result.RSV2 = band(tmp_byte, 0x20) > 0;
55 result.RSV3 = band(tmp_byte, 0x10) > 0;
56 result.opcode = band(tmp_byte, 0x0F) > 0;
57
58 pos = pos + 1;
59 tmp_byte = string.byte(frame, pos);
60 result.MASK = band(tmp_byte, 0x80) > 0;
61 result.length = band(tmp_byte, 0x7F);
62
63 if result.length == 126 then
64 length_bytes = 2;
65 result.length = 0;
66 elseif result.length == 127 then
67 length_bytes = 8;
68 result.length = 0;
69 end
70
71 for i = 1, length_bytes do
72 pos = pos + 1;
73 result.length = result.length * 255 + string.byte(frame, pos);
74 end
75
76 if result.MASK then
77 result.key = {string.byte(frame, pos+1), string.byte(frame, pos+2),
78 string.byte(frame, pos+3), string.byte(frame, pos+4)}
79
80 pos = pos + 5;
81 result.data = "";
82 for i = pos, pos + result.length - 1 do
83 result.data = result.data .. string.char(bxor(result.key[counter+1], string.byte(frame, i)));
84 counter = (counter + 1) % 4;
85 end
86 else
87 result.data = frame:sub(pos + 1, pos + result.length);
88 end
89
90 return result;
91 end
41 92
42 --- Stream events handlers 93 --- Stream events handlers
43 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; 94 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
44 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; 95 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
45 96
59 -- We don't serve this host... 110 -- We don't serve this host...
60 session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; 111 session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
61 return; 112 return;
62 end 113 end
63 114
115 -- COMPAT: Current client implementations need this to be self-closing
64 send("<?xml version='1.0'?>"..(tostring(st.stanza("stream:stream", { 116 send("<?xml version='1.0'?>"..(tostring(st.stanza("stream:stream", {
65 xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams'; 117 xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
66 id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag()):gsub(">", "/>"))); 118 id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag()):gsub(">", "/>")));
67 119
68 (session.log or log)("debug", "Sent reply <stream:stream> to client"); 120 (session.log or log)("debug", "Sent reply <stream:stream> to client");
183 session.stream:reset(); 235 session.stream:reset();
184 end 236 end
185 237
186 local filter = session.filter; 238 local filter = session.filter;
187 function session.data(data) 239 function session.data(data)
188 local off = 0; 240 data = parse_frame(data).data;
189 local len = string.byte(data, 2) - 0x80; 241 module:log("debug", "Websocket received: %s %i", data, #data)
190 if len == 126 then 242 -- COMPAT: Current client implementations send a self-closing <stream:stream>
191 off = 2; 243 data = data:gsub("/>$", ">");
192 elseif len ==127 then 244
193 off = 8; 245 data = filter("bytes/in", data);
194 end
195 local key = {string.byte(data, off+3), string.byte(data, off+4), string.byte(data, off+5), string.byte(data, off+6)}
196 local decoded = "";
197 local counter = 0;
198 for i = off+7, #data do
199 decoded = decoded .. string.char(bxor(key[counter+1], string.byte(data, i)));
200 counter = (counter + 1) % 4;
201 end
202 module:log("debug", "Websocket received: %s %i", decoded, #decoded)
203 decoded = decoded:gsub("/>$", ">");
204
205 data = filter("bytes/in", decoded);
206 if data then 246 if data then
207 local ok, err = stream:feed(data); 247 local ok, err = stream:feed(data);
208 if ok then return; end 248 if ok then return; end
209 log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); 249 log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
210 session:close("not-well-formed"); 250 session:close("not-well-formed");