net/websocket.lua
author Kim Alvefur <zash@zash.se>
Thu, 28 Mar 2024 15:39:59 +0100
changeset 13473 f9171624fd03
parent 12978 ba409c67353b
permissions -rw-r--r--
MUC: Fix legacy hats (thanks nicoco) Why do we not have tests for this?
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
12978
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    11
local http = require "prosody.net.http";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    12
local frames = require "prosody.net.websocket.frames";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    13
local base64 = require "prosody.util.encodings".base64;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    14
local sha1 = require "prosody.util.hashes".sha1;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    15
local random_bytes = require "prosody.util.random".bytes;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    16
local timer = require "prosody.util.timer";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10457
diff changeset
    17
local log = require "prosody.util.logger".init "websocket";
6389
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];
10457
926d6086a95a net.websocket: Fix traceback in case of ondisconnect being called twice
Matthew Wild <mwild1@gmail.com>
parents: 10117
diff changeset
    26
	if not s then return; end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    27
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    28
	if s.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    29
		timer.stop(s.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    30
		s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    31
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    32
	s.readyState = 3;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    33
	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
    34
	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
    35
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    36
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    37
function websocket_listeners.ondetach(conn)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    38
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    39
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    40
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    41
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
    42
	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
    43
	s:close(code, reason);
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    44
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    45
	return false
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    46
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    47
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    48
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
    49
	local s = websockets[conn];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    50
	s.readbuffer = s.readbuffer..buffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    51
	while true do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    52
		local frame, len = frames.parse(s.readbuffer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    53
		if frame == nil then break end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    54
		s.readbuffer = s.readbuffer:sub(len+1);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    55
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    56
		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
    57
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    58
		-- Error cases
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    59
		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
    60
			return fail(s, 1002, "Reserved bits not zero");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    61
		end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    62
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    63
		if frame.opcode < 0x8 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    64
			local databuffer = s.databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    65
			if frame.opcode == 0x0 then -- Continuation frames
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    66
				if not databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    67
					return fail(s, 1002, "Unexpected continuation frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    68
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    69
				databuffer[#databuffer+1] = frame.data;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    70
			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
    71
				if databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    72
					return fail(s, 1002, "Continuation frame expected");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    73
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    74
				databuffer = {type=frame.opcode, frame.data};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    75
				s.databuffer = databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    76
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    77
				return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    78
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    79
			if frame.FIN then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    80
				s.databuffer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    81
				if s.onmessage then
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
    82
					s:onmessage(t_concat(databuffer), databuffer.type);
6389
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
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    85
		else -- Control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    86
			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
    87
				return fail(s, 1002, "Payload too large");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    88
			elseif not frame.FIN then -- Fragmented control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    89
				return fail(s, 1002, "Fragmented control frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    90
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    91
			if frame.opcode == 0x8 then -- Close request
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    92
				if frame.length == 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    93
					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
    94
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    95
				local status_code, message = frames.parse_close(frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    96
				if status_code == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    97
					--[[ RFC 6455 7.4.1
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    98
					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
    99
					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
   100
					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
   101
					code was actually present.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   102
					]]
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   103
					status_code = 1005
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   104
				elseif status_code < 1000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   105
					return fail(s, 1002, "Closed with invalid status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   106
				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
   107
					return fail(s, 1002, "Closed with reserved status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   108
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   109
				s.close_code, s.close_message = status_code, message;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   110
				s:close(1000);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   111
				return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   112
			elseif frame.opcode == 0x9 then -- Ping frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   113
				frame.opcode = 0xA;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   114
				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
   115
				conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   116
			elseif frame.opcode == 0xA then -- Pong frame
10117
66a9bc2d5c8d net.websocket: Fix log call to pass data via format string instead of concatenation
Kim Alvefur <zash@zash.se>
parents: 10116
diff changeset
   117
				log("debug", "Received unexpected pong frame: %s", frame.data);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   118
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   119
				return fail(s, 1002, "Reserved opcode");
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
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   123
	return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   124
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   125
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   126
local websocket_methods = {};
7762
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6458
diff changeset
   127
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
   128
	s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   129
	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
   130
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   131
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   132
function websocket_methods:close(code, reason)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   133
	if self.readyState < 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   134
		code = code or 1000;
10116
b327f2870382 net.*: Remove tostring call from logging
Kim Alvefur <zash@zash.se>
parents: 8896
diff changeset
   135
		log("debug", "closing WebSocket with code %i: %s" , code , reason);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   136
		self.readyState = 2;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   137
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   138
		conn:write(frames.build_close(code, reason, true));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   139
		-- 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
   140
		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
   141
	elseif self.readyState == 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   142
		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
   143
		-- Stop timer
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   144
		if self.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   145
			timer.stop(self.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   146
			self.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   147
		end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   148
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   149
		conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   150
	else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   151
		log("debug", "tried to close a closed WebSocket, ignoring.");
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
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   154
function websocket_methods:send(data, opcode)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   155
	if self.readyState < 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   156
		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
   157
	elseif self.readyState >= 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   158
		return nil, "WebSocket closed, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   159
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   160
	if opcode == "text" or opcode == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   161
		opcode = 0x1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   162
	elseif opcode == "binary" then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   163
		opcode = 0x2;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   164
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   165
	local frame = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   166
		FIN = true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   167
		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
   168
		opcode = opcode;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   169
		data = tostring(data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   170
	};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   171
	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
   172
	return self.conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   173
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   174
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   175
local websocket_metatable = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   176
	__index = websocket_methods;
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
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   179
local function connect(url, ex, listeners)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   180
	ex = ex or {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   181
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   182
	--[[RFC 6455 4.1.7:
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   183
		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
   184
	|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
   185
	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
   186
	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
   187
	MUST be selected randomly for each connection.
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   188
	]]
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   189
	local key = base64.encode(random_bytes(16));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   190
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   191
	-- 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
   192
	local protocol = ex.protocol;
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
   193
	if type(protocol) == "string" then
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   194
		protocol = { protocol, [protocol] = true };
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   195
	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
   196
		for _, v in ipairs(protocol) do
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   197
			protocol[v] = true;
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   198
		end
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   199
	else
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   200
		protocol = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   201
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   202
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   203
	local headers = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   204
		["Upgrade"] = "websocket";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   205
		["Connection"] = "Upgrade";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   206
		["Sec-WebSocket-Key"] = key;
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   207
		["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", ");
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   208
		["Sec-WebSocket-Version"] = "13";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   209
		["Sec-WebSocket-Extensions"] = ex.extensions;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   210
	}
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   211
	if ex.headers then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   212
		for k,v in pairs(ex.headers) do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   213
			headers[k] = v;
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
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   216
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   217
	local s = setmetatable({
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   218
		readbuffer = "";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   219
		databuffer = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   220
		conn = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   221
		close_code = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   222
		close_message = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   223
		close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   224
		readyState = 0;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   225
		protocol = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   226
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   227
		url = url;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   228
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   229
		onopen = listeners.onopen;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   230
		onclose = listeners.onclose;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   231
		onmessage = listeners.onmessage;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   232
		onerror = listeners.onerror;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   233
	}, websocket_metatable);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   234
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   235
	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
   236
	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
   237
		method = "GET";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   238
		headers = headers;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   239
		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
   240
		insecure = ex.insecure;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   241
	}, function(b, c, r, http_req)
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   242
		if c ~= 101
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   243
		   or r.headers["connection"]:lower() ~= "upgrade"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   244
		   or r.headers["upgrade"] ~= "websocket"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   245
		   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
   246
		   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
   247
		   then
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   248
			s.readyState = 3;
10116
b327f2870382 net.*: Remove tostring call from logging
Kim Alvefur <zash@zash.se>
parents: 8896
diff changeset
   249
			log("warn", "WebSocket connection to %s failed: %s", url, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   250
			if s.onerror then s:onerror("connecting-failed"); end
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   251
			return;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   252
		end
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   253
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   254
		s.protocol = r.headers["sec-websocket-protocol"];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   255
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   256
		-- 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
   257
		local conn = http_req.conn;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   258
		http_req.conn = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   259
		s.conn = conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   260
		websockets[conn] = s;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   261
		conn:setlistener(websocket_listeners);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   262
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   263
		log("debug", "WebSocket connected successfully to %s", url);
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   264
		s.readyState = 1;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   265
		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
   266
		websocket_listeners.onincoming(conn, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   267
	end);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   268
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   269
	return s;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   270
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   271
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   272
return {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   273
	connect = connect;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   274
};