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:
parent
e6aad326c5
commit
2c8bea27a0
152
core/client.js
152
core/client.js
@ -108,83 +108,58 @@ function split(container, children) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function receive() {
|
function receive(data) {
|
||||||
$.ajax({
|
for (var i in data.lines) {
|
||||||
url: url() + "/receive?sessionId=" + gSessionId,
|
var line = data.lines[i];
|
||||||
method: "POST",
|
|
||||||
data: gHaveIndex.toString(),
|
|
||||||
dataType: "json",
|
|
||||||
}).then(function(data) {
|
|
||||||
for (var i in data.lines) {
|
|
||||||
var line = data.lines[i];
|
|
||||||
|
|
||||||
var target = document.getElementsByClassName("terminal")[0].id;
|
var target = document.getElementsByClassName("terminal")[0].id;
|
||||||
if (line && line.terminal) {
|
if (line && line.terminal) {
|
||||||
if (document.getElementById("terminal_" + line.terminal)) {
|
if (document.getElementById("terminal_" + line.terminal)) {
|
||||||
target = "terminal_" + line.terminal;
|
target = "terminal_" + line.terminal;
|
||||||
}
|
|
||||||
line = line.value;
|
|
||||||
}
|
}
|
||||||
if (line && line.action == "ping") {
|
line = line.value;
|
||||||
// PONG
|
}
|
||||||
} else if (line && line.action == "session") {
|
if (line && line.action == "ping") {
|
||||||
gSessionId = line.session.sessionId;
|
gSocket.send(JSON.stringify({action: "pong"}));
|
||||||
gCredentials = line.session.credentials;
|
} else if (line && line.action == "session") {
|
||||||
updateLogin();
|
gSessionId = line.session.sessionId;
|
||||||
} else if (line && line[0] && line[0].action == "ready") {
|
gCredentials = line.session.credentials;
|
||||||
if (window.location.hash) {
|
updateLogin();
|
||||||
send({event: "hashChange", hash: window.location.hash});
|
} else if (line && line[0] && line[0].action == "ready") {
|
||||||
}
|
if (window.location.hash) {
|
||||||
} else if (line && line[0] && line[0].action == "notify") {
|
send({event: "hashChange", hash: window.location.hash});
|
||||||
new Notification(line[0].title, line[0].options);
|
}
|
||||||
} else if (line && line[0] && line[0].action == "title") {
|
} else if (line && line[0] && line[0].action == "notify") {
|
||||||
window.document.title = line[0].value;
|
new Notification(line[0].title, line[0].options);
|
||||||
} else if (line && line[0] && line[0].action == "prompt") {
|
} else if (line && line[0] && line[0].action == "title") {
|
||||||
var prompt = document.getElementById("prompt");
|
window.document.title = line[0].value;
|
||||||
while (prompt.firstChild) {
|
} else if (line && line[0] && line[0].action == "prompt") {
|
||||||
prompt.removeChild(prompt.firstChild);
|
var prompt = document.getElementById("prompt");
|
||||||
}
|
while (prompt.firstChild) {
|
||||||
prompt.appendChild(document.createTextNode(line[0].value));
|
prompt.removeChild(prompt.firstChild);
|
||||||
} else if (line && line[0] && line[0].action == "password") {
|
}
|
||||||
var prompt = document.getElementById("input");
|
prompt.appendChild(document.createTextNode(line[0].value));
|
||||||
prompt.setAttribute("type", line[0].value ? "password" : "text");
|
} else if (line && line[0] && line[0].action == "password") {
|
||||||
} else if (line && line[0] && line[0].action == "hash") {
|
var prompt = document.getElementById("input");
|
||||||
window.location.hash = line[0].value;
|
prompt.setAttribute("type", line[0].value ? "password" : "text");
|
||||||
} else if (line && line[0] && line[0].action == "update") {
|
} else if (line && line[0] && line[0].action == "hash") {
|
||||||
document.getElementById("update").setAttribute("Style", "display: inline");
|
window.location.hash = line[0].value;
|
||||||
} else if (line && line[0] && line[0].action == "split") {
|
} else if (line && line[0] && line[0].action == "update") {
|
||||||
split(document.getElementById("terminals"), line[0].options);
|
document.getElementById("update").setAttribute("Style", "display: inline");
|
||||||
} else if (line && line[0] && line[0].action == "postMessageToIframe") {
|
} else if (line && line[0] && line[0].action == "split") {
|
||||||
var iframe = document.getElementById("iframe_" + line[0].name);
|
split(document.getElementById("terminals"), line[0].options);
|
||||||
if (iframe) {
|
} else if (line && line[0] && line[0].action == "postMessageToIframe") {
|
||||||
iframe.contentWindow.postMessage(line[0].message, "*");
|
var iframe = document.getElementById("iframe_" + line[0].name);
|
||||||
}
|
if (iframe) {
|
||||||
} else {
|
iframe.contentWindow.postMessage(line[0].message, "*");
|
||||||
print(document.getElementById(target), line);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
setTimeout(receive, 60 * 1000);
|
print(document.getElementById(target), line);
|
||||||
}
|
}
|
||||||
gErrorCount++;
|
}
|
||||||
});
|
if ("index" in data) {
|
||||||
|
gHaveIndex = data.index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoNewLine(terminal) {
|
function autoNewLine(terminal) {
|
||||||
@ -282,19 +257,16 @@ function send(command) {
|
|||||||
value = prefix + $("#input").val();
|
value = prefix + $("#input").val();
|
||||||
$("#input").val("");
|
$("#input").val("");
|
||||||
}
|
}
|
||||||
$.ajax({
|
try {
|
||||||
url: url() + "/send?sessionId=" + gSessionId,
|
gSocket.send(JSON.stringify({action: "command", command: value}));
|
||||||
method: "POST",
|
} catch (error) {
|
||||||
data: JSON.stringify(value),
|
|
||||||
dataType: "text",
|
|
||||||
}).fail(function(xhr, status, error) {
|
|
||||||
var node = document.getElementById("status");
|
var node = document.getElementById("status");
|
||||||
while (node.firstChild) {
|
while (node.firstChild) {
|
||||||
node.removeChild(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");
|
node.setAttribute("style", "display: inline; color: #dc322f");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLogin() {
|
function updateLogin() {
|
||||||
@ -437,6 +409,8 @@ function onMessage(event) {
|
|||||||
send({event: "onWindowMessage", message: event.data});
|
send({event: "onWindowMessage", message: event.data});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gSocket;
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
if (Notification) {
|
if (Notification) {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
@ -448,10 +422,18 @@ $(document).ready(function() {
|
|||||||
window.addEventListener("blur", blur);
|
window.addEventListener("blur", blur);
|
||||||
window.addEventListener("message", onMessage, false);
|
window.addEventListener("message", onMessage, false);
|
||||||
enableDragDrop();
|
enableDragDrop();
|
||||||
});
|
|
||||||
|
|
||||||
$(window).load(function() {
|
gSocket = new WebSocket("ws://"
|
||||||
setTimeout(function() {
|
+ window.location.hostname
|
||||||
receive();
|
+ (window.location.port.length ? ":" + window.location.port : "")
|
||||||
}, 0);
|
+ "/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;
|
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) {
|
function postMessageInternal(from, to, message) {
|
||||||
return invoke(to.eventHandlers['onMessage'], [getUser(from, from), message]);
|
return invoke(to.eventHandlers['onMessage'], [getUser(from, from), message]);
|
||||||
}
|
}
|
||||||
@ -273,9 +252,6 @@ function getProcess(packageOwner, packageName, key, options) {
|
|||||||
process.connections.length = 0;
|
process.connections.length = 0;
|
||||||
delete gProcesses[key];
|
delete gProcesses[key];
|
||||||
};
|
};
|
||||||
if (process.timeout > 0) {
|
|
||||||
setTimeout(ping.bind(process), process.timeout);
|
|
||||||
}
|
|
||||||
var imports = {
|
var imports = {
|
||||||
'core': {
|
'core': {
|
||||||
'broadcast': broadcast.bind(process),
|
'broadcast': broadcast.bind(process),
|
||||||
@ -352,8 +328,6 @@ function getProcess(packageOwner, packageName, key, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (manifest && manifest.require) {
|
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.addPath(manifest.require.map(packageNameToPath.bind(process)));
|
||||||
}
|
}
|
||||||
process.task.setImports(imports);
|
process.task.setImports(imports);
|
||||||
@ -439,3 +413,4 @@ httpd.all("", function(request, response) {
|
|||||||
return response.end(data);
|
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 gHandlers = [];
|
||||||
|
var gSocketHandlers = [];
|
||||||
|
|
||||||
function logError(error) {
|
function logError(error) {
|
||||||
print("ERROR " + 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) {
|
function Request(method, uri, version, headers, body, client) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
var index = uri.indexOf("?");
|
var index = uri.indexOf("?");
|
||||||
@ -56,8 +67,21 @@ function findHandler(request) {
|
|||||||
return matchedHandler;
|
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) {
|
function Response(request, client) {
|
||||||
var kStatusText = {
|
var kStatusText = {
|
||||||
|
101: "Switching Protocols",
|
||||||
200: 'OK',
|
200: 'OK',
|
||||||
303: 'See other',
|
303: 'See other',
|
||||||
403: 'Forbidden',
|
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) {
|
function handleConnection(client) {
|
||||||
var inputBuffer = "";
|
var inputBuffer = "";
|
||||||
var request;
|
var request;
|
||||||
@ -207,6 +361,12 @@ function handleConnection(client) {
|
|||||||
lineByLine = false;
|
lineByLine = false;
|
||||||
body = "";
|
body = "";
|
||||||
return true;
|
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 {
|
} else {
|
||||||
finish();
|
finish();
|
||||||
return false;
|
return false;
|
||||||
@ -291,3 +451,4 @@ if (privateKey && certificate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.all = all;
|
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');
|
var form = require('form');
|
||||||
|
|
||||||
function Terminal() {
|
function Terminal() {
|
||||||
this._waiting = [];
|
|
||||||
this._index = 0;
|
this._index = 0;
|
||||||
this._firstLine = 0;
|
this._firstLine = 0;
|
||||||
|
this._sentIndex = -1;
|
||||||
this._lines = [];
|
this._lines = [];
|
||||||
this._lastRead = null;
|
this._lastRead = null;
|
||||||
this._lastWrite = null;
|
this._lastWrite = null;
|
||||||
@ -24,29 +24,31 @@ function Terminal() {
|
|||||||
this._readLine = null;
|
this._readLine = null;
|
||||||
this._selected = null;
|
this._selected = null;
|
||||||
this._corked = 0;
|
this._corked = 0;
|
||||||
|
this._onOutput = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Terminal.kBacklog = 64;
|
Terminal.kBacklog = 64;
|
||||||
|
|
||||||
Terminal.prototype.dispatch = function(data) {
|
Terminal.prototype.readOutput = function(callback) {
|
||||||
for (var i in this._waiting) {
|
this._onOutput = callback;
|
||||||
this.feedWaiting(this._waiting[i], data);
|
this.dispatch();
|
||||||
}
|
|
||||||
this._waiting.length = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Terminal.prototype.feedWaiting = function(waiting, data) {
|
Terminal.prototype.dispatch = function(data) {
|
||||||
var terminal = this;
|
var payload = this._lines.slice(Math.max(0, this._sentIndex + 1 - this._firstLine));
|
||||||
var payload = terminal._lines.slice(Math.max(0, waiting.haveIndex + 1 - terminal._firstLine));
|
|
||||||
if (data) {
|
if (data) {
|
||||||
payload.push(data);
|
payload.push(data);
|
||||||
}
|
}
|
||||||
if (waiting.haveIndex < terminal._index - 1 || data) {
|
if (this._onOutput && (this._sentIndex < this._index - 1 || data)) {
|
||||||
waiting.resolve({index: terminal._index - 1, lines: payload});
|
this._sentIndex = this._index - 1;
|
||||||
|
this._onOutput({lines: payload});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Terminal.prototype.feedWaiting = function(waiting, data) {
|
||||||
|
}
|
||||||
|
|
||||||
Terminal.prototype.print = function() {
|
Terminal.prototype.print = function() {
|
||||||
var data = arguments;
|
var data = arguments;
|
||||||
if (this._selected) {
|
if (this._selected) {
|
||||||
@ -104,8 +106,6 @@ Terminal.prototype.postMessageToIframe = function(name, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Terminal.prototype.clear = function() {
|
Terminal.prototype.clear = function() {
|
||||||
//this._lines.length = 0;
|
|
||||||
//this._firstLine = this._index;
|
|
||||||
this.print({action: "clear"});
|
this.print({action: "clear"});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,18 +113,6 @@ Terminal.prototype.ping = function() {
|
|||||||
this.dispatch({action: "ping"});
|
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) {
|
Terminal.prototype.setEcho = function(echo) {
|
||||||
this._echo = echo;
|
this._echo = echo;
|
||||||
}
|
}
|
||||||
@ -145,10 +133,7 @@ Terminal.prototype.cork = function() {
|
|||||||
|
|
||||||
Terminal.prototype.uncork = function() {
|
Terminal.prototype.uncork = function() {
|
||||||
if (--this._corked == 0) {
|
if (--this._corked == 0) {
|
||||||
for (var i = 0; i < this._waiting.length; i++) {
|
this.dispatch();
|
||||||
this.feedWaiting(this._waiting[i]);
|
|
||||||
}
|
|
||||||
this._waiting.length = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +147,97 @@ function invoke(handlers, argv) {
|
|||||||
return Promise.all(promises);
|
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) {
|
function handler(request, response, packageOwner, packageName, uri) {
|
||||||
var found = false;
|
var found = false;
|
||||||
|
|
||||||
@ -226,62 +302,9 @@ function handler(request, response, packageOwner, packageName, uri) {
|
|||||||
response.end("Problem saving: " + packageName);
|
response.end("Problem saving: " + packageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (uri === "/submit") {
|
||||||
var options = {};
|
var process = getServiceProcess(packageOwner, packageName, "submit");
|
||||||
var credentials = auth.query(request.headers);
|
process.lastActive = Date.now();
|
||||||
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") {
|
|
||||||
return process.ready.then(function() {
|
return process.ready.then(function() {
|
||||||
var payload = form.decodeForm(request.body, form.decodeForm(request.query));
|
var payload = form.decodeForm(request.body, form.decodeForm(request.query));
|
||||||
return invoke(process.eventHandlers['onSubmit'], [payload]).then(function() {
|
return invoke(process.eventHandlers['onSubmit'], [payload]).then(function() {
|
||||||
@ -295,63 +318,26 @@ function handler(request, response, packageOwner, packageName, uri) {
|
|||||||
return response.end("");
|
return response.end("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (uri === "/atom") {
|
} else if (uri === "/atom") {
|
||||||
return process.ready.then(function() {
|
var process = getServiceProcess(packageOwner, packageName, "atom");
|
||||||
var payload = form.decodeForm(request.body, form.decodeForm(request.query));
|
process.lastActive = Date.now();
|
||||||
return invoke(process.eventHandlers['onAtom'], [payload]).then(function(content) {
|
return process.ready.then(function() {
|
||||||
var atomContent = content.join();
|
var payload = form.decodeForm(request.body, form.decodeForm(request.query));
|
||||||
response.writeHead(200, {
|
return invoke(process.eventHandlers['onAtom'], [payload]).then(function(content) {
|
||||||
"Content-Type": "application/atom+xml; charset=utf-8",
|
var atomContent = content.join();
|
||||||
"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,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
"Content-Type": "text/plain; charset=utf-8",
|
"Content-Type": "application/atom+xml; charset=utf-8",
|
||||||
"Content-Length": data.length.toString(),
|
"Content-Length": atomContent.length.toString(),
|
||||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||||
"Pragma": "no-cache",
|
"Pragma": "no-cache",
|
||||||
"Expires": "0",
|
"Expires": "0",
|
||||||
});
|
});
|
||||||
process.ready.then(function() {
|
return response.end(atomContent);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handler = handler;
|
exports.handler = handler;
|
||||||
|
exports.socket = socket;
|
||||||
|
Loading…
Reference in New Issue
Block a user