Commit b9633f8b authored by Joel Martin's avatar Joel Martin

Import/merge gimite/web-socket-js up to da7caff96496c7d7bfb3.

Bug fixes, restore RFC2817 proxy for non wss://, and handle new
closing handshake from WebSockets 76.
parent e091b47a
...@@ -67,10 +67,11 @@ The class RFC2817Socket (by Christian Cantrell) effectively lets us implement th ...@@ -67,10 +67,11 @@ The class RFC2817Socket (by Christian Cantrell) effectively lets us implement th
* How to build WebSocketMain.swf * How to build WebSocketMain.swf
Install Flex SDK. Install Flex 4 SDK:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
$ cd flash-src $ cd flash-src
$ mxmlc -output=../WebSocketMain.swf WebSocketMain.as $ ./build.sh
* License * License
......
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/> // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License // License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/ // Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
package { package {
...@@ -34,7 +34,6 @@ public class WebSocket extends EventDispatcher { ...@@ -34,7 +34,6 @@ public class WebSocket extends EventDispatcher {
private static var CLOSING:int = 2; private static var CLOSING:int = 2;
private static var CLOSED:int = 3; private static var CLOSED:int = 3;
//private var rawSocket:RFC2817Socket;
private var rawSocket:Socket; private var rawSocket:Socket;
private var tlsSocket:TLSSocket; private var tlsSocket:TLSSocket;
private var tlsConfig:TLSConfig; private var tlsConfig:TLSConfig;
...@@ -76,51 +75,49 @@ public class WebSocket extends EventDispatcher { ...@@ -76,51 +75,49 @@ public class WebSocket extends EventDispatcher {
// "Header1: xxx\r\nHeader2: yyyy\r\n" // "Header1: xxx\r\nHeader2: yyyy\r\n"
this.headers = headers; this.headers = headers;
/*
socket = new RFC2817Socket();
// if no proxy information is supplied, it acts like a normal Socket
// @see RFC2817Socket::connect
if (proxyHost != null && proxyPort != 0){ if (proxyHost != null && proxyPort != 0){
socket.setProxyInfo(proxyHost, proxyPort); if (scheme == "wss") {
main.fatal("wss with proxy is not supported");
} }
*/ var proxySocket:RFC2817Socket = new RFC2817Socket();
proxySocket.setProxyInfo(proxyHost, proxyPort);
ExternalInterface.call("console.log", "[WebSocket] scheme: " + scheme); proxySocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
rawSocket = socket = proxySocket;
} else {
rawSocket = new Socket(); rawSocket = new Socket();
rawSocket.addEventListener(Event.CLOSE, onSocketClose);
rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
if (scheme == "wss") { if (scheme == "wss") {
tlsConfig= new TLSConfig(TLSEngine.CLIENT, tlsConfig= new TLSConfig(TLSEngine.CLIENT,
null, null, null, null, null, null, null, null, null, null,
TLSSecurityParameters.PROTOCOL_VERSION); TLSSecurityParameters.PROTOCOL_VERSION);
tlsConfig.trustSelfSignedCertificates = true; tlsConfig.trustSelfSignedCertificates = true;
tlsConfig.ignoreCommonNameMismatch = true; tlsConfig.ignoreCommonNameMismatch = true;
tlsSocket = new TLSSocket(); tlsSocket = new TLSSocket();
tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData); tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
socket = (tlsSocket as Socket); socket = tlsSocket;
} else { } else {
rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData); rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
socket = (rawSocket as Socket); socket = rawSocket;
}
} }
rawSocket.addEventListener(Event.CLOSE, onSocketClose);
rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
rawSocket.connect(host, port); rawSocket.connect(host, port);
} }
public function send(data:String):int { public function send(encData:String):int {
var data:String = decodeURIComponent(encData);
if (readyState == OPEN) { if (readyState == OPEN) {
socket.writeByte(0x00); socket.writeByte(0x00);
socket.writeUTFBytes(decodeURIComponent(data)); socket.writeUTFBytes(data);
socket.writeByte(0xff); socket.writeByte(0xff);
socket.flush(); socket.flush();
main.log("sent: " + data); main.log("sent: " + data);
return -1; return -1;
} else if (readyState == CLOSED) { } else if (readyState == CLOSED) {
var bytes:ByteArray = new ByteArray(); var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(decodeURIComponent(data)); bytes.writeUTFBytes(data);
bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
// We use return value to let caller know bufferedAmount because we cannot fire // We use return value to let caller know bufferedAmount because we cannot fire
// stateChange event here which causes weird error: // stateChange event here which causes weird error:
...@@ -136,6 +133,9 @@ public class WebSocket extends EventDispatcher { ...@@ -136,6 +133,9 @@ public class WebSocket extends EventDispatcher {
main.log("close"); main.log("close");
dataQueue = []; dataQueue = [];
try { try {
socket.writeByte(0xff);
socket.writeByte(0x00);
socket.flush();
socket.close(); socket.close();
} catch (ex:Error) { } } catch (ex:Error) { }
readyState = CLOSED; readyState = CLOSED;
...@@ -156,7 +156,7 @@ public class WebSocket extends EventDispatcher { ...@@ -156,7 +156,7 @@ public class WebSocket extends EventDispatcher {
main.log("connected"); main.log("connected");
if (scheme == "wss") { if (scheme == "wss") {
ExternalInterface.call("console.log", "[WebSocket] starting SSL/TLS"); main.log("starting SSL/TLS");
tlsSocket.startTLS(rawSocket, host, tlsConfig); tlsSocket.startTLS(rawSocket, host, tlsConfig);
} }
...@@ -190,7 +190,6 @@ public class WebSocket extends EventDispatcher { ...@@ -190,7 +190,6 @@ public class WebSocket extends EventDispatcher {
main.log("request header:\n" + req); main.log("request header:\n" + req);
socket.writeUTFBytes(req); socket.writeUTFBytes(req);
main.log("sent key3: " + key3); main.log("sent key3: " + key3);
main.log("expected digest: " + expectedDigest);
writeBytes(key3); writeBytes(key3);
socket.flush(); socket.flush();
} }
...@@ -247,13 +246,16 @@ public class WebSocket extends EventDispatcher { ...@@ -247,13 +246,16 @@ public class WebSocket extends EventDispatcher {
headerState = 0; headerState = 0;
} }
if (headerState == 4) { if (headerState == 4) {
buffer.position = 0;
var headerStr:String = buffer.readUTFBytes(pos + 1); var headerStr:String = buffer.readUTFBytes(pos + 1);
main.log("response header:\n" + headerStr); main.log("response header:\n" + headerStr);
if (!validateHeader(headerStr)) return; if (!validateHeader(headerStr)) return;
makeBufferCompact(); removeBufferBefore(pos + 1);
pos = -1; pos = -1;
} }
} else if (headerState == 4) { } else if (headerState == 4) {
if (pos == 15) {
buffer.position = 0;
var replyDigest:String = readBytes(buffer, 16); var replyDigest:String = readBytes(buffer, 16);
main.log("reply digest: " + replyDigest); main.log("reply digest: " + replyDigest);
if (replyDigest != expectedDigest) { if (replyDigest != expectedDigest) {
...@@ -261,25 +263,32 @@ public class WebSocket extends EventDispatcher { ...@@ -261,25 +263,32 @@ public class WebSocket extends EventDispatcher {
return; return;
} }
headerState = 5; headerState = 5;
makeBufferCompact(); removeBufferBefore(pos + 1);
pos = -1; pos = -1;
readyState = OPEN; readyState = OPEN;
notifyStateChange(); notifyStateChange();
dispatchEvent(new Event("open")); dispatchEvent(new Event("open"));
}
} else { } else {
if (buffer[pos] == 0xff) { if (buffer[pos] == 0xff && pos > 0) {
//if (buffer.bytesAvailable > 1) { if (buffer[0] != 0x00) {
if (buffer.readByte() != 0x00) {
onError("data must start with \\x00"); onError("data must start with \\x00");
return; return;
} }
buffer.position = 1;
var data:String = buffer.readUTFBytes(pos - 1); var data:String = buffer.readUTFBytes(pos - 1);
main.log("received: " + data); main.log("received: " + data);
dataQueue.push(encodeURIComponent(data)); dataQueue.push(encodeURIComponent(data));
dispatchEvent(new WebSocketMessageEvent("message", data.length.toString())); dispatchEvent(new WebSocketMessageEvent("message", data.length.toString()));
buffer.readByte(); removeBufferBefore(pos + 1);
makeBufferCompact(); pos = -1;
} else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
main.log("received closing packet");
removeBufferBefore(pos + 1);
pos = -1; pos = -1;
close();
notifyStateChange();
dispatchEvent(new Event("close"));
} }
} }
} }
...@@ -318,6 +327,18 @@ public class WebSocket extends EventDispatcher { ...@@ -318,6 +327,18 @@ public class WebSocket extends EventDispatcher {
onError("invalid Connection: " + header["Connection"]); onError("invalid Connection: " + header["Connection"]);
return false; return false;
} }
if (!header["Sec-WebSocket-Origin"]) {
if (header["WebSocket-Origin"]) {
onError(
"The WebSocket server speaks old WebSocket protocol, " +
"which is not supported by web-socket-js. " +
"It requires WebSocket protocol 76 or later. " +
"Try newer version of the server if available.");
} else {
onError("header Sec-WebSocket-Origin is missing");
}
return false;
}
var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase(); var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
if (resOrigin != origin) { if (resOrigin != origin) {
onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'"); onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
...@@ -331,9 +352,10 @@ public class WebSocket extends EventDispatcher { ...@@ -331,9 +352,10 @@ public class WebSocket extends EventDispatcher {
return true; return true;
} }
private function makeBufferCompact():void { private function removeBufferBefore(pos:int):void {
if (buffer.position == 0) return; if (pos == 0) return;
var nextBuffer:ByteArray = new ByteArray(); var nextBuffer:ByteArray = new ByteArray();
buffer.position = pos;
buffer.readBytes(nextBuffer); buffer.readBytes(nextBuffer);
buffer = nextBuffer; buffer = nextBuffer;
} }
...@@ -399,12 +421,16 @@ public class WebSocket extends EventDispatcher { ...@@ -399,12 +421,16 @@ public class WebSocket extends EventDispatcher {
return bytes; return bytes;
} }
// Writes byte sequence to socket.
// bytes is String in special format where bytes[i] is i-th byte, not i-th character.
private function writeBytes(bytes:String):void { private function writeBytes(bytes:String):void {
for (var i:int = 0; i < bytes.length; ++i) { for (var i:int = 0; i < bytes.length; ++i) {
socket.writeByte(bytes.charCodeAt(i)); socket.writeByte(bytes.charCodeAt(i));
} }
} }
// Reads specified number of bytes from buffer, and returns it as special format String
// where bytes[i] is i-th byte (not i-th character).
private function readBytes(buffer:ByteArray, numBytes:int):String { private function readBytes(buffer:ByteArray, numBytes:int):String {
var bytes:String = ""; var bytes:String = "";
for (var i:int = 0; i < numBytes; ++i) { for (var i:int = 0; i < numBytes; ++i) {
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
// Set URL of your WebSocketMain.swf here: // Set URL of your WebSocketMain.swf here:
WebSocket.__swfLocation = "WebSocketMain.swf"; WebSocket.__swfLocation = "WebSocketMain.swf";
// Set this to dump debug message from Flash to console.log:
WebSocket.__debug = true;
var ws; var ws;
......
...@@ -292,7 +292,7 @@ ...@@ -292,7 +292,7 @@
WebSocket.__tasks = []; WebSocket.__tasks = [];
WebSocket.__initialize = function(debug) { WebSocket.__initialize = function() {
if (!WebSocket.__swfLocation) { if (!WebSocket.__swfLocation) {
console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf"); console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf");
return; return;
...@@ -320,9 +320,7 @@ ...@@ -320,9 +320,7 @@
//console.log("[WebSocket] FABridge initializad"); //console.log("[WebSocket] FABridge initializad");
WebSocket.__flash = FABridge.webSocket.root(); WebSocket.__flash = FABridge.webSocket.root();
WebSocket.__flash.setCallerUrl(location.href); WebSocket.__flash.setCallerUrl(location.href);
if (typeof debug !== "undefined") { WebSocket.__flash.setDebug(!!WebSocket.__debug);
WebSocket.__flash.setDebug(debug);
}
for (var i = 0; i < WebSocket.__tasks.length; ++i) { for (var i = 0; i < WebSocket.__tasks.length; ++i) {
WebSocket.__tasks[i](); WebSocket.__tasks[i]();
} }
...@@ -351,12 +349,12 @@ ...@@ -351,12 +349,12 @@
console.error(decodeURIComponent(message)); console.error(decodeURIComponent(message));
}; };
/* if (!WebSocket.__disableAutoInitialization) {
if (window.addEventListener) { if (window.addEventListener) {
window.addEventListener("load", WebSocket.__initialize, false); window.addEventListener("load", WebSocket.__initialize, false);
} else { } else {
window.attachEvent("onload", WebSocket.__initialize); window.attachEvent("onload", WebSocket.__initialize);
} }
*/ }
})(); })();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment