From 350a267ffa89f5d2946bb94e0c202a2795917bd8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 8 May 2011 14:06:30 +0900 Subject: Fixing README style. --- README.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d0e73df..79515a5 100644 --- a/README.md +++ b/README.md @@ -19,29 +19,31 @@ Assuming you have Web server (e.g. Apache) running at **http://example.com/** . - Copy swfobject.js, web_socket.js, WebSocketMain.swf to your application directory. - Write JavaScript code: - - - - - +```html + + + + + +``` - Put Flash socket policy file to your server unless you use web-socket-ruby or em-websocket as your WebSocket server. See "Flash socket policy file" section below for details. -- cgit v1.2.1 From ed231f4779e1d599c19d2be77966d228542d1abe Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 8 May 2011 14:16:45 +0900 Subject: README fixes. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79515a5..5ccfc66 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Assuming you have Web server (e.g. Apache) running at **http://example.com/** . 1. Download [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master). 2. Run sample Web Socket server (echo server) in example.com with: (#1) - $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 +``` +$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 +``` 3. If your server already provides socket policy file at port **843**, modify the file to allow access to port **10081**. Otherwise you can skip this step. See below for details. 4. Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html). 5. Change ws://localhost:10081 to **ws://example.com:10081** in sample.html. @@ -67,7 +69,7 @@ and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.l 3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html. -4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details. +4. If you are NOT using web-socket-ruby or em-websocket as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details. 5. Check if sample.html bundled with web-socket-js works. @@ -99,7 +101,7 @@ It may or may not work on other browsers such as Safari, Opera or IE 6. Patch fo This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash. -If you use [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master) or [em-websocket](https://github.com/igrigorik/em-websocket), you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port **843**, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides. +If you use [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master) or [em-websocket](https://github.com/igrigorik/em-websocket), you don't need anything special, because they handle Flash socket policy file request. But if you already provide socket policy file at port **843**, you need to modify the file to allow access to Web Socket port, because it precedes what the libraries provide. If you use other Web Socket server implementation, you need to provide socket policy file yourself. See [Setting up A Flash Socket Policy File](http://www.lightsphere.com/dev/articles/flash_socket_policy.html) for details and sample script to run socket policy file server. [node.js implementation is available here](http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js). -- cgit v1.2.1 From a6b616c5362dd7bb918ce4247f1cce8b7e863c55 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 8 May 2011 14:18:08 +0900 Subject: README fixes. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ccfc66..f901c32 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Assuming you have Web server (e.g. Apache) running at **http://example.com/** . 1. Download [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master). -2. Run sample Web Socket server (echo server) in example.com with: (#1) +2. Run sample Web Socket server (echo server) in example.com with: (#1)
``` $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 ``` -- cgit v1.2.1 From b940ead1f7b07c91e11cbd84fb398281e8212e40 Mon Sep 17 00:00:00 2001 From: Max Roeleveld Date: Wed, 15 Jun 2011 09:37:15 +0200 Subject: Use getFlashPlayerVersion() to detect the Flash version more reliably --- web_socket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_socket.js b/web_socket.js index bed899c..90ecb5e 100644 --- a/web_socket.js +++ b/web_socket.js @@ -12,7 +12,7 @@ console = {log: function(){ }, error: function(){ }}; } - if (!swfobject.hasFlashPlayerVersion("10.0.0")) { + if (!swfobject.getFlashPlayerVersion().major >= 10) { console.error("Flash Player >= 10.0.0 is required."); return; } -- cgit v1.2.1 From d8405960214b5a590693c9e738e150b1f87ceafd Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sat, 25 Jun 2011 12:51:24 +0900 Subject: Adding comment about Flash version check. --- web_socket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_socket.js b/web_socket.js index 90ecb5e..bf81019 100644 --- a/web_socket.js +++ b/web_socket.js @@ -12,6 +12,7 @@ console = {log: function(){ }, error: function(){ }}; } + // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. if (!swfobject.getFlashPlayerVersion().major >= 10) { console.error("Flash Player >= 10.0.0 is required."); return; -- cgit v1.2.1 From c6b157474f313660edca568ccc76035276e2da14 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 26 Jun 2011 10:53:21 +0900 Subject: Adding error message when HTML and SWF is in the different domains. --- web_socket.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web_socket.js b/web_socket.js index bf81019..3cabbd4 100644 --- a/web_socket.js +++ b/web_socket.js @@ -226,6 +226,17 @@ console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } + if (!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { + var swfHost = RegExp.$1; + if (location.host != swfHost) { + console.error( + "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + + "('" + location.host + "' != '" + swfHost + "'). " + + "See also 'How to host HTML file and SWF file in different domains' section " + + "in README.md."); + } + } var container = document.createElement("div"); container.id = "webSocketContainer"; // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents -- cgit v1.2.1 From 940e435d00ce3e40908f86650f265fbf6ba79a53 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 26 Jun 2011 10:56:45 +0900 Subject: Adding HTML/SWF domain issue to 'Troubleshooting' section of README. --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f901c32..7d963d8 100644 --- a/README.md +++ b/README.md @@ -69,13 +69,15 @@ and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.l 3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html. -4. If you are NOT using web-socket-ruby or em-websocket as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details. +4. Make sure you host your HTML page and WebSocketMain.swf in the same domain. Otherwise, see "How to host HTML file and SWF file in different domains" section. -5. Check if sample.html bundled with web-socket-js works. +5. If you are NOT using web-socket-ruby or em-websocket as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details. -6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall. +6. Check if sample.html bundled with web-socket-js works. -7. Install [debugger version of Flash Player](http://www.adobe.com/support/flashplayer/downloads.html) to see Flash errors. +7. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall. + +8. Install [debugger version of Flash Player](http://www.adobe.com/support/flashplayer/downloads.html) to see Flash errors. ## Supported environments -- cgit v1.2.1 From 840bade50b095168e49992d1f8614ec189498c12 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Mon, 18 Jul 2011 21:02:54 +0900 Subject: Adding event.text to error message on IoError and SecurityError. --- WebSocketMain.swf | Bin 175830 -> 175845 bytes WebSocketMainInsecure.zip | Bin 166610 -> 166515 bytes flash-src/WebSocket.as | 12 ++++++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/WebSocketMain.swf b/WebSocketMain.swf index 20a451f..5c8ab83 100644 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip index 58e2a4e..e996b94 100644 Binary files a/WebSocketMainInsecure.zip and b/WebSocketMainInsecure.zip differ diff --git a/flash-src/WebSocket.as b/flash-src/WebSocket.as index 813f517..43b274a 100644 --- a/flash-src/WebSocket.as +++ b/flash-src/WebSocket.as @@ -208,9 +208,11 @@ public class WebSocket extends EventDispatcher { private function onSocketIoError(event:IOErrorEvent):void { var message:String; if (readyState == CONNECTING) { - message = "cannot connect to Web Socket server at " + url + " (IoError)"; + message = "cannot connect to Web Socket server at " + url + " (IoError: " + event.text + ")"; } else { - message = "error communicating with Web Socket server at " + url + " (IoError)"; + message = + "error communicating with Web Socket server at " + url + + " (IoError: " + event.text + ")"; } onError(message); } @@ -219,10 +221,12 @@ public class WebSocket extends EventDispatcher { var message:String; if (readyState == CONNECTING) { message = - "cannot connect to Web Socket server at " + url + " (SecurityError)\n" + + "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)"; + message = + "error communicating with Web Socket server at " + url + + " (SecurityError: " + event.text + ")"; } onError(message); } -- cgit v1.2.1 From 49c2b030f9bd2933bfbae7d3ebeac915303e703d Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Tue, 19 Jul 2011 13:28:09 +0900 Subject: Updating link to node.js implementation of Flash socket policy file server. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d963d8..7b34ace 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ This implementation uses Flash's socket, which means that your server must provi If you use [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master) or [em-websocket](https://github.com/igrigorik/em-websocket), you don't need anything special, because they handle Flash socket policy file request. But if you already provide socket policy file at port **843**, you need to modify the file to allow access to Web Socket port, because it precedes what the libraries provide. -If you use other Web Socket server implementation, you need to provide socket policy file yourself. See [Setting up A Flash Socket Policy File](http://www.lightsphere.com/dev/articles/flash_socket_policy.html) for details and sample script to run socket policy file server. [node.js implementation is available here](http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js). +If you use other Web Socket server implementation, you need to provide socket policy file yourself. See [Setting up A Flash Socket Policy File](http://www.lightsphere.com/dev/articles/flash_socket_policy.html) for details and sample script to run socket policy file server. [node.js implementation is available here](https://github.com/3rd-Eden/FlashPolicyFileServer). Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby or em-websocket. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster. -- cgit v1.2.1 From 693e1556132709934868190a49fbf646ce56c8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Bystr=C3=B6m?= Date: Fri, 22 Jul 2011 14:15:13 +0200 Subject: Setting for forcing use of Flash over native Web Socket implementation --- web_socket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_socket.js b/web_socket.js index 3cabbd4..3a50f90 100644 --- a/web_socket.js +++ b/web_socket.js @@ -4,8 +4,8 @@ // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol (function() { - - if (window.WebSocket) return; + + if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return; var console = window.console; if (!console || !console.log || !console.error) { -- cgit v1.2.1 From 8cb0574ce8dc693e5f142ab4de147c58844cea5b Mon Sep 17 00:00:00 2001 From: Bernard Potocki Date: Fri, 5 Aug 2011 15:06:59 +0200 Subject: Add WebSocketLogger and use it in every submodule. This allow to easly manage debug switch, but also allow overwrite of logger for use in other libraries using web-socket-js. --- web_socket.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/web_socket.js b/web_socket.js index 3cabbd4..6d3a2d4 100644 --- a/web_socket.js +++ b/web_socket.js @@ -6,19 +6,25 @@ (function() { if (window.WebSocket) return; - - var console = window.console; - if (!console || !console.log || !console.error) { - console = {log: function(){ }, error: function(){ }}; + + if (!window.WebSocketLogger || !window.WebSocketLogger.log || !window.WebSocketLogger.error) { + var temp_console = window.console; + if (!temp_console || !temp_console.log || !temp_console.error) { + temp_console = {log: function(){ }, error: function(){ }}; + } + window.WebSocketLogger = { + log: function(msg) { if (window.WEB_SOCKET_DEBUG) temp_console.log(msg) }, + error: function(msg) { if (window.WEB_SOCKET_DEBUG) temp_console.error(msg) } + } } // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. if (!swfobject.getFlashPlayerVersion().major >= 10) { - console.error("Flash Player >= 10.0.0 is required."); + window.WebSocketLogger.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { - console.error( + window.WebSocketLogger.error( "WARNING: web-socket-js doesn't work in file:///... URL " + "unless you set Flash Security Settings properly. " + "Open the page via Web server i.e. http://..."); @@ -223,14 +229,14 @@ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } if (!window.WEB_SOCKET_SWF_LOCATION) { - console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + window.WebSocketLogger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } if (!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { var swfHost = RegExp.$1; if (location.host != swfHost) { - console.error( + window.WebSocketLogger.error( "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + "('" + location.host + "' != '" + swfHost + "'). " + "See also 'How to host HTML file and SWF file in different domains' section " + @@ -270,7 +276,7 @@ null, function(e) { if (!e.success) { - console.error("[WebSocket] swfobject.embedSWF failed"); + window.WebSocketLogger.error("[WebSocket] swfobject.embedSWF failed"); } }); }; @@ -307,7 +313,7 @@ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); } } catch (e) { - console.error(e); + window.WebSocketLogger.error(e); } }, 0); return true; @@ -315,12 +321,12 @@ // Called by Flash. WebSocket.__log = function(message) { - console.log(decodeURIComponent(message)); + window.WebSocketLogger.log(decodeURIComponent(message)); }; // Called by Flash. WebSocket.__error = function(message) { - console.error(decodeURIComponent(message)); + window.WebSocketLogger.error(decodeURIComponent(message)); }; WebSocket.__addTask = function(task) { -- cgit v1.2.1 From 179a0a97c23061d555672c367bda78bd7d84e2ac Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sat, 6 Aug 2011 23:06:56 +0900 Subject: Fixing import. --- WebSocketMain.swf | Bin 175845 -> 175846 bytes WebSocketMainInsecure.zip | Bin 166515 -> 166477 bytes flash-src/WebSocketMainInsecure.as | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/WebSocketMain.swf b/WebSocketMain.swf index 5c8ab83..b058c99 100644 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip index e996b94..b2ba830 100644 Binary files a/WebSocketMainInsecure.zip and b/WebSocketMainInsecure.zip differ diff --git a/flash-src/WebSocketMainInsecure.as b/flash-src/WebSocketMainInsecure.as index ea377be..ea4f496 100644 --- a/flash-src/WebSocketMainInsecure.as +++ b/flash-src/WebSocketMainInsecure.as @@ -5,7 +5,7 @@ package { -import flash.system.*; +import flash.system.Security; public class WebSocketMainInsecure extends WebSocketMain { -- cgit v1.2.1 From cf1ad5327834853b753f3206bfbfddc743f5b26c Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sat, 6 Aug 2011 23:48:06 +0900 Subject: Moving Flash classes to package net.gimite.websocket. --- WebSocketMain.swf | Bin 175846 -> 175933 bytes WebSocketMainInsecure.zip | Bin 166477 -> 166736 bytes flash-src/IWebSocketLogger.as | 8 - flash-src/WebSocket.as | 468 --------------------- flash-src/WebSocketEvent.as | 33 -- flash-src/WebSocketMain.as | 150 ------- flash-src/WebSocketMainInsecure.as | 19 - flash-src/build.sh | 4 +- flash-src/net/gimite/websocket/IWebSocketLogger.as | 11 + flash-src/net/gimite/websocket/WebSocket.as | 468 +++++++++++++++++++++ flash-src/net/gimite/websocket/WebSocketEvent.as | 33 ++ flash-src/net/gimite/websocket/WebSocketMain.as | 150 +++++++ .../net/gimite/websocket/WebSocketMainInsecure.as | 19 + 13 files changed, 683 insertions(+), 680 deletions(-) delete mode 100644 flash-src/IWebSocketLogger.as delete mode 100644 flash-src/WebSocket.as delete mode 100644 flash-src/WebSocketEvent.as delete mode 100644 flash-src/WebSocketMain.as delete mode 100644 flash-src/WebSocketMainInsecure.as create mode 100644 flash-src/net/gimite/websocket/IWebSocketLogger.as create mode 100644 flash-src/net/gimite/websocket/WebSocket.as create mode 100644 flash-src/net/gimite/websocket/WebSocketEvent.as create mode 100644 flash-src/net/gimite/websocket/WebSocketMain.as create mode 100644 flash-src/net/gimite/websocket/WebSocketMainInsecure.as diff --git a/WebSocketMain.swf b/WebSocketMain.swf index b058c99..423980c 100644 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip index b2ba830..560a8d4 100644 Binary files a/WebSocketMainInsecure.zip and b/WebSocketMainInsecure.zip differ diff --git a/flash-src/IWebSocketLogger.as b/flash-src/IWebSocketLogger.as deleted file mode 100644 index 24f4ef7..0000000 --- a/flash-src/IWebSocketLogger.as +++ /dev/null @@ -1,8 +0,0 @@ -package { - -public interface IWebSocketLogger { - function log(message:String):void; - function error(message:String):void; -} - -} diff --git a/flash-src/WebSocket.as b/flash-src/WebSocket.as deleted file mode 100644 index 43b274a..0000000 --- a/flash-src/WebSocket.as +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright: Hiroshi Ichikawa -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - -package { - -import com.adobe.net.proxies.RFC2817Socket; -import com.gsolo.encryption.MD5; -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.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 var CONNECTING:int = 0; - private static var OPEN:int = 1; - private static var CLOSING:int = 2; - private static var CLOSED:int = 3; - - private var id:int; - private var rawSocket:Socket; - private var tlsSocket:TLSSocket; - private var tlsConfig:TLSConfig; - private var socket:Socket; - 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 acceptedProtocol:String; - private var buffer:ByteArray = new ByteArray(); - private var headerState:int = 0; - private var readyState:int = CONNECTING; - private var cookie:String; - private var headers:String; - private var noiseChars:Array; - private var expectedDigest:String; - private var logger:IWebSocketLogger; - - 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; - initNoiseChars(); - 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 = decodeURIComponent(encData); - if (readyState == OPEN) { - socket.writeByte(0x00); - socket.writeUTFBytes(data); - socket.writeByte(0xff); - socket.flush(); - logger.log("sent: " + data); - return -1; - } else if (readyState == CLOSING || readyState == CLOSED) { - var bytes:ByteArray = new ByteArray(); - bytes.writeUTFBytes(data); - return bytes.length; // not sure whether it should include \x00 and \xff - } else { - fatal("invalid state"); - return 0; - } - } - - public function close(isError:Boolean = false):void { - logger.log("close"); - try { - if (readyState == OPEN && !isError) { - socket.writeByte(0xff); - socket.writeByte(0x00); - socket.flush(); - } - socket.close(); - } catch (ex:Error) { } - readyState = CLOSED; - this.dispatchEvent(new WebSocketEvent(isError ? "error" : "close")); - } - - 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 key1:String = generateKey(); - var key2:String = generateKey(); - var key3:String = generateKey3(); - expectedDigest = getSecurityDigest(key1, key2, key3); - 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" + - "Upgrade: WebSocket\r\n" + - "Connection: Upgrade\r\n" + - "Host: {1}\r\n" + - "Origin: {2}\r\n" + - "Cookie: {3}\r\n" + - "Sec-WebSocket-Key1: {4}\r\n" + - "Sec-WebSocket-Key2: {5}\r\n" + - "{6}" + - "\r\n", - path, hostValue, origin, cookie, key1, key2, opt); - logger.log("request header:\n" + req); - socket.writeUTFBytes(req); - logger.log("sent key3: " + key3); - writeBytes(key3); - socket.flush(); - } - - private function onSocketClose(event:Event):void { - logger.log("closed"); - readyState = CLOSED; - this.dispatchEvent(new WebSocketEvent("close")); - } - - 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 + ")"; - } - onError(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 + ")"; - } - onError(message); - } - - private function onError(message:String):void { - if (readyState == CLOSED) return; - logger.error(message); - close(readyState != CONNECTING); - } - - 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 (!validateHeader(headerStr)) return; - removeBufferBefore(pos + 1); - pos = -1; - } - } else if (headerState == 4) { - if (pos == 15) { - var replyDigest:String = readBytes(buffer, 0, 16); - logger.log("reply digest: " + replyDigest); - if (replyDigest != expectedDigest) { - onError("digest doesn't match: " + replyDigest + " != " + expectedDigest); - return; - } - headerState = 5; - removeBufferBefore(pos + 1); - pos = -1; - readyState = OPEN; - this.dispatchEvent(new WebSocketEvent("open")); - } - } else { - if (buffer[pos] == 0xff && pos > 0) { - if (buffer[0] != 0x00) { - onError("data must start with \\x00"); - return; - } - var data:String = readUTFBytes(buffer, 1, pos - 1); - logger.log("received: " + data); - this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data))); - removeBufferBefore(pos + 1); - pos = -1; - } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing - logger.log("received closing packet"); - removeBufferBefore(pos + 1); - pos = -1; - close(); - } - } - } - } - - private function validateHeader(headerStr:String):Boolean { - var lines:Array = headerStr.split(/\r\n/); - if (!lines[0].match(/^HTTP\/1.1 101 /)) { - onError("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) { - onError("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") { - onError("invalid Upgrade: " + header["Upgrade"]); - return false; - } - if (lowerHeader["connection"] != "upgrade") { - onError("invalid Connection: " + header["Connection"]); - return false; - } - if (!lowerHeader["sec-websocket-origin"]) { - if (lowerHeader["websocket-origin"]) { - onError( - "The WebSocket server speaks old WebSocket protocol, " + - "which is not supported by web-socket-js. " + - "It requires WebSocket protocol 76 or later. " + - "Try newer version of the server if available."); - } else { - onError("header Sec-WebSocket-Origin is missing"); - } - return false; - } - var resOrigin:String = lowerHeader["sec-websocket-origin"]; - if (resOrigin != origin) { - onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'"); - return false; - } - if (requestedProtocols.length > 0) { - acceptedProtocol = header["sec-websocket-protocol"]; - if (requestedProtocols.indexOf(acceptedProtocol) < 0) { - onError("protocol doesn't match: '" + - acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'"); - return false; - } - } - return true; - } - - 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 initNoiseChars():void { - noiseChars = new Array(); - for (var i:int = 0x21; i <= 0x2f; ++i) { - noiseChars.push(String.fromCharCode(i)); - } - for (var j:int = 0x3a; j <= 0x7a; ++j) { - noiseChars.push(String.fromCharCode(j)); - } - } - - private function generateKey():String { - var spaces:uint = randomInt(1, 12); - var max:uint = uint.MAX_VALUE / spaces; - var number:uint = randomInt(0, max); - var key:String = (number * spaces).toString(); - var noises:int = randomInt(1, 12); - var pos:int; - for (var i:int = 0; i < noises; ++i) { - var char:String = noiseChars[randomInt(0, noiseChars.length - 1)]; - pos = randomInt(0, key.length); - key = key.substr(0, pos) + char + key.substr(pos); - } - for (var j:int = 0; j < spaces; ++j) { - pos = randomInt(1, key.length - 1); - key = key.substr(0, pos) + " " + key.substr(pos); - } - return key; - } - - private function generateKey3():String { - var key3:String = ""; - for (var i:int = 0; i < 8; ++i) { - key3 += String.fromCharCode(randomInt(0, 255)); - } - return key3; - } - - private function getSecurityDigest(key1:String, key2:String, key3:String):String { - var bytes1:String = keyToBytes(key1); - var bytes2:String = keyToBytes(key2); - return MD5.rstr_md5(bytes1 + bytes2 + key3); - } - - private function keyToBytes(key:String):String { - var keyNum:uint = parseInt(key.replace(/[^\d]/g, "")); - var spaces:uint = 0; - for (var i:int = 0; i < key.length; ++i) { - if (key.charAt(i) == " ") ++spaces; - } - var resultNum:uint = keyNum / spaces; - var bytes:String = ""; - for (var j:int = 3; j >= 0; --j) { - bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff); - } - return bytes; - } - - // Writes byte sequence to socket. - // bytes is String in special format where bytes[i] is i-th byte, not i-th character. - private function writeBytes(bytes:String):void { - for (var i:int = 0; i < bytes.length; ++i) { - socket.writeByte(bytes.charCodeAt(i)); - } - } - - // Reads specified number of bytes from buffer, and returns it as special format String - // where bytes[i] is i-th byte (not i-th character). - private function readBytes(buffer:ByteArray, start:int, numBytes:int):String { - buffer.position = start; - var bytes:String = ""; - for (var i:int = 0; i < numBytes; ++i) { - // & 0xff is to make \x80-\xff positive number. - bytes += String.fromCharCode(buffer.readByte() & 0xff); - } - return bytes; - } - - 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; - } - - // for debug - private function dumpBytes(bytes:String):void { - var output:String = ""; - for (var i:int = 0; i < bytes.length; ++i) { - output += bytes.charCodeAt(i).toString() + ", "; - } - logger.log(output); - } - -} - -} diff --git a/flash-src/WebSocketEvent.as b/flash-src/WebSocketEvent.as deleted file mode 100644 index 598eeb2..0000000 --- a/flash-src/WebSocketEvent.as +++ /dev/null @@ -1,33 +0,0 @@ -package { - -import flash.events.Event; - -/** - * This class represents a generic websocket event. It contains the standard "type" - * parameter as well as a "message" parameter. - */ -public class WebSocketEvent extends Event { - - public static const OPEN:String = "open"; - public static const CLOSE:String = "close"; - public static const MESSAGE:String = "message"; - public static const ERROR:String = "error"; - - public var message:String; - - public function WebSocketEvent( - type:String, message:String = null, bubbles:Boolean = false, cancelable:Boolean = false) { - super(type, bubbles, cancelable); - this.message = message; - } - - public override function clone():Event { - return new WebSocketEvent(this.type, this.message, this.bubbles, this.cancelable); - } - - public override function toString():String { - return "WebSocketEvent: " + this.type + ": " + this.message; - } -} - -} diff --git a/flash-src/WebSocketMain.as b/flash-src/WebSocketMain.as deleted file mode 100644 index 1bf3d7e..0000000 --- a/flash-src/WebSocketMain.as +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright: Hiroshi Ichikawa -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - -package { - -import flash.display.Sprite; -import flash.external.ExternalInterface; -import flash.system.Security; -import flash.utils.setTimeout; - -import mx.utils.URLUtil; - -/** - * Provides JavaScript API of WebSocket. - */ -public class WebSocketMain extends Sprite implements IWebSocketLogger{ - - private var callerUrl:String; - private var debug:Boolean = false; - private var manualPolicyFileLoaded:Boolean = false; - private var webSockets:Array = []; - private var eventQueue:Array = []; - - public function WebSocketMain() { - ExternalInterface.addCallback("setCallerUrl", setCallerUrl); - ExternalInterface.addCallback("setDebug", setDebug); - ExternalInterface.addCallback("create", create); - ExternalInterface.addCallback("send", send); - ExternalInterface.addCallback("close", close); - ExternalInterface.addCallback("loadManualPolicyFile", loadManualPolicyFile); - ExternalInterface.addCallback("receiveEvents", receiveEvents); - ExternalInterface.call("WebSocket.__onFlashInitialized"); - } - - public function setCallerUrl(url:String):void { - callerUrl = url; - } - - public function setDebug(val:Boolean):void { - debug = val; - } - - private function loadDefaultPolicyFile(wsUrl:String):void { - var policyUrl:String = "xmlsocket://" + URLUtil.getServerName(wsUrl) + ":843"; - log("policy file: " + policyUrl); - Security.loadPolicyFile(policyUrl); - } - - public function loadManualPolicyFile(policyUrl:String):void { - log("policy file: " + policyUrl); - Security.loadPolicyFile(policyUrl); - manualPolicyFileLoaded = true; - } - - public function log(message:String):void { - if (debug) { - ExternalInterface.call("WebSocket.__log", encodeURIComponent("[WebSocket] " + message)); - } - } - - public function error(message:String):void { - ExternalInterface.call("WebSocket.__error", encodeURIComponent("[WebSocket] " + message)); - } - - private function parseEvent(event:WebSocketEvent):Object { - var webSocket:WebSocket = event.target as WebSocket; - var eventObj:Object = {}; - eventObj.type = event.type; - eventObj.webSocketId = webSocket.getId(); - eventObj.readyState = webSocket.getReadyState(); - eventObj.protocol = webSocket.getAcceptedProtocol(); - if (event.message !== null) { - eventObj.message = event.message; - } - return eventObj; - } - - public function create( - webSocketId:int, - url:String, protocols:Array, - proxyHost:String = null, proxyPort:int = 0, - headers:String = null):void { - if (!manualPolicyFileLoaded) { - loadDefaultPolicyFile(url); - } - var newSocket:WebSocket = new WebSocket( - webSocketId, url, protocols, getOrigin(), proxyHost, proxyPort, - getCookie(url), headers, this); - newSocket.addEventListener("open", onSocketEvent); - newSocket.addEventListener("close", onSocketEvent); - newSocket.addEventListener("error", onSocketEvent); - newSocket.addEventListener("message", onSocketEvent); - webSockets[webSocketId] = newSocket; - } - - public function send(webSocketId:int, encData:String):int { - var webSocket:WebSocket = webSockets[webSocketId]; - return webSocket.send(encData); - } - - public function close(webSocketId:int):void { - var webSocket:WebSocket = webSockets[webSocketId]; - webSocket.close(); - } - - public function receiveEvents():Object { - var result:Object = eventQueue; - eventQueue = []; - return result; - } - - private function getOrigin():String { - return (URLUtil.getProtocol(this.callerUrl) + "://" + - URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase(); - } - - private function getCookie(url:String):String { - if (URLUtil.getServerName(url).toLowerCase() == - URLUtil.getServerName(this.callerUrl).toLowerCase()) { - return ExternalInterface.call("function(){return document.cookie}"); - } else { - return ""; - } - } - - /** - * Socket event handler. - */ - public function onSocketEvent(event:WebSocketEvent):void { - var eventObj:Object = parseEvent(event); - eventQueue.push(eventObj); - processEvents(); - } - - /** - * Process our event queue. If javascript is unresponsive, set - * a timeout and try again. - */ - public function processEvents():void { - if (eventQueue.length == 0) return; - if (!ExternalInterface.call("WebSocket.__onFlashEvent")) { - setTimeout(processEvents, 500); - } - } - -} - -} diff --git a/flash-src/WebSocketMainInsecure.as b/flash-src/WebSocketMainInsecure.as deleted file mode 100644 index ea4f496..0000000 --- a/flash-src/WebSocketMainInsecure.as +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright: Hiroshi Ichikawa -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - -package { - -import flash.system.Security; - -public class WebSocketMainInsecure extends WebSocketMain { - - public function WebSocketMainInsecure() { - Security.allowDomain("*"); - super(); - } - -} - -} diff --git a/flash-src/build.sh b/flash-src/build.sh index 598674a..4d33853 100755 --- a/flash-src/build.sh +++ b/flash-src/build.sh @@ -3,8 +3,8 @@ # You need Flex 4 SDK: # http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4 -mxmlc -static-link-runtime-shared-libraries -target-player=10.0.0 -output=../WebSocketMain.swf WebSocketMain.as && -mxmlc -static-link-runtime-shared-libraries -output=../WebSocketMainInsecure.swf WebSocketMainInsecure.as && +mxmlc -static-link-runtime-shared-libraries -target-player=10.0.0 -output=../WebSocketMain.swf -source-path=. net/gimite/websocket/WebSocketMain.as && +mxmlc -static-link-runtime-shared-libraries -output=../WebSocketMainInsecure.swf -source-path=. net/gimite/websocket/WebSocketMainInsecure.as && cd .. && zip WebSocketMainInsecure.zip WebSocketMainInsecure.swf && rm WebSocketMainInsecure.swf diff --git a/flash-src/net/gimite/websocket/IWebSocketLogger.as b/flash-src/net/gimite/websocket/IWebSocketLogger.as new file mode 100644 index 0000000..c3384f3 --- /dev/null +++ b/flash-src/net/gimite/websocket/IWebSocketLogger.as @@ -0,0 +1,11 @@ +// Copyright: Hiroshi Ichikawa +// License: New BSD License + +package net.gimite.websocket { + +public interface IWebSocketLogger { + function log(message:String):void; + function error(message:String):void; +} + +} diff --git a/flash-src/net/gimite/websocket/WebSocket.as b/flash-src/net/gimite/websocket/WebSocket.as new file mode 100644 index 0000000..f043b60 --- /dev/null +++ b/flash-src/net/gimite/websocket/WebSocket.as @@ -0,0 +1,468 @@ +// Copyright: Hiroshi Ichikawa +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + +package net.gimite.websocket { + +import com.adobe.net.proxies.RFC2817Socket; +import com.gsolo.encryption.MD5; +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.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 var CONNECTING:int = 0; + private static var OPEN:int = 1; + private static var CLOSING:int = 2; + private static var CLOSED:int = 3; + + private var id:int; + private var rawSocket:Socket; + private var tlsSocket:TLSSocket; + private var tlsConfig:TLSConfig; + private var socket:Socket; + 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 acceptedProtocol:String; + private var buffer:ByteArray = new ByteArray(); + private var headerState:int = 0; + private var readyState:int = CONNECTING; + private var cookie:String; + private var headers:String; + private var noiseChars:Array; + private var expectedDigest:String; + private var logger:IWebSocketLogger; + + 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; + initNoiseChars(); + 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 = decodeURIComponent(encData); + if (readyState == OPEN) { + socket.writeByte(0x00); + socket.writeUTFBytes(data); + socket.writeByte(0xff); + socket.flush(); + logger.log("sent: " + data); + return -1; + } else if (readyState == CLOSING || readyState == CLOSED) { + var bytes:ByteArray = new ByteArray(); + bytes.writeUTFBytes(data); + return bytes.length; // not sure whether it should include \x00 and \xff + } else { + fatal("invalid state"); + return 0; + } + } + + public function close(isError:Boolean = false):void { + logger.log("close"); + try { + if (readyState == OPEN && !isError) { + socket.writeByte(0xff); + socket.writeByte(0x00); + socket.flush(); + } + socket.close(); + } catch (ex:Error) { } + readyState = CLOSED; + this.dispatchEvent(new WebSocketEvent(isError ? "error" : "close")); + } + + 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 key1:String = generateKey(); + var key2:String = generateKey(); + var key3:String = generateKey3(); + expectedDigest = getSecurityDigest(key1, key2, key3); + 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" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Host: {1}\r\n" + + "Origin: {2}\r\n" + + "Cookie: {3}\r\n" + + "Sec-WebSocket-Key1: {4}\r\n" + + "Sec-WebSocket-Key2: {5}\r\n" + + "{6}" + + "\r\n", + path, hostValue, origin, cookie, key1, key2, opt); + logger.log("request header:\n" + req); + socket.writeUTFBytes(req); + logger.log("sent key3: " + key3); + writeBytes(key3); + socket.flush(); + } + + private function onSocketClose(event:Event):void { + logger.log("closed"); + readyState = CLOSED; + this.dispatchEvent(new WebSocketEvent("close")); + } + + 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 + ")"; + } + onError(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 + ")"; + } + onError(message); + } + + private function onError(message:String):void { + if (readyState == CLOSED) return; + logger.error(message); + close(readyState != CONNECTING); + } + + 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 (!validateHeader(headerStr)) return; + removeBufferBefore(pos + 1); + pos = -1; + } + } else if (headerState == 4) { + if (pos == 15) { + var replyDigest:String = readBytes(buffer, 0, 16); + logger.log("reply digest: " + replyDigest); + if (replyDigest != expectedDigest) { + onError("digest doesn't match: " + replyDigest + " != " + expectedDigest); + return; + } + headerState = 5; + removeBufferBefore(pos + 1); + pos = -1; + readyState = OPEN; + this.dispatchEvent(new WebSocketEvent("open")); + } + } else { + if (buffer[pos] == 0xff && pos > 0) { + if (buffer[0] != 0x00) { + onError("data must start with \\x00"); + return; + } + var data:String = readUTFBytes(buffer, 1, pos - 1); + logger.log("received: " + data); + this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data))); + removeBufferBefore(pos + 1); + pos = -1; + } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing + logger.log("received closing packet"); + removeBufferBefore(pos + 1); + pos = -1; + close(); + } + } + } + } + + private function validateHeader(headerStr:String):Boolean { + var lines:Array = headerStr.split(/\r\n/); + if (!lines[0].match(/^HTTP\/1.1 101 /)) { + onError("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) { + onError("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") { + onError("invalid Upgrade: " + header["Upgrade"]); + return false; + } + if (lowerHeader["connection"] != "upgrade") { + onError("invalid Connection: " + header["Connection"]); + return false; + } + if (!lowerHeader["sec-websocket-origin"]) { + if (lowerHeader["websocket-origin"]) { + onError( + "The WebSocket server speaks old WebSocket protocol, " + + "which is not supported by web-socket-js. " + + "It requires WebSocket protocol 76 or later. " + + "Try newer version of the server if available."); + } else { + onError("header Sec-WebSocket-Origin is missing"); + } + return false; + } + var resOrigin:String = lowerHeader["sec-websocket-origin"]; + if (resOrigin != origin) { + onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'"); + return false; + } + if (requestedProtocols.length > 0) { + acceptedProtocol = header["sec-websocket-protocol"]; + if (requestedProtocols.indexOf(acceptedProtocol) < 0) { + onError("protocol doesn't match: '" + + acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'"); + return false; + } + } + return true; + } + + 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 initNoiseChars():void { + noiseChars = new Array(); + for (var i:int = 0x21; i <= 0x2f; ++i) { + noiseChars.push(String.fromCharCode(i)); + } + for (var j:int = 0x3a; j <= 0x7a; ++j) { + noiseChars.push(String.fromCharCode(j)); + } + } + + private function generateKey():String { + var spaces:uint = randomInt(1, 12); + var max:uint = uint.MAX_VALUE / spaces; + var number:uint = randomInt(0, max); + var key:String = (number * spaces).toString(); + var noises:int = randomInt(1, 12); + var pos:int; + for (var i:int = 0; i < noises; ++i) { + var char:String = noiseChars[randomInt(0, noiseChars.length - 1)]; + pos = randomInt(0, key.length); + key = key.substr(0, pos) + char + key.substr(pos); + } + for (var j:int = 0; j < spaces; ++j) { + pos = randomInt(1, key.length - 1); + key = key.substr(0, pos) + " " + key.substr(pos); + } + return key; + } + + private function generateKey3():String { + var key3:String = ""; + for (var i:int = 0; i < 8; ++i) { + key3 += String.fromCharCode(randomInt(0, 255)); + } + return key3; + } + + private function getSecurityDigest(key1:String, key2:String, key3:String):String { + var bytes1:String = keyToBytes(key1); + var bytes2:String = keyToBytes(key2); + return MD5.rstr_md5(bytes1 + bytes2 + key3); + } + + private function keyToBytes(key:String):String { + var keyNum:uint = parseInt(key.replace(/[^\d]/g, "")); + var spaces:uint = 0; + for (var i:int = 0; i < key.length; ++i) { + if (key.charAt(i) == " ") ++spaces; + } + var resultNum:uint = keyNum / spaces; + var bytes:String = ""; + for (var j:int = 3; j >= 0; --j) { + bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff); + } + return bytes; + } + + // Writes byte sequence to socket. + // bytes is String in special format where bytes[i] is i-th byte, not i-th character. + private function writeBytes(bytes:String):void { + for (var i:int = 0; i < bytes.length; ++i) { + socket.writeByte(bytes.charCodeAt(i)); + } + } + + // Reads specified number of bytes from buffer, and returns it as special format String + // where bytes[i] is i-th byte (not i-th character). + private function readBytes(buffer:ByteArray, start:int, numBytes:int):String { + buffer.position = start; + var bytes:String = ""; + for (var i:int = 0; i < numBytes; ++i) { + // & 0xff is to make \x80-\xff positive number. + bytes += String.fromCharCode(buffer.readByte() & 0xff); + } + return bytes; + } + + 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; + } + + // for debug + private function dumpBytes(bytes:String):void { + var output:String = ""; + for (var i:int = 0; i < bytes.length; ++i) { + output += bytes.charCodeAt(i).toString() + ", "; + } + logger.log(output); + } + +} + +} diff --git a/flash-src/net/gimite/websocket/WebSocketEvent.as b/flash-src/net/gimite/websocket/WebSocketEvent.as new file mode 100644 index 0000000..aa17cac --- /dev/null +++ b/flash-src/net/gimite/websocket/WebSocketEvent.as @@ -0,0 +1,33 @@ +package net.gimite.websocket { + +import flash.events.Event; + +/** + * This class represents a generic websocket event. It contains the standard "type" + * parameter as well as a "message" parameter. + */ +public class WebSocketEvent extends Event { + + public static const OPEN:String = "open"; + public static const CLOSE:String = "close"; + public static const MESSAGE:String = "message"; + public static const ERROR:String = "error"; + + public var message:String; + + public function WebSocketEvent( + type:String, message:String = null, bubbles:Boolean = false, cancelable:Boolean = false) { + super(type, bubbles, cancelable); + this.message = message; + } + + public override function clone():Event { + return new WebSocketEvent(this.type, this.message, this.bubbles, this.cancelable); + } + + public override function toString():String { + return "WebSocketEvent: " + this.type + ": " + this.message; + } +} + +} diff --git a/flash-src/net/gimite/websocket/WebSocketMain.as b/flash-src/net/gimite/websocket/WebSocketMain.as new file mode 100644 index 0000000..28cd66d --- /dev/null +++ b/flash-src/net/gimite/websocket/WebSocketMain.as @@ -0,0 +1,150 @@ +// Copyright: Hiroshi Ichikawa +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + +package net.gimite.websocket { + +import flash.display.Sprite; +import flash.external.ExternalInterface; +import flash.system.Security; +import flash.utils.setTimeout; + +import mx.utils.URLUtil; + +/** + * Provides JavaScript API of WebSocket. + */ +public class WebSocketMain extends Sprite implements IWebSocketLogger{ + + private var callerUrl:String; + private var debug:Boolean = false; + private var manualPolicyFileLoaded:Boolean = false; + private var webSockets:Array = []; + private var eventQueue:Array = []; + + public function WebSocketMain() { + ExternalInterface.addCallback("setCallerUrl", setCallerUrl); + ExternalInterface.addCallback("setDebug", setDebug); + ExternalInterface.addCallback("create", create); + ExternalInterface.addCallback("send", send); + ExternalInterface.addCallback("close", close); + ExternalInterface.addCallback("loadManualPolicyFile", loadManualPolicyFile); + ExternalInterface.addCallback("receiveEvents", receiveEvents); + ExternalInterface.call("WebSocket.__onFlashInitialized"); + } + + public function setCallerUrl(url:String):void { + callerUrl = url; + } + + public function setDebug(val:Boolean):void { + debug = val; + } + + private function loadDefaultPolicyFile(wsUrl:String):void { + var policyUrl:String = "xmlsocket://" + URLUtil.getServerName(wsUrl) + ":843"; + log("policy file: " + policyUrl); + Security.loadPolicyFile(policyUrl); + } + + public function loadManualPolicyFile(policyUrl:String):void { + log("policy file: " + policyUrl); + Security.loadPolicyFile(policyUrl); + manualPolicyFileLoaded = true; + } + + public function log(message:String):void { + if (debug) { + ExternalInterface.call("WebSocket.__log", encodeURIComponent("[WebSocket] " + message)); + } + } + + public function error(message:String):void { + ExternalInterface.call("WebSocket.__error", encodeURIComponent("[WebSocket] " + message)); + } + + private function parseEvent(event:WebSocketEvent):Object { + var webSocket:WebSocket = event.target as WebSocket; + var eventObj:Object = {}; + eventObj.type = event.type; + eventObj.webSocketId = webSocket.getId(); + eventObj.readyState = webSocket.getReadyState(); + eventObj.protocol = webSocket.getAcceptedProtocol(); + if (event.message !== null) { + eventObj.message = event.message; + } + return eventObj; + } + + public function create( + webSocketId:int, + url:String, protocols:Array, + proxyHost:String = null, proxyPort:int = 0, + headers:String = null):void { + if (!manualPolicyFileLoaded) { + loadDefaultPolicyFile(url); + } + var newSocket:WebSocket = new WebSocket( + webSocketId, url, protocols, getOrigin(), proxyHost, proxyPort, + getCookie(url), headers, this); + newSocket.addEventListener("open", onSocketEvent); + newSocket.addEventListener("close", onSocketEvent); + newSocket.addEventListener("error", onSocketEvent); + newSocket.addEventListener("message", onSocketEvent); + webSockets[webSocketId] = newSocket; + } + + public function send(webSocketId:int, encData:String):int { + var webSocket:WebSocket = webSockets[webSocketId]; + return webSocket.send(encData); + } + + public function close(webSocketId:int):void { + var webSocket:WebSocket = webSockets[webSocketId]; + webSocket.close(); + } + + public function receiveEvents():Object { + var result:Object = eventQueue; + eventQueue = []; + return result; + } + + private function getOrigin():String { + return (URLUtil.getProtocol(this.callerUrl) + "://" + + URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase(); + } + + private function getCookie(url:String):String { + if (URLUtil.getServerName(url).toLowerCase() == + URLUtil.getServerName(this.callerUrl).toLowerCase()) { + return ExternalInterface.call("function(){return document.cookie}"); + } else { + return ""; + } + } + + /** + * Socket event handler. + */ + public function onSocketEvent(event:WebSocketEvent):void { + var eventObj:Object = parseEvent(event); + eventQueue.push(eventObj); + processEvents(); + } + + /** + * Process our event queue. If javascript is unresponsive, set + * a timeout and try again. + */ + public function processEvents():void { + if (eventQueue.length == 0) return; + if (!ExternalInterface.call("WebSocket.__onFlashEvent")) { + setTimeout(processEvents, 500); + } + } + +} + +} diff --git a/flash-src/net/gimite/websocket/WebSocketMainInsecure.as b/flash-src/net/gimite/websocket/WebSocketMainInsecure.as new file mode 100644 index 0000000..e845839 --- /dev/null +++ b/flash-src/net/gimite/websocket/WebSocketMainInsecure.as @@ -0,0 +1,19 @@ +// Copyright: Hiroshi Ichikawa +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + +package net.gimite.websocket { + +import flash.system.Security; + +public class WebSocketMainInsecure extends WebSocketMain { + + public function WebSocketMainInsecure() { + Security.allowDomain("*"); + super(); + } + +} + +} -- cgit v1.2.1 From 734625062581438b019967330028848c1b8b531f Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 7 Aug 2011 11:00:36 +0900 Subject: Adding Ant build file to build SWF and SWC files. --- .gitignore | 1 + WebSocketMain.swf | Bin 175933 -> 175928 bytes flash-src/.gitignore | 1 + flash-src/build.properties.sample | 2 ++ flash-src/build.sh | 2 ++ flash-src/build.xml | 65 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 flash-src/.gitignore create mode 100644 flash-src/build.properties.sample create mode 100644 flash-src/build.xml diff --git a/.gitignore b/.gitignore index 80e8f7a..ecf5763 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ test.html +WebSocket.swc diff --git a/WebSocketMain.swf b/WebSocketMain.swf index 423980c..657c762 100644 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/flash-src/.gitignore b/flash-src/.gitignore new file mode 100644 index 0000000..56fb545 --- /dev/null +++ b/flash-src/.gitignore @@ -0,0 +1 @@ +build.properties diff --git a/flash-src/build.properties.sample b/flash-src/build.properties.sample new file mode 100644 index 0000000..6ae8786 --- /dev/null +++ b/flash-src/build.properties.sample @@ -0,0 +1,2 @@ +# Point this to your Flex SDK directory. +FLEX_HOME=/usr/local/share/flex_sdk_4 diff --git a/flash-src/build.sh b/flash-src/build.sh index 4d33853..0fb02a5 100755 --- a/flash-src/build.sh +++ b/flash-src/build.sh @@ -1,5 +1,7 @@ #!/bin/sh +# A script to build WebSocketMain.swf and WebSocketMainInsecure.zip. + # You need Flex 4 SDK: # http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4 diff --git a/flash-src/build.xml b/flash-src/build.xml new file mode 100644 index 0000000..967ff89 --- /dev/null +++ b/flash-src/build.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + true + 10 + + + + + + + + + true + 10 + + + + + + + + + true + + + + + + + + \ No newline at end of file -- cgit v1.2.1 From a71f371861dc0c19002ae7ecf8ce6dd60bc5f259 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 7 Aug 2011 13:04:04 +0900 Subject: Adding flag WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR in case the user rename WebSocketMainInsecure.swf. --- web_socket.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web_socket.js b/web_socket.js index 3a50f90..8507468 100644 --- a/web_socket.js +++ b/web_socket.js @@ -226,7 +226,8 @@ console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } - if (!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && + !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { var swfHost = RegExp.$1; if (location.host != swfHost) { @@ -234,7 +235,8 @@ "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + "('" + location.host + "' != '" + swfHost + "'). " + "See also 'How to host HTML file and SWF file in different domains' section " + - "in README.md."); + "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + + "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); } } var container = document.createElement("div"); -- cgit v1.2.1 From 0595df7c40581bdf930662154efdbd6617626348 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 7 Aug 2011 17:59:07 +0900 Subject: Fixing a bug checking Flash Player version. --- web_socket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_socket.js b/web_socket.js index 7c18745..5397332 100644 --- a/web_socket.js +++ b/web_socket.js @@ -18,7 +18,7 @@ } // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. - if (!swfobject.getFlashPlayerVersion().major >= 10) { + if (swfobject.getFlashPlayerVersion().major < 10) { logger.error("Flash Player >= 10.0.0 is required."); return; } -- cgit v1.2.1 From efec7adc76d8905c5fec001e5eef89e0c85e09b9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 14 Aug 2011 16:56:36 +0900 Subject: Fixing build.xml syntax error. --- flash-src/build.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flash-src/build.xml b/flash-src/build.xml index 967ff89..6ab810a 100644 --- a/flash-src/build.xml +++ b/flash-src/build.xml @@ -1,3 +1,5 @@ + + - -- cgit v1.2.1 From ed0622a890de811eb6d766d5c5a9a62d726f0593 Mon Sep 17 00:00:00 2001 From: Hiroshi Ichikawa Date: Sun, 14 Aug 2011 21:45:09 +0900 Subject: Setting static-link-runtime-shared-libraries to false for swc file to avoid errors, per issue #86 . --- flash-src/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flash-src/build.xml b/flash-src/build.xml index 6ab810a..9f42796 100644 --- a/flash-src/build.xml +++ b/flash-src/build.xml @@ -54,7 +54,7 @@ strict="true" accessible="false" fork="true" - static-link-runtime-shared-libraries="true"> + static-link-runtime-shared-libraries="false"> true -- cgit v1.2.1