Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ee27864
Add gsolo SHA1 algorithm.
May 2, 2011
8b52ec4
com/gsolo/encryption/SHA1.as: stop compile warnings.
May 2, 2011
20c16e6
Fix protocol attribute handling.
May 2, 2011
4084a2c
Implement HyBi-07 version of the protocol.
May 2, 2011
128b3c7
Add HyBi-07 client masking.
May 2, 2011
9e67e24
Use byte length rather than character length.
kanaka May 6, 2011
fd5fa42
Merging.
gimite May 7, 2011
fa9e686
Merge branch 'master' into hybi-07
gimite May 7, 2011
4be643b
Merge remote branch 'gimite/master' into hybi-08
kanaka Aug 29, 2011
a308f86
Merge branch 'master' into hybi-07
gimite Sep 17, 2011
1646609
Style fixes.
gimite Sep 17, 2011
7e12c8a
Switching to hybi-10 protocol.
gimite Sep 17, 2011
a14e6f0
Implementing pong.
gimite Sep 18, 2011
c1e7a1a
Some more refactoring.
gimite Sep 18, 2011
24acbab
Fixing closing handshake.
gimite Sep 18, 2011
77507da
Updating README and adding NEWS.
gimite Sep 18, 2011
b54ae42
Updating NEWS.
gimite Sep 18, 2011
ba3d7af
Fixing a bug that decoded key was not 16 bytes.
gimite Sep 19, 2011
d3f2b90
Sending closing frame for some errors.
gimite Sep 19, 2011
23e7488
Handling IOError on send().
gimite Sep 25, 2011
f056e9f
Using status code to express connection error and server closing.
gimite Sep 25, 2011
55ae639
Adding wasClean, code, reason to onclose event object. Code was parti…
gimite Sep 26, 2011
1402307
Closing connection on masked frame from server. Issue #103
gimite Nov 5, 2011
4f7b9f8
Initializing automatically when web_socket.js is dynamically loaded.
gimite Dec 11, 2011
841b01d
Using swfobject.addDomLoadEvent() to simplify the initialization.
gimite Dec 14, 2011
b16e4ce
Using MozWebSocket when available. Issue #87
gimite Dec 17, 2011
47c316c
Firing close event on error in send(). Hopefully fixes issue #92 .
gimite Dec 17, 2011
30b0e3c
Switching to WebSocket version defined in RFC 6455.
gimite Dec 27, 2011
6ca1919
Updating README.
gimite Jan 30, 2012
2ee87e9
Adding reference to futurechimp's Ruby implementation of Flash socket…
gimite Mar 17, 2012
5c5ae54
@= flag of WebSocket implementation
abonec Aug 9, 2012
7677e7a
Renaming flash_implemented to __isFlashImplementation.
gimite Aug 9, 2012
1eb68ad
Include the patch from http://code.google.com/p/as3crypto/issues/deta…
jamadden Nov 1, 2012
10410eb
Rebuilding SWF files.
gimite Nov 3, 2012
b5f4ab7
Fixing a bug that it required whitespace after colon in the header. #126
gimite Dec 4, 2012
5c313e9
Add swfobject.js v2.2 source for DFSG compatibility.
kanaka Apr 12, 2013
7c4558f
Merge branch 'dfsg' of https://github.com/kanaka/web-socket-js
gimite Apr 25, 2013
c0855c6
Moving swfobject-src.js.
gimite Apr 25, 2013
0991abd
handle continuation frame from server #129
ken107 Jun 24, 2013
15db822
Recompiling SWF files.
gimite Jul 9, 2013
08cd896
Adding LICENCE.txt.
gimite Jul 9, 2013
c1fd4e0
Renaming license file.
gimite Jul 9, 2013
9c755f9
Fix for Issue #142
cope Dec 12, 2013
338760f
Using typeof(MessageEvent) to switch two ways to create MessageEvent.
gimite Dec 15, 2013
4405e7d
Adding bower.json. #148
gimite Jan 16, 2014
87ffc52
fix #151
x25 Mar 12, 2014
f64a2b9
Updating SWF files.
gimite Apr 3, 2014
35778e4
Remove moot `version` property from bower.json
kkirsche Jun 12, 2015
282e4d4
Merge pull request #162 from kkirsche/patch-1
gimite Jun 17, 2015
534dd4b
fix the empty cookie bug
lightsocks Oct 10, 2015
1ab03b3
Merge pull request #169 from lightsocks/master
gimite Oct 24, 2015
3010e94
Suppress SecurityError on processEvents(). #159
gimite Nov 7, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement HyBi-07 version of the protocol.
This is an initial implementation of
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07

Limitations:
- This does not implement the binary frame type since the API for this
  is still under discussion.
- The client to server XOR mask is 0, i.e. masking is not used.
  • Loading branch information
Joel Martin committed May 2, 2011
commit 4084a2cb88ef540da65fabf919c5d2b3af797071
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Note that it's technically possible that client sends arbitrary string as Cookie

### Proxy support

[The WebSocket spec](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
[The WebSocket spec](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.

The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.

Expand Down
Binary file modified WebSocketMain.swf
Binary file not shown.
Binary file modified WebSocketMainInsecure.zip
Binary file not shown.
254 changes: 132 additions & 122 deletions flash-src/WebSocket.as
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
// Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07

package {

import com.adobe.net.proxies.RFC2817Socket;
import com.gsolo.encryption.MD5;
import com.gsolo.encryption.SHA1;
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
Expand All @@ -30,6 +31,7 @@ public class WebSocket extends EventDispatcher {
private static var OPEN:int = 1;
private static var CLOSING:int = 2;
private static var CLOSED:int = 3;
private static var GUID:String = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

private var id:int;
private var rawSocket:Socket;
Expand All @@ -48,9 +50,14 @@ public class WebSocket extends EventDispatcher {
private var readyState:int = CONNECTING;
private var cookie:String;
private var headers:String;
private var noiseChars:Array;
private var expectedDigest:String;
private var logger:IWebSocketLogger;
private var b64encoder:Base64Encoder = new Base64Encoder();

private var frame_fin:int = -1;
private var frame_opcode:int = -1;
private var frame_hlength:uint = 0;
private var frame_plength:uint = 0;

public function WebSocket(
id:int, url:String, protocol:String, origin:String,
Expand All @@ -59,7 +66,6 @@ public class WebSocket extends EventDispatcher {
logger:IWebSocketLogger) {
this.logger = logger;
this.id = id;
initNoiseChars();
this.url = url;
var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
Expand Down Expand Up @@ -126,30 +132,100 @@ public class WebSocket extends EventDispatcher {
}

public function send(encData:String):int {
var data:String = decodeURIComponent(encData);
var raw_str:String = decodeURIComponent(encData);
var data:ByteArray = new ByteArray();
if (readyState == OPEN) {
socket.writeByte(0x00);
socket.writeUTFBytes(data);
socket.writeByte(0xff);
// TODO: binary API support
data.writeByte(0x80 | 0x01); // FIN + text opcode

var plength:uint = raw_str.length;
if (plength <= 125) {
data.writeByte(0x80 | plength); // Masked + length
} else if (plength > 125 && plength < 65536) {
data.writeByte(0x80 | 126); // Masked + 126
data.writeShort(plength);
} else if (plength >= 65536 && plength < 4294967296) {
data.writeByte(0x80 | 127); // Masked + 127
data.writeUnsignedInt(0); // zero high order bits
data.writeUnsignedInt(plength);
} else {
fatal("Send frame size too large");
return 0;
}

var mask:uint = 0; // Null mask
//var mask:uint = randomInt(0, 4294967295);
data.writeUnsignedInt(mask);
data.writeUTFBytes(raw_str);

socket.writeBytes(data);
socket.flush();
logger.log("sent: " + data);
return -1;
} else if (readyState == CLOSING || readyState == CLOSED) {
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(data);
return bytes.length; // not sure whether it should include \x00 and \xff
data.writeUTFBytes(raw_str);
return data.length; // not sure whether it should include \x00 and \xff
} else {
fatal("invalid state");
return 0;
}
}

public function parseFrame():int {
var cur_pos:int = buffer.position;

frame_hlength = 2;
if (buffer.length < frame_hlength) {
return -1;
}

frame_opcode = buffer[0] & 0x0f;
frame_fin = (buffer[0] & 0x80) >> 7;
frame_plength = buffer[1] & 0x7f;

if (frame_plength == 126) {
frame_hlength = 4;
if (buffer.length < frame_hlength) {
return -1;
}

buffer.endian = Endian.BIG_ENDIAN;
buffer.position = 2;
frame_plength = buffer.readUnsignedShort();
buffer.position = cur_pos;
} else if (frame_plength == 127) {
frame_hlength = 10;
if (buffer.length < frame_hlength) {
return -1;
}

buffer.endian = Endian.BIG_ENDIAN;
buffer.position = 2;
// Protocol allows 64-bit length, but we only handle 32-bit
var big:uint = buffer.readUnsignedInt(); // Skip high 32-bits
frame_plength = buffer.readUnsignedInt(); // Low 32-bits
buffer.position = cur_pos;
if (big != 0) {
onError("Frame length exceeds 4294967295. Bailing out!");
return -1;
}
}

if (buffer.length < frame_hlength + frame_plength) {
return -1;
}

return 1;
}

public function close(isError:Boolean = false):void {
logger.log("close");
try {
if (readyState == OPEN && !isError) {
socket.writeByte(0xff);
socket.writeByte(0x00);
// TODO: send code and reason
socket.writeByte(0x80 | 0x08); // FIN + close opcode
socket.writeByte(0x80 | 0x00); // Masked + no payload
socket.writeUnsignedInt(0x00); // Mask
socket.flush();
}
socket.close();
Expand All @@ -168,31 +244,31 @@ public class WebSocket extends EventDispatcher {

var defaultPort:int = scheme == "wss" ? 443 : 80;
var hostValue:String = host + (port == defaultPort ? "" : ":" + port);
var key1:String = generateKey();
var key2:String = generateKey();
var key3:String = generateKey3();
expectedDigest = getSecurityDigest(key1, key2, key3);
var key:String = generateKey();

SHA1.b64pad = "=";
expectedDigest = SHA1.b64_sha1(key + GUID);
logger.error("expectedDigest: " + expectedDigest);

var opt:String = "";
if (protocol) opt += "Sec-WebSocket-Protocol: " + protocol + "\r\n";
// if caller passes additional headers they must end with "\r\n"
if (headers) opt += headers;

var req:String = StringUtil.substitute(
"GET {0} HTTP/1.1\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Host: {1}\r\n" +
"Origin: {2}\r\n" +
"Cookie: {3}\r\n" +
"Sec-WebSocket-Key1: {4}\r\n" +
"Sec-WebSocket-Key2: {5}\r\n" +
"{6}" +
"Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Key: {2}\r\n" +
"Sec-WebSocket-Origin: {3}\r\n" +
"Sec-WebSocket-Version: 7\r\n" +
"Cookie: {4}\r\n" +
"{5}" +
"\r\n",
path, hostValue, origin, cookie, key1, key2, opt);
path, hostValue, key, origin, cookie, opt);
logger.log("request header:\n" + req);
socket.writeUTFBytes(req);
logger.log("sent key3: " + key3);
writeBytes(key3);
socket.flush();
}

Expand Down Expand Up @@ -246,46 +322,30 @@ public class WebSocket extends EventDispatcher {
if (headerState == 4) {
var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
logger.log("response header:\n" + headerStr);
if (!validateHeader(headerStr)) return;
removeBufferBefore(pos + 1);
pos = -1;
}
} else if (headerState == 4) {
if (pos == 15) {
var replyDigest:String = readBytes(buffer, 0, 16);
logger.log("reply digest: " + replyDigest);
if (replyDigest != expectedDigest) {
onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
return;
}
headerState = 5;
if (!validateHandshake(headerStr)) return;
removeBufferBefore(pos + 1);
pos = -1;
readyState = OPEN;
this.dispatchEvent(new WebSocketEvent("open"));
}
} else {
if (buffer[pos] == 0xff && pos > 0) {
if (buffer[0] != 0x00) {
onError("data must start with \\x00");
return;
}
var data:String = readUTFBytes(buffer, 1, pos - 1);
logger.log("received: " + data);
this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
removeBufferBefore(pos + 1);
pos = -1;
} else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
logger.log("received closing packet");
removeBufferBefore(pos + 1);
if (parseFrame() == 1) {
var data:String = readUTFBytes(buffer, frame_hlength, frame_plength);
removeBufferBefore(frame_hlength + frame_plength);
pos = -1;
close();
if (frame_opcode == 0x01 || frame_opcode == 0x02) {
this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
} else if (frame_opcode == 0x08) {
// TODO: extract code and reason string
logger.log("received closing packet");
close();
}
}
}
}
}

private function validateHeader(headerStr:String):Boolean {
private function validateHandshake(headerStr:String):Boolean {
var lines:Array = headerStr.split(/\r\n/);
if (!lines[0].match(/^HTTP\/1.1 101 /)) {
onError("bad response: " + lines[0]);
Expand All @@ -311,21 +371,12 @@ public class WebSocket extends EventDispatcher {
onError("invalid Connection: " + header["Connection"]);
return false;
}
if (!lowerHeader["sec-websocket-origin"]) {
if (lowerHeader["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 = lowerHeader["sec-websocket-origin"];
if (resOrigin != origin) {
onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
if (!lowerHeader["sec-websocket-accept"]) {
onError(
"The WebSocket server speaks old WebSocket protocol, " +
"which is not supported by web-socket-js. " +
"It requires WebSocket protocol HyBi 7. " +
"Try newer version of the server if available.");
return false;
}
if (protocol) {
Expand All @@ -337,6 +388,12 @@ public class WebSocket extends EventDispatcher {
return false;
}
}

var replyDigest:String = header["sec-websocket-accept"]
if (replyDigest != expectedDigest) {
onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
return false;
}
return true;
}

Expand All @@ -348,61 +405,14 @@ public class WebSocket extends EventDispatcher {
buffer = nextBuffer;
}

private function initNoiseChars():void {
noiseChars = new Array();
for (var i:int = 0x21; i <= 0x2f; ++i) {
noiseChars.push(String.fromCharCode(i));
}
for (var j:int = 0x3a; j <= 0x7a; ++j) {
noiseChars.push(String.fromCharCode(j));
}
}

private function generateKey():String {
var spaces:uint = randomInt(1, 12);
var max:uint = uint.MAX_VALUE / spaces;
var number:uint = randomInt(0, max);
var key:String = (number * spaces).toString();
var noises:int = randomInt(1, 12);
var pos:int;
for (var i:int = 0; i < noises; ++i) {
var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
pos = randomInt(0, key.length);
key = key.substr(0, pos) + char + key.substr(pos);
}
for (var j:int = 0; j < spaces; ++j) {
pos = randomInt(1, key.length - 1);
key = key.substr(0, pos) + " " + key.substr(pos);
}
return key;
}

private function generateKey3():String {
var key3:String = "";
for (var i:int = 0; i < 8; ++i) {
key3 += String.fromCharCode(randomInt(0, 255));
}
return key3;
}

private function getSecurityDigest(key1:String, key2:String, key3:String):String {
var bytes1:String = keyToBytes(key1);
var bytes2:String = keyToBytes(key2);
return MD5.rstr_md5(bytes1 + bytes2 + key3);
}

private function keyToBytes(key:String):String {
var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
var spaces:uint = 0;
for (var i:int = 0; i < key.length; ++i) {
if (key.charAt(i) == " ") ++spaces;
var vals:String = "";
for (var i:int = 0; i < 16; i++) {
vals = vals + randomInt(0, 127).toString();
}
var resultNum:uint = keyNum / spaces;
var bytes:String = "";
for (var j:int = 3; j >= 0; --j) {
bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
}
return bytes;
b64encoder.reset();
b64encoder.encode(vals);
return b64encoder.toString();
}

// Writes byte sequence to socket.
Expand Down
5 changes: 4 additions & 1 deletion flash-src/WebSocketMain.as
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
// Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07

package {

Expand Down Expand Up @@ -40,6 +40,9 @@ public class WebSocketMain extends Sprite implements IWebSocketLogger{

public function setDebug(val:Boolean):void {
debug = val;
if (val) {
log("debug enabled");
}
}

private function loadDefaultPolicyFile(wsUrl:String):void {
Expand Down
Loading