Skip to content
Snippets Groups Projects
Select Git revision
  • dd_msg_area_chooser_coloring_fix_and_separator_char_fix
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

websocket-proxy.js

Blame
  • websocket-proxy.js 14.55 KiB
    load('sha1.js');
    
    var WebSocketProxy = function(client) {
    
    	var WEBSOCKET_NEED_PACKET_START = 0;
    	var WEBSOCKET_NEED_PAYLOAD_LENGTH = 1;
    	var WEBSOCKET_NEED_MASKING_KEY = 2;
    	var WEBSOCKET_DATA = 3;
    
    	var FFrameMask = [];
    	var FFrameOpCode = 0;
    	var FFramePayloadLength = 0;
    	var FFramePayloadReceived = 0;
    	var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
    
    	var self = this;
    	this.headers = [];
    
    	var ClientDataBuffer = []; // From client
    	var ServerDataBuffer = []; // From server
    
    	function CalculateWebSocketKey(InLine) {
    		var Digits = '';
    		var Spaces = 0;
    		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 GetFromWebSocketClient() {
    		switch (self.headers['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.push(InByte);
    						} else if ((InByte > 191) && (InByte < 224)) {
    							// Handle UTF-8 decode
    							InByte2 = client.socket.recvBin(1);
    							Result.push(((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.push(
    								((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) {
    			// 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
    					FFrameMask = [];
    					FFrameOpCode = client.socket.recvBin(1);
    					FFramePayloadLength = 0;
    					FFramePayloadReceived = 0;
    					FWebSocketState = WEBSOCKET_NEED_PAYLOAD_LENGTH;
    					break;
    				case WEBSOCKET_NEED_PAYLOAD_LENGTH:
    					FFramePayloadLength = (client.socket.recvBin(1) & 0x7F);
    					if (FFramePayloadLength === 126) {
    						FFramePayloadLength = client.socket.recvBin(2);
    					} else if (FFramePayloadLength === 127) {
    						FFramePayloadLength = client.socket.recvBin(8);
    					}
    					FWebSocketState = WEBSOCKET_NEED_MASKING_KEY;
    					break;
    				case WEBSOCKET_NEED_MASKING_KEY:
    					InByte = client.socket.recvBin(4);
    					FFrameMask[0] = (InByte & 0xFF000000) >> 24;
    					FFrameMask[1] = (InByte & 0x00FF0000) >> 16;
    					FFrameMask[2] = (InByte & 0x0000FF00) >> 8;
    					FFrameMask[3] = InByte & 0x000000FF;
    					FWebSocketState = WEBSOCKET_DATA;
    					break;
    				case WEBSOCKET_DATA:
    					InByte = (
    						client.socket.recvBin(1)^FFrameMask[
    							FFramePayloadReceived++ % 4
    						]
    					);
    
    					// Check if the byte needs to be UTF-8 decoded
    					if ((InByte & 0x80) === 0) {
    						Result.push(InByte);
    					} else if ((InByte & 0xE0) === 0xC0) {
    						// Handle UTF-8 decode
    						InByte2 = (
    							client.socket.recvBin(1)^FFrameMask[
    								FFramePayloadReceived++ % 4
    							]
    						);
    						Result.push(((InByte & 31) << 6) | (InByte2 & 63));
    					} else {
    						log(LOG_NOTICE, 'Byte out of range: ' + InByte);
    					}
    
    					// Check if we've received the full payload
    					if (FFramePayloadReceived === FFramePayloadLength) {
    						FWebSocketState = WEBSOCKET_NEED_PACKET_START;
    					}
    					break;
    			}
    		}
    
    		return Result;
    	}
    
    	function SendToWebSocketClient(AData) {
    		switch (self.headers['Version']) {
    			case 0: 
    				SendToWebSocketClientDraft0(AData); 
    				break;
    			case 7: 
    			case 8: 
    			case 13:
    				SendToWebSocketClientVersion7(AData); 
    				break;
    		}
    	}
    
    	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 {
    				log(LOG_NOTICE, 'Byte out of range: ' + AData[i]);
    			}
    		}
    
    		// Send 0xFF to indicate the end of a data packet
    		client.socket.sendBin(0xFF, 1);
    	}
    
    	function SendToWebSocketClientVersion7(AData) {
    		if (AData.length > 0) {
    			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.push(AData[i]);
    				} else if (
    					((AData[i] & 0xFF) >= 128) && ((AData[i] & 0xFF) <= 2047)
    				) {
    					// Handle UTF-8 encode
    					ToSend.push((AData[i] >> 6) | 192);
    					ToSend.push((AData[i] & 63) | 128);
    				} else {
    					log(LOG_NOTICE, '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);
    			}
    			
    			for (var i = 0; i < ToSend.length; i++) {
    				client.socket.sendBin(ToSend[i] & 0xFF, 1);
    			}
    		}
    	}
    
    	function ShakeHands() {
    		self.headers['Version'] = 0;
    
    		try {
    			// Keep reading header data until we get all the data we want
    			while (true) {
    				// Read another line, abort if we don't get one in 5 seconds
    				var InLine = client.socket.recvline(1024, 5);
    				if (InLine === null) {
    					log(LOG_ERR,
    						'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 (self.headers['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.indexOf('Connection:') === 0) {
    					// Example: "Connection: Upgrade"
    					self.headers['Connection'] = InLine.replace(
    						/Connection:\s?/i,
    						''
    					);
    				} else if (InLine.indexOf('GET') === 0) {
    					// Example: "GET /demo HTTP/1.1"
    					var GET = InLine.split(' ');
    					self.headers['Path'] = GET[1];
    				} else if (InLine.indexOf('Host:') === 0) {
    					// Example: "Host: example.com"
    					self.headers['Host'] = InLine.replace(/Host:\s?/i, '');
    				} else if (InLine.indexOf('Origin:') === 0) {
    					// Example: "Origin: http://example.com"
    					self.headers['Origin'] = InLine.replace(/Origin:\s?/i, '');
    				} else if (InLine.indexOf('Sec-WebSocket-Key:') === 0) {
    					// Example: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
    					self.headers['Key'] = InLine.replace(
    						/Sec-WebSocket-Key:\s?/i,
    						''
    					);
    				} else if (InLine.indexOf('Sec-WebSocket-Key1:') === 0) {
    					// Example: "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5"
    					self.headers['Key1'] = CalculateWebSocketKey(
    						InLine.replace(/Sec-WebSocket-Key1:\s?/i, '')
    					);
    				} else if (InLine.indexOf('Sec-WebSocket-Key2:') === 0) {
    					// Example: "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00"
    					self.headers['Key2'] = CalculateWebSocketKey(
    						InLine.replace(/Sec-WebSocket-Key2:\s?/i, '')
    					);
    				} else if (InLine.indexOf('Sec-WebSocket-Origin:') === 0) {
    					// Example: "Sec-WebSocket-Origin: http://example.com"
    					self.headers['Origin'] = InLine.replace(
    						/Sec-WebSocket-Origin:\s?/i,
    						''
    					);
    				} else if (InLine.indexOf('Sec-WebSocket-Protocol:') === 0) {
    					// Example: "Sec-WebSocket-Protocol: sample"
    					self.headers['SubProtocol'] = InLine.replace(
    						/Sec-WebSocket-Protocol:\s?/i,
    						''
    					);
    				} else if (InLine.indexOf('Sec-WebSocket-Draft') === 0) {
    					// Example: "Sec-WebSocket-Draft: 2"
    					try {
    						self.headers['Version'] = parseInt(
    							InLine.replace(/Sec-WebSocket-Draft:\s?/i, '')
    						);
    					} catch (err) {
    						self.headers['Version'] = 0;
    					}
    				} else if (InLine.indexOf('Sec-WebSocket-Version') === 0) {
    					// Example: "Sec-WebSocket-Version: 8"
    					try {
    						self.headers['Version'] = parseInt(
    							InLine.replace(/Sec-WebSocket-Version:\s?/i, '')
    						);
    					} catch (err) {
    						self.headers['Version'] = 0;
    					}
    				} else if (InLine.indexOf('Upgrade:') === 0) {
    					// Example: "Upgrade: websocket"
    					self.headers['Upgrade'] = InLine.replace(/Upgrade:\s?/i,'');
    				} else if (InLine.indexOf('Cookie:') === 0) {
    				 	self.headers['Cookie'] = InLine.replace(/Cookie:\s?/i, '');
    				}
    			}
    		} catch (err) {
    			log(LOG_ERR, 'ShakeHands() error: ' + err.toString());
    		}
    		
    		return false;
    	}
    
    	function ShakeHandsDraft0() {
    		// Ensure we have all the data we need
    		if (('Key1' in self.headers) &&
    			('Key2' in self.headers) &&
    			('Host' in self.headers) &&
    			('Origin' in self.headers !== '') &&
    			('Path' in self.headers)
    		) {
    			// Combine Key1, Key2, and the last 8 bytes into a string that we
    			// will later hash
    			var ToHash = ''
    			ToHash += String.fromCharCode(
    				(self.headers['Key1'] & 0xFF000000) >> 24
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key1'] & 0x00FF0000) >> 16
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key1'] & 0x0000FF00) >> 8
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key1'] & 0x000000FF) >> 0
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key2'] & 0xFF000000) >> 24
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key2'] & 0x00FF0000) >> 16
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['Key2'] & 0x0000FF00) >> 8
    			);
    			ToHash += String.fromCharCode(
    				(self.headers['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: ' + self.headers['Origin'] +
    						   '\r\n' +
    						   'Sec-WebSocket-Location: ws://' +
    						   self.headers['Host'] +
    						   self.headers['Path'] +
    						   '\r\n';
    			if ('SubProtocol' in self.headers) {
    				Response +=
    					'Sec-WebSocket-Protocol: ' +
    					self.headers['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_ERR,
    				'Missing some piece of handshake data.  Here\'s what we have:'
    			);
    			for(var x in self.headers) { 
    				log(LOG_ERR, x + ' => ' + self.headers[x]); 
    			}
    			return false;
    		}
    	}
    
    	function ShakeHandsVersion7() {
    		// Ensure we have all the data we need
    		if (('Key' in self.headers) &&
    			('Host' in self.headers) &&
    			('Origin' in self.headers !== '') &&
    			('Path' in self.headers)
    		) {
    			var AcceptGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    
    			// Combine Key and GUID
    			var ToHash = self.headers['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 self.headers) {
    				// Only sub-protocol we support
    				Response += 'Sec-WebSocket-Protocol: plain\r\n';
    			}
    			Response += '\r\n';
    			
    			// Send the response and return
    			client.socket.send(Response);
    			return true;
    		} else {
    			log(LOG_ERR,
    				'Missing some piece of handshake data.  Here\'s what we have:'
    			);
    			for(var x in self.headers) { 
    				log(LOG_ERR, x + ' => ' + self.headers[x]); 
    			}
    			return false;
    		}
    	}
    
    	this.__defineGetter__(
    		'data_waiting',
    		function() {
    			return (ClientDataBuffer.length > 0);
    		}
    	);
    
    	this.send = function(data) {
    		if (typeof data === 'string') {
    			data = data.split('').map(
    				function (d) {
    					return ascii(d);
    				}
    			);
    		}
    		ServerDataBuffer = ServerDataBuffer.concat(data);
    	}
    
    	this.receive = function() {
    		var data = '';
    		while (ClientDataBuffer.length > 0) {
    			data += ascii(ClientDataBuffer.shift());
    		}
    		return data;
    	}
    
    	this.receiveArray = function(len) {
    		return ClientDataBuffer.splice(
    			0,
    			(typeof len === 'number' ? len : ClientDataBuffer.length)
    		);
    	}
    
    	this.cycle = function() {
    		ClientDataBuffer = ClientDataBuffer.concat(GetFromWebSocketClient());
    		SendToWebSocketClient(ServerDataBuffer.splice(0, 4096));
    	}
    
    	if(!ShakeHands()) throw 'ShakeHands() failed';
    
    }