forked from cory/tildefriends
		
	Now with more WebSockets.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3197 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		
							
								
								
									
										152
									
								
								core/client.js
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								core/client.js
									
									
									
									
									
								
							| @@ -108,83 +108,58 @@ function split(container, children) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function receive() { | ||||
| 	$.ajax({ | ||||
| 		url: url() + "/receive?sessionId=" + gSessionId, | ||||
| 			method: "POST", | ||||
| 			data: gHaveIndex.toString(), | ||||
| 			dataType: "json", | ||||
| 	}).then(function(data) { | ||||
| 		for (var i in data.lines) { | ||||
| 			var line = data.lines[i]; | ||||
| function receive(data) { | ||||
| 	for (var i in data.lines) { | ||||
| 		var line = data.lines[i]; | ||||
|  | ||||
| 			var target = document.getElementsByClassName("terminal")[0].id; | ||||
| 			if (line && line.terminal) { | ||||
| 				if (document.getElementById("terminal_" + line.terminal)) { | ||||
| 					target = "terminal_" + line.terminal; | ||||
| 				} | ||||
| 				line = line.value; | ||||
| 		var target = document.getElementsByClassName("terminal")[0].id; | ||||
| 		if (line && line.terminal) { | ||||
| 			if (document.getElementById("terminal_" + line.terminal)) { | ||||
| 				target = "terminal_" + line.terminal; | ||||
| 			} | ||||
| 			if (line && line.action == "ping") { | ||||
| 				// PONG | ||||
| 			} else if (line && line.action == "session") { | ||||
| 				gSessionId = line.session.sessionId; | ||||
| 				gCredentials = line.session.credentials; | ||||
| 				updateLogin(); | ||||
| 			} else if (line && line[0] && line[0].action == "ready") { | ||||
| 				if (window.location.hash) { | ||||
| 					send({event: "hashChange", hash: window.location.hash}); | ||||
| 				} | ||||
| 			} else if (line && line[0] && line[0].action == "notify") { | ||||
| 				new Notification(line[0].title, line[0].options); | ||||
| 			} else if (line && line[0] && line[0].action == "title") { | ||||
| 				window.document.title = line[0].value; | ||||
| 			} else if (line && line[0] && line[0].action == "prompt") { | ||||
| 				var prompt = document.getElementById("prompt"); | ||||
| 				while (prompt.firstChild) { | ||||
| 					prompt.removeChild(prompt.firstChild); | ||||
| 				} | ||||
| 				prompt.appendChild(document.createTextNode(line[0].value)); | ||||
| 			} else if (line && line[0] && line[0].action == "password") { | ||||
| 				var prompt = document.getElementById("input"); | ||||
| 				prompt.setAttribute("type", line[0].value ? "password" : "text"); | ||||
| 			} else if (line && line[0] && line[0].action == "hash") { | ||||
| 				window.location.hash = line[0].value; | ||||
| 			} else if (line && line[0] && line[0].action == "update") { | ||||
| 				document.getElementById("update").setAttribute("Style", "display: inline"); | ||||
| 			} else if (line && line[0] && line[0].action == "split") { | ||||
| 				split(document.getElementById("terminals"), line[0].options); | ||||
| 			} else if (line && line[0] && line[0].action == "postMessageToIframe") { | ||||
| 				var iframe = document.getElementById("iframe_" + line[0].name); | ||||
| 				if (iframe) { | ||||
| 					iframe.contentWindow.postMessage(line[0].message, "*"); | ||||
| 				} | ||||
| 			} else { | ||||
| 				print(document.getElementById(target), line); | ||||
| 			line = line.value; | ||||
| 		} | ||||
| 		if (line && line.action == "ping") { | ||||
| 			gSocket.send(JSON.stringify({action: "pong"})); | ||||
| 		} else if (line && line.action == "session") { | ||||
| 			gSessionId = line.session.sessionId; | ||||
| 			gCredentials = line.session.credentials; | ||||
| 			updateLogin(); | ||||
| 		} else if (line && line[0] && line[0].action == "ready") { | ||||
| 			if (window.location.hash) { | ||||
| 				send({event: "hashChange", hash: window.location.hash}); | ||||
| 			} | ||||
| 		} else if (line && line[0] && line[0].action == "notify") { | ||||
| 			new Notification(line[0].title, line[0].options); | ||||
| 		} else if (line && line[0] && line[0].action == "title") { | ||||
| 			window.document.title = line[0].value; | ||||
| 		} else if (line && line[0] && line[0].action == "prompt") { | ||||
| 			var prompt = document.getElementById("prompt"); | ||||
| 			while (prompt.firstChild) { | ||||
| 				prompt.removeChild(prompt.firstChild); | ||||
| 			} | ||||
| 			prompt.appendChild(document.createTextNode(line[0].value)); | ||||
| 		} else if (line && line[0] && line[0].action == "password") { | ||||
| 			var prompt = document.getElementById("input"); | ||||
| 			prompt.setAttribute("type", line[0].value ? "password" : "text"); | ||||
| 		} else if (line && line[0] && line[0].action == "hash") { | ||||
| 			window.location.hash = line[0].value; | ||||
| 		} else if (line && line[0] && line[0].action == "update") { | ||||
| 			document.getElementById("update").setAttribute("Style", "display: inline"); | ||||
| 		} else if (line && line[0] && line[0].action == "split") { | ||||
| 			split(document.getElementById("terminals"), line[0].options); | ||||
| 		} else if (line && line[0] && line[0].action == "postMessageToIframe") { | ||||
| 			var iframe = document.getElementById("iframe_" + line[0].name); | ||||
| 			if (iframe) { | ||||
| 				iframe.contentWindow.postMessage(line[0].message, "*"); | ||||
| 			} | ||||
| 		} | ||||
| 		if ("index" in data) { | ||||
| 			gHaveIndex = data.index; | ||||
| 		} | ||||
| 		receive(); | ||||
| 		if (gErrorCount) { | ||||
| 			document.getElementById("status").setAttribute("style", "display: none"); | ||||
| 		} | ||||
| 		gErrorCount = 0; | ||||
| 	}).fail(function(xhr, message, error) { | ||||
| 		var node = document.getElementById("status"); | ||||
| 		while (node.firstChild) { | ||||
| 			node.removeChild(node.firstChild); | ||||
| 		} | ||||
| 		node.appendChild(document.createTextNode("ERROR: " + JSON.stringify([message, error]))); | ||||
| 		node.setAttribute("style", "display: inline; color: #dc322f"); | ||||
| 		if (gErrorCount < 60) { | ||||
| 			setTimeout(receive, 1000); | ||||
| 		} else { | ||||
| 			setTimeout(receive, 60 * 1000); | ||||
| 			print(document.getElementById(target), line); | ||||
| 		} | ||||
| 		gErrorCount++; | ||||
| 	}); | ||||
| 	} | ||||
| 	if ("index" in data) { | ||||
| 		gHaveIndex = data.index; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function autoNewLine(terminal) { | ||||
| @@ -282,19 +257,16 @@ function send(command) { | ||||
| 		value = prefix + $("#input").val(); | ||||
| 		$("#input").val(""); | ||||
| 	} | ||||
| 	$.ajax({ | ||||
| 		url: url() + "/send?sessionId=" + gSessionId, | ||||
| 			method: "POST", | ||||
| 			data: JSON.stringify(value), | ||||
| 			dataType: "text", | ||||
| 	}).fail(function(xhr, status, error) { | ||||
| 	try { | ||||
| 		gSocket.send(JSON.stringify({action: "command", command: value})); | ||||
| 	} catch (error) { | ||||
| 		var node = document.getElementById("status"); | ||||
| 		while (node.firstChild) { | ||||
| 			node.removeChild(node.firstChild); | ||||
| 		} | ||||
| 		node.appendChild(document.createTextNode("Send failed: " + JSON.stringify([status, error]))); | ||||
| 		node.appendChild(document.createTextNode("Send failed: " + error)); | ||||
| 		node.setAttribute("style", "display: inline; color: #dc322f"); | ||||
| 	}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function updateLogin() { | ||||
| @@ -437,6 +409,8 @@ function onMessage(event) { | ||||
| 	send({event: "onWindowMessage", message: event.data}); | ||||
| } | ||||
|  | ||||
| var gSocket; | ||||
|  | ||||
| $(document).ready(function() { | ||||
| 	if (Notification) { | ||||
| 		Notification.requestPermission(); | ||||
| @@ -448,10 +422,18 @@ $(document).ready(function() { | ||||
| 	window.addEventListener("blur", blur); | ||||
| 	window.addEventListener("message", onMessage, false); | ||||
| 	enableDragDrop(); | ||||
| }); | ||||
|  | ||||
| $(window).load(function() { | ||||
| 	setTimeout(function() { | ||||
| 		receive(); | ||||
| 	}, 0); | ||||
| 	gSocket = new WebSocket("ws://" | ||||
| 		+ window.location.hostname | ||||
| 		+ (window.location.port.length ? ":" + window.location.port : "") | ||||
| 		+ "/terminal/socket"); | ||||
| 	gSocket.onopen = function() { | ||||
| 		gSocket.send(JSON.stringify({ | ||||
| 			action: "hello", | ||||
| 			path: window.location.pathname, | ||||
| 		})); | ||||
| 	} | ||||
| 	gSocket.onmessage = function(event) { | ||||
| 		receive(JSON.parse(event.data)); | ||||
| 	} | ||||
| }); | ||||
|   | ||||
							
								
								
									
										27
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								core/core.js
									
									
									
									
									
								
							| @@ -146,27 +146,6 @@ function getUsers(packageOwner, packageName) { | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| function ping() { | ||||
| 	var process = this; | ||||
| 	var now = Date.now(); | ||||
| 	var again = true; | ||||
| 	if (now - process.lastActive < process.timeout) { | ||||
| 		// Active. | ||||
| 	} else if (process.lastPing > process.lastActive) { | ||||
| 		// We lost them. | ||||
| 		process.task.kill(); | ||||
| 		again = false; | ||||
| 	} else { | ||||
| 		// Idle.  Ping them. | ||||
| 		process.terminal.ping(); | ||||
| 		process.lastPing = now; | ||||
| 	} | ||||
|  | ||||
| 	if (again) { | ||||
| 		setTimeout(ping.bind(process), process.timeout); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function postMessageInternal(from, to, message) { | ||||
| 	return invoke(to.eventHandlers['onMessage'], [getUser(from, from), message]); | ||||
| } | ||||
| @@ -273,9 +252,6 @@ function getProcess(packageOwner, packageName, key, options) { | ||||
| 				process.connections.length = 0; | ||||
| 				delete gProcesses[key]; | ||||
| 			}; | ||||
| 			if (process.timeout > 0) { | ||||
| 				setTimeout(ping.bind(process), process.timeout); | ||||
| 			} | ||||
| 			var imports = { | ||||
| 				'core': { | ||||
| 					'broadcast': broadcast.bind(process), | ||||
| @@ -352,8 +328,6 @@ function getProcess(packageOwner, packageName, key, options) { | ||||
| 				} | ||||
| 			} | ||||
| 			if (manifest && manifest.require) { | ||||
| 				print("manifest.require = ", manifest.require); | ||||
| 				print(manifest.require.map(packageNameToPath.bind(process))); | ||||
| 				process.task.addPath(manifest.require.map(packageNameToPath.bind(process))); | ||||
| 			} | ||||
| 			process.task.setImports(imports); | ||||
| @@ -439,3 +413,4 @@ httpd.all("", function(request, response) { | ||||
| 		return response.end(data); | ||||
| 	} | ||||
| }); | ||||
| httpd.registerSocketHandler("/terminal/socket", terminal.socket); | ||||
|   | ||||
							
								
								
									
										161
									
								
								core/httpd.js
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								core/httpd.js
									
									
									
									
									
								
							| @@ -1,4 +1,7 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var gHandlers = []; | ||||
| var gSocketHandlers = []; | ||||
|  | ||||
| function logError(error) { | ||||
| 	print("ERROR " + error); | ||||
| @@ -27,6 +30,14 @@ function all(prefix, handler) { | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function registerSocketHandler(prefix, handler) { | ||||
| 	gSocketHandlers.push({ | ||||
| 		owner: this, | ||||
| 		path: prefix, | ||||
| 		invoke: handler, | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function Request(method, uri, version, headers, body, client) { | ||||
| 	this.method = method; | ||||
| 	var index = uri.indexOf("?"); | ||||
| @@ -56,8 +67,21 @@ function findHandler(request) { | ||||
| 	return matchedHandler; | ||||
| } | ||||
|  | ||||
| function findSocketHandler(request) { | ||||
| 	var matchedHandler = null; | ||||
| 	for (var name in gSocketHandlers) { | ||||
| 		var handler = gSocketHandlers[name]; | ||||
| 		if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') { | ||||
| 			matchedHandler = handler; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return matchedHandler; | ||||
| } | ||||
|  | ||||
| function Response(request, client) { | ||||
| 	var kStatusText = { | ||||
| 		101: "Switching Protocols", | ||||
| 		200: 'OK', | ||||
| 		303: 'See other', | ||||
| 		403: 'Forbidden', | ||||
| @@ -160,6 +184,136 @@ function handleRequest(request, response) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function handleWebSocketRequest(request, response, client) { | ||||
| 	var buffer = ""; | ||||
| 	var frame = ""; | ||||
| 	var frameOpCode = 0x0; | ||||
|  | ||||
| 	var handler = findSocketHandler(request); | ||||
| 	if (!handler) { | ||||
| 		client.close(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	response.send = function(message, opCode) { | ||||
| 		if (opCode === undefined) { | ||||
| 			opCode = 0x2; | ||||
| 		} | ||||
| 		var fin = true; | ||||
| 		var packet = String.fromCharCode((fin ? (1 << 7) : 0) | (opCode & 0xf)); | ||||
| 		var mask = false; | ||||
| 		if (message.length < 126) { | ||||
| 			packet += String.fromCharCode((mask ? (1 << 7) : 0) | message.length); | ||||
| 		} else if (message.length < (1 << 16)) { | ||||
| 			packet += String.fromCharCode((mask ? (1 << 7) : 0) | 126); | ||||
| 			packet += String.fromCharCode((message.length >> 8) & 0xff); | ||||
| 			packet += String.fromCharCode(message.length & 0xff); | ||||
| 		} else { | ||||
| 			packet += String.fromCharCode((mask ? (1 << 7) : 0) | 127); | ||||
| 			packet += String.fromCharCode((message.length >> 24) & 0xff); | ||||
| 			packet += String.fromCharCode((message.length >> 16) & 0xff); | ||||
| 			packet += String.fromCharCode((message.length >> 8) & 0xff); | ||||
| 			packet += String.fromCharCode(message.length & 0xff); | ||||
| 		} | ||||
| 		packet += message; | ||||
| 		return client.write(packet); | ||||
| 	} | ||||
| 	response.onMessage = null; | ||||
|  | ||||
| 	handler.invoke(request, response); | ||||
|  | ||||
| 	client.read(function(data) { | ||||
| 		if (data) { | ||||
| 			buffer += data; | ||||
| 			if (buffer.length >= 2) { | ||||
| 				var bits0 = buffer.charCodeAt(0); | ||||
| 				var bits1 = buffer.charCodeAt(1); | ||||
| 				if (bits1 & (1 << 7) == 0) { | ||||
| 					// Unmasked message. | ||||
| 					client.close(); | ||||
| 				} | ||||
| 				var opCode = bits0 & 0xf; | ||||
| 				var fin = bits0 & (1 << 7); | ||||
| 				var payloadLength = bits1 & 0x7f; | ||||
| 				var maskStart = 2; | ||||
|  | ||||
| 				if (payloadLength == 126) { | ||||
| 					payloadLength = 0; | ||||
| 					for (var i = 0; i < 2; i++) { | ||||
| 						payloadLength <<= 8; | ||||
| 						payloadLength |= buffer.charCodeAt(2 + i); | ||||
| 					} | ||||
| 					maskStart = 4; | ||||
| 				} else if (payloadLength == 127) { | ||||
| 					payloadLength = 0; | ||||
| 					for (var i = 0; i < 8; i++) { | ||||
| 						payloadLength <<= 8; | ||||
| 						payloadLength |= buffer.charCodeAt(2 + i); | ||||
| 					} | ||||
| 					maskStart = 10; | ||||
| 				} | ||||
| 				var havePayload = buffer.length >= payloadLength + 2 + 4; | ||||
| 				if (havePayload) { | ||||
| 					var mask = buffer.substring(maskStart, maskStart + 4); | ||||
| 					var dataStart = maskStart + 4; | ||||
| 					var decoded = ""; | ||||
| 					var payload = buffer.substring(dataStart, dataStart + payloadLength); | ||||
| 					buffer = buffer.substring(dataStart + payloadLength); | ||||
| 					for (var i = 0; i < payloadLength; i++) { | ||||
| 						decoded += String.fromCharCode(payload.charCodeAt(i) ^ mask.charCodeAt(i % 4)); | ||||
| 					} | ||||
|  | ||||
| 					frame += decoded; | ||||
| 					if (opCode) { | ||||
| 						frameOpCode = opCode; | ||||
| 					} | ||||
|  | ||||
| 					if (fin) { | ||||
| 						if (response.onMessage) { | ||||
| 							response.onMessage({ | ||||
| 								data: frame, | ||||
| 								opCode: frameOpCode, | ||||
| 							}); | ||||
| 						} | ||||
| 						frame = ""; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| 	client.onError(function(error) { | ||||
| 		logError(client.peerName + " - - [" + new Date() + "] " + error); | ||||
| 	}); | ||||
|  | ||||
| 	response.writeHead(101, { | ||||
| 		"Upgrade": "websocket", | ||||
| 		"Connection": "Upgrade", | ||||
| 		"Sec-WebSocket-Accept": webSocketAcceptResponse(request.headers["sec-websocket-key"]), | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function webSocketAcceptResponse(key) { | ||||
| 	var kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | ||||
| 	var kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | ||||
| 	var hex = require("sha1").hash(key + kMagic) | ||||
| 	var binary = ""; | ||||
| 	for (var i = 0; i < hex.length; i += 6) { | ||||
| 		var characters = hex.substring(i, i + 6); | ||||
| 		if (characters.length < 6) { | ||||
| 			characters += "0".repeat(6 - characters.length); | ||||
| 		} | ||||
| 		var value = parseInt(characters, 16); | ||||
| 		for (var bit = 0; bit < 8 * 3; bit += 6) { | ||||
| 			if (i * 8 / 2 + bit >= 8 * hex.length / 2) { | ||||
| 				binary += kAlphabet.charAt(64); | ||||
| 			} else { | ||||
| 				binary += kAlphabet.charAt((value >> (18 - bit)) & 63); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return binary; | ||||
| } | ||||
|  | ||||
| function handleConnection(client) { | ||||
| 	var inputBuffer = ""; | ||||
| 	var request; | ||||
| @@ -207,6 +361,12 @@ function handleConnection(client) { | ||||
| 					lineByLine = false; | ||||
| 					body = ""; | ||||
| 					return true; | ||||
| 				} else if (headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1 | ||||
| 					&& headers["upgrade"].toLowerCase() == "websocket") { | ||||
| 					var requestObject = new Request(request[0], request[1], request[2], headers, body, client); | ||||
| 					var response = new Response(requestObject, client); | ||||
| 					handleWebSocketRequest(requestObject, response, client); | ||||
| 					return false; | ||||
| 				} else { | ||||
| 					finish(); | ||||
| 					return false; | ||||
| @@ -291,3 +451,4 @@ if (privateKey && certificate) { | ||||
| } | ||||
|  | ||||
| exports.all = all; | ||||
| exports.registerSocketHandler = registerSocketHandler; | ||||
|   | ||||
							
								
								
									
										160
									
								
								core/sha1.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								core/sha1.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | ||||
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */ | ||||
| /*  SHA-1 implementation in JavaScript                  (c) Chris Veness 2002-2014 / MIT Licence  */ | ||||
| /*                                                                                                */ | ||||
| /*  - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                              */ | ||||
| /*        http://csrc.nist.gov/groups/ST/toolkit/examples.html                                    */ | ||||
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */ | ||||
|  | ||||
| /* jshint node:true *//* global define, escape, unescape */ | ||||
| 'use strict'; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * SHA-1 hash function reference implementation. | ||||
|  * | ||||
|  * @namespace | ||||
|  */ | ||||
| var Sha1 = {}; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Generates SHA-1 hash of string. | ||||
|  * | ||||
|  * @param   {string} msg - (Unicode) string to be hashed. | ||||
|  * @returns {string} Hash of msg as hex character string. | ||||
|  */ | ||||
| Sha1.hash = function(msg) { | ||||
|     // convert string to UTF-8, as SHA only deals with byte-streams | ||||
|     msg = msg.utf8Encode(); | ||||
|  | ||||
|     // constants [§4.2.1] | ||||
|     var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; | ||||
|  | ||||
|     // PREPROCESSING | ||||
|  | ||||
|     msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1] | ||||
|  | ||||
|     // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] | ||||
|     var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length | ||||
|     var N = Math.ceil(l/16);  // number of 16-integer-blocks required to hold 'l' ints | ||||
|     var M = new Array(N); | ||||
|  | ||||
|     for (var i=0; i<N; i++) { | ||||
|         M[i] = new Array(16); | ||||
|         for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding | ||||
|             M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) | | ||||
|                 (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3)); | ||||
|         } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0 | ||||
|     } | ||||
|     // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1] | ||||
|     // note: most significant word would be (len-1)*8 >>> 32, but since JS converts | ||||
|     // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators | ||||
|     M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); | ||||
|     M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; | ||||
|  | ||||
|     // set initial hash value [§5.3.1] | ||||
|     var H0 = 0x67452301; | ||||
|     var H1 = 0xefcdab89; | ||||
|     var H2 = 0x98badcfe; | ||||
|     var H3 = 0x10325476; | ||||
|     var H4 = 0xc3d2e1f0; | ||||
|  | ||||
|     // HASH COMPUTATION [§6.1.2] | ||||
|  | ||||
|     var W = new Array(80); var a, b, c, d, e; | ||||
|     for (var i=0; i<N; i++) { | ||||
|  | ||||
|         // 1 - prepare message schedule 'W' | ||||
|         for (var t=0;  t<16; t++) W[t] = M[i][t]; | ||||
|         for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); | ||||
|  | ||||
|         // 2 - initialise five working variables a, b, c, d, e with previous hash value | ||||
|         a = H0; b = H1; c = H2; d = H3; e = H4; | ||||
|  | ||||
|         // 3 - main loop | ||||
|         for (var t=0; t<80; t++) { | ||||
|             var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants | ||||
|             var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff; | ||||
|             e = d; | ||||
|             d = c; | ||||
|             c = Sha1.ROTL(b, 30); | ||||
|             b = a; | ||||
|             a = T; | ||||
|         } | ||||
|  | ||||
|         // 4 - compute the new intermediate hash value (note 'addition modulo 2^32') | ||||
|         H0 = (H0+a) & 0xffffffff; | ||||
|         H1 = (H1+b) & 0xffffffff; | ||||
|         H2 = (H2+c) & 0xffffffff; | ||||
|         H3 = (H3+d) & 0xffffffff; | ||||
|         H4 = (H4+e) & 0xffffffff; | ||||
|     } | ||||
|  | ||||
|     return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + Sha1.toHexStr(H2) + | ||||
|            Sha1.toHexStr(H3) + Sha1.toHexStr(H4); | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Function 'f' [§4.1.1]. | ||||
|  * @private | ||||
|  */ | ||||
| Sha1.f = function(s, x, y, z)  { | ||||
|     switch (s) { | ||||
|         case 0: return (x & y) ^ (~x & z);           // Ch() | ||||
|         case 1: return  x ^ y  ^  z;                 // Parity() | ||||
|         case 2: return (x & y) ^ (x & z) ^ (y & z);  // Maj() | ||||
|         case 3: return  x ^ y  ^  z;                 // Parity() | ||||
|     } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Rotates left (circular left shift) value x by n positions [§3.2.5]. | ||||
|  * @private | ||||
|  */ | ||||
| Sha1.ROTL = function(x, n) { | ||||
|     return (x<<n) | (x>>>(32-n)); | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Hexadecimal representation of a number. | ||||
|  * @private | ||||
|  */ | ||||
| Sha1.toHexStr = function(n) { | ||||
|     // note can't use toString(16) as it is implementation-dependant, | ||||
|     // and in IE returns signed numbers when used on full words | ||||
|     var s="", v; | ||||
|     for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); } | ||||
|     return s; | ||||
| }; | ||||
|  | ||||
|  | ||||
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */ | ||||
|  | ||||
|  | ||||
| /** Extend String object with method to encode multi-byte string to utf8 | ||||
|  *  - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */ | ||||
| if (typeof String.prototype.utf8Encode == 'undefined') { | ||||
|     String.prototype.utf8Encode = function() { | ||||
|         return unescape( encodeURIComponent( this ) ); | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** Extend String object with method to decode utf8 string to multi-byte */ | ||||
| if (typeof String.prototype.utf8Decode == 'undefined') { | ||||
|     String.prototype.utf8Decode = function() { | ||||
|         try { | ||||
|             return decodeURIComponent( escape( this ) ); | ||||
|         } catch (e) { | ||||
|             return this; // invalid UTF-8? return as-is | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */ | ||||
| if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export | ||||
| if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD | ||||
|  | ||||
| exports.hash = Sha1.hash; | ||||
							
								
								
									
										256
									
								
								core/terminal.js
									
									
									
									
									
								
							
							
						
						
									
										256
									
								
								core/terminal.js
									
									
									
									
									
								
							| @@ -14,9 +14,9 @@ var auth = require('auth'); | ||||
| var form = require('form'); | ||||
|  | ||||
| function Terminal() { | ||||
| 	this._waiting = []; | ||||
| 	this._index = 0; | ||||
| 	this._firstLine = 0; | ||||
| 	this._sentIndex = -1; | ||||
| 	this._lines = []; | ||||
| 	this._lastRead = null; | ||||
| 	this._lastWrite = null; | ||||
| @@ -24,29 +24,31 @@ function Terminal() { | ||||
| 	this._readLine = null; | ||||
| 	this._selected = null; | ||||
| 	this._corked = 0; | ||||
| 	this._onOutput = null; | ||||
| 	return this; | ||||
| } | ||||
|  | ||||
| Terminal.kBacklog = 64; | ||||
|  | ||||
| Terminal.prototype.dispatch = function(data) { | ||||
| 	for (var i in this._waiting) { | ||||
| 		this.feedWaiting(this._waiting[i], data); | ||||
| 	} | ||||
| 	this._waiting.length = 0; | ||||
| Terminal.prototype.readOutput = function(callback) { | ||||
| 	this._onOutput = callback; | ||||
| 	this.dispatch(); | ||||
| } | ||||
|  | ||||
| Terminal.prototype.feedWaiting = function(waiting, data) { | ||||
| 	var terminal = this; | ||||
| 	var payload = terminal._lines.slice(Math.max(0, waiting.haveIndex + 1 - terminal._firstLine)); | ||||
| Terminal.prototype.dispatch = function(data) { | ||||
| 	var payload = this._lines.slice(Math.max(0, this._sentIndex + 1 - this._firstLine)); | ||||
| 	if (data) { | ||||
| 		payload.push(data); | ||||
| 	} | ||||
| 	if (waiting.haveIndex < terminal._index - 1 || data) { | ||||
| 		waiting.resolve({index: terminal._index - 1, lines: payload}); | ||||
| 	if (this._onOutput && (this._sentIndex < this._index - 1 || data)) { | ||||
| 		this._sentIndex = this._index - 1; | ||||
| 		this._onOutput({lines: payload}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Terminal.prototype.feedWaiting = function(waiting, data) { | ||||
| } | ||||
|  | ||||
| Terminal.prototype.print = function() { | ||||
| 	var data = arguments; | ||||
| 	if (this._selected) { | ||||
| @@ -104,8 +106,6 @@ Terminal.prototype.postMessageToIframe = function(name, message) { | ||||
| } | ||||
|  | ||||
| Terminal.prototype.clear = function() { | ||||
| 	//this._lines.length = 0; | ||||
| 	//this._firstLine = this._index; | ||||
| 	this.print({action: "clear"}); | ||||
| } | ||||
|  | ||||
| @@ -113,18 +113,6 @@ Terminal.prototype.ping = function() { | ||||
| 	this.dispatch({action: "ping"}); | ||||
| } | ||||
|  | ||||
| Terminal.prototype.getOutput = function(haveIndex) { | ||||
| 	var terminal = this; | ||||
| 	terminal._lastRead = new Date(); | ||||
| 	return new Promise(function(resolve) { | ||||
| 		if (haveIndex < terminal._index - 1) { | ||||
| 			resolve({index: terminal._index - 1, lines: terminal._lines.slice(Math.max(0, haveIndex + 1 - terminal._firstLine))}); | ||||
| 		} else { | ||||
| 			terminal._waiting.push({haveIndex: haveIndex, resolve: resolve}); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| Terminal.prototype.setEcho = function(echo) { | ||||
| 	this._echo = echo; | ||||
| } | ||||
| @@ -145,10 +133,7 @@ Terminal.prototype.cork = function() { | ||||
|  | ||||
| Terminal.prototype.uncork = function() { | ||||
| 	if (--this._corked == 0) { | ||||
| 		for (var i = 0; i < this._waiting.length; i++) { | ||||
| 			this.feedWaiting(this._waiting[i]); | ||||
| 		} | ||||
| 		this._waiting.length = 0; | ||||
| 		this.dispatch(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -162,6 +147,97 @@ function invoke(handlers, argv) { | ||||
| 	return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| function socket(request, response, client) { | ||||
| 	var process; | ||||
|  | ||||
| 	var options = {}; | ||||
| 	var credentials = auth.query(request.headers); | ||||
| 	if (credentials && credentials.session) { | ||||
| 		options.userName = credentials.session.name; | ||||
| 	} | ||||
| 	options.credentials = credentials; | ||||
|  | ||||
| 	response.onMessage = function(event) { | ||||
| 		if (event.opCode == 0x1 || event.opCode == 0x2) { | ||||
| 			var message; | ||||
| 			try { | ||||
| 				message = JSON.parse(event.data); | ||||
| 			} catch (error) { | ||||
| 				print("ERROR", error, event.data, event.data.length, event.opCode); | ||||
| 				return; | ||||
| 			} | ||||
| 			if (message.action == "hello") { | ||||
| 				var packageOwner; | ||||
| 				var packageName; | ||||
| 				var match; | ||||
| 				if (match = /^\/\~([^\/]+)\/([^\/]+)(.*)/.exec(message.path)) { | ||||
| 					packageOwner = match[1]; | ||||
| 					packageName = match[2]; | ||||
| 				} | ||||
| 				response.send(JSON.stringify({action: "hello"}), 0x1); | ||||
|  | ||||
| 				process = getSessionProcess(packageOwner, packageName, makeSessionId(), options); | ||||
| 				process.terminal.readOutput(function(message) { | ||||
| 					response.send(JSON.stringify(message), 0x1); | ||||
| 				}); | ||||
|  | ||||
| 				var ping = function() { | ||||
| 					var now = Date.now(); | ||||
| 					var again = true; | ||||
| 					if (now - process.lastActive < process.timeout) { | ||||
| 						// Active. | ||||
| 					} else if (process.lastPing > process.lastActive) { | ||||
| 						// We lost them. | ||||
| 						process.task.kill(); | ||||
| 						again = false; | ||||
| 					} else { | ||||
| 						// Idle.  Ping them. | ||||
| 						response.send("", 0x9); | ||||
| 						process.lastPing = now; | ||||
| 					} | ||||
|  | ||||
| 					if (again) { | ||||
| 						setTimeout(ping, process.timeout); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (process.timeout > 0) { | ||||
| 					setTimeout(ping, process.timeout); | ||||
| 				} | ||||
| 			} else if (message.action == "command") { | ||||
| 				var command = message.command; | ||||
| 				var eventName = 'unknown'; | ||||
| 				if (typeof command == "string") { | ||||
| 					if (process.terminal._echo) { | ||||
| 						process.terminal.print("> " + command); | ||||
| 					} | ||||
| 					if (process.terminal._readLine) { | ||||
| 						let promise = process.terminal._readLine; | ||||
| 						process.terminal._readLine = null; | ||||
| 						promise[0](command); | ||||
| 					} | ||||
| 					eventName = 'onInput'; | ||||
| 				} else if (command.event) { | ||||
| 					eventName = command.event; | ||||
| 				} | ||||
| 				return invoke(process.eventHandlers[eventName], [command]).catch(function(error) { | ||||
| 					process.terminal.print(error); | ||||
| 				}); | ||||
| 			} | ||||
| 		} else if (event.opCode == 0x8) { | ||||
| 			// Close. | ||||
| 			process.task.kill(); | ||||
| 			response.send(event.data, 0x8); | ||||
| 		} else if (event.opCode == 0xa) { | ||||
| 			// PONG | ||||
| 		} | ||||
|  | ||||
| 		if (process) { | ||||
| 			process.lastActive = Date.now(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function handler(request, response, packageOwner, packageName, uri) { | ||||
| 	var found = false; | ||||
|  | ||||
| @@ -226,62 +302,9 @@ function handler(request, response, packageOwner, packageName, uri) { | ||||
| 					response.end("Problem saving: " + packageName); | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			var options = {}; | ||||
| 			var credentials = auth.query(request.headers); | ||||
| 			if (credentials && credentials.session) { | ||||
| 				options.userName = credentials.session.name; | ||||
| 			} | ||||
| 			options.credentials = credentials; | ||||
| 			if (uri == "/submit") { | ||||
| 				process = getServiceProcess(packageOwner, packageName, "submit"); | ||||
| 			} else if (uri == "/atom") { | ||||
| 				process = getServiceProcess(packageOwner, packageName, "atom"); | ||||
| 			} else { | ||||
| 				var sessionId = form.decodeForm(request.query).sessionId; | ||||
| 				var isNewSession = false; | ||||
| 				if (!getSessionProcess(packageOwner, packageName, sessionId, {create: false})) { | ||||
| 					sessionId = makeSessionId(); | ||||
| 					isNewSession = true; | ||||
| 				} | ||||
| 				process = getSessionProcess(packageOwner, packageName, sessionId, options); | ||||
| 			} | ||||
| 			process.lastActive = Date.now(); | ||||
|  | ||||
| 			if (uri === "/send") { | ||||
| 				if (isNewSession) { | ||||
| 					response.writeHead(403, {"Content-Type": "text/plain; charset=utf-8"}); | ||||
| 					response.end("Too soon."); | ||||
| 				} else { | ||||
| 					var command = JSON.parse(request.body); | ||||
| 					var eventName = 'unknown'; | ||||
| 					if (typeof command == "string") { | ||||
| 						if (process.terminal._echo) { | ||||
| 							process.terminal.print("> " + command); | ||||
| 						} | ||||
| 						if (process.terminal._readLine) { | ||||
| 							let promise = process.terminal._readLine; | ||||
| 							process.terminal._readLine = null; | ||||
| 							promise[0](command); | ||||
| 						} | ||||
| 						eventName = 'onInput'; | ||||
| 					} else if (command.event) { | ||||
| 						eventName = command.event; | ||||
| 					} | ||||
| 					return invoke(process.eventHandlers[eventName], [command]).then(function() { | ||||
| 						response.writeHead(200, { | ||||
| 							"Content-Type": "text/plain; charset=utf-8", | ||||
| 							"Content-Length": "0", | ||||
| 							"Cache-Control": "no-cache, no-store, must-revalidate", | ||||
| 							"Pragma": "no-cache", | ||||
| 							"Expires": "0", | ||||
| 						}); | ||||
| 						response.end(""); | ||||
| 					}).catch(function(error) { | ||||
| 						process.terminal.print(error); | ||||
| 					}); | ||||
| 				} | ||||
| 			} else if (uri === "/submit") { | ||||
| 		} else if (uri === "/submit") { | ||||
| 				var process = getServiceProcess(packageOwner, packageName, "submit"); | ||||
| 				process.lastActive = Date.now(); | ||||
| 				return process.ready.then(function() { | ||||
| 					var payload = form.decodeForm(request.body, form.decodeForm(request.query)); | ||||
| 					return invoke(process.eventHandlers['onSubmit'], [payload]).then(function() { | ||||
| @@ -295,63 +318,26 @@ function handler(request, response, packageOwner, packageName, uri) { | ||||
| 						return response.end(""); | ||||
| 					}); | ||||
| 				}); | ||||
| 			} else if (uri === "/atom") { | ||||
| 				return process.ready.then(function() { | ||||
| 					var payload = form.decodeForm(request.body, form.decodeForm(request.query)); | ||||
| 					return invoke(process.eventHandlers['onAtom'], [payload]).then(function(content) { | ||||
| 						var atomContent = content.join(); | ||||
| 						response.writeHead(200, { | ||||
| 							"Content-Type": "application/atom+xml; charset=utf-8", | ||||
| 							"Content-Length": atomContent.length.toString(), | ||||
| 							"Cache-Control": "no-cache, no-store, must-revalidate", | ||||
| 							"Pragma": "no-cache", | ||||
| 							"Expires": "0", | ||||
| 						}); | ||||
| 						return response.end(atomContent); | ||||
| 					}); | ||||
| 				}); | ||||
| 			} else if (uri === "/receive") { | ||||
| 				if (isNewSession) { | ||||
| 					var data = JSON.stringify({ | ||||
| 						lines: [ | ||||
| 							{ | ||||
| 								action: "session", | ||||
| 								session: { | ||||
| 									sessionId: sessionId, | ||||
| 									credentials: credentials, | ||||
| 								} | ||||
| 							}, | ||||
| 						] | ||||
| 					}); | ||||
| 		} else if (uri === "/atom") { | ||||
| 			var process = getServiceProcess(packageOwner, packageName, "atom"); | ||||
| 			process.lastActive = Date.now(); | ||||
| 			return process.ready.then(function() { | ||||
| 				var payload = form.decodeForm(request.body, form.decodeForm(request.query)); | ||||
| 				return invoke(process.eventHandlers['onAtom'], [payload]).then(function(content) { | ||||
| 					var atomContent = content.join(); | ||||
| 					response.writeHead(200, { | ||||
| 						"Content-Type": "text/plain; charset=utf-8", | ||||
| 						"Content-Length": data.length.toString(), | ||||
| 						"Content-Type": "application/atom+xml; charset=utf-8", | ||||
| 						"Content-Length": atomContent.length.toString(), | ||||
| 						"Cache-Control": "no-cache, no-store, must-revalidate", | ||||
| 						"Pragma": "no-cache", | ||||
| 						"Expires": "0", | ||||
| 					}); | ||||
| 					process.ready.then(function() { | ||||
| 						process.terminal.print({action: "ready", ready: true}); | ||||
| 					}).catch(function(error) { | ||||
| 						process.terminal.print({action: "ready", error: error}); | ||||
| 					}); | ||||
| 					response.end(data); | ||||
| 				} else { | ||||
| 					return process.terminal.getOutput(parseInt(request.body)).then(function(output) { | ||||
| 						var data = JSON.stringify(output); | ||||
| 						response.writeHead(200, { | ||||
| 							"Content-Type": "text/plain; charset=utf-8", | ||||
| 							"Content-Length": data.length.toString(), | ||||
| 							"Cache-Control": "no-cache, no-store, must-revalidate", | ||||
| 							"Pragma": "no-cache", | ||||
| 							"Expires": "0", | ||||
| 						}); | ||||
| 						response.end(data); | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 					return response.end(atomContent); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| exports.handler = handler; | ||||
| exports.socket = socket; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user