Skip to content
Snippets Groups Projects
Commit f16bc7cd authored by Rick Parrish's avatar Rick Parrish
Browse files

Send websocket metadata back to client to troubleshoot garbled input

parent d232ecdc
No related branches found
No related tags found
1 merge request!458Fix #782 - websocketservice.js garbles input on Banana Pi running Armbian
// Synchronet Service for redirecting incoming WebSocket connections to the Telnet and/or RLogin server
// Mainly to be used in conjunction with fTelnet
// Command-line syntax:
// websocketservice.js [hostname] [port]
// Example configuration (in ctrl/services.ini)
//
// [WS]
// Port=1123
// Options=NO_HOST_LOOKUP
// Command=websocketservice.js
//
// [WSS]
// Port=11235
// Options=NO_HOST_LOOKUP|TLS
// Command=websocketservice.js
//include definitions for synchronet
load("nodedefs.js");
load("sockdefs.js");
load("sbbsdefs.js");
load("ftelnethelper.js");
load("sha1.js");
// State flags
const WEBSOCKET_NEED_PACKET_START = 0;
const WEBSOCKET_NEED_PAYLOAD_LENGTH = 1;
const WEBSOCKET_NEED_MASKING_KEY = 2;
const WEBSOCKET_DATA = 3;
// Frame types
const WEBSOCKET_FRAME_UNKNOWN = 0;
const WEBSOCKET_FRAME_TEXT = 1;
const WEBSOCKET_FRAME_BINARY = 2;
// Global variables
var FFrameMask = [];
var FFrameMasked = false;
var FFrameOpCode = 0;
var FFramePayloadLength = 0;
var FFramePayloadReceived = 0;
var FFrameType = WEBSOCKET_FRAME_UNKNOWN;
var FServerSocket = null;
var FWebSocketDataQueue = '';
var FWebSocketHeader = {};
var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
var FWebSocketSubProtocol = '';
var ipFile;
// Main line
try {
// Parse and respond to the WebSocket handshake request
if (ShakeHands()) {
if (UsingHAProxy() && FWebSocketHeader['X-Forwarded-For'] === undefined) {
throw new Error('BBS is using HAProxy, but no X-Forwarded-For header present.');
}
SendToWebSocketClient(StringToBytes("Redirecting to server...\r\n"));
// Default to localhost on the telnet port
var TargetHostname = GetTelnetInterface();
var TargetPort = GetTelnetPort();
// If fTelnet client sent a port on the querystring, try to use that
var Path = ParsePathHeader();
if (Path.query.Port) {
RequestedPort = parseInt(Path.query.Port);
// Confirm the user requested either the telnet or rlogin ports (we don't want to allow them to request any arbitrary port as that would be a gaping security hole)
if ((RequestedPort > 0) && (RequestedPort <= 65535) && ((RequestedPort == GetTelnetPort()) || (RequestedPort == GetRLoginPort()))) {
TargetPort = RequestedPort;
log(LOG_DEBUG, "Using user-requested port " + Path.query.Port);
if (TargetPort == GetRLoginPort()) TargetHostname = GetRLoginInterface();
} else {
log(LOG_NOTICE, "Client requested to connect to port " + Path.query.Port + ", which was denied");
}
} else {
// If SysOp gave an alternate hostname/port when installing the service, use that instead
for(var i in argv) {
var port = parseInt(argv[i], 10);
if (argv[i].search(/\D/) > -1 || port < 0 || port > 65535) {
TargetHostname = argv[i];
} else if (!isNaN(port)) {
TargetPort = port;
}
}
}
// Connect to the server
FServerSocket = new Socket();
log(LOG_DEBUG, "Connecting to " + TargetHostname + ":" + TargetPort);
if (FServerSocket.connect(TargetHostname, TargetPort)) {
ipFile = new File(system.temp_dir + 'sbbs-ws-' + FServerSocket.local_port + '.ip');
if (ipFile.open('w')) {
ipFile.write(client.ip_address);
ipFile.close();
}
// Variables we'll use in the loop
var DoYield = true;
var ClientData = [];
var ServerData = [];
if (UsingHAProxy()) {
var hapstr = '\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21';
if (client.socket.family === PF_INET) {
hapstr += '\x11\x00\x0C';
} else if (client.socket.family === PF_INET6) {
hapstr += '\x21\x00\x24';
}
hapstr += inet_pton(FWebSocketHeader['X-Forwarded-For']);
hapstr += inet_pton(FServerSocket.remote_ip_address);
hapstr += client.port.toString(16);
hapstr += TargetPort.toString(16);
FServerSocket.send(hapstr);
}
// Loop while we're still connected on both ends
while ((client.socket.is_connected) && (FServerSocket.is_connected)) {
// Should we yield or not (default true, but disable if we had line activity)
DoYield = true;
// Check if the client sent anything
ClientData = GetFromWebSocketClient();
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);
DoYield = false;
}
// Check if the server sent anything
ServerData = GetFromServer();
if (ServerData === null) {
// null ServerData means the server socket is no longer connected
break;
} else if (ServerData.length > 0) {
SendToWebSocketClient(ServerData);
DoYield = false;
}
// Yield if we didn't transfer any data
if (DoYield) {
mswait(10);
yield();
}
}
if (!client.socket.is_connected) log(LOG_DEBUG, 'Client socket no longer connected');
if (!FServerSocket.is_connected) log(LOG_DEBUG, 'Server socket no longer connected');
} else {
// FServerSocket.connect() failed
log(LOG_ERR, "Error " + FServerSocket.error + " connecting to server at " + TargetHostname + ":" + TargetPort);
SendToWebSocketClient(StringToBytes("ERROR: Unable to connect to server\r\n"));
mswait(2500);
}
} else {
log(LOG_DEBUG, "WebSocket handshake failed (likely a port scanner)");
}
} catch (err) {
log(LOG_ERR, err.toString());
} finally {
if (FServerSocket != null) {
FServerSocket.close();
}
if (ipFile instanceof File) ipFile.remove();
}
function CalculateWebSocketKey(InLine) {
var Digits = "";
var Spaces = 0;
// Loop through the line, looking for digits and spaces
for (var i = 0; i < InLine.length; i++) {
if (InLine.charAt(i) == " ") {
Spaces++;
} else if (!isNaN(InLine.charAt(i))) {
Digits += InLine.charAt(i);
}
}
return Digits / Spaces;
}
function GetFromServer() {
if (!FServerSocket.is_connected) return null;
var Result = [];
var InStr = '';
if (FServerSocket.data_waiting) {
InStr = FServerSocket.recv(4096);
for (var i = 0; i < InStr.length; i++) {
Result.push(InStr.charCodeAt(i));
}
}
return Result;
}
function GetFromWebSocketClient() {
if (!client.socket.is_connected) return null;
switch (FWebSocketHeader['Version']) {
case 0: return GetFromWebSocketClientDraft0();
case 7:
case 8:
case 13:
return GetFromWebSocketClientVersion7();
}
}
function GetFromWebSocketClientDraft0() {
var Result = '';
var InByte = 0;
var InByte2 = 0;
var InByte3 = 0;
while (client.socket.data_waiting) {
InByte = client.socket.recvBin(1);
// Check what the client packet state is
switch (FWebSocketState) {
case WEBSOCKET_NEED_PACKET_START:
// Check for 0x00 to indicate the start of a data packet
if (InByte === 0x00) {
FWebSocketState = WEBSOCKET_DATA;
}
break;
case WEBSOCKET_DATA:
// We're in a data packet, so check for 0xFF, which indicates the data packet is done
if (InByte == 0xFF) {
FWebSocketState = WEBSOCKET_NEED_PACKET_START;
} else {
// Check if the byte needs to be UTF-8 decoded
if (InByte < 128) {
Result += String.fromCharCode(InByte);
} else if ((InByte > 191) && (InByte < 224)) {
// Handle UTF-8 decode
InByte2 = client.socket.recvBin(1);
Result += String.fromCharCode(((InByte & 31) << 6) | (InByte2 & 63));
} else {
// Handle UTF-8 decode (should never need this, but included anyway)
InByte2 = client.socket.recvBin(1);
InByte3 = client.socket.recvBin(1);
Result += String.fromCharCode(((InByte & 15) << 12) | ((InByte2 & 63) << 6) | (InByte3 & 63));
}
}
break;
}
}
return Result;
}
function GetFromWebSocketClientVersion7() {
var Result = '';
var InByte = 0;
var InByte2 = 0;
var InByte3 = 0;
while (client.socket.data_waiting && (Result.length <= 4096)) {
// Check what the client packet state is
switch (FWebSocketState) {
case WEBSOCKET_NEED_PACKET_START:
// Next byte will give us the opcode, and also tell is if the message is fragmented
FFrameOpCode = client.socket.recvBin(1) & 0x0F;
SendToWebSocketClient_RickDebug('\r\nFWebSocketState=WEBSOCKET_NEED_PACKET_START, FFrameOpCode=' + FFrameOpCode + '\r\n');
switch (FFrameOpCode) {
case 0: // Continuation frame, keep same opcode as previous frame
break;
case 1: // Text frame
case 2: // Binary frame
FFrameType = FFrameOpCode;
break;
case 8: // Close frame
log(LOG_DEBUG, "Client sent a close frame");
// TODO Protocol says to respond with a close frame
client.socket.close();
return null;
case 9: // Ping frame
log(LOG_DEBUG, "Client setnt a ping frame");
// TODO Protocol says to respond with a pong frame
break;
case 10: // Pong frame
log(LOG_DEBUG, "Client sent a pong frame");
break;
default: // Reserved or unknown frame
throw new Error('Client sent a reserved/unknown frame: ' + FFrameOpCode);
}
// If we get here, we want to move on to the next state
FFrameMask = [];
FFrameMasked = false;
FFramePayloadLength = 0;
FFramePayloadReceived = 0;
FWebSocketState = WEBSOCKET_NEED_PAYLOAD_LENGTH;
break;
case WEBSOCKET_NEED_PAYLOAD_LENGTH:
InByte = client.socket.recvBin(1);
FFrameMasked = ((InByte & 0x80) == 128);
FFramePayloadLength = (InByte & 0x7F);
if (FFramePayloadLength === 126) {
FFramePayloadLength = client.socket.recvBin(2);
} else if (FFramePayloadLength === 127) {
FFramePayloadLength = client.socket.recvBin(8);
}
SendToWebSocketClient_RickDebug('\r\nFWebSocketState=WEBSOCKET_NEED_PAYLOAD_LENGTH, InByte=' + InByte + ', FFrameMasked = ' + FFrameMasked + ', FFramePayloadLength = ' + FFramePayloadLength + '\r\n');
if (FFrameMasked) {
FWebSocketState = WEBSOCKET_NEED_MASKING_KEY;
} else {
FWebSocketState = (FFramePayloadLength > 0 ? WEBSOCKET_DATA : WEBSOCKET_NEED_PACKET_START); // NB: Might not be any data to read, so check for payload length before setting state
}
break;
case WEBSOCKET_NEED_MASKING_KEY:
InByte = client.socket.recvBin(4);
FFrameMask[0] = (InByte >> 24) & 0xFF; // (InByte & 0xFF000000) >> 24;
FFrameMask[1] = (InByte >> 16) & 0xFF; // (InByte & 0x00FF0000) >> 16;
FFrameMask[2] = (InByte >> 8) & 0xFF; // (InByte & 0x0000FF00) >> 8;
FFrameMask[3] = (InByte >> 0) & 0xFF; // InByte & 0x000000FF;
SendToWebSocketClient_RickDebug('\r\nFWebSocketState=WEBSOCKET_NEED_MASKING_KEY, InByte=' + InByte + ', FFrameMask = ' + FFrameMask[0] + ' ' + FFrameMask[1] + ' ' + FFrameMask[2] + ' ' + FFrameMask[3] + '\r\n');
FWebSocketState = (FFramePayloadLength > 0 ? WEBSOCKET_DATA : WEBSOCKET_NEED_PACKET_START); // NB: Might not be any data to read, so check for payload length before setting state
break;
case WEBSOCKET_DATA:
var BytesToRead = FFramePayloadLength - FWebSocketDataQueue.length;
var InStr = client.socket.recv(BytesToRead);
SendToWebSocketClient_RickDebug('\r\nFWebSocketState=WEBSOCKET_DATA, InStr=' + StringToBytes(InStr).join(' ') + '\r\n');
if (InStr) {
if (InStr.length != BytesToRead) {
// Didn't get the whole websocket data packet this read, so queue up the latest read, then return the data we have so far
log(LOG_DEBUG, 'Asked for ' + BytesToRead + ' but got ' + InStr.length + ', queuing for a subsequent read');
FWebSocketDataQueue += InStr;
return Result;
}
// Prepend the queued text, if we have any
if (FWebSocketDataQueue.length > 0) {
InStr = FWebSocketDataQueue + InStr;
}
var tempBytes = [];
if (FFrameType == WEBSOCKET_FRAME_TEXT) {
for (var i = 0; i < InStr.length; i++) {
InByte = InStr.charCodeAt(i);
if (FFrameMasked) InByte ^= FFrameMask[FFramePayloadReceived++ % 4];
tempBytes.push(InByte);
// 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];
tempBytes.push(InByte2);
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];
tempBytes.push(InByte);
Result += String.fromCharCode(InByte);
}
} else {
throw new Error('GetFromWebSocketClientVersion7 Invalid frame type: ' + FFrameType);
}
SendToWebSocketClient_RickDebug('\r\n tempBytes = ' + tempBytes.join(' ') + '\r\n');
// Finished the data packet, so reset variables
FWebSocketDataQueue = '';
FWebSocketState = WEBSOCKET_NEED_PACKET_START;
}
break;
}
}
return Result;
}
function ParsePathHeader() {
var Result = {};
Result.query = {};
var Path = FWebSocketHeader['Path'];
if (Path) {
if (Path.indexOf('?') === -1) {
// No querystring
Result.path_info = Path;
} else {
// Have querystring, so parse it out
Result.path_info = Path.split('?')[0];
var KeyValues = Path.split('?')[1].split('&');
for (var i = 0; i < KeyValues.length; i++) {
var KeyValue = KeyValues[i].split('=');
Result.query[KeyValue[0]] = KeyValue[1];
}
}
}
return Result;
}
function SendToServer(AStr) {
if (!FServerSocket.is_connected) return;
if (AStr.length == 0) return;
var bytesSent = FServerSocket.send(AStr);
if (bytesSent != AStr.length) {
throw new Error('SendToServer only sent ' + bytesSent + ' of ' + AStr.length + ' bytes');
}
}
function SendToWebSocketClient(AData) {
if (!client.socket.is_connected) return;
if (AData.length == 0) return;
switch (FWebSocketHeader['Version']) {
case 0:
SendToWebSocketClientDraft0(AData);
break;
case 7:
case 8:
case 13:
SendToWebSocketClientVersion7(AData);
break;
}
}
function SendToWebSocketClient_RickDebug(AStr) {
if (!client.socket.is_connected) return;
if (AStr.length == 0) return;
SendToWebSocketClient(StringToBytes(AStr));
}
function SendToWebSocketClientDraft0(AData) {
// Send 0x00 to indicate the start of a data packet
client.socket.sendBin(0x00, 1);
for (var i = 0; i < AData.length; i++) {
// Check if the byte needs to be UTF-8 encoded
if ((AData[i] & 0xFF) <= 127) {
client.socket.sendBin(AData[i], 1);
} else if ((AData[i] & 0xFF) <= 2047) {
// Handle UTF-8 encode
client.socket.sendBin((AData[i] >> 6) | 192, 1);
client.socket.sendBin((AData[i] & 63) | 128, 1);
} else {
throw new Error('SendToWebSocketClientDraft0 Byte out of range: ' + AData[i]);
}
}
// Send 0xFF to indicate the end of a data packet
client.socket.sendBin(0xFF, 1);
}
function SendToWebSocketClientVersion7(AData) {
var ToSend = '';
if (FWebSocketSubProtocol === 'binary') {
for (var i = 0; i < AData.length; i++) {
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');
}
}
function ShakeHands() {
FWebSocketHeader['Version'] = 0;
try {
// Keep reading header data until we get all the data we want
while (true) {
// Read another line, and abort if we don't get one within 5 seconds
var InLine = client.socket.recvline(512, 5);
if (InLine === null) {
log(LOG_DEBUG, "Timeout exceeded while waiting for complete handshake");
return false;
}
log(LOG_DEBUG, "Handshake Line: " + InLine);
// Check for blank line (indicates we have most of the header, and only the last 8 bytes remain
if (InLine === "") {
switch (FWebSocketHeader['Version']) {
case 0:
return ShakeHandsDraft0();
case 7:
case 8:
case 13:
return ShakeHandsVersion7();
default:
// TODO If this version does not
// match a version understood by the server, the server MUST
// abort the websocket handshake described in this section and
// instead send an appropriate HTTP error code (such as 426
// Upgrade Required), and a |Sec-WebSocket-Version| header
// indicating the version(s) the server is capable of
// understanding.
break;
}
break;
} else if (InLine.search(/^Connection:/i) === 0) {
// Example: "Connection: Upgrade"
FWebSocketHeader['Connection'] = InLine.replace(/Connection:\s?/i, "");
} else if (InLine.search(/^GET/i) === 0) {
// Example: "GET /demo HTTP/1.1"
var GET = InLine.split(" ");
FWebSocketHeader['Path'] = GET[1];
} else if (InLine.search(/^Host:/i) === 0) {
// Example: "Host: example.com"
FWebSocketHeader['Host'] = InLine.replace(/Host:\s?/i, "");
} else if (InLine.search(/^Origin:/i) === 0) {
// Example: "Origin: http://example.com"
FWebSocketHeader['Origin'] = InLine.replace(/Origin:\s?/i, "");
} else if (InLine.search(/^Sec-WebSocket-Key:/i) === 0) {
// Example: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
FWebSocketHeader['Key'] = InLine.replace(/Sec-WebSocket-Key:\s?/i, "");
} else if (InLine.search(/^Sec-WebSocket-Key1:/i) === 0) {
// Example: "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5"
FWebSocketHeader['Key1'] = CalculateWebSocketKey(InLine.replace(/Sec-WebSocket-Key1:\s?/i, ""));
} else if (InLine.search(/^Sec-WebSocket-Key2:/i) === 0) {
// Example: "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00"
FWebSocketHeader['Key2'] = CalculateWebSocketKey(InLine.replace(/Sec-WebSocket-Key2:\s?/i, ""));
} else if (InLine.search(/^Sec-WebSocket-Origin:/i) === 0) {
// Example: "Sec-WebSocket-Origin: http://example.com"
FWebSocketHeader['Origin'] = InLine.replace(/Sec-WebSocket-Origin:\s?/i, "");
} else if (InLine.search(/^Sec-WebSocket-Protocol:/i) === 0) {
// Example: "Sec-WebSocket-Protocol: sample"
FWebSocketHeader['SubProtocol'] = InLine.replace(/Sec-WebSocket-Protocol:\s?/i, "");
} else if (InLine.search(/^Sec-WebSocket-Draft/i) === 0) {
// Example: "Sec-WebSocket-Draft: 2"
try {
FWebSocketHeader['Version'] = parseInt(InLine.replace(/Sec-WebSocket-Draft:\s?/i, ""));
} catch (err) {
FWebSocketHeader['Version'] = 0;
}
} else if (InLine.search(/^Sec-WebSocket-Version/i) === 0) {
// Example: "Sec-WebSocket-Version: 8"
try {
FWebSocketHeader['Version'] = parseInt(InLine.replace(/Sec-WebSocket-Version:\s?/i, ""));
} catch (err) {
FWebSocketHeader['Version'] = 0;
}
} else if (InLine.search(/^Upgrade:/i) === 0) {
// Example: "Upgrade: websocket"
FWebSocketHeader['Upgrade'] = InLine.replace(/Upgrade:\s?/i, "");
} else if (InLine.search(/^X-Forwarded-For/i) === 0) {
FWebSocketHeader['X-Forwarded-For'] = InLine.replace(/X-Forwarded-For:\s?/i, "");
}
}
} catch (err) {
log(LOG_DEBUG, "ShakeHands() error: " + err.toString());
}
return false;
}
function ShakeHandsDraft0() {
// Ensure we have all the data we need
if (('Key1' in FWebSocketHeader) && ('Key2' in FWebSocketHeader) && ('Host' in FWebSocketHeader) && ('Origin' in FWebSocketHeader !== "") && ('Path' in FWebSocketHeader)) {
// Combine Key1, Key2, and the last 8 bytes into a string that we will later hash
var ToHash = ""
ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0xFF000000) >> 24);
ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x00FF0000) >> 16);
ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x0000FF00) >> 8);
ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x000000FF) >> 0);
ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0xFF000000) >> 24);
ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x00FF0000) >> 16);
ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x0000FF00) >> 8);
ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x000000FF) >> 0);
for (var i = 0; i < 8; i++) {
ToHash += String.fromCharCode(client.socket.recvBin(1));
}
// Hash the string
var Hashed = md5_calc(ToHash, true);
// Setup the handshake response
var Response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Origin: " + FWebSocketHeader['Origin'] + "\r\n" +
"Sec-WebSocket-Location: ws://" + FWebSocketHeader['Host'] + FWebSocketHeader['Path'] + "\r\n";
if ('SubProtocol' in FWebSocketHeader) Response += "Sec-WebSocket-Protocol: " + FWebSocketHeader['SubProtocol'] + "\r\n";
Response += "\r\n";
// Loop through the hash string (which is hex encoded) and append the individual bytes to the response
for (var i = 0; i < Hashed.length; i += 2) {
Response += String.fromCharCode(parseInt(Hashed.charAt(i) + Hashed.charAt(i + 1), 16));
}
// Send the response and return
client.socket.send(Response);
return true;
} else {
// We're missing some pice of data, log what we do have
log(LOG_DEBUG, "Missing some piece of handshake data. Here's what we have:");
for(var x in FWebSocketHeader) {
log(LOG_DEBUG, x + " => " + FWebSocketHeader[x]);
}
return false;
}
}
function ShakeHandsVersion7() {
// Ensure we have all the data we need
if (('Key' in FWebSocketHeader) && ('Host' in FWebSocketHeader) && ('Origin' in FWebSocketHeader !== "") && ('Path' in FWebSocketHeader)) {
var AcceptGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// Combine Key and GUID
var ToHash = FWebSocketHeader['Key'] + AcceptGUID;
// Hash the string
var Hashed = Sha1.hash(ToHash, false);
// Encode the hash
var ToEncode = '';
for (var i = 0; i <= 38; i += 2) {
ToEncode += String.fromCharCode(parseInt(Hashed.substr(i, 2), 16));
}
var Encoded = base64_encode(ToEncode);
// Setup the handshake response
var Response = "HTTP/1.1 101 Switching Protocols\r\n" +
"Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Accept: " + Encoded + "\r\n";
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";
// Send the response and return
client.socket.send(Response);
return true;
} else {
// We're missing some pice of data, log what we do have
log(LOG_DEBUG, "Missing some piece of handshake data. Here's what we have:");
for(var x in FWebSocketHeader) {
log(LOG_DEBUG, x + " => " + FWebSocketHeader[x]);
}
return false;
}
}
function inet_pton (a) {
// http://kevin.vanzonneveld.net
// + original by: Theriault
// * example 1: inet_pton('::');
// * returns 1: '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' (binary)
// * example 2: inet_pton('127.0.0.1');
// * returns 2: '\x7F\x00\x00\x01' (binary)
var r, m, x, i, j, f = String.fromCharCode;
m = a.match(/^(?:\d{1,3}(?:\.|$)){4}/); // IPv4
if (m) {
m = m[0].split('.');
m = f(m[0]) + f(m[1]) + f(m[2]) + f(m[3]);
// Return if 4 bytes, otherwise false.
return m.length === 4 ? m : false;
}
r = /^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/;
m = a.match(r); // IPv6
if (m) {
// Translate each hexadecimal value.
for (j = 1; j < 4; j++) {
// Indice 2 is :: and if no length, continue.
if (j === 2 || m[j].length === 0) {
continue;
}
m[j] = m[j].split(':');
for (i = 0; i < m[j].length; i++) {
m[j][i] = parseInt(m[j][i], 16);
// Would be NaN if it was blank, return false.
if (isNaN(m[j][i])) {
return false; // Invalid IP.
}
m[j][i] = f(m[j][i] >> 8) + f(m[j][i] & 0xFF);
}
m[j] = m[j].join('');
}
x = m[1].length + m[3].length;
if (x === 16) {
return m[1] + m[3];
} else if (x < 16 && m[2].length > 0) {
return m[1] + (new Array(16 - x + 1)).join('\x00') + m[3];
}
}
return false; // Invalid IP.
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment