PageRenderTime 63ms CodeModel.GetById 42ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

/js/lib/Socket.IO-node/support/socket.io-client/lib/vendor/web-socket-js/flash-src/WebSocket.as

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