summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/websocket-codec/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/websocket-codec/init.lua')
-rw-r--r--plugins/websocket-codec/init.lua261
1 files changed, 261 insertions, 0 deletions
diff --git a/plugins/websocket-codec/init.lua b/plugins/websocket-codec/init.lua
new file mode 100644
index 00000000000..8a7da7f9190
--- /dev/null
+++ b/plugins/websocket-codec/init.lua
@@ -0,0 +1,261 @@
+local exports = {}
+
+exports.name = "creationix/websocket-codec"
+exports.version = "1.0.7"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/websocket-codec.lua"
+exports.description = "A codec implementing websocket framing and helpers for handshakeing"
+exports.tags = {"http", "websocket", "codec"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local digest = require('openssl').digest.digest
+local base64 = require('openssl').base64
+local random = require('openssl').random
+local bit = require('bit')
+
+local band = bit.band
+local bor = bit.bor
+local bxor = bit.bxor
+local rshift = bit.rshift
+local lshift = bit.lshift
+local char = string.char
+local byte = string.byte
+local sub = string.sub
+local gmatch = string.gmatch
+local lower = string.lower
+local gsub = string.gsub
+local concat = table.concat
+
+local function applyMask(data, mask)
+ local bytes = {
+ [0] = byte(mask, 1),
+ [1] = byte(mask, 2),
+ [2] = byte(mask, 3),
+ [3] = byte(mask, 4)
+ }
+ local out = {}
+ for i = 1, #data do
+ out[i] = char(
+ bxor(byte(data, i), bytes[(i - 1) % 4])
+ )
+ end
+ return concat(out)
+end
+
+function exports.decode(chunk)
+ if #chunk < 2 then return end
+ local second = byte(chunk, 2)
+ local len = band(second, 0x7f)
+ local offset
+ if len == 126 then
+ if #chunk < 4 then return end
+ len = bor(
+ lshift(byte(chunk, 3), 8),
+ byte(chunk, 4))
+ offset = 4
+ elseif len == 127 then
+ if #chunk < 10 then return end
+ len = bor(
+ lshift(byte(chunk, 3), 56),
+ lshift(byte(chunk, 4), 48),
+ lshift(byte(chunk, 5), 40),
+ lshift(byte(chunk, 6), 32),
+ lshift(byte(chunk, 7), 24),
+ lshift(byte(chunk, 8), 16),
+ lshift(byte(chunk, 9), 8),
+ byte(chunk, 10))
+ offset = 10
+ else
+ offset = 2
+ end
+ local mask = band(second, 0x80) > 0
+ if mask then
+ offset = offset + 4
+ end
+ if #chunk < offset + len then return end
+
+ local first = byte(chunk, 1)
+ local payload = sub(chunk, offset + 1, offset + len)
+ assert(#payload == len, "Length mismatch")
+ if mask then
+ payload = applyMask(payload, sub(chunk, offset - 3, offset))
+ end
+ local extra = sub(chunk, offset + len + 1)
+ return {
+ fin = band(first, 0x80) > 0,
+ rsv1 = band(first, 0x40) > 0,
+ rsv2 = band(first, 0x20) > 0,
+ rsv3 = band(first, 0x10) > 0,
+ opcode = band(first, 0xf),
+ mask = mask,
+ len = len,
+ payload = payload
+ }, extra
+end
+
+function exports.encode(item)
+ if type(item) == "string" then
+ item = {
+ opcode = 2,
+ payload = item
+ }
+ end
+ local payload = item.payload
+ assert(type(payload) == "string", "payload must be string")
+ local len = #payload
+ local fin = item.fin
+ if fin == nil then fin = true end
+ local rsv1 = item.rsv1
+ local rsv2 = item.rsv2
+ local rsv3 = item.rsv3
+ local opcode = item.opcode or 2
+ local mask = item.mask
+ local chars = {
+ char(bor(
+ fin and 0x80 or 0,
+ rsv1 and 0x40 or 0,
+ rsv2 and 0x20 or 0,
+ rsv3 and 0x10 or 0,
+ opcode
+ )),
+ char(bor(
+ mask and 0x80 or 0,
+ len < 126 and len or (len < 0x10000) and 126 or 127
+ ))
+ }
+ if len >= 0x10000 then
+ chars[3] = char(band(rshift(len, 56), 0xff))
+ chars[4] = char(band(rshift(len, 48), 0xff))
+ chars[5] = char(band(rshift(len, 40), 0xff))
+ chars[6] = char(band(rshift(len, 32), 0xff))
+ chars[7] = char(band(rshift(len, 24), 0xff))
+ chars[8] = char(band(rshift(len, 16), 0xff))
+ chars[9] = char(band(rshift(len, 8), 0xff))
+ chars[10] = char(band(len, 0xff))
+ elseif len >= 126 then
+ chars[3] = char(band(rshift(len, 8), 0xff))
+ chars[4] = char(band(len, 0xff))
+ end
+ if mask then
+ local key = random(4)
+ return concat(chars) .. key .. applyMask(payload, key)
+ end
+ return concat(chars) .. payload
+end
+
+local websocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+function exports.acceptKey(key)
+ return gsub(base64(digest("sha1", key .. websocketGuid, true)), "\n", "")
+end
+local acceptKey = exports.acceptKey
+
+-- Make a client handshake connection
+function exports.handshake(options, request)
+ local key = gsub(base64(random(20)), "\n", "")
+ local host = options.host
+ local path = options.path or "/"
+ local protocol = options.protocol
+ local req = {
+ method = "GET",
+ path = path,
+ {"Connection", "Upgrade"},
+ {"Upgrade", "websocket"},
+ {"Sec-WebSocket-Version", "13"},
+ {"Sec-WebSocket-Key", key},
+ }
+ for i = 1, #options do
+ req[#req + 1] = options[i]
+ end
+ if host then
+ req[#req + 1] = {"Host", host}
+ end
+ if protocol then
+ req[#req + 1] = {"Sec-WebSocket-Protocol", protocol}
+ end
+ local res = request(req)
+ if not res then
+ return nil, "Missing response from server"
+ end
+ -- Parse the headers for quick reading
+ if res.code ~= 101 then
+ return nil, "response must be code 101"
+ end
+
+ local headers = {}
+ for i = 1, #res do
+ local name, value = unpack(res[i])
+ headers[lower(name)] = value
+ end
+
+ if not headers.connection or lower(headers.connection) ~= "upgrade" then
+ return nil, "Invalid or missing connection upgrade header in response"
+ end
+ if headers["sec-websocket-accept"] ~= acceptKey(key) then
+ return nil, "challenge key missing or mismatched"
+ end
+ if protocol and headers["sec-websocket-protocol"] ~= protocol then
+ return nil, "protocol missing or mistmatched"
+ end
+ return true
+end
+
+function exports.handleHandshake(head, protocol)
+
+ -- WebSocket connections must be GET requests
+ if not head.method == "GET" then return end
+
+ -- Parse the headers for quick reading
+ local headers = {}
+ for i = 1, #head do
+ local name, value = unpack(head[i])
+ headers[lower(name)] = value
+ end
+
+ -- Must have 'Upgrade: websocket' and 'Connection: Upgrade' headers
+ if not (headers.connection and headers.upgrade and
+ headers.connection:lower():find("upgrade", 1, true) and
+ headers.upgrade:lower():find("websocket", 1, true)) then return end
+
+ -- Make sure it's a new client speaking v13 of the protocol
+ if tonumber(headers["sec-websocket-version"]) < 13 then
+ return nil, "only websocket protocol v13 supported"
+ end
+
+ local key = headers["sec-websocket-key"]
+ if not key then
+ return nil, "websocket security key missing"
+ end
+
+ -- If the server wants a specified protocol, check for it.
+ if protocol then
+ local foundProtocol = false
+ local list = headers["sec-websocket-protocol"]
+ if list then
+ for item in gmatch(list, "[^, ]+") do
+ if item == protocol then
+ foundProtocol = true
+ break
+ end
+ end
+ end
+ if not foundProtocol then
+ return nil, "specified protocol missing in request"
+ end
+ end
+
+ local accept = acceptKey(key)
+
+ local res = {
+ code = 101,
+ {"Upgrade", "websocket"},
+ {"Connection", "Upgrade"},
+ {"Sec-WebSocket-Accept", accept},
+ }
+ if protocol then
+ res[#res + 1] = {"Sec-WebSocket-Protocol", protocol}
+ end
+
+ return res
+end
+return exports