sonixlabs-em-websocket 0.3.8 → 0.5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +69 -0
- data/Gemfile +6 -0
- data/LICENCE +7 -0
- data/README.md +100 -56
- data/README.md.BACKUP.14928.md +195 -0
- data/README.md.BASE.14928.md +77 -0
- data/README.md.LOCAL.14928.md +98 -0
- data/README.md.REMOTE.14928.md +142 -0
- data/examples/echo.rb +23 -7
- data/examples/ping.rb +24 -0
- data/examples/test.html +5 -6
- data/lib/em-websocket.rb +4 -2
- data/lib/em-websocket/close03.rb +3 -0
- data/lib/em-websocket/close05.rb +3 -0
- data/lib/em-websocket/close06.rb +3 -0
- data/lib/em-websocket/close75.rb +2 -1
- data/lib/em-websocket/connection.rb +219 -73
- data/lib/em-websocket/framing03.rb +6 -11
- data/lib/em-websocket/framing05.rb +6 -11
- data/lib/em-websocket/framing07.rb +25 -20
- data/lib/em-websocket/framing76.rb +6 -15
- data/lib/em-websocket/handler.rb +69 -28
- data/lib/em-websocket/handler03.rb +0 -1
- data/lib/em-websocket/handler05.rb +0 -1
- data/lib/em-websocket/handler06.rb +0 -1
- data/lib/em-websocket/handler07.rb +0 -1
- data/lib/em-websocket/handler08.rb +0 -1
- data/lib/em-websocket/handler13.rb +0 -1
- data/lib/em-websocket/handler76.rb +2 -0
- data/lib/em-websocket/handshake.rb +156 -0
- data/lib/em-websocket/handshake04.rb +18 -56
- data/lib/em-websocket/handshake75.rb +15 -8
- data/lib/em-websocket/handshake76.rb +15 -14
- data/lib/em-websocket/masking04.rb +4 -30
- data/lib/em-websocket/message_processor_03.rb +13 -4
- data/lib/em-websocket/message_processor_06.rb +25 -13
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +35 -24
- data/spec/helper.rb +82 -55
- data/spec/integration/common_spec.rb +90 -70
- data/spec/integration/draft03_spec.rb +84 -56
- data/spec/integration/draft05_spec.rb +14 -12
- data/spec/integration/draft06_spec.rb +66 -9
- data/spec/integration/draft13_spec.rb +59 -29
- data/spec/integration/draft75_spec.rb +46 -40
- data/spec/integration/draft76_spec.rb +113 -109
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/integration/shared_examples.rb +174 -0
- data/spec/unit/framing_spec.rb +83 -110
- data/spec/unit/handshake_spec.rb +216 -0
- data/spec/unit/masking_spec.rb +2 -0
- metadata +31 -71
- data/examples/flash_policy_file_server.rb +0 -21
- data/examples/js/FABridge.js +0 -604
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +0 -4
- data/examples/js/web_socket.js +0 -312
- data/lib/em-websocket/handler_factory.rb +0 -107
- data/spec/unit/handler_spec.rb +0 -147
Binary file
|
data/examples/js/swfobject.js
DELETED
@@ -1,4 +0,0 @@
|
|
1
|
-
/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
|
2
|
-
is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
|
3
|
-
*/
|
4
|
-
var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();
|
data/examples/js/web_socket.js
DELETED
@@ -1,312 +0,0 @@
|
|
1
|
-
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
2
|
-
// Lincense: New BSD Lincense
|
3
|
-
// Reference: http://dev.w3.org/html5/websockets/
|
4
|
-
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
5
|
-
|
6
|
-
(function() {
|
7
|
-
|
8
|
-
if (window.WebSocket) return;
|
9
|
-
|
10
|
-
var console = window.console;
|
11
|
-
if (!console) console = {log: function(){ }, error: function(){ }};
|
12
|
-
|
13
|
-
function hasFlash() {
|
14
|
-
if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) {
|
15
|
-
return !!navigator.plugins['Shockwave Flash'].description;
|
16
|
-
}
|
17
|
-
if ('ActiveXObject' in window) {
|
18
|
-
try {
|
19
|
-
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
|
20
|
-
} catch (e) {}
|
21
|
-
}
|
22
|
-
return false;
|
23
|
-
}
|
24
|
-
|
25
|
-
if (!hasFlash()) {
|
26
|
-
console.error("Flash Player is not installed.");
|
27
|
-
return;
|
28
|
-
}
|
29
|
-
|
30
|
-
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
31
|
-
var self = this;
|
32
|
-
self.readyState = WebSocket.CONNECTING;
|
33
|
-
self.bufferedAmount = 0;
|
34
|
-
WebSocket.__addTask(function() {
|
35
|
-
self.__flash =
|
36
|
-
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
37
|
-
|
38
|
-
self.__flash.addEventListener("open", function(fe) {
|
39
|
-
try {
|
40
|
-
if (self.onopen) self.onopen();
|
41
|
-
} catch (e) {
|
42
|
-
console.error(e.toString());
|
43
|
-
}
|
44
|
-
});
|
45
|
-
|
46
|
-
self.__flash.addEventListener("close", function(fe) {
|
47
|
-
try {
|
48
|
-
if (self.onclose) self.onclose();
|
49
|
-
} catch (e) {
|
50
|
-
console.error(e.toString());
|
51
|
-
}
|
52
|
-
});
|
53
|
-
|
54
|
-
self.__flash.addEventListener("message", function(fe) {
|
55
|
-
var data = decodeURIComponent(fe.getData());
|
56
|
-
try {
|
57
|
-
if (self.onmessage) {
|
58
|
-
var e;
|
59
|
-
if (window.MessageEvent) {
|
60
|
-
e = document.createEvent("MessageEvent");
|
61
|
-
e.initMessageEvent("message", false, false, data, null, null, window);
|
62
|
-
} else { // IE
|
63
|
-
e = {data: data};
|
64
|
-
}
|
65
|
-
self.onmessage(e);
|
66
|
-
}
|
67
|
-
} catch (e) {
|
68
|
-
console.error(e.toString());
|
69
|
-
}
|
70
|
-
});
|
71
|
-
|
72
|
-
self.__flash.addEventListener("stateChange", function(fe) {
|
73
|
-
try {
|
74
|
-
self.readyState = fe.getReadyState();
|
75
|
-
self.bufferedAmount = fe.getBufferedAmount();
|
76
|
-
} catch (e) {
|
77
|
-
console.error(e.toString());
|
78
|
-
}
|
79
|
-
});
|
80
|
-
|
81
|
-
//console.log("[WebSocket] Flash object is ready");
|
82
|
-
});
|
83
|
-
}
|
84
|
-
|
85
|
-
WebSocket.prototype.send = function(data) {
|
86
|
-
if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
|
87
|
-
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
|
88
|
-
}
|
89
|
-
var result = this.__flash.send(data);
|
90
|
-
if (result < 0) { // success
|
91
|
-
return true;
|
92
|
-
} else {
|
93
|
-
this.bufferedAmount = result;
|
94
|
-
return false;
|
95
|
-
}
|
96
|
-
};
|
97
|
-
|
98
|
-
WebSocket.prototype.close = function() {
|
99
|
-
if (!this.__flash) return;
|
100
|
-
if (this.readyState != WebSocket.OPEN) return;
|
101
|
-
this.__flash.close();
|
102
|
-
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
|
103
|
-
// which causes weird error:
|
104
|
-
// > You are trying to call recursively into the Flash Player which is not allowed.
|
105
|
-
this.readyState = WebSocket.CLOSED;
|
106
|
-
if (this.onclose) this.onclose();
|
107
|
-
};
|
108
|
-
|
109
|
-
/**
|
110
|
-
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
111
|
-
*
|
112
|
-
* @param {string} type
|
113
|
-
* @param {function} listener
|
114
|
-
* @param {boolean} useCapture !NB Not implemented yet
|
115
|
-
* @return void
|
116
|
-
*/
|
117
|
-
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
|
118
|
-
if (!('__events' in this)) {
|
119
|
-
this.__events = {};
|
120
|
-
}
|
121
|
-
if (!(type in this.__events)) {
|
122
|
-
this.__events[type] = [];
|
123
|
-
if ('function' == typeof this['on' + type]) {
|
124
|
-
this.__events[type].defaultHandler = this['on' + type];
|
125
|
-
this['on' + type] = WebSocket_FireEvent(this, type);
|
126
|
-
}
|
127
|
-
}
|
128
|
-
this.__events[type].push(listener);
|
129
|
-
};
|
130
|
-
|
131
|
-
/**
|
132
|
-
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
133
|
-
*
|
134
|
-
* @param {string} type
|
135
|
-
* @param {function} listener
|
136
|
-
* @param {boolean} useCapture NB! Not implemented yet
|
137
|
-
* @return void
|
138
|
-
*/
|
139
|
-
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
|
140
|
-
if (!('__events' in this)) {
|
141
|
-
this.__events = {};
|
142
|
-
}
|
143
|
-
if (!(type in this.__events)) return;
|
144
|
-
for (var i = this.__events.length; i > -1; --i) {
|
145
|
-
if (listener === this.__events[type][i]) {
|
146
|
-
this.__events[type].splice(i, 1);
|
147
|
-
break;
|
148
|
-
}
|
149
|
-
}
|
150
|
-
};
|
151
|
-
|
152
|
-
/**
|
153
|
-
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
154
|
-
*
|
155
|
-
* @param {WebSocketEvent} event
|
156
|
-
* @return void
|
157
|
-
*/
|
158
|
-
WebSocket.prototype.dispatchEvent = function(event) {
|
159
|
-
if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
160
|
-
if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
161
|
-
|
162
|
-
for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
|
163
|
-
this.__events[event.type][i](event);
|
164
|
-
if (event.cancelBubble) break;
|
165
|
-
}
|
166
|
-
|
167
|
-
if (false !== event.returnValue &&
|
168
|
-
'function' == typeof this.__events[event.type].defaultHandler)
|
169
|
-
{
|
170
|
-
this.__events[event.type].defaultHandler(event);
|
171
|
-
}
|
172
|
-
};
|
173
|
-
|
174
|
-
/**
|
175
|
-
*
|
176
|
-
* @param {object} object
|
177
|
-
* @param {string} type
|
178
|
-
*/
|
179
|
-
function WebSocket_FireEvent(object, type) {
|
180
|
-
return function(data) {
|
181
|
-
var event = new WebSocketEvent();
|
182
|
-
event.initEvent(type, true, true);
|
183
|
-
event.target = event.currentTarget = object;
|
184
|
-
for (var key in data) {
|
185
|
-
event[key] = data[key];
|
186
|
-
}
|
187
|
-
object.dispatchEvent(event, arguments);
|
188
|
-
};
|
189
|
-
}
|
190
|
-
|
191
|
-
/**
|
192
|
-
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
|
193
|
-
*
|
194
|
-
* @class
|
195
|
-
* @constructor
|
196
|
-
*/
|
197
|
-
function WebSocketEvent(){}
|
198
|
-
|
199
|
-
/**
|
200
|
-
*
|
201
|
-
* @type boolean
|
202
|
-
*/
|
203
|
-
WebSocketEvent.prototype.cancelable = true;
|
204
|
-
|
205
|
-
/**
|
206
|
-
*
|
207
|
-
* @type boolean
|
208
|
-
*/
|
209
|
-
WebSocketEvent.prototype.cancelBubble = false;
|
210
|
-
|
211
|
-
/**
|
212
|
-
*
|
213
|
-
* @return void
|
214
|
-
*/
|
215
|
-
WebSocketEvent.prototype.preventDefault = function() {
|
216
|
-
if (this.cancelable) {
|
217
|
-
this.returnValue = false;
|
218
|
-
}
|
219
|
-
};
|
220
|
-
|
221
|
-
/**
|
222
|
-
*
|
223
|
-
* @return void
|
224
|
-
*/
|
225
|
-
WebSocketEvent.prototype.stopPropagation = function() {
|
226
|
-
this.cancelBubble = true;
|
227
|
-
};
|
228
|
-
|
229
|
-
/**
|
230
|
-
*
|
231
|
-
* @param {string} eventTypeArg
|
232
|
-
* @param {boolean} canBubbleArg
|
233
|
-
* @param {boolean} cancelableArg
|
234
|
-
* @return void
|
235
|
-
*/
|
236
|
-
WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
|
237
|
-
this.type = eventTypeArg;
|
238
|
-
this.cancelable = cancelableArg;
|
239
|
-
this.timeStamp = new Date();
|
240
|
-
};
|
241
|
-
|
242
|
-
|
243
|
-
WebSocket.CONNECTING = 0;
|
244
|
-
WebSocket.OPEN = 1;
|
245
|
-
WebSocket.CLOSED = 2;
|
246
|
-
|
247
|
-
WebSocket.__tasks = [];
|
248
|
-
|
249
|
-
WebSocket.__initialize = function() {
|
250
|
-
if (!WebSocket.__swfLocation) {
|
251
|
-
//console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf");
|
252
|
-
//return;
|
253
|
-
WebSocket.__swfLocation = "js/WebSocketMain.swf";
|
254
|
-
}
|
255
|
-
var container = document.createElement("div");
|
256
|
-
container.id = "webSocketContainer";
|
257
|
-
// Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden
|
258
|
-
// here because it prevents Flash from loading at least in IE.
|
259
|
-
container.style.position = "absolute";
|
260
|
-
container.style.left = "-100px";
|
261
|
-
container.style.top = "-100px";
|
262
|
-
var holder = document.createElement("div");
|
263
|
-
holder.id = "webSocketFlash";
|
264
|
-
container.appendChild(holder);
|
265
|
-
document.body.appendChild(container);
|
266
|
-
swfobject.embedSWF(
|
267
|
-
WebSocket.__swfLocation, "webSocketFlash", "8", "8", "9.0.0",
|
268
|
-
null, {bridgeName: "webSocket"}, null, null,
|
269
|
-
function(e) {
|
270
|
-
if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
|
271
|
-
}
|
272
|
-
);
|
273
|
-
FABridge.addInitializationCallback("webSocket", function() {
|
274
|
-
try {
|
275
|
-
//console.log("[WebSocket] FABridge initializad");
|
276
|
-
WebSocket.__flash = FABridge.webSocket.root();
|
277
|
-
WebSocket.__flash.setCallerUrl(location.href);
|
278
|
-
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
|
279
|
-
WebSocket.__tasks[i]();
|
280
|
-
}
|
281
|
-
WebSocket.__tasks = [];
|
282
|
-
} catch (e) {
|
283
|
-
console.error("[WebSocket] " + e.toString());
|
284
|
-
}
|
285
|
-
});
|
286
|
-
};
|
287
|
-
|
288
|
-
WebSocket.__addTask = function(task) {
|
289
|
-
if (WebSocket.__flash) {
|
290
|
-
task();
|
291
|
-
} else {
|
292
|
-
WebSocket.__tasks.push(task);
|
293
|
-
}
|
294
|
-
}
|
295
|
-
|
296
|
-
// called from Flash
|
297
|
-
function webSocketLog(message) {
|
298
|
-
console.log(decodeURIComponent(message));
|
299
|
-
}
|
300
|
-
|
301
|
-
// called from Flash
|
302
|
-
function webSocketError(message) {
|
303
|
-
console.error(decodeURIComponent(message));
|
304
|
-
}
|
305
|
-
|
306
|
-
if (window.addEventListener) {
|
307
|
-
window.addEventListener("load", WebSocket.__initialize, false);
|
308
|
-
} else {
|
309
|
-
window.attachEvent("onload", WebSocket.__initialize);
|
310
|
-
}
|
311
|
-
|
312
|
-
})();
|
@@ -1,107 +0,0 @@
|
|
1
|
-
module EventMachine
|
2
|
-
module WebSocket
|
3
|
-
class HandlerFactory
|
4
|
-
PATH = /^(\w+) (\/[^\s]*) HTTP\/1\.1$/
|
5
|
-
HEADER = /^([^:]+):\s*(.+)$/
|
6
|
-
|
7
|
-
def self.build(connection, data, secure = false, debug = false)
|
8
|
-
(header, remains) = data.split("\r\n\r\n", 2)
|
9
|
-
unless remains
|
10
|
-
# The whole header has not been received yet.
|
11
|
-
return nil
|
12
|
-
end
|
13
|
-
|
14
|
-
request = {}
|
15
|
-
|
16
|
-
lines = header.split("\r\n")
|
17
|
-
|
18
|
-
# extract request path
|
19
|
-
first_line = lines.shift.match(PATH)
|
20
|
-
raise HandshakeError, "Invalid HTTP header" unless first_line
|
21
|
-
request['method'] = first_line[1].strip
|
22
|
-
request['path'] = first_line[2].strip
|
23
|
-
|
24
|
-
unless request["method"] == "GET"
|
25
|
-
raise HandshakeError, "Must be GET request"
|
26
|
-
end
|
27
|
-
|
28
|
-
# extract query string values
|
29
|
-
request['query'] = Addressable::URI.parse(request['path']).query_values ||= {}
|
30
|
-
# extract remaining headers
|
31
|
-
lines.each do |line|
|
32
|
-
h = HEADER.match(line)
|
33
|
-
request[h[1].strip.downcase] = h[2].strip if h
|
34
|
-
end
|
35
|
-
|
36
|
-
build_with_request(connection, request, remains, secure, debug)
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.build_with_request(connection, request, remains, secure = false, debug = false)
|
40
|
-
# Determine version heuristically
|
41
|
-
version = if request['sec-websocket-version']
|
42
|
-
# Used from drafts 04 onwards
|
43
|
-
request['sec-websocket-version'].to_i
|
44
|
-
elsif request['sec-websocket-draft']
|
45
|
-
# Used in drafts 01 - 03
|
46
|
-
request['sec-websocket-draft'].to_i
|
47
|
-
elsif request['sec-websocket-key1']
|
48
|
-
76
|
49
|
-
else
|
50
|
-
75
|
51
|
-
end
|
52
|
-
|
53
|
-
# Additional handling of bytes after the header if required
|
54
|
-
case version
|
55
|
-
when 75
|
56
|
-
if !remains.empty?
|
57
|
-
raise HandshakeError, "Extra bytes after header"
|
58
|
-
end
|
59
|
-
when 76, 1..3
|
60
|
-
if remains.length < 8
|
61
|
-
# The whole third-key has not been received yet.
|
62
|
-
return nil
|
63
|
-
elsif remains.length > 8
|
64
|
-
raise HandshakeError, "Extra bytes after third key"
|
65
|
-
end
|
66
|
-
request['third-key'] = remains
|
67
|
-
end
|
68
|
-
|
69
|
-
# Validate that Connection and Upgrade headers
|
70
|
-
unless request['connection'] && request['connection'] =~ /Upgrade/ && request['upgrade'] && request['upgrade'].downcase == 'websocket'
|
71
|
-
raise HandshakeError, "Connection and Upgrade headers required"
|
72
|
-
end
|
73
|
-
|
74
|
-
# transform headers
|
75
|
-
protocol = (secure ? "wss" : "ws")
|
76
|
-
request['host'] = Addressable::URI.parse("#{protocol}://"+request['host'])
|
77
|
-
|
78
|
-
case version
|
79
|
-
when 75
|
80
|
-
Handler75.new(connection, request, debug)
|
81
|
-
when 76
|
82
|
-
Handler76.new(connection, request, debug)
|
83
|
-
when 1..3
|
84
|
-
# We'll use handler03 - I believe they're all compatible
|
85
|
-
Handler03.new(connection, request, debug)
|
86
|
-
when 5
|
87
|
-
Handler05.new(connection, request, debug)
|
88
|
-
when 6
|
89
|
-
Handler06.new(connection, request, debug)
|
90
|
-
when 7
|
91
|
-
Handler07.new(connection, request, debug)
|
92
|
-
when 8
|
93
|
-
# drafts 9, 10, 11 and 12 should never change the version
|
94
|
-
# number as they are all the same as version 08.
|
95
|
-
Handler08.new(connection, request, debug)
|
96
|
-
when 13
|
97
|
-
# drafts 13 to 17 all identify as version 13 as they are
|
98
|
-
# only minor changes or text changes.
|
99
|
-
Handler13.new(connection, request, debug)
|
100
|
-
else
|
101
|
-
# According to spec should abort the connection
|
102
|
-
raise WebSocketError, "Protocol version #{version} not supported"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
data/spec/unit/handler_spec.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
describe "EventMachine::WebSocket::Handler" do
|
4
|
-
before :each do
|
5
|
-
@request = {
|
6
|
-
:port => 80,
|
7
|
-
:method => "GET",
|
8
|
-
:path => "/demo",
|
9
|
-
:headers => {
|
10
|
-
'Host' => 'example.com',
|
11
|
-
'Connection' => 'Upgrade',
|
12
|
-
'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
13
|
-
'Sec-WebSocket-Protocol' => 'sample',
|
14
|
-
'Upgrade' => 'WebSocket',
|
15
|
-
'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
16
|
-
'Origin' => 'http://example.com'
|
17
|
-
},
|
18
|
-
:body => '^n:ds[4U'
|
19
|
-
}
|
20
|
-
@secure_request = @request.merge(:port => 443)
|
21
|
-
|
22
|
-
@response = {
|
23
|
-
:headers => {
|
24
|
-
"Upgrade" => "WebSocket",
|
25
|
-
"Connection" => "Upgrade",
|
26
|
-
"Sec-WebSocket-Location" => "ws://example.com/demo",
|
27
|
-
"Sec-WebSocket-Origin" => "http://example.com",
|
28
|
-
"Sec-WebSocket-Protocol" => "sample"
|
29
|
-
},
|
30
|
-
:body => "8jKS\'y:G*Co,Wxa-"
|
31
|
-
}
|
32
|
-
@secure_response = @response.merge(:headers => @response[:headers].merge('Sec-WebSocket-Location' => "wss://example.com/demo"))
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should handle good request" do
|
36
|
-
handler(@request).should send_handshake(@response)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should handle good request to secure default port if secure mode is enabled" do
|
40
|
-
handler(@secure_request, true).should send_handshake(@secure_response)
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should not handle good request to secure default port if secure mode is disabled" do
|
44
|
-
handler(@secure_request, false).should_not send_handshake(@secure_response)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should handle good request on nondefault port" do
|
48
|
-
@request[:port] = 8081
|
49
|
-
@request[:headers]['Host'] = 'example.com:8081'
|
50
|
-
@response[:headers]['Sec-WebSocket-Location'] =
|
51
|
-
'ws://example.com:8081/demo'
|
52
|
-
|
53
|
-
handler(@request).should send_handshake(@response)
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should handle good request to secure nondefault port" do
|
57
|
-
@secure_request[:port] = 8081
|
58
|
-
@secure_request[:headers]['Host'] = 'example.com:8081'
|
59
|
-
@secure_response[:headers]['Sec-WebSocket-Location'] = 'wss://example.com:8081/demo'
|
60
|
-
handler(@secure_request, true).should send_handshake(@secure_response)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should handle good request with no protocol" do
|
64
|
-
@request[:headers].delete('Sec-WebSocket-Protocol')
|
65
|
-
@response[:headers].delete("Sec-WebSocket-Protocol")
|
66
|
-
|
67
|
-
handler(@request).should send_handshake(@response)
|
68
|
-
end
|
69
|
-
|
70
|
-
it "should handle extra headers by simply ignoring them" do
|
71
|
-
@request[:headers]['EmptyValue'] = ""
|
72
|
-
@request[:headers]['AKey'] = "AValue"
|
73
|
-
|
74
|
-
handler(@request).should send_handshake(@response)
|
75
|
-
end
|
76
|
-
|
77
|
-
it "should raise error on HTTP request" do
|
78
|
-
@request[:headers] = {
|
79
|
-
'Host' => 'www.google.com',
|
80
|
-
'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB6 GTBA',
|
81
|
-
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
82
|
-
'Accept-Language' => 'en-us,en;q=0.5',
|
83
|
-
'Accept-Encoding' => 'gzip,deflate',
|
84
|
-
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
85
|
-
'Keep-Alive' => '300',
|
86
|
-
'Connection' => 'keep-alive',
|
87
|
-
}
|
88
|
-
|
89
|
-
lambda {
|
90
|
-
handler(@request).handshake
|
91
|
-
}.should raise_error(EM::WebSocket::HandshakeError)
|
92
|
-
end
|
93
|
-
|
94
|
-
it "should raise error on wrong method" do
|
95
|
-
@request[:method] = 'POST'
|
96
|
-
|
97
|
-
lambda {
|
98
|
-
handler(@request).handshake
|
99
|
-
}.should raise_error(EM::WebSocket::HandshakeError)
|
100
|
-
end
|
101
|
-
|
102
|
-
it "should raise error if upgrade header incorrect" do
|
103
|
-
@request[:headers]['Upgrade'] = 'NonWebSocket'
|
104
|
-
|
105
|
-
lambda {
|
106
|
-
handler(@request).handshake
|
107
|
-
}.should raise_error(EM::WebSocket::HandshakeError)
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should raise error if Sec-WebSocket-Protocol is empty" do
|
111
|
-
@request[:headers]['Sec-WebSocket-Protocol'] = ''
|
112
|
-
|
113
|
-
lambda {
|
114
|
-
handler(@request).handshake
|
115
|
-
}.should raise_error(EM::WebSocket::HandshakeError)
|
116
|
-
end
|
117
|
-
|
118
|
-
%w[Sec-WebSocket-Key1 Sec-WebSocket-Key2].each do |header|
|
119
|
-
it "should raise error if #{header} has zero spaces" do
|
120
|
-
@request[:headers][header] = 'nospaces'
|
121
|
-
|
122
|
-
lambda {
|
123
|
-
handler(@request).handshake
|
124
|
-
}.should raise_error(EM::WebSocket::HandshakeError, 'Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack')
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
|
129
|
-
@request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
|
130
|
-
|
131
|
-
lambda {
|
132
|
-
handler(@request).handshake
|
133
|
-
}.should raise_error(EM::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
|
134
|
-
end
|
135
|
-
|
136
|
-
it "should leave request with incomplete header" do
|
137
|
-
data = format_request(@request)
|
138
|
-
# Sends only half of the request
|
139
|
-
EM::WebSocket::HandlerFactory.build(mock(EM::WebSocket::Connection), data[0...(data.length / 2)]).should == nil
|
140
|
-
end
|
141
|
-
|
142
|
-
it "should leave request with incomplete third key" do
|
143
|
-
data = format_request(@request)
|
144
|
-
# Removes last two bytes of the third key
|
145
|
-
EM::WebSocket::HandlerFactory.build(mock(EM::WebSocket::Connection), data[0...(data.length - 2)]).should == nil
|
146
|
-
end
|
147
|
-
end
|