PageRenderTime 30ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/nodejs_server/static/socket.io.js/lib/vendor/web-socket-js/flash-src/WebSocket.as

https://github.com/vtemian/outclan
ActionScript | 452 lines | 393 code | 37 blank | 22 comment | 112 complexity | ce2d8e229ce9b11b52479ab2dd495d27 MD5 | raw file
  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-76
  5. package {
  6. import com.adobe.net.proxies.RFC2817Socket;
  7. import com.gsolo.encryption.MD5;
  8. import com.hurlant.crypto.tls.TLSConfig;
  9. import com.hurlant.crypto.tls.TLSEngine;
  10. import com.hurlant.crypto.tls.TLSSecurityParameters;
  11. import com.hurlant.crypto.tls.TLSSocket;
  12. import flash.display.*;
  13. import flash.events.*;
  14. import flash.external.*;
  15. import flash.net.*;
  16. import flash.system.*;
  17. import flash.utils.*;
  18. import mx.controls.*;
  19. import mx.core.*;
  20. import mx.events.*;
  21. import mx.utils.*;
  22. public class WebSocket extends EventDispatcher {
  23. private static var CONNECTING:int = 0;
  24. private static var OPEN:int = 1;
  25. private static var CLOSING:int = 2;
  26. private static var CLOSED:int = 3;
  27. private var id:int;
  28. private var rawSocket:Socket;
  29. private var tlsSocket:TLSSocket;
  30. private var tlsConfig:TLSConfig;
  31. private var socket:Socket;
  32. private var url:String;
  33. private var scheme:String;
  34. private var host:String;
  35. private var port:uint;
  36. private var path:String;
  37. private var origin:String;
  38. private var protocol:String;
  39. private var buffer:ByteArray = new ByteArray();
  40. private var headerState:int = 0;
  41. private var readyState:int = CONNECTING;
  42. private var cookie:String;
  43. private var headers:String;
  44. private var noiseChars:Array;
  45. private var expectedDigest:String;
  46. private var logger:IWebSocketLogger;
  47. public function WebSocket(
  48. id:int, url:String, protocol:String, origin:String,
  49. proxyHost:String, proxyPort:int,
  50. cookie:String, headers:String,
  51. logger:IWebSocketLogger) {
  52. this.logger = logger;
  53. this.id = id;
  54. initNoiseChars();
  55. this.url = url;
  56. var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
  57. if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
  58. this.scheme = m[1];
  59. this.host = m[2];
  60. this.port = parseInt(m[4] || "80");
  61. this.path = (m[5] || "/") + (m[6] || "");
  62. this.origin = origin;
  63. this.protocol = protocol;
  64. this.cookie = cookie;
  65. // if present and not the empty string, headers MUST end with \r\n
  66. // headers should be zero or more complete lines, for example
  67. // "Header1: xxx\r\nHeader2: yyyy\r\n"
  68. this.headers = headers;
  69. if (proxyHost != null && proxyPort != 0){
  70. if (scheme == "wss") {
  71. fatal("wss with proxy is not supported");
  72. }
  73. var proxySocket:RFC2817Socket = new RFC2817Socket();
  74. proxySocket.setProxyInfo(proxyHost, proxyPort);
  75. proxySocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  76. rawSocket = socket = proxySocket;
  77. } else {
  78. rawSocket = new Socket();
  79. if (scheme == "wss") {
  80. tlsConfig= new TLSConfig(TLSEngine.CLIENT,
  81. null, null, null, null, null,
  82. TLSSecurityParameters.PROTOCOL_VERSION);
  83. tlsConfig.trustAllCertificates = true;
  84. tlsConfig.ignoreCommonNameMismatch = true;
  85. tlsSocket = new TLSSocket();
  86. tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  87. socket = tlsSocket;
  88. } else {
  89. rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  90. socket = rawSocket;
  91. }
  92. }
  93. rawSocket.addEventListener(Event.CLOSE, onSocketClose);
  94. rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
  95. rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
  96. rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
  97. rawSocket.connect(host, port);
  98. }
  99. /**
  100. * @return This WebSocket's ID.
  101. */
  102. public function getId():int {
  103. return this.id;
  104. }
  105. /**
  106. * @return this WebSocket's readyState.
  107. */
  108. public function getReadyState():int {
  109. return this.readyState;
  110. }
  111. public function send(encData:String):int {
  112. var data:String = decodeURIComponent(encData);
  113. if (readyState == OPEN) {
  114. socket.writeByte(0x00);
  115. socket.writeUTFBytes(data);
  116. socket.writeByte(0xff);
  117. socket.flush();
  118. logger.log("sent: " + data);
  119. return -1;
  120. } else if (readyState == CLOSING || readyState == CLOSED) {
  121. var bytes:ByteArray = new ByteArray();
  122. bytes.writeUTFBytes(data);
  123. return bytes.length; // not sure whether it should include \x00 and \xff
  124. } else {
  125. fatal("invalid state");
  126. return 0;
  127. }
  128. }
  129. public function close(isError:Boolean = false):void {
  130. logger.log("close");
  131. try {
  132. if (readyState == OPEN && !isError) {
  133. socket.writeByte(0xff);
  134. socket.writeByte(0x00);
  135. socket.flush();
  136. }
  137. socket.close();
  138. } catch (ex:Error) { }
  139. readyState = CLOSED;
  140. this.dispatchEvent(new WebSocketEvent(isError ? "error" : "close"));
  141. }
  142. private function onSocketConnect(event:Event):void {
  143. logger.log("connected");
  144. if (scheme == "wss") {
  145. logger.log("starting SSL/TLS");
  146. tlsSocket.startTLS(rawSocket, host, tlsConfig);
  147. }
  148. var hostValue:String = host + (port == 80 ? "" : ":" + port);
  149. var key1:String = generateKey();
  150. var key2:String = generateKey();
  151. var key3:String = generateKey3();
  152. expectedDigest = getSecurityDigest(key1, key2, key3);
  153. var opt:String = "";
  154. if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
  155. // if caller passes additional headers they must end with "\r\n"
  156. if (headers) opt += headers;
  157. var req:String = StringUtil.substitute(
  158. "GET {0} HTTP/1.1\r\n" +
  159. "Upgrade: WebSocket\r\n" +
  160. "Connection: Upgrade\r\n" +
  161. "Host: {1}\r\n" +
  162. "Origin: {2}\r\n" +
  163. "Cookie: {3}\r\n" +
  164. "Sec-WebSocket-Key1: {4}\r\n" +
  165. "Sec-WebSocket-Key2: {5}\r\n" +
  166. "{6}" +
  167. "\r\n",
  168. path, hostValue, origin, cookie, key1, key2, opt);
  169. logger.log("request header:\n" + req);
  170. socket.writeUTFBytes(req);
  171. logger.log("sent key3: " + key3);
  172. writeBytes(key3);
  173. socket.flush();
  174. }
  175. private function onSocketClose(event:Event):void {
  176. logger.log("closed");
  177. readyState = CLOSED;
  178. this.dispatchEvent(new WebSocketEvent("close"));
  179. }
  180. private function onSocketIoError(event:IOErrorEvent):void {
  181. var message:String;
  182. if (readyState == CONNECTING) {
  183. message = "cannot connect to Web Socket server at " + url + " (IoError)";
  184. } else {
  185. message = "error communicating with Web Socket server at " + url + " (IoError)";
  186. }
  187. onError(message);
  188. }
  189. private function onSocketSecurityError(event:SecurityErrorEvent):void {
  190. var message:String;
  191. if (readyState == CONNECTING) {
  192. message =
  193. "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
  194. "make sure the server is running and Flash socket policy file is correctly placed";
  195. } else {
  196. message = "error communicating with Web Socket server at " + url + " (SecurityError)";
  197. }
  198. onError(message);
  199. }
  200. private function onError(message:String):void {
  201. if (readyState == CLOSED) return;
  202. logger.error(message);
  203. close(readyState != CONNECTING);
  204. }
  205. private function onSocketData(event:ProgressEvent):void {
  206. var pos:int = buffer.length;
  207. socket.readBytes(buffer, pos);
  208. for (; pos < buffer.length; ++pos) {
  209. if (headerState < 4) {
  210. // try to find "\r\n\r\n"
  211. if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
  212. ++headerState;
  213. } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
  214. ++headerState;
  215. } else {
  216. headerState = 0;
  217. }
  218. if (headerState == 4) {
  219. var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
  220. logger.log("response header:\n" + headerStr);
  221. if (!validateHeader(headerStr)) return;
  222. removeBufferBefore(pos + 1);
  223. pos = -1;
  224. }
  225. } else if (headerState == 4) {
  226. if (pos == 15) {
  227. var replyDigest:String = readBytes(buffer, 0, 16);
  228. logger.log("reply digest: " + replyDigest);
  229. if (replyDigest != expectedDigest) {
  230. onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
  231. return;
  232. }
  233. headerState = 5;
  234. removeBufferBefore(pos + 1);
  235. pos = -1;
  236. readyState = OPEN;
  237. this.dispatchEvent(new WebSocketEvent("open"));
  238. }
  239. } else {
  240. if (buffer[pos] == 0xff && pos > 0) {
  241. if (buffer[0] != 0x00) {
  242. onError("data must start with \\x00");
  243. return;
  244. }
  245. var data:String = readUTFBytes(buffer, 1, pos - 1);
  246. logger.log("received: " + data);
  247. this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
  248. removeBufferBefore(pos + 1);
  249. pos = -1;
  250. } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
  251. logger.log("received closing packet");
  252. removeBufferBefore(pos + 1);
  253. pos = -1;
  254. close();
  255. }
  256. }
  257. }
  258. }
  259. private function validateHeader(headerStr:String):Boolean {
  260. var lines:Array = headerStr.split(/\r\n/);
  261. if (!lines[0].match(/^HTTP\/1.1 101 /)) {
  262. onError("bad response: " + lines[0]);
  263. return false;
  264. }
  265. var header:Object = {};
  266. var lowerHeader:Object = {};
  267. for (var i:int = 1; i < lines.length; ++i) {
  268. if (lines[i].length == 0) continue;
  269. var m:Array = lines[i].match(/^(\S+): (.*)$/);
  270. if (!m) {
  271. onError("failed to parse response header line: " + lines[i]);
  272. return false;
  273. }
  274. header[m[1].toLowerCase()] = m[2];
  275. lowerHeader[m[1].toLowerCase()] = m[2].toLowerCase();
  276. }
  277. if (lowerHeader["upgrade"] != "websocket") {
  278. onError("invalid Upgrade: " + header["Upgrade"]);
  279. return false;
  280. }
  281. if (lowerHeader["connection"] != "upgrade") {
  282. onError("invalid Connection: " + header["Connection"]);
  283. return false;
  284. }
  285. if (!lowerHeader["sec-websocket-origin"]) {
  286. if (lowerHeader["websocket-origin"]) {
  287. onError(
  288. "The WebSocket server speaks old WebSocket protocol, " +
  289. "which is not supported by web-socket-js. " +
  290. "It requires WebSocket protocol 76 or later. " +
  291. "Try newer version of the server if available.");
  292. } else {
  293. onError("header Sec-WebSocket-Origin is missing");
  294. }
  295. return false;
  296. }
  297. var resOrigin:String = lowerHeader["sec-websocket-origin"];
  298. if (resOrigin != origin) {
  299. onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
  300. return false;
  301. }
  302. if (protocol && header["sec-websocket-protocol"] != protocol) {
  303. onError("protocol doesn't match: '" +
  304. header["websocket-protocol"] + "' != '" + protocol + "'");
  305. return false;
  306. }
  307. return true;
  308. }
  309. private function removeBufferBefore(pos:int):void {
  310. if (pos == 0) return;
  311. var nextBuffer:ByteArray = new ByteArray();
  312. buffer.position = pos;
  313. buffer.readBytes(nextBuffer);
  314. buffer = nextBuffer;
  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. // Writes byte sequence to socket.
  369. // bytes is String in special format where bytes[i] is i-th byte, not i-th character.
  370. private function writeBytes(bytes:String):void {
  371. for (var i:int = 0; i < bytes.length; ++i) {
  372. socket.writeByte(bytes.charCodeAt(i));
  373. }
  374. }
  375. // Reads specified number of bytes from buffer, and returns it as special format String
  376. // where bytes[i] is i-th byte (not i-th character).
  377. private function readBytes(buffer:ByteArray, start:int, numBytes:int):String {
  378. buffer.position = start;
  379. var bytes:String = "";
  380. for (var i:int = 0; i < numBytes; ++i) {
  381. // & 0xff is to make \x80-\xff positive number.
  382. bytes += String.fromCharCode(buffer.readByte() & 0xff);
  383. }
  384. return bytes;
  385. }
  386. private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
  387. buffer.position = start;
  388. var data:String = "";
  389. for(var i:int = start; i < start + numBytes; ++i) {
  390. // Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
  391. if (buffer[i] == 0x00) {
  392. data += buffer.readUTFBytes(i - buffer.position) + "\x00";
  393. buffer.position = i + 1;
  394. }
  395. }
  396. data += buffer.readUTFBytes(start + numBytes - buffer.position);
  397. return data;
  398. }
  399. private function randomInt(min:uint, max:uint):uint {
  400. return min + Math.floor(Math.random() * (Number(max) - min + 1));
  401. }
  402. private function fatal(message:String):void {
  403. logger.error(message);
  404. throw message;
  405. }
  406. // for debug
  407. private function dumpBytes(bytes:String):void {
  408. var output:String = "";
  409. for (var i:int = 0; i < bytes.length; ++i) {
  410. output += bytes.charCodeAt(i).toString() + ", ";
  411. }
  412. logger.log(output);
  413. }
  414. }
  415. }