Skip to content
Snippets Groups Projects
Commit 639fad65 authored by Rick Parrish's avatar Rick Parrish Committed by Rob Swindell
Browse files

Add support for binary frames to websocket service

parent 3e8b6f28
No related branches found
No related tags found
2 merge requests!463MRC mods by Codefenix (2024-10-20),!330Add support for binary frames to websocket service
...@@ -23,22 +23,29 @@ load("sbbsdefs.js"); ...@@ -23,22 +23,29 @@ load("sbbsdefs.js");
load("ftelnethelper.js"); load("ftelnethelper.js");
load("sha1.js"); load("sha1.js");
// Global constants // State flags
const WEBSOCKET_NEED_PACKET_START = 0; const WEBSOCKET_NEED_PACKET_START = 0;
const WEBSOCKET_NEED_PAYLOAD_LENGTH = 1; const WEBSOCKET_NEED_PAYLOAD_LENGTH = 1;
const WEBSOCKET_NEED_MASKING_KEY = 2; const WEBSOCKET_NEED_MASKING_KEY = 2;
const WEBSOCKET_DATA = 3; const WEBSOCKET_DATA = 3;
// Frame types
const WEBSOCKET_FRAME_UNKNOWN = 0;
const WEBSOCKET_FRAME_TEXT = 1;
const WEBSOCKET_FRAME_BINARY = 2;
// Global variables // Global variables
var FFrameMask = []; var FFrameMask = [];
var FFrameMasked = false; var FFrameMasked = false;
var FFrameOpCode = 0; var FFrameOpCode = 0;
var FFramePayloadLength = 0; var FFramePayloadLength = 0;
var FFramePayloadReceived = 0; var FFramePayloadReceived = 0;
var FFrameType = WEBSOCKET_FRAME_UNKNOWN;
var FServerSocket = null; var FServerSocket = null;
var FWebSocketDataQueue = ''; var FWebSocketDataQueue = '';
var FWebSocketHeader = {}; var FWebSocketHeader = {};
var FWebSocketState = WEBSOCKET_NEED_PACKET_START; var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
var FWebSocketSubProtocol = '';
var ipFile; var ipFile;
// Main line // Main line
...@@ -118,14 +125,20 @@ try { ...@@ -118,14 +125,20 @@ try {
// Check if the client sent anything // Check if the client sent anything
ClientData = GetFromWebSocketClient(); ClientData = GetFromWebSocketClient();
if (ClientData.length > 0) { if (ClientData === null) {
// null ClientData means the client socket is no longer connected, or a close frame was received
break;
} else if (ClientData.length > 0) {
SendToServer(ClientData); SendToServer(ClientData);
DoYield = false; DoYield = false;
} }
// Check if the server sent anything // Check if the server sent anything
ServerData = GetFromServer(); ServerData = GetFromServer();
if (ServerData.length > 0) { if (ServerData === null) {
// null ServerData means the server socket is no longer connected
break;
} else if (ServerData.length > 0) {
SendToWebSocketClient(ServerData); SendToWebSocketClient(ServerData);
DoYield = false; DoYield = false;
} }
...@@ -173,6 +186,8 @@ function CalculateWebSocketKey(InLine) { ...@@ -173,6 +186,8 @@ function CalculateWebSocketKey(InLine) {
} }
function GetFromServer() { function GetFromServer() {
if (!FServerSocket.is_connected) return null;
var Result = []; var Result = [];
var InStr = ''; var InStr = '';
...@@ -187,6 +202,8 @@ function GetFromServer() { ...@@ -187,6 +202,8 @@ function GetFromServer() {
} }
function GetFromWebSocketClient() { function GetFromWebSocketClient() {
if (!client.socket.is_connected) return null;
switch (FWebSocketHeader['Version']) { switch (FWebSocketHeader['Version']) {
case 0: return GetFromWebSocketClientDraft0(); case 0: return GetFromWebSocketClientDraft0();
case 7: case 7:
...@@ -252,20 +269,19 @@ function GetFromWebSocketClientVersion7() { ...@@ -252,20 +269,19 @@ function GetFromWebSocketClientVersion7() {
// Next byte will give us the opcode, and also tell is if the message is fragmented // Next byte will give us the opcode, and also tell is if the message is fragmented
FFrameOpCode = client.socket.recvBin(1) & 0x0F; FFrameOpCode = client.socket.recvBin(1) & 0x0F;
switch (FFrameOpCode) { switch (FFrameOpCode) {
case 0: // Continuation frame case 0: // Continuation frame, keep same opcode as previous frame
case 1: // Text frame
break; break;
case 1: // Text frame
case 2: // Binary frame case 2: // Binary frame
log(LOG_DEBUG, "Client sent a binary frame, which is unsupported"); FFrameType = FFrameOpCode;
client.socket.close(); break;
return [];
case 8: // Close frame case 8: // Close frame
log(LOG_DEBUG, "Client sent a close frame"); log(LOG_DEBUG, "Client sent a close frame");
// TODO Protocol says to respond with a close frame // TODO Protocol says to respond with a close frame
client.socket.close(); client.socket.close();
return []; return null;
case 9: // Ping frame case 9: // Ping frame
log(LOG_DEBUG, "Client setnt a ping frame"); log(LOG_DEBUG, "Client setnt a ping frame");
...@@ -277,9 +293,7 @@ function GetFromWebSocketClientVersion7() { ...@@ -277,9 +293,7 @@ function GetFromWebSocketClientVersion7() {
break; break;
default: // Reserved or unknown frame default: // Reserved or unknown frame
log(LOG_DEBUG, "Client sent a reserved/unknown frame: " + FFrameOpCode); throw new Error('Client sent a reserved/unknown frame: ' + FFrameOpCode);
client.socket.close();
return [];
} }
// If we get here, we want to move on to the next state // If we get here, we want to move on to the next state
...@@ -331,21 +345,31 @@ function GetFromWebSocketClientVersion7() { ...@@ -331,21 +345,31 @@ function GetFromWebSocketClientVersion7() {
InStr = FWebSocketDataQueue + InStr; InStr = FWebSocketDataQueue + InStr;
} }
for (var i = 0; i < InStr.length; i++) { if (FFrameType == WEBSOCKET_FRAME_TEXT) {
InByte = InStr.charCodeAt(i); for (var i = 0; i < InStr.length; i++) {
if (FFrameMasked) InByte ^= FFrameMask[FFramePayloadReceived++ % 4]; InByte = InStr.charCodeAt(i);
if (FFrameMasked) InByte ^= FFrameMask[FFramePayloadReceived++ % 4];
// Check if the byte needs to be UTF-8 decoded
if ((InByte & 0x80) === 0) { // Check if the byte needs to be UTF-8 decoded
if ((InByte & 0x80) === 0) {
Result += String.fromCharCode(InByte);
} else if ((InByte & 0xE0) === 0xC0) {
// Handle UTF-8 decode
InByte2 = InStr.charCodeAt(++i);
if (FFrameMasked) InByte2 ^= FFrameMask[FFramePayloadReceived++ % 4];
Result += String.fromCharCode(((InByte & 31) << 6) | (InByte2 & 63));
} else {
throw new Error('GetFromWebSocketClientVersion7 Byte out of range: ' + InByte);
}
}
} else if (FFrameType === WEBSOCKET_FRAME_BINARY) {
for (var i = 0; i < InStr.length; i++) {
InByte = InStr.charCodeAt(i);
if (FFrameMasked) InByte ^= FFrameMask[FFramePayloadReceived++ % 4];
Result += String.fromCharCode(InByte); Result += String.fromCharCode(InByte);
} else if ((InByte & 0xE0) === 0xC0) {
// Handle UTF-8 decode
InByte2 = InStr.charCodeAt(++i);
if (FFrameMasked) InByte2 ^= FFrameMask[FFramePayloadReceived++ % 4];
Result += String.fromCharCode(((InByte & 31) << 6) | (InByte2 & 63));
} else {
log(LOG_DEBUG, "GetFromWebSocketClientVersion7 Byte out of range: " + InByte);
} }
} else {
throw new Error('GetFromWebSocketClientVersion7 Invalid frame type: ' + FFrameType);
} }
// Finished the data packet, so reset variables // Finished the data packet, so reset variables
...@@ -383,13 +407,19 @@ function ParsePathHeader() { ...@@ -383,13 +407,19 @@ function ParsePathHeader() {
} }
function SendToServer(AStr) { function SendToServer(AStr) {
if (!FServerSocket.is_connected) return;
if (AStr.length == 0) return;
var bytesSent = FServerSocket.send(AStr); var bytesSent = FServerSocket.send(AStr);
if (bytesSent != AStr.length) { if (bytesSent != AStr.length) {
log(LOG_ERR, 'SendToServer only sent ' + bytesSent + ' of ' + AStr.length + ' bytes'); throw new Error('SendToServer only sent ' + bytesSent + ' of ' + AStr.length + ' bytes');
} }
} }
function SendToWebSocketClient(AData) { function SendToWebSocketClient(AData) {
if (!client.socket.is_connected) return;
if (AData.length == 0) return;
switch (FWebSocketHeader['Version']) { switch (FWebSocketHeader['Version']) {
case 0: case 0:
SendToWebSocketClientDraft0(AData); SendToWebSocketClientDraft0(AData);
...@@ -415,7 +445,7 @@ function SendToWebSocketClientDraft0(AData) { ...@@ -415,7 +445,7 @@ function SendToWebSocketClientDraft0(AData) {
client.socket.sendBin((AData[i] >> 6) | 192, 1); client.socket.sendBin((AData[i] >> 6) | 192, 1);
client.socket.sendBin((AData[i] & 63) | 128, 1); client.socket.sendBin((AData[i] & 63) | 128, 1);
} else { } else {
log(LOG_DEBUG, "SendToWebSocketClientDraft0 Byte out of range: " + AData[i]); throw new Error('SendToWebSocketClientDraft0 Byte out of range: ' + AData[i]);
} }
} }
...@@ -424,41 +454,46 @@ function SendToWebSocketClientDraft0(AData) { ...@@ -424,41 +454,46 @@ function SendToWebSocketClientDraft0(AData) {
} }
function SendToWebSocketClientVersion7(AData) { function SendToWebSocketClientVersion7(AData) {
if (AData.length > 0) { var ToSend = '';
var ToSend = '';
for (var i = 0; i < AData.length; i++) {
// Check if the byte needs to be UTF-8 encoded
if ((AData[i] & 0xFF) <= 127) {
ToSend += String.fromCharCode(AData[i]);
} else if (((AData[i] & 0xFF) >= 128) && ((AData[i] & 0xFF) <= 2047)) {
// Handle UTF-8 encode
ToSend += String.fromCharCode((AData[i] >> 6) | 192);
ToSend += String.fromCharCode((AData[i] & 63) | 128);
} else {
log(LOG_DEBUG, "SendToWebSocketClientVersion7 Byte out of range: " + AData[i]);
}
}
client.socket.sendBin(0x81, 1);
if (ToSend.length <= 125) {
client.socket.sendBin(ToSend.length, 1);
} else if (ToSend.length <= 65535) {
client.socket.sendBin(126, 1);
client.socket.sendBin(ToSend.length, 2);
} else {
// NOTE: client.socket.sendBin(ToSend.length, 8); didn't work, so this
// modification limits the send to 2^32 bytes (probably not an issue)
// Probably should look into a proper fix at some point though
client.socket.sendBin(127, 1);
client.socket.sendBin(0, 4);
client.socket.sendBin(ToSend.length, 4);
}
var bytesSent = client.socket.send(ToSend); if (FWebSocketSubProtocol === 'binary') {
if (bytesSent != ToSend.length) { for (var i = 0; i < AData.length; i++) {
log(LOG_ERR, 'SendToWebSocketClientVersion7 only sent ' + bytesSent + ' of ' + ToSend.length + ' bytes'); ToSend += String.fromCharCode(AData[i]);
}
client.socket.sendBin(0x82, 1);
} else {
for (var i = 0; i < AData.length; i++) {
// Check if the byte needs to be UTF-8 encoded
if ((AData[i] & 0xFF) <= 127) {
ToSend += String.fromCharCode(AData[i]);
} else if (((AData[i] & 0xFF) >= 128) && ((AData[i] & 0xFF) <= 2047)) {
// Handle UTF-8 encode
ToSend += String.fromCharCode((AData[i] >> 6) | 192);
ToSend += String.fromCharCode((AData[i] & 63) | 128);
} else {
throw new Error('SendToWebSocketClientVersion7 Byte out of range: ' + AData[i]);
}
} }
client.socket.sendBin(0x81, 1);
}
if (ToSend.length <= 125) {
client.socket.sendBin(ToSend.length, 1);
} else if (ToSend.length <= 65535) {
client.socket.sendBin(126, 1);
client.socket.sendBin(ToSend.length, 2);
} else {
// NOTE: client.socket.sendBin(ToSend.length, 8); didn't work, so this
// modification limits the send to 2^32 bytes (probably not an issue)
// Probably should look into a proper fix at some point though
client.socket.sendBin(127, 1);
client.socket.sendBin(0, 4);
client.socket.sendBin(ToSend.length, 4);
}
var bytesSent = client.socket.send(ToSend);
if (bytesSent != ToSend.length) {
throw new Error('SendToWebSocketClientVersion7 only sent ' + bytesSent + ' of ' + ToSend.length + ' bytes');
} }
} }
...@@ -623,7 +658,19 @@ function ShakeHandsVersion7() { ...@@ -623,7 +658,19 @@ function ShakeHandsVersion7() {
"Upgrade: websocket\r\n" + "Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" + "Connection: Upgrade\r\n" +
"Sec-WebSocket-Accept: " + Encoded + "\r\n"; "Sec-WebSocket-Accept: " + Encoded + "\r\n";
if ('SubProtocol' in FWebSocketHeader) Response += "Sec-WebSocket-Protocol: plain\r\n"; // Only sub-protocol we support if ('SubProtocol' in FWebSocketHeader) {
if (FWebSocketHeader['SubProtocol'].indexOf('binary') >= 0) {
FWebSocketSubProtocol = 'binary';
} else if (FWebSocketHeader['SubProtocol'].indexOf('plain') >= 0) {
FWebSocketSubProtocol = 'plain';
} else {
log(LOG_ERR, 'No supported SubProtocols found ("binary" and "plain" are only supported options');
return false;
}
log(LOG_DEBUG, 'Selecting "' + FWebSocketSubProtocol + '" SubProtocol');
Response += "Sec-WebSocket-Protocol: " + FWebSocketSubProtocol + "\r\n";
}
Response += "\r\n"; Response += "\r\n";
// Send the response and return // Send the response and return
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment