mod_websocket/mod_websocket.lua
changeset 842 32df4d13f178
parent 840 f568661c9d39
child 844 a0987c0e4e1d
equal deleted inserted replaced
841:0649883de4d3 842:32df4d13f178
    45 	local pos = 1;
    45 	local pos = 1;
    46 	local length_bytes = 0;
    46 	local length_bytes = 0;
    47 	local counter = 0;
    47 	local counter = 0;
    48 	local tmp_byte;
    48 	local tmp_byte;
    49 
    49 
       
    50 	if #frame < 2 then return; end
       
    51 
    50 	tmp_byte = string.byte(frame, pos);
    52 	tmp_byte = string.byte(frame, pos);
    51 	result.FIN = band(tmp_byte, 0x80) > 0;
    53 	result.FIN = band(tmp_byte, 0x80) > 0;
    52 	result.RSV1 = band(tmp_byte, 0x40) > 0;
    54 	result.RSV1 = band(tmp_byte, 0x40) > 0;
    53 	result.RSV2 = band(tmp_byte, 0x20) > 0;
    55 	result.RSV2 = band(tmp_byte, 0x20) > 0;
    54 	result.RSV3 = band(tmp_byte, 0x10) > 0;
    56 	result.RSV3 = band(tmp_byte, 0x10) > 0;
    65 	elseif result.length == 127 then
    67 	elseif result.length == 127 then
    66 		length_bytes = 8;
    68 		length_bytes = 8;
    67 		result.length = 0;
    69 		result.length = 0;
    68 	end
    70 	end
    69 
    71 
       
    72 	if #frame < (2 + length_bytes) then return; end
       
    73 
    70 	for i = 1, length_bytes do
    74 	for i = 1, length_bytes do
    71 		pos = pos + 1;
    75 		pos = pos + 1;
    72 		result.length = result.length * 256 + string.byte(frame, pos);
    76 		result.length = result.length * 256 + string.byte(frame, pos);
    73 	end
    77 	end
       
    78 
       
    79 	if #frame < (2 + length_bytes + (result.MASK and 4 or 0) + result.length) then return; end
    74 
    80 
    75 	if result.MASK then
    81 	if result.MASK then
    76 		result.key = {string.byte(frame, pos+1), string.byte(frame, pos+2),
    82 		result.key = {string.byte(frame, pos+1), string.byte(frame, pos+2),
    77 				string.byte(frame, pos+3), string.byte(frame, pos+4)}
    83 				string.byte(frame, pos+3), string.byte(frame, pos+4)}
    78 
    84 
    84 		end
    90 		end
    85 	else
    91 	else
    86 		result.data = frame:sub(pos + 1, pos + result.length);
    92 		result.data = frame:sub(pos + 1, pos + result.length);
    87 	end
    93 	end
    88 
    94 
    89 	return result;
    95 	return result, 2 + length_bytes + (result.MASK and 4 or 0) + result.length;
    90 end
    96 end
    91 
    97 
    92 local function build_frame(desc)
    98 local function build_frame(desc)
    93 	local length;
    99 	local length;
    94 	local result = "";
   100 	local result = "";
       
   101 	local data = desc.data or "";
    95 
   102 
    96 	result = result .. string.char(0x80 * (desc.FIN and 1 or 0) + desc.opcode);
   103 	result = result .. string.char(0x80 * (desc.FIN and 1 or 0) + desc.opcode);
    97 
   104 
    98 	length = #desc.data;
   105 	length = #data;
    99 	if length <= 125 then -- 7-bit length
   106 	if length <= 125 then -- 7-bit length
   100 		result = result .. string.char(length);
   107 		result = result .. string.char(length);
   101 	elseif length <= 0xFFFF then -- 2-byte length
   108 	elseif length <= 0xFFFF then -- 2-byte length
   102 		result = result .. string.char(126);
   109 		result = result .. string.char(126);
   103 		result = result .. string.char(length/0x100) .. string.char(length%0x100);
   110 		result = result .. string.char(length/0x100) .. string.char(length%0x100);
   106 		for i = 7, 0, -1 do
   113 		for i = 7, 0, -1 do
   107 			result = result .. string.char(( length / (2^(8*i)) ) % 0x100);
   114 			result = result .. string.char(( length / (2^(8*i)) ) % 0x100);
   108 		end
   115 		end
   109 	end
   116 	end
   110 
   117 
   111 	result = result .. desc.data;
   118 	result = result .. data;
   112 
   119 
   113 	return result;
   120 	return result;
   114 end
   121 end
   115 
   122 
   116 --- Stream events handlers
   123 --- Stream events handlers
   294 	function session.reset_stream()
   301 	function session.reset_stream()
   295 		session.notopen = true;
   302 		session.notopen = true;
   296 		session.stream:reset();
   303 		session.stream:reset();
   297 	end
   304 	end
   298 
   305 
       
   306 	local function websocket_close(code, message)
       
   307 		local data = string.char(code/0x100) .. string.char(code%0x100) .. message;
       
   308 		conn:write(build_frame({opcode = 0x8, FIN = true, data = data}));
       
   309 		conn:close();
       
   310 	end
       
   311 
   299 	local filter = session.filter;
   312 	local filter = session.filter;
   300 	local buffer = "";
   313 	local dataBuffer;
   301 	function session.data(data)
   314 	local function handle_frame(frame)
   302 		local frame = parse_frame(data);
       
   303 
       
   304 		module:log("debug", "Websocket received: %s (%i bytes)", frame.data, #frame.data);
   315 		module:log("debug", "Websocket received: %s (%i bytes)", frame.data, #frame.data);
   305 		if frame.opcode == 0x0 or frame.opcode == 0x1 then -- Text or continuation frame
   316 
   306 			buffer = buffer .. frame.data;
   317 		-- Error cases
       
   318 		if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero
       
   319 			websocket_close(1002, "Reserved bits not zero");
       
   320 			return false;
       
   321 		end
       
   322 
       
   323 		if frame.opcode >= 0x8 and frame.length > 125 then -- Control frame with too much payload
       
   324 			websocket_close(1002, "Payload too large");
       
   325 			return false;
       
   326 		end
       
   327 
       
   328 		if frame.opcode >= 0x8 and not frame.FIN then -- Fragmented control frame
       
   329 			websocket_close(1002, "Fragmented control frame");
       
   330 			return false;
       
   331 		end
       
   332 
       
   333 		if (frame.opcode > 0x2 and frame.opcode < 0x8) or (frame.opcode > 0xA) then
       
   334 			websocket_close(1002, "Reserved opcode");
       
   335 			return false;
       
   336 		end
       
   337 
       
   338 		if frame.opcode == 0x0 and not dataBuffer then
       
   339 			websocket_close(1002, "Unexpected continuation frame");
       
   340 			return false;
       
   341 		end
       
   342 
       
   343 		if (frame.opcode == 0x1 or frame.opcode == 0x2) and dataBuffer then
       
   344 			websocket_close(1002, "Continuation frame expected");
       
   345 			return false;
       
   346 		end
       
   347 
       
   348 		-- Valid cases
       
   349 		if frame.opcode == 0x0 then -- Continuation frame
       
   350 			dataBuffer = dataBuffer .. frame.data;
       
   351 		elseif frame.opcode == 0x1 then -- Text frame
       
   352 			dataBuffer = frame.data;
       
   353 		elseif frame.opcode == 0x2 then -- Binary frame
       
   354 			websocket_close(1003, "Only text frames are supported");
       
   355 			return false;
       
   356 		elseif frame.opcode == 0x8 then -- Close request
       
   357 			websocket_close(1000, "Goodbye");
       
   358 			return false;
   307 		elseif frame.opcode == 0x9 then -- Ping frame
   359 		elseif frame.opcode == 0x9 then -- Ping frame
   308 			frame.opcode = 0xA;
   360 			frame.opcode = 0xA;
   309 			conn:write(build_frame(frame));
   361 			conn:write(build_frame(frame));
   310 			return;
   362 			return true;
   311 		else
   363 		else
   312 			log("warn", "Received frame with unsupported opcode %i", frame.opcode);
   364 			log("warn", "Received frame with unsupported opcode %i", frame.opcode);
   313 			return;
   365 			return true;
   314 		end
   366 		end
   315 
   367 
   316 		if frame.FIN then
   368 		if frame.FIN then
   317 			data = buffer;
   369 			data = dataBuffer;
   318 			buffer = "";
   370 			dataBuffer = nil;
   319 
   371 
   320 			-- COMPAT: Current client implementations send a self-closing <stream:stream>
   372 			-- COMPAT: Current client implementations send a self-closing <stream:stream>
   321 			if self_closing_stream then
   373 			if self_closing_stream then
   322 				data = data:gsub("(<stream:stream.*)/>$", "%1>");
   374 				data = data:gsub("(<stream:stream.*)/>$", "%1>");
   323 			end
   375 			end
   327 				local ok, err = stream:feed(data);
   379 				local ok, err = stream:feed(data);
   328 				if ok then return; end
   380 				if ok then return; end
   329 				log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
   381 				log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
   330 				session:close("not-well-formed");
   382 				session:close("not-well-formed");
   331 			end
   383 			end
       
   384 		end
       
   385 		return true;
       
   386 	end
       
   387 
       
   388 	local frameBuffer = "";
       
   389 	function session.data(data)
       
   390 		frameBuffer = frameBuffer .. data;
       
   391 		local frame, length = parse_frame(frameBuffer);
       
   392 
       
   393 		while frame do
       
   394 			frameBuffer = frameBuffer:sub(length + 1);
       
   395 			if not handle_frame(frame) then return; end
       
   396 			frame, length = parse_frame(frameBuffer);
   332 		end
   397 		end
   333 	end
   398 	end
   334 
   399 
   335 	function session.send(s)
   400 	function session.send(s)
   336 		conn:write(build_frame({ FIN = true, opcode = 0x01, data = tostring(s)}));
   401 		conn:write(build_frame({ FIN = true, opcode = 0x01, data = tostring(s)}));
   379 		return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
   444 		return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
   380 			<p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
   445 			<p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
   381 			</body></html>]];
   446 			</body></html>]];
   382 	end
   447 	end
   383 
   448 
       
   449 	-- TODO: Handle requested subprotocols
       
   450 
   384 	response.conn:setlistener(listener);
   451 	response.conn:setlistener(listener);
   385 	response.status = "101 Switching Protocols";
   452 	response.status = "101 Switching Protocols";
   386 	response.headers.Upgrade = "websocket";
   453 	response.headers.Upgrade = "websocket";
   387 	response.headers.Connection = "Upgrade";
   454 	response.headers.Connection = "Upgrade";
   388 	response.headers.Sec_WebSocket_Accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
   455 	response.headers.Sec_WebSocket_Accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));