tweetcast/client/lib/websocket-js/flash-src/src/net/gimite/websocket/WebSocket.as
changeset 311 13702105c5ee
parent 310 526d3e411736
child 312 f8336354d107
child 314 0f1e6ce19b6d
--- a/tweetcast/client/lib/websocket-js/flash-src/src/net/gimite/websocket/WebSocket.as	Mon Oct 10 15:24:28 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,584 +0,0 @@
-// 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-ietf-hybi-thewebsocketprotocol-10
-
-package net.gimite.websocket {
-
-import com.adobe.net.proxies.RFC2817Socket;
-import com.gsolo.encryption.SHA1;
-import com.hurlant.crypto.tls.TLSConfig;
-import com.hurlant.crypto.tls.TLSEngine;
-import com.hurlant.crypto.tls.TLSSecurityParameters;
-import com.hurlant.crypto.tls.TLSSocket;
-
-import flash.display.*;
-import flash.errors.*;
-import flash.events.*;
-import flash.external.*;
-import flash.net.*;
-import flash.system.*;
-import flash.utils.*;
-
-import mx.controls.*;
-import mx.core.*;
-import mx.events.*;
-import mx.utils.*;
-
-public class WebSocket extends EventDispatcher {
-  
-  private static const WEB_SOCKET_GUID:String = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-  
-  private static const CONNECTING:int = 0;
-  private static const OPEN:int = 1;
-  private static const CLOSING:int = 2;
-  private static const CLOSED:int = 3;
-  
-  private static const OPCODE_CONTINUATION:int = 0x00;
-  private static const OPCODE_TEXT:int = 0x01;
-  private static const OPCODE_BINARY:int = 0x02;
-  private static const OPCODE_CLOSE:int = 0x08;
-  private static const OPCODE_PING:int = 0x09;
-  private static const OPCODE_PONG:int = 0x0a;
-  
-  private static const STATUS_NORMAL_CLOSURE:int = 1000;
-  private static const STATUS_NO_CODE:int = 1005;
-  private static const STATUS_CLOSED_ABNORMALLY:int = 1006;
-  private static const STATUS_CONNECTION_ERROR:int = 5000;
-  
-  private var id:int;
-  private var url:String;
-  private var scheme:String;
-  private var host:String;
-  private var port:uint;
-  private var path:String;
-  private var origin:String;
-  private var requestedProtocols:Array;
-  private var cookie:String;
-  private var headers:String;
-  
-  private var rawSocket:Socket;
-  private var tlsSocket:TLSSocket;
-  private var tlsConfig:TLSConfig;
-  private var socket:Socket;
-  
-  private var acceptedProtocol:String;
-  private var expectedDigest:String;
-  
-  private var buffer:ByteArray = new ByteArray();
-  private var headerState:int = 0;
-  private var readyState:int = CONNECTING;
-  
-  private var logger:IWebSocketLogger;
-  private var base64Encoder:Base64Encoder = new Base64Encoder();
-  
-  public function WebSocket(
-      id:int, url:String, protocols:Array, origin:String,
-      proxyHost:String, proxyPort:int,
-      cookie:String, headers:String,
-      logger:IWebSocketLogger) {
-    this.logger = logger;
-    this.id = id;
-    this.url = url;
-    var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
-    if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
-    this.scheme = m[1];
-    this.host = m[2];
-    var defaultPort:int = scheme == "wss" ? 443 : 80;
-    this.port = parseInt(m[4]) || defaultPort;
-    this.path = (m[5] || "/") + (m[6] || "");
-    this.origin = origin;
-    this.requestedProtocols = protocols;
-    this.cookie = cookie;
-    // if present and not the empty string, headers MUST end with \r\n
-    // headers should be zero or more complete lines, for example
-    // "Header1: xxx\r\nHeader2: yyyy\r\n"
-    this.headers = headers;
-    
-    if (proxyHost != null && proxyPort != 0){
-      if (scheme == "wss") {
-        fatal("wss with proxy is not supported");
-      }
-      var proxySocket:RFC2817Socket = new RFC2817Socket();
-      proxySocket.setProxyInfo(proxyHost, proxyPort);
-      proxySocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
-      rawSocket = socket = proxySocket;
-    } else {
-      rawSocket = new Socket();
-      if (scheme == "wss") {
-        tlsConfig= new TLSConfig(TLSEngine.CLIENT,
-            null, null, null, null, null,
-            TLSSecurityParameters.PROTOCOL_VERSION);
-        tlsConfig.trustAllCertificates = true;
-        tlsConfig.ignoreCommonNameMismatch = true;
-        tlsSocket = new TLSSocket();
-        tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
-        socket = tlsSocket;
-      } else {
-        rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
-        socket = rawSocket;
-      }
-    }
-    rawSocket.addEventListener(Event.CLOSE, onSocketClose);
-    rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
-    rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
-    rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
-    rawSocket.connect(host, port);
-  }
-  
-  /**
-   * @return  This WebSocket's ID.
-   */
-  public function getId():int {
-    return this.id;
-  }
-  
-  /**
-   * @return this WebSocket's readyState.
-   */
-  public function getReadyState():int {
-    return this.readyState;
-  }
-
-  public function getAcceptedProtocol():String {
-    return this.acceptedProtocol;
-  }
-  
-  public function send(encData:String):int {
-    var data:String;
-    try {
-      data = decodeURIComponent(encData);
-    } catch (ex:URIError) {
-      logger.error("SYNTAX_ERR: URIError in send()");
-      return 0;
-    }
-    logger.log("send: " + data);
-    var dataBytes:ByteArray = new ByteArray();
-    dataBytes.writeUTFBytes(data);
-    if (readyState == OPEN) {
-      // TODO: binary API support
-      var frame:WebSocketFrame = new WebSocketFrame();
-      frame.opcode = OPCODE_TEXT;
-      frame.payload = dataBytes;
-      if (sendFrame(frame)) {
-        return -1;
-      } else {
-        return dataBytes.length;
-      }
-    } else if (readyState == CLOSING || readyState == CLOSED) {
-      return dataBytes.length;
-    } else {
-      fatal("invalid state");
-      return 0;
-    }
-  }
-  
-  public function close(
-      code:int = STATUS_NO_CODE, reason:String = "", origin:String = "client"):void {
-    if (code != STATUS_NORMAL_CLOSURE &&
-        code != STATUS_NO_CODE &&
-        code != STATUS_CONNECTION_ERROR) {
-      logger.error(StringUtil.substitute(
-          "Fail connection by {0}: code={1} reason={2}", origin, code, reason));
-    }
-    var closeConnection:Boolean =
-        code == STATUS_CONNECTION_ERROR || origin == "server";
-    try {
-      if (readyState == OPEN && code != STATUS_CONNECTION_ERROR) {
-        var frame:WebSocketFrame = new WebSocketFrame();
-        frame.opcode = OPCODE_CLOSE;
-        frame.payload = new ByteArray();
-        if (origin == "client" && code != STATUS_NO_CODE) {
-          frame.payload.writeShort(code);
-          frame.payload.writeUTFBytes(reason);
-        }
-        sendFrame(frame);
-      }
-      if (closeConnection) {
-        socket.close();
-      }
-    } catch (ex:Error) {
-      logger.error("Error: " + ex.message);
-    }
-    if (closeConnection) {
-      logger.log("closed");
-      var fireErrorEvent:Boolean = readyState != CONNECTING && code == STATUS_CONNECTION_ERROR;
-      readyState = CLOSED;
-      if (fireErrorEvent) {
-        dispatchEvent(new WebSocketEvent("error"));
-      } else {
-        var wasClean:Boolean = code != STATUS_CLOSED_ABNORMALLY && code != STATUS_CONNECTION_ERROR;
-        var eventCode:int = code == STATUS_CONNECTION_ERROR ? STATUS_CLOSED_ABNORMALLY : code;
-        dispatchCloseEvent(wasClean, eventCode, reason);
-      }
-    } else {
-      logger.log("closing");
-      readyState = CLOSING;
-    }
-  }
-  
-  private function onSocketConnect(event:Event):void {
-    logger.log("connected");
-
-    if (scheme == "wss") {
-      logger.log("starting SSL/TLS");
-      tlsSocket.startTLS(rawSocket, host, tlsConfig);
-    }
-    
-    var defaultPort:int = scheme == "wss" ? 443 : 80;
-    var hostValue:String = host + (port == defaultPort ? "" : ":" + port);
-    var key:String = generateKey();
-
-    SHA1.b64pad = "=";
-    expectedDigest = SHA1.b64_sha1(key + WEB_SOCKET_GUID);
-
-    var opt:String = "";
-    if (requestedProtocols.length > 0) {
-      opt += "Sec-WebSocket-Protocol: " + requestedProtocols.join(",") + "\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" +
-      "Host: {1}\r\n" +
-      "Upgrade: websocket\r\n" +
-      "Connection: Upgrade\r\n" +
-      "Sec-WebSocket-Key: {2}\r\n" +
-      "Sec-WebSocket-Origin: {3}\r\n" +
-      "Sec-WebSocket-Version: 8\r\n" +
-      "Cookie: {4}\r\n" +
-      "{5}" +
-      "\r\n",
-      path, hostValue, key, origin, cookie, opt);
-    logger.log("request header:\n" + req);
-    socket.writeUTFBytes(req);
-    socket.flush();
-  }
-
-  private function onSocketClose(event:Event):void {
-    logger.log("closed");
-    readyState = CLOSED;
-    dispatchCloseEvent(false, STATUS_CLOSED_ABNORMALLY, "");
-  }
-
-  private function onSocketIoError(event:IOErrorEvent):void {
-    var message:String;
-    if (readyState == CONNECTING) {
-      message = "cannot connect to Web Socket server at " + url + " (IoError: " + event.text + ")";
-    } else {
-      message =
-          "error communicating with Web Socket server at " + url +
-          " (IoError: " + event.text + ")";
-    }
-    onConnectionError(message);
-  }
-
-  private function onSocketSecurityError(event:SecurityErrorEvent):void {
-    var message:String;
-    if (readyState == CONNECTING) {
-      message =
-          "cannot connect to Web Socket server at " + url + " (SecurityError: " + event.text + ")\n" +
-          "make sure the server is running and Flash socket policy file is correctly placed";
-    } else {
-      message =
-          "error communicating with Web Socket server at " + url +
-          " (SecurityError: " + event.text + ")";
-    }
-    onConnectionError(message);
-  }
-  
-  private function onConnectionError(message:String):void {
-    if (readyState == CLOSED) return;
-    logger.error(message);
-    close(STATUS_CONNECTION_ERROR);
-  }
-
-  private function onSocketData(event:ProgressEvent):void {
-    var pos:int = buffer.length;
-    socket.readBytes(buffer, pos);
-    for (; pos < buffer.length; ++pos) {
-      if (headerState < 4) {
-        // try to find "\r\n\r\n"
-        if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
-          ++headerState;
-        } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
-          ++headerState;
-        } else {
-          headerState = 0;
-        }
-        if (headerState == 4) {
-          var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
-          logger.log("response header:\n" + headerStr);
-          if (!validateHandshake(headerStr)) return;
-          removeBufferBefore(pos + 1);
-          pos = -1;
-          readyState = OPEN;
-          this.dispatchEvent(new WebSocketEvent("open"));
-        }
-      } else {
-        var frame:WebSocketFrame = parseFrame();
-        if (frame) {
-          removeBufferBefore(frame.length);
-          pos = -1;
-          if (frame.rsv != 0) {
-            close(1002, "RSV must be 0.");
-          } else if (frame.opcode >= 0x08 && frame.opcode <= 0x0f && frame.payload.length >= 126) {
-            close(1004, "Payload of control frame must be less than 126 bytes.");
-          } else {
-            switch (frame.opcode) {
-              case OPCODE_CONTINUATION:
-                close(1003, "Received continuation frame, which is not implemented.");
-                break;
-              case OPCODE_TEXT:
-                var data:String = readUTFBytes(frame.payload, 0, frame.payload.length);
-                try {
-                  this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
-                } catch (ex:URIError) {
-                  close(1007, "URIError while encoding the received data.");
-                }
-                break;
-              case OPCODE_BINARY:
-                close(1003, "Received binary data, which is not supported.");
-                break;
-              case OPCODE_CLOSE:
-                // Extracts code and reason string.
-                var code:int = STATUS_NO_CODE;
-                var reason:String = "";
-                if (frame.payload.length >= 2) {
-                  frame.payload.endian = Endian.BIG_ENDIAN;
-                  frame.payload.position = 0;
-                  code = frame.payload.readUnsignedShort();
-                  reason = readUTFBytes(frame.payload, 2, frame.payload.length - 2);
-                }
-                logger.log("received closing frame");
-                close(code, reason, "server");
-                break;
-              case OPCODE_PING:
-                sendPong(frame.payload);
-                break;
-              case OPCODE_PONG:
-                break;
-              default:
-                close(1002, "Received unknown opcode: " + frame.opcode);
-                break;
-            }
-          }
-        }
-      }
-    }
-  }
-  
-  private function validateHandshake(headerStr:String):Boolean {
-    var lines:Array = headerStr.split(/\r\n/);
-    if (!lines[0].match(/^HTTP\/1.1 101 /)) {
-      onConnectionError("bad response: " + lines[0]);
-      return false;
-    }
-    var header:Object = {};
-    var lowerHeader:Object = {};
-    for (var i:int = 1; i < lines.length; ++i) {
-      if (lines[i].length == 0) continue;
-      var m:Array = lines[i].match(/^(\S+): (.*)$/);
-      if (!m) {
-        onConnectionError("failed to parse response header line: " + lines[i]);
-        return false;
-      }
-      header[m[1].toLowerCase()] = m[2];
-      lowerHeader[m[1].toLowerCase()] = m[2].toLowerCase();
-    }
-    if (lowerHeader["upgrade"] != "websocket") {
-      onConnectionError("invalid Upgrade: " + header["Upgrade"]);
-      return false;
-    }
-    if (lowerHeader["connection"] != "upgrade") {
-      onConnectionError("invalid Connection: " + header["Connection"]);
-      return false;
-    }
-    if (!lowerHeader["sec-websocket-accept"]) {
-      onConnectionError(
-        "The WebSocket server speaks old WebSocket protocol, " +
-        "which is not supported by web-socket-js. " +
-        "It requires WebSocket protocol HyBi 10. " +
-        "Try newer version of the server if available.");
-      return false;
-    }
-    var replyDigest:String = header["sec-websocket-accept"]
-    if (replyDigest != expectedDigest) {
-      onConnectionError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
-      return false;
-    }
-    if (requestedProtocols.length > 0) {
-      acceptedProtocol = header["sec-websocket-protocol"];
-      if (requestedProtocols.indexOf(acceptedProtocol) < 0) {
-        onConnectionError("protocol doesn't match: '" +
-          acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'");
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private function sendPong(payload:ByteArray):Boolean {
-    var frame:WebSocketFrame = new WebSocketFrame();
-    frame.opcode = OPCODE_PONG;
-    frame.payload = payload;
-    return sendFrame(frame);
-  }
-  
-  private function sendFrame(frame:WebSocketFrame):Boolean {
-    
-    var plength:uint = frame.payload.length;
-    
-    // Generates a mask.
-    var mask:ByteArray = new ByteArray();
-    for (var i:int = 0; i < 4; i++) {
-      mask.writeByte(randomInt(0, 255));
-    }
-    
-    var header:ByteArray = new ByteArray();
-    // FIN + RSV + opcode
-    header.writeByte((frame.fin ? 0x80 : 0x00) | (frame.rsv << 4) | frame.opcode);
-    if (plength <= 125) {
-      header.writeByte(0x80 | plength);  // Masked + length
-    } else if (plength > 125 && plength < 65536) {
-      header.writeByte(0x80 | 126);  // Masked + 126
-      header.writeShort(plength);
-    } else if (plength >= 65536 && plength < 4294967296) {
-      header.writeByte(0x80 | 127);  // Masked + 127
-      header.writeUnsignedInt(0);  // zero high order bits
-      header.writeUnsignedInt(plength);
-    } else {
-      fatal("Send frame size too large");
-    }
-    header.writeBytes(mask);
-    
-    var maskedPayload:ByteArray = new ByteArray();
-    maskedPayload.length = frame.payload.length;
-    for (i = 0; i < frame.payload.length; i++) {
-      maskedPayload[i] = mask[i % 4] ^ frame.payload[i];
-    }
-
-    try {
-      socket.writeBytes(header);
-      socket.writeBytes(maskedPayload);
-      socket.flush();
-    } catch (ex:IOError) {
-      logger.error("IOError while sending frame");
-      // TODO Fire close event if it hasn't
-      readyState = CLOSED;
-      return false;
-    }
-    return true;
-    
-  }
-
-  private function parseFrame():WebSocketFrame {
-    
-    var frame:WebSocketFrame = new WebSocketFrame();
-    var hlength:uint = 0;
-    var plength:uint = 0;
-    
-    hlength = 2;
-    if (buffer.length < hlength) {
-      return null;
-    }
-
-    frame.fin = (buffer[0] & 0x80) != 0;
-    frame.rsv = (buffer[0] & 0x70) >> 4;
-    frame.opcode  = buffer[0] & 0x0f;
-    plength = buffer[1] & 0x7f;
-
-    if (plength == 126) {
-      
-      hlength = 4;
-      if (buffer.length < hlength) {
-        return null;
-      }
-      buffer.endian = Endian.BIG_ENDIAN;
-      buffer.position = 2;
-      plength = buffer.readUnsignedShort();
-      
-    } else if (plength == 127) {
-      
-      hlength = 10;
-      if (buffer.length < hlength) {
-        return null;
-      }
-      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
-      plength = buffer.readUnsignedInt(); // Low 32-bits
-      if (big != 0) {
-        fatal("Frame length exceeds 4294967295. Bailing out!");
-        return null;
-      }
-      
-    }
-
-    if (buffer.length < hlength + plength) {
-      return null;
-    }
-    
-    frame.length = hlength + plength;
-    frame.payload = new ByteArray();
-    buffer.position = hlength;
-    buffer.readBytes(frame.payload, 0, plength);
-    return frame;
-    
-  }
-  
-  private function dispatchCloseEvent(wasClean:Boolean, code:int, reason:String):void {
-    var event:WebSocketEvent = new WebSocketEvent("close");
-    event.wasClean = wasClean;
-    event.code = code;
-    event.reason = reason;
-    dispatchEvent(event);
-  }
-  
-  private function removeBufferBefore(pos:int):void {
-    if (pos == 0) return;
-    var nextBuffer:ByteArray = new ByteArray();
-    buffer.position = pos;
-    buffer.readBytes(nextBuffer);
-    buffer = nextBuffer;
-  }
-  
-  private function generateKey():String {
-    var vals:ByteArray = new ByteArray();
-    vals.length = 16;
-    for (var i:int = 0; i < vals.length; ++i) {
-        vals[i] = randomInt(0, 127);
-    }
-    base64Encoder.reset();
-    base64Encoder.encodeBytes(vals);
-    return base64Encoder.toString();
-  }
-  
-  private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
-    buffer.position = start;
-    var data:String = "";
-    for(var i:int = start; i < start + numBytes; ++i) {
-      // Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
-      if (buffer[i] == 0x00) {
-        data += buffer.readUTFBytes(i - buffer.position) + "\x00";
-        buffer.position = i + 1;
-      }
-    }
-    data += buffer.readUTFBytes(start + numBytes - buffer.position);
-    return data;
-  }
-  
-  private function randomInt(min:uint, max:uint):uint {
-    return min + Math.floor(Math.random() * (Number(max) - min + 1));
-  }
-  
-  private function fatal(message:String):void {
-    logger.error(message);
-    throw message;
-  }
-
-}
-
-}