WebSocket.as 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
  2. // License: New BSD License
  3. // Reference: http://dev.w3.org/html5/websockets/
  4. // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
  5. package {
  6. import flash.display.*;
  7. import flash.events.*;
  8. import flash.external.*;
  9. import flash.net.*;
  10. import flash.system.*;
  11. import flash.utils.*;
  12. import mx.core.*;
  13. import mx.controls.*;
  14. import mx.events.*;
  15. import mx.utils.*;
  16. import com.adobe.net.proxies.RFC2817Socket;
  17. import com.hurlant.crypto.tls.TLSSocket;
  18. import com.hurlant.crypto.tls.TLSConfig;
  19. import com.hurlant.crypto.tls.TLSEngine;
  20. import com.hurlant.crypto.tls.TLSSecurityParameters;
  21. import com.gsolo.encryption.MD5;
  22. [Event(name="message", type="WebSocketMessageEvent")]
  23. [Event(name="open", type="flash.events.Event")]
  24. [Event(name="close", type="flash.events.Event")]
  25. [Event(name="error", type="flash.events.Event")]
  26. [Event(name="stateChange", type="WebSocketStateEvent")]
  27. public class WebSocket extends EventDispatcher {
  28. private static var CONNECTING:int = 0;
  29. private static var OPEN:int = 1;
  30. private static var CLOSING:int = 2;
  31. private static var CLOSED:int = 3;
  32. //private var rawSocket:RFC2817Socket;
  33. private var rawSocket:Socket;
  34. private var tlsSocket:TLSSocket;
  35. private var tlsConfig:TLSConfig;
  36. private var socket:Socket;
  37. private var main:WebSocketMain;
  38. private var url:String;
  39. private var scheme:String;
  40. private var host:String;
  41. private var port:uint;
  42. private var path:String;
  43. private var origin:String;
  44. private var protocol:String;
  45. private var buffer:ByteArray = new ByteArray();
  46. private var dataQueue:Array;
  47. private var headerState:int = 0;
  48. private var readyState:int = CONNECTING;
  49. private var bufferedAmount:int = 0;
  50. private var headers:String;
  51. private var noiseChars:Array;
  52. private var expectedDigest:String;
  53. public function WebSocket(
  54. main:WebSocketMain, url:String, protocol:String,
  55. proxyHost:String = null, proxyPort:int = 0,
  56. headers:String = null) {
  57. this.main = main;
  58. initNoiseChars();
  59. this.url = url;
  60. var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
  61. if (!m) main.fatal("SYNTAX_ERR: invalid url: " + url);
  62. this.scheme = m[1];
  63. this.host = m[2];
  64. this.port = parseInt(m[4] || "80");
  65. this.path = m[5] || "/";
  66. this.origin = main.getOrigin();
  67. this.protocol = protocol;
  68. // if present and not the empty string, headers MUST end with \r\n
  69. // headers should be zero or more complete lines, for example
  70. // "Header1: xxx\r\nHeader2: yyyy\r\n"
  71. this.headers = headers;
  72. /*
  73. socket = new RFC2817Socket();
  74. // if no proxy information is supplied, it acts like a normal Socket
  75. // @see RFC2817Socket::connect
  76. if (proxyHost != null && proxyPort != 0){
  77. socket.setProxyInfo(proxyHost, proxyPort);
  78. }
  79. */
  80. ExternalInterface.call("console.log", "[WebSocket] scheme: " + scheme);
  81. rawSocket = new Socket();
  82. rawSocket.addEventListener(Event.CLOSE, onSocketClose);
  83. rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
  84. rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
  85. rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
  86. if (scheme == "wss") {
  87. tlsConfig= new TLSConfig(TLSEngine.CLIENT,
  88. null, null, null, null, null,
  89. TLSSecurityParameters.PROTOCOL_VERSION);
  90. tlsConfig.trustSelfSignedCertificates = true;
  91. tlsConfig.ignoreCommonNameMismatch = true;
  92. tlsSocket = new TLSSocket();
  93. tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  94. socket = (tlsSocket as Socket);
  95. } else {
  96. rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  97. socket = (rawSocket as Socket);
  98. }
  99. rawSocket.connect(host, port);
  100. }
  101. public function send(data:String):int {
  102. if (readyState == OPEN) {
  103. socket.writeByte(0x00);
  104. socket.writeUTFBytes(decodeURIComponent(data));
  105. socket.writeByte(0xff);
  106. socket.flush();
  107. main.log("sent: " + data);
  108. return -1;
  109. } else if (readyState == CLOSED) {
  110. var bytes:ByteArray = new ByteArray();
  111. bytes.writeUTFBytes(decodeURIComponent(data));
  112. bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
  113. // We use return value to let caller know bufferedAmount because we cannot fire
  114. // stateChange event here which causes weird error:
  115. // > You are trying to call recursively into the Flash Player which is not allowed.
  116. return bufferedAmount;
  117. } else {
  118. main.fatal("INVALID_STATE_ERR: invalid state");
  119. return 0;
  120. }
  121. }
  122. public function close():void {
  123. main.log("close");
  124. dataQueue = [];
  125. try {
  126. socket.close();
  127. } catch (ex:Error) { }
  128. readyState = CLOSED;
  129. // We don't fire any events here because it causes weird error:
  130. // > You are trying to call recursively into the Flash Player which is not allowed.
  131. // We do something equivalent in JavaScript WebSocket#close instead.
  132. }
  133. public function getReadyState():int {
  134. return readyState;
  135. }
  136. public function getBufferedAmount():int {
  137. return bufferedAmount;
  138. }
  139. private function onSocketConnect(event:Event):void {
  140. main.log("connected");
  141. if (scheme == "wss") {
  142. ExternalInterface.call("console.log", "[WebSocket] starting SSL/TLS");
  143. tlsSocket.startTLS(rawSocket, host, tlsConfig);
  144. }
  145. dataQueue = [];
  146. var hostValue:String = host + (port == 80 ? "" : ":" + port);
  147. var cookie:String = "";
  148. if (main.getCallerHost() == host) {
  149. cookie = ExternalInterface.call("function(){return document.cookie}");
  150. }
  151. var key1:String = generateKey();
  152. var key2:String = generateKey();
  153. var key3:String = generateKey3();
  154. expectedDigest = getSecurityDigest(key1, key2, key3);
  155. var opt:String = "";
  156. if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
  157. // if caller passes additional headers they must end with "\r\n"
  158. if (headers) opt += headers;
  159. var req:String = StringUtil.substitute(
  160. "GET {0} HTTP/1.1\r\n" +
  161. "Upgrade: WebSocket\r\n" +
  162. "Connection: Upgrade\r\n" +
  163. "Host: {1}\r\n" +
  164. "Origin: {2}\r\n" +
  165. "Cookie: {3}\r\n" +
  166. "Sec-WebSocket-Key1: {4}\r\n" +
  167. "Sec-WebSocket-Key2: {5}\r\n" +
  168. "{6}" +
  169. "\r\n",
  170. path, hostValue, origin, cookie, key1, key2, opt);
  171. main.log("request header:\n" + req);
  172. socket.writeUTFBytes(req);
  173. main.log("sent key3: " + key3);
  174. main.log("expected digest: " + expectedDigest);
  175. writeBytes(key3);
  176. socket.flush();
  177. }
  178. private function onSocketClose(event:Event):void {
  179. main.log("closed");
  180. readyState = CLOSED;
  181. notifyStateChange();
  182. dispatchEvent(new Event("close"));
  183. }
  184. private function onSocketIoError(event:IOErrorEvent):void {
  185. var message:String;
  186. if (readyState == CONNECTING) {
  187. message = "cannot connect to Web Socket server at " + url + " (IoError)";
  188. } else {
  189. message = "error communicating with Web Socket server at " + url + " (IoError)";
  190. }
  191. onError(message);
  192. }
  193. private function onSocketSecurityError(event:SecurityErrorEvent):void {
  194. var message:String;
  195. if (readyState == CONNECTING) {
  196. message =
  197. "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
  198. "make sure the server is running and Flash socket policy file is correctly placed";
  199. } else {
  200. message = "error communicating with Web Socket server at " + url + " (SecurityError)";
  201. }
  202. onError(message);
  203. }
  204. private function onError(message:String):void {
  205. var state:int = readyState;
  206. if (state == CLOSED) return;
  207. main.error(message);
  208. close();
  209. notifyStateChange();
  210. dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
  211. }
  212. private function onSocketData(event:ProgressEvent):void {
  213. var pos:int = buffer.length;
  214. socket.readBytes(buffer, pos);
  215. for (; pos < buffer.length; ++pos) {
  216. if (headerState < 4) {
  217. // try to find "\r\n\r\n"
  218. if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
  219. ++headerState;
  220. } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
  221. ++headerState;
  222. } else {
  223. headerState = 0;
  224. }
  225. if (headerState == 4) {
  226. var headerStr:String = buffer.readUTFBytes(pos + 1);
  227. main.log("response header:\n" + headerStr);
  228. if (!validateHeader(headerStr)) return;
  229. makeBufferCompact();
  230. pos = -1;
  231. }
  232. } else if (headerState == 4) {
  233. var replyDigest:String = readBytes(buffer, 16);
  234. main.log("reply digest: " + replyDigest);
  235. if (replyDigest != expectedDigest) {
  236. onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
  237. return;
  238. }
  239. headerState = 5;
  240. makeBufferCompact();
  241. pos = -1;
  242. readyState = OPEN;
  243. notifyStateChange();
  244. dispatchEvent(new Event("open"));
  245. } else {
  246. if (buffer[pos] == 0xff) {
  247. //if (buffer.bytesAvailable > 1) {
  248. if (buffer.readByte() != 0x00) {
  249. onError("data must start with \\x00");
  250. return;
  251. }
  252. var data:String = buffer.readUTFBytes(pos - 1);
  253. main.log("received: " + data);
  254. dataQueue.push(encodeURIComponent(data));
  255. dispatchEvent(new WebSocketMessageEvent("message", data.length.toString()));
  256. buffer.readByte();
  257. makeBufferCompact();
  258. pos = -1;
  259. }
  260. }
  261. }
  262. }
  263. public function readSocketData():Array {
  264. var q:Array = dataQueue;
  265. if (dataQueue.length > 0) {
  266. // Reset to empty
  267. dataQueue = [];
  268. }
  269. return q;
  270. }
  271. private function validateHeader(headerStr:String):Boolean {
  272. var lines:Array = headerStr.split(/\r\n/);
  273. if (!lines[0].match(/^HTTP\/1.1 101 /)) {
  274. onError("bad response: " + lines[0]);
  275. return false;
  276. }
  277. var header:Object = {};
  278. for (var i:int = 1; i < lines.length; ++i) {
  279. if (lines[i].length == 0) continue;
  280. var m:Array = lines[i].match(/^(\S+): (.*)$/);
  281. if (!m) {
  282. onError("failed to parse response header line: " + lines[i]);
  283. return false;
  284. }
  285. header[m[1]] = m[2];
  286. }
  287. if (header["Upgrade"] != "WebSocket") {
  288. onError("invalid Upgrade: " + header["Upgrade"]);
  289. return false;
  290. }
  291. if (header["Connection"] != "Upgrade") {
  292. onError("invalid Connection: " + header["Connection"]);
  293. return false;
  294. }
  295. var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
  296. if (resOrigin != origin) {
  297. onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
  298. return false;
  299. }
  300. if (protocol && header["Sec-WebSocket-Protocol"] != protocol) {
  301. onError("protocol doesn't match: '" +
  302. header["WebSocket-Protocol"] + "' != '" + protocol + "'");
  303. return false;
  304. }
  305. return true;
  306. }
  307. private function makeBufferCompact():void {
  308. if (buffer.position == 0) return;
  309. var nextBuffer:ByteArray = new ByteArray();
  310. buffer.readBytes(nextBuffer);
  311. buffer = nextBuffer;
  312. }
  313. private function notifyStateChange():void {
  314. dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
  315. }
  316. private function initNoiseChars():void {
  317. noiseChars = new Array();
  318. for (var i:int = 0x21; i <= 0x2f; ++i) {
  319. noiseChars.push(String.fromCharCode(i));
  320. }
  321. for (var j:int = 0x3a; j <= 0x7a; ++j) {
  322. noiseChars.push(String.fromCharCode(j));
  323. }
  324. }
  325. private function generateKey():String {
  326. var spaces:uint = randomInt(1, 12);
  327. var max:uint = uint.MAX_VALUE / spaces;
  328. var number:uint = randomInt(0, max);
  329. var key:String = (number * spaces).toString();
  330. var noises:int = randomInt(1, 12);
  331. var pos:int;
  332. for (var i:int = 0; i < noises; ++i) {
  333. var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
  334. pos = randomInt(0, key.length);
  335. key = key.substr(0, pos) + char + key.substr(pos);
  336. }
  337. for (var j:int = 0; j < spaces; ++j) {
  338. pos = randomInt(1, key.length - 1);
  339. key = key.substr(0, pos) + " " + key.substr(pos);
  340. }
  341. return key;
  342. }
  343. private function generateKey3():String {
  344. var key3:String = "";
  345. for (var i:int = 0; i < 8; ++i) {
  346. key3 += String.fromCharCode(randomInt(0, 255));
  347. }
  348. return key3;
  349. }
  350. private function getSecurityDigest(key1:String, key2:String, key3:String):String {
  351. var bytes1:String = keyToBytes(key1);
  352. var bytes2:String = keyToBytes(key2);
  353. return MD5.rstr_md5(bytes1 + bytes2 + key3);
  354. }
  355. private function keyToBytes(key:String):String {
  356. var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
  357. var spaces:uint = 0;
  358. for (var i:int = 0; i < key.length; ++i) {
  359. if (key.charAt(i) == " ") ++spaces;
  360. }
  361. var resultNum:uint = keyNum / spaces;
  362. var bytes:String = "";
  363. for (var j:int = 3; j >= 0; --j) {
  364. bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
  365. }
  366. return bytes;
  367. }
  368. private function writeBytes(bytes:String):void {
  369. for (var i:int = 0; i < bytes.length; ++i) {
  370. socket.writeByte(bytes.charCodeAt(i));
  371. }
  372. }
  373. private function readBytes(buffer:ByteArray, numBytes:int):String {
  374. var bytes:String = "";
  375. for (var i:int = 0; i < numBytes; ++i) {
  376. // & 0xff is to make \x80-\xff positive number.
  377. bytes += String.fromCharCode(buffer.readByte() & 0xff);
  378. }
  379. return bytes;
  380. }
  381. private function randomInt(min:uint, max:uint):uint {
  382. return min + Math.floor(Math.random() * (Number(max) - min + 1));
  383. }
  384. // for debug
  385. private function dumpBytes(bytes:String):void {
  386. var output:String = "";
  387. for (var i:int = 0; i < bytes.length; ++i) {
  388. output += bytes.charCodeAt(i).toString() + ", ";
  389. }
  390. main.log(output);
  391. }
  392. }
  393. }