net/websocket.lua
author Jonas Schäfer <jonas@wielicki.name>
Mon, 10 Jan 2022 18:23:54 +0100
branch0.11
changeset 12185 783056b4e448
parent 8896 eb710675f7f8
child 10116 b327f2870382
permissions -rw-r--r--
util.xml: Do not allow doctypes, comments or processing instructions Yes. This is as bad as it sounds. CVE pending. In Prosody itself, this only affects mod_websocket, which uses util.xml to parse the <open/> frame, thus allowing unauthenticated remote DoS using Billion Laughs. However, third-party modules using util.xml may also be affected by this. This commit installs handlers which disallow the use of doctype declarations and processing instructions without any escape hatch. It, by default, also introduces such a handler for comments, however, there is a way to enable comments nontheless. This is because util.xml is used to parse human-facing data, where comments are generally a desirable feature, and also because comments are generally harmless.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     1
-- Prosody IM
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     2
-- Copyright (C) 2012 Florian Zeitz
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     3
-- Copyright (C) 2014 Daurnimator
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     4
--
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     5
-- This project is MIT/X11 licensed. Please see the
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     6
-- COPYING file in the source package for more information.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     7
--
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
     8
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
     9
local t_concat = table.concat;
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
    10
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    11
local http = require "net.http";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    12
local frames = require "net.websocket.frames";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    13
local base64 = require "util.encodings".base64;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    14
local sha1 = require "util.hashes".sha1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    15
local random_bytes = require "util.random".bytes;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    16
local timer = require "util.timer";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    17
local log = require "util.logger".init "websocket";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    18
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    19
local close_timeout = 3; -- Seconds to wait after sending close frame until closing connection.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    20
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    21
local websockets = {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    22
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    23
local websocket_listeners = {};
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    24
function websocket_listeners.ondisconnect(conn, err)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    25
	local s = websockets[conn];
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    26
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    27
	if s.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    28
		timer.stop(s.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    29
		s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    30
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    31
	s.readyState = 3;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    32
	if s.close_code == nil and s.onerror then s:onerror(err); end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    33
	if s.onclose then s:onclose(s.close_code, s.close_message or err); end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    34
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    35
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    36
function websocket_listeners.ondetach(conn)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    37
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    38
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    39
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    40
local function fail(s, code, reason)
8192
a3565d7dd304 net.websocket: Remove stray module api reference, shouldn't be used in here
Kim Alvefur <zash@zash.se>
parents: 7762
diff changeset
    41
	log("warn", "WebSocket connection failed, closing. %d %s", code, reason);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    42
	s:close(code, reason);
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    43
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    44
	return false
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    45
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    46
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    47
function websocket_listeners.onincoming(conn, buffer, err) -- luacheck: ignore 212/err
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    48
	local s = websockets[conn];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    49
	s.readbuffer = s.readbuffer..buffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    50
	while true do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    51
		local frame, len = frames.parse(s.readbuffer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    52
		if frame == nil then break end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    53
		s.readbuffer = s.readbuffer:sub(len+1);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    54
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    55
		log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    56
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    57
		-- Error cases
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    58
		if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    59
			return fail(s, 1002, "Reserved bits not zero");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    60
		end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    61
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    62
		if frame.opcode < 0x8 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    63
			local databuffer = s.databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    64
			if frame.opcode == 0x0 then -- Continuation frames
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    65
				if not databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    66
					return fail(s, 1002, "Unexpected continuation frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    67
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    68
				databuffer[#databuffer+1] = frame.data;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    69
			elseif frame.opcode == 0x1 or frame.opcode == 0x2 then -- Text or Binary frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    70
				if databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    71
					return fail(s, 1002, "Continuation frame expected");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    72
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    73
				databuffer = {type=frame.opcode, frame.data};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    74
				s.databuffer = databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    75
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    76
				return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    77
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    78
			if frame.FIN then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    79
				s.databuffer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    80
				if s.onmessage then
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
    81
					s:onmessage(t_concat(databuffer), databuffer.type);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    82
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    83
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    84
		else -- Control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    85
			if frame.length > 125 then -- Control frame with too much payload
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    86
				return fail(s, 1002, "Payload too large");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    87
			elseif not frame.FIN then -- Fragmented control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    88
				return fail(s, 1002, "Fragmented control frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    89
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    90
			if frame.opcode == 0x8 then -- Close request
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    91
				if frame.length == 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    92
					return fail(s, 1002, "Close frame with payload, but too short for status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    93
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    94
				local status_code, message = frames.parse_close(frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    95
				if status_code == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    96
					--[[ RFC 6455 7.4.1
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    97
					1005 is a reserved value and MUST NOT be set as a status code in a
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    98
					Close control frame by an endpoint.  It is designated for use in
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    99
					applications expecting a status code to indicate that no status
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   100
					code was actually present.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   101
					]]
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   102
					status_code = 1005
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   103
				elseif status_code < 1000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   104
					return fail(s, 1002, "Closed with invalid status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   105
				elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   106
					return fail(s, 1002, "Closed with reserved status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   107
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   108
				s.close_code, s.close_message = status_code, message;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   109
				s:close(1000);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   110
				return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   111
			elseif frame.opcode == 0x9 then -- Ping frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   112
				frame.opcode = 0xA;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   113
				frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   114
				conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   115
			elseif frame.opcode == 0xA then -- Pong frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   116
				log("debug", "Received unexpected pong frame: " .. tostring(frame.data));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   117
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   118
				return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   119
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   120
		end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   121
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   122
	return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   123
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   124
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   125
local websocket_methods = {};
7762
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6458
diff changeset
   126
local function close_timeout_cb(now, timerid, s) -- luacheck: ignore 212/now 212/timerid
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   127
	s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   128
	log("warn", "Close timeout waiting for server to close, closing manually.");
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   129
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   130
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   131
function websocket_methods:close(code, reason)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   132
	if self.readyState < 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   133
		code = code or 1000;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   134
		log("debug", "closing WebSocket with code %i: %s" , code , tostring(reason));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   135
		self.readyState = 2;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   136
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   137
		conn:write(frames.build_close(code, reason, true));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   138
		-- Do not close socket straight away, wait for acknowledgement from server.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   139
		self.close_timer = timer.add_task(close_timeout, close_timeout_cb, self);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   140
	elseif self.readyState == 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   141
		log("debug", "tried to close a closing WebSocket, closing the raw socket.");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   142
		-- Stop timer
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   143
		if self.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   144
			timer.stop(self.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   145
			self.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   146
		end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   147
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   148
		conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   149
	else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   150
		log("debug", "tried to close a closed WebSocket, ignoring.");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   151
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   152
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   153
function websocket_methods:send(data, opcode)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   154
	if self.readyState < 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   155
		return nil, "WebSocket not open yet, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   156
	elseif self.readyState >= 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   157
		return nil, "WebSocket closed, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   158
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   159
	if opcode == "text" or opcode == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   160
		opcode = 0x1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   161
	elseif opcode == "binary" then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   162
		opcode = 0x2;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   163
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   164
	local frame = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   165
		FIN = true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   166
		MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   167
		opcode = opcode;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   168
		data = tostring(data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   169
	};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   170
	log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   171
	return self.conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   172
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   173
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   174
local websocket_metatable = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   175
	__index = websocket_methods;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   176
};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   177
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   178
local function connect(url, ex, listeners)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   179
	ex = ex or {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   180
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   181
	--[[RFC 6455 4.1.7:
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   182
		The request MUST include a header field with the name
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   183
	|Sec-WebSocket-Key|.  The value of this header field MUST be a
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   184
	nonce consisting of a randomly selected 16-byte value that has
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   185
	been base64-encoded (see Section 4 of [RFC4648]).  The nonce
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   186
	MUST be selected randomly for each connection.
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   187
	]]
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   188
	local key = base64.encode(random_bytes(16));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   189
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   190
	-- Either a single protocol string or an array of protocol strings.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   191
	local protocol = ex.protocol;
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
   192
	if type(protocol) == "string" then
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   193
		protocol = { protocol, [protocol] = true };
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   194
	elseif type(protocol) == "table" and protocol[1] then
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   195
		for _, v in ipairs(protocol) do
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   196
			protocol[v] = true;
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   197
		end
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   198
	else
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   199
		protocol = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   200
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   201
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   202
	local headers = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   203
		["Upgrade"] = "websocket";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   204
		["Connection"] = "Upgrade";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   205
		["Sec-WebSocket-Key"] = key;
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   206
		["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", ");
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   207
		["Sec-WebSocket-Version"] = "13";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   208
		["Sec-WebSocket-Extensions"] = ex.extensions;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   209
	}
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   210
	if ex.headers then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   211
		for k,v in pairs(ex.headers) do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   212
			headers[k] = v;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   213
		end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   214
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   215
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   216
	local s = setmetatable({
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   217
		readbuffer = "";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   218
		databuffer = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   219
		conn = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   220
		close_code = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   221
		close_message = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   222
		close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   223
		readyState = 0;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   224
		protocol = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   225
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   226
		url = url;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   227
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   228
		onopen = listeners.onopen;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   229
		onclose = listeners.onclose;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   230
		onmessage = listeners.onmessage;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   231
		onerror = listeners.onerror;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   232
	}, websocket_metatable);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   233
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   234
	local http_url = url:gsub("^(ws)", "http");
7762
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6458
diff changeset
   235
	local http_req = http.request(http_url, { -- luacheck: ignore 211/http_req
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   236
		method = "GET";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   237
		headers = headers;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   238
		sslctx = ex.sslctx;
8896
eb710675f7f8 net.websocket: Honour ex.insecure to match net.http's new parameter for that
Matthew Wild <mwild1@gmail.com>
parents: 8895
diff changeset
   239
		insecure = ex.insecure;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   240
	}, function(b, c, r, http_req)
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   241
		if c ~= 101
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   242
		   or r.headers["connection"]:lower() ~= "upgrade"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   243
		   or r.headers["upgrade"] ~= "websocket"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   244
		   or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   245
		   or (protocol and not protocol[r.headers["sec-websocket-protocol"]])
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   246
		   then
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   247
			s.readyState = 3;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   248
			log("warn", "WebSocket connection to %s failed: %s", url, tostring(b));
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   249
			if s.onerror then s:onerror("connecting-failed"); end
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   250
			return;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   251
		end
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   252
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   253
		s.protocol = r.headers["sec-websocket-protocol"];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   254
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   255
		-- Take possession of socket from http
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   256
		local conn = http_req.conn;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   257
		http_req.conn = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   258
		s.conn = conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   259
		websockets[conn] = s;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   260
		conn:setlistener(websocket_listeners);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   261
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   262
		log("debug", "WebSocket connected successfully to %s", url);
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   263
		s.readyState = 1;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   264
		if s.onopen then s:onopen(); end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   265
		websocket_listeners.onincoming(conn, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   266
	end);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   267
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   268
	return s;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   269
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   270
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   271
return {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   272
	connect = connect;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   273
};