├── websockify.py ├── tests ├── include ├── latency.py ├── base64.js ├── utf8-list.py ├── b64_vs_utf8.py ├── test_auth_plugins.py ├── echo.rb ├── simple.html ├── echo.py ├── base64.html ├── echo.html ├── test_websocketproxy.py ├── load.py ├── plain_echo.html ├── load.html └── latency.html ├── test-requirements.txt ├── MANIFEST.in ├── websockify ├── __init__.py ├── token_plugins.py └── auth_plugins.py ├── run ├── include ├── web-socket-js │ ├── WebSocketMain.swf │ ├── README.txt │ └── swfobject.js ├── base64.js ├── keysym.js ├── wsirc.js ├── webutil.js ├── wstelnet.js └── websock.js ├── .gitmodules ├── Makefile ├── docs ├── TODO ├── flash_policy.txt ├── release.txt ├── notes ├── latency_results.txt ├── websockify.1 └── LICENSE.LGPL-3 ├── .gitignore ├── .travis.yml ├── other ├── js │ ├── README.md │ ├── package.json │ └── websockify.js ├── Makefile ├── wswrap ├── project.clj ├── README.md ├── websocket.h ├── launch.sh ├── websockify.rb └── websockify.clj ├── rebind ├── Windows ├── noVNC Websocket Service Project │ ├── ProjectInstaller.cs │ ├── Program.cs │ ├── noVNC Websocket.sln │ ├── Service1.Designer.cs │ ├── Service1.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ProjectInstaller.Designer.cs │ ├── noVNC Websocket.csproj │ └── ProjectInstaller.resx └── Windows Service Readme.txt ├── tox.ini ├── LICENSE.txt ├── setup.py ├── wstelnet.html ├── CHANGES.txt ├── rebind.c ├── wsirc.html └── README.md /websockify.py: -------------------------------------------------------------------------------- 1 | run -------------------------------------------------------------------------------- /tests/include: -------------------------------------------------------------------------------- 1 | ../include -------------------------------------------------------------------------------- /tests/latency.py: -------------------------------------------------------------------------------- 1 | echo.py -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | mox3 2 | nose 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.txt README.md LICENSE.txt 2 | graft include 3 | -------------------------------------------------------------------------------- /websockify/__init__.py: -------------------------------------------------------------------------------- 1 | from websockify.websocket import * 2 | from websockify.websocketproxy import * 3 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import websockify 4 | 5 | websockify.websocketproxy.websockify_init() 6 | -------------------------------------------------------------------------------- /include/web-socket-js/WebSocketMain.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/websockify/HEAD/include/web-socket-js/WebSocketMain.swf -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "include/web-socket-js-project"] 2 | path = include/web-socket-js-project 3 | url = https://github.com/gimite/web-socket-js.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGETS=rebind.so 2 | CFLAGS += -fPIC 3 | 4 | all: $(TARGETS) 5 | 6 | rebind.so: rebind.o 7 | $(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -o $@ 8 | 9 | clean: 10 | rm -f rebind.o rebind.so 11 | 12 | -------------------------------------------------------------------------------- /docs/TODO: -------------------------------------------------------------------------------- 1 | - Go implementation 2 | - Rust implementation 3 | - Add sub-protocol support to upstream einaros/ws module and use that 4 | instead of the patched module. 5 | - wstelnet: support CSI L and CSI M 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | *.so 4 | other/.lein-deps-sum 5 | other/classes 6 | other/lib 7 | other/js/node_modules 8 | .project 9 | .pydevproject 10 | target.cfg 11 | target.cfg.d 12 | .tox 13 | *.egg-info 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | - 3.3 6 | - 3.4 7 | 8 | install: pip install -r test-requirements.txt 9 | 10 | script: python setup.py nosetests --verbosity=3 11 | -------------------------------------------------------------------------------- /docs/flash_policy.txt: -------------------------------------------------------------------------------- 1 | Manual setup: 2 | 3 | DATA="echo \'\'" 4 | /usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:"$DATA" 5 | -------------------------------------------------------------------------------- /other/js/README.md: -------------------------------------------------------------------------------- 1 | A JavaScript implementation of the websockify WebSocket-to-TCP bridge/proxy. 2 | 3 | Copyright (C) 2013 - Joel Martin (github.com/kanaka) 4 | 5 | Licensed under LGPL-3. 6 | 7 | See http://github.com/kanaka/websockify for more info. 8 | -------------------------------------------------------------------------------- /other/Makefile: -------------------------------------------------------------------------------- 1 | TARGETS=websockify 2 | CFLAGS += -fPIC 3 | 4 | all: $(TARGETS) 5 | 6 | websockify: websockify.o websocket.o 7 | $(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@ 8 | 9 | websocket.o: websocket.c websocket.h 10 | websockify.o: websockify.c websocket.h 11 | 12 | clean: 13 | rm -f websockify *.o 14 | 15 | -------------------------------------------------------------------------------- /tests/base64.js: -------------------------------------------------------------------------------- 1 | // The following results in 'hello [MANGLED]' 2 | // 3 | // Filed as https://github.com/ry/node/issues/issue/402 4 | 5 | var sys = require("sys"), 6 | buf = new Buffer(1024), len, 7 | str1 = "aGVsbG8g", // 'hello ' 8 | str2 = "d29ybGQ=", // 'world' 9 | 10 | len = buf.write(str1, 0, 'base64'); 11 | len += buf.write(str2, len, 'base64'); 12 | sys.log("decoded result: " + buf.toString('binary', 0, len)); 13 | -------------------------------------------------------------------------------- /docs/release.txt: -------------------------------------------------------------------------------- 1 | - Update setup.py, CHANGES.txt and other/package.json and commit 2 | - Create version tag and tarball from tag 3 | WVER=0.1.0 4 | git tag v${WVER} 5 | git push origin master 6 | git push origin v${WVER} 7 | - Register with pypi.python.org (once): 8 | python setup.py register 9 | - Upload the source distribution to pypi 10 | python setup.py sdist upload 11 | - Register with npmjs.org (once) 12 | - Upload websockify.js npmjs.org package 13 | npm publish other/js 14 | -------------------------------------------------------------------------------- /rebind: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | echo "Usage: $(basename $0) OLD_PORT NEW_PORT COMMAND_LINE" 5 | echo 6 | echo "Launch COMMAND_LINE, but intercept system calls to bind" 7 | echo "to OLD_PORT and instead bind them to localhost:NEW_PORT" 8 | exit 2 9 | } 10 | 11 | # Parameter defaults 12 | mydir=$(readlink -f $(dirname ${0})) 13 | 14 | export REBIND_PORT_OLD="${1}"; shift 15 | export REBIND_PORT_NEW="${1}"; shift 16 | 17 | LD_PRELOAD=${mydir}/rebind.so "${@}" 18 | 19 | -------------------------------------------------------------------------------- /other/wswrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | echo "Usage: $(basename $0) PORT CMDLINE" 5 | echo 6 | echo " PORT Port to wrap with WebSockets support" 7 | echo " CMDLINE Command line to wrap" 8 | exit 2 9 | } 10 | 11 | # Parameter defaults 12 | mydir=$(readlink -f $(dirname ${0})) 13 | 14 | # Process parameters 15 | #while [ "${1}" != "${1#-}" ]; do 16 | # param=$1; shift 17 | #done 18 | 19 | export WSWRAP_PORT="${1}"; shift 20 | 21 | LD_PRELOAD=${mydir}/wswrapper.so "${@}" 22 | 23 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/ProjectInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Configuration.Install; 6 | using System.Linq; 7 | 8 | 9 | namespace MELT_Command_Websocket 10 | { 11 | [RunInstaller(true)] 12 | public partial class ProjectInstaller : System.Configuration.Install.Installer 13 | { 14 | public ProjectInstaller() 15 | { 16 | InitializeComponent(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py24,py26,py27,py33,py34 8 | 9 | [testenv] 10 | commands = nosetests {posargs} 11 | deps = -r{toxinidir}/test-requirements.txt 12 | 13 | # At some point we should enable this since tox expects it to exist but 14 | # the code will need pep8ising first. 15 | #[testenv:pep8] 16 | #commands = flake8 17 | #dep = flake8 18 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.ServiceProcess; 5 | using System.Text; 6 | 7 | namespace MELT_Command_Websocket 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | static void Main() 15 | { 16 | ServiceBase[] ServicesToRun; 17 | ServicesToRun = new ServiceBase[] 18 | { 19 | new Service1() 20 | }; 21 | ServiceBase.Run(ServicesToRun); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /other/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Joel Martin (http://github.com/kanaka)", 3 | "name": "websockify", 4 | "description": "websockify is a WebSocket-to-TCP proxy/bridge", 5 | "license": "LGPL-3.0", 6 | "version": "0.8.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/kanaka/websockify.git" 10 | }, 11 | "files": ["../../docs/LICENSE.LGPL-3","websockify.js"], 12 | "bin": { 13 | "websockify": "./websockify.js" 14 | }, 15 | "engines": { 16 | "node": ">=0.8.9" 17 | }, 18 | "dependencies": { 19 | "ws": ">=0.4.27", 20 | "optimist": "latest", 21 | "policyfile": "latest" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and 2 | docs/LICENSE.LGPL-3) with the following exceptions: 3 | 4 | include/websock.js : MPL 2.0 5 | 6 | include/base64.js : MPL 2.0 7 | 8 | include/des.js : Various BSD style licenses 9 | 10 | include/web-socket-js/ : New BSD license (3-clause). Source code at 11 | https://github.com/gimite/web-socket-js 12 | 13 | other/kumina.c : Simplified BSD license (2 clause). 14 | Original source at 15 | https://github.com/kumina/wsproxy 16 | 17 | -------------------------------------------------------------------------------- /other/project.clj: -------------------------------------------------------------------------------- 1 | (defproject websockify "1.0.0-SNAPSHOT" 2 | :description "Clojure implementation of Websockify" 3 | :url "https://github.com/kanaka/websockify" 4 | :dependencies [[org.clojure/clojure "1.2.1"] 5 | [org.clojure/tools.cli "0.2.1"] 6 | [ring/ring-jetty-adapter "1.0.0-beta2"] 7 | [org.eclipse.jetty/jetty-websocket "7.5.4.v20111024"] 8 | [org.eclipse.jetty/jetty-server "7.5.4.v20111024"] 9 | [org.eclipse.jetty/jetty-servlet "7.5.4.v20111024"] 10 | [org.jboss.netty/netty "3.2.5.Final"]] 11 | ;:dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]] 12 | :main websockify 13 | ) 14 | -------------------------------------------------------------------------------- /tests/utf8-list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Display UTF-8 encoding for 0-255.''' 5 | 6 | import sys, os, socket, ssl, time, traceback 7 | from select import select 8 | sys.path.insert(0,os.path.join(os.path.dirname(__file__), "..")) 9 | from websockify.websocket import WebSocketServer 10 | 11 | if __name__ == '__main__': 12 | print "val: hybi_base64 | hybi_binary" 13 | for c in range(0, 256): 14 | hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1, 15 | base64=True) 16 | hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2, 17 | base64=False) 18 | print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary)) 19 | 20 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/noVNC Websocket.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "noVNC Websocket", "noVNC Websocket.csproj", "{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.ActiveCfg = Debug|x86 13 | {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.Build.0 = Debug|x86 14 | {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.ActiveCfg = Release|x86 15 | {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /docs/notes: -------------------------------------------------------------------------------- 1 | Some implementation notes: 2 | 3 | There is an included flash object (web-socket-js) that is used to 4 | emulate websocket support on browsers without websocket support 5 | (currently only Chrome has WebSocket support). 6 | 7 | Javascript doesn't have a bytearray type, so what you get out of 8 | a WebSocket object is just Javascript strings. Javascript has UTF-16 9 | unicode strings and anything sent through the WebSocket gets converted 10 | to UTF-8 and vice-versa. So, one additional (and necessary) function 11 | of wsproxy is base64 encoding/decoding what is sent to/from the 12 | browser. 13 | 14 | Building web-socket-js emulator: 15 | 16 | cd include/web-socket-js/flash-src 17 | mxmlc -static-link-runtime-shared-libraries WebSocketMain.as 18 | 19 | Building release tarball: 20 | - not really necessary since tagged revision can be downloaded 21 | from github as tarballs 22 | 23 | git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar 24 | gzip websockify-${WVER}.tar 25 | -------------------------------------------------------------------------------- /tests/b64_vs_utf8.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from base64 import b64encode, b64decode 4 | from codecs import (utf_8_encode, utf_8_decode, 5 | latin_1_encode, latin_1_decode) 6 | import random, time 7 | 8 | buf_len = 10000 9 | iterations = 10000 10 | 11 | print "Generating random input buffer" 12 | r = random.Random() 13 | buf = "".join([chr(r.randint(0, 255)) for i in range(buf_len)]) 14 | 15 | tests = {'UTF8 encode': lambda: utf_8_encode(unicode(buf, 'latin-1'))[0], 16 | 'B64 encode': lambda: b64encode(buf)} 17 | utf8_buf = tests['UTF8 encode']() 18 | b64_buf = tests['B64 encode']() 19 | tests.update({'UTF8 decode': lambda: latin_1_encode(utf_8_decode(utf8_buf)[0])[0], 20 | 'B64 decode': lambda: b64decode(b64_buf)}) 21 | 22 | print "Running tests" 23 | for test in 'UTF8 encode', 'B64 encode', 'UTF8 decode', 'B64 decode': 24 | start = time.time() 25 | for i in range(iterations): 26 | res_buf = tests[test]() 27 | print "%s took %s seconds (result size %s)" % ( 28 | test, (time.time() - start), len(res_buf)) 29 | 30 | -------------------------------------------------------------------------------- /tests/test_auth_plugins.py: -------------------------------------------------------------------------------- 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 2 | 3 | """ Unit tests for Authentication plugins""" 4 | 5 | from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError 6 | import unittest 7 | 8 | 9 | class BasicHTTPAuthTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.plugin = BasicHTTPAuth('Aladdin:open sesame') 13 | 14 | def test_no_auth(self): 15 | headers = {} 16 | self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') 17 | 18 | def test_invalid_password(self): 19 | headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'} 20 | self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') 21 | 22 | def test_valid_password(self): 23 | headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='} 24 | self.plugin.authenticate(headers, 'localhost', '1234') 25 | 26 | def test_garbage_auth(self): 27 | headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'} 28 | self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') 29 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/Service1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MELT_Command_Websocket 2 | { 3 | partial class Service1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | this.ServiceName = "Service1"; 33 | } 34 | 35 | #endregion 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/Service1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.ServiceProcess; 8 | using System.Text; 9 | using System.IO; 10 | 11 | namespace MELT_Command_Websocket 12 | { 13 | public partial class Service1 : ServiceBase 14 | { 15 | Process websockify; 16 | public Service1() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | protected override void OnStart(string[] args) 22 | { 23 | 24 | string configpath = AppDomain.CurrentDomain.BaseDirectory + "\\noVNCConfig.ini"; 25 | string sockifypath = AppDomain.CurrentDomain.BaseDirectory + "\\websockify.exe"; 26 | //Load commandline arguements from config file. 27 | StreamReader streamReader = new StreamReader(configpath); 28 | string arguements = streamReader.ReadLine(); 29 | streamReader.Close(); 30 | 31 | //Start websockify. 32 | websockify = System.Diagnostics.Process.Start(sockifypath, arguements); 33 | } 34 | 35 | protected override void OnStop() 36 | { 37 | //Service stopped. Close websockify. 38 | websockify.Kill(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /other/README.md: -------------------------------------------------------------------------------- 1 | This directory contain alternate implementations of 2 | WebSockets-to-TCP-Socket proxies (for noVNC). 3 | 4 | ## websockify.c (C) 5 | 6 | ### Description 7 | 8 | This is a C version of the original websockify. It is more limited in 9 | functionality than the original. 10 | 11 | 12 | ## websockify.js 13 | 14 | ### Description 15 | 16 | This is a Node.JS (server-side event driven Javascript) implementation 17 | of websockify. 18 | 19 | 20 | ## kumina.c (C) 21 | 22 | ### Description 23 | 24 | The upstream source of the kumina proxy is [here](https://github.com/kumina/wsproxy). 25 | 26 | [This article](http://blog.kumina.nl/2011/06/proxying-and-multiplexing-novnc-using-wsproxy/) 27 | describes the kumina proxy. 28 | 29 | kumina is an application that is run from inetd, which allows noVNC 30 | to connect to an unmodified VNC server. Furthermore, it makes use of 31 | the recently added support in noVNC for file names. The file name is 32 | used to denote the port number. Say, you connect to: 33 | 34 | ws://host:41337/25900 35 | 36 | The kumina proxy opens a connection to: 37 | 38 | vnc://host:25900/ 39 | 40 | The address to which kumina connects, is the same as the address to 41 | which the client connected (using getsockname()). 42 | 43 | ### Configuration 44 | 45 | kumina can be enabled by adding the following line to inetd.conf: 46 | 47 | 41337 stream tcp nowait nobody /usr/sbin/kumina kumina 25900 25909 48 | 49 | The two parameters of kumina denote the minimum and the maximum allowed 50 | port numbers. This allows a single kumina instance to multiplex 51 | connections to multiple VNC servers. 52 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MELT Command Websocket")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("MELT Command Websocket")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5ab831cb-6852-4ce1-849c-b26725b0e10b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = '0.8.0' 4 | name = 'websockify' 5 | long_description = open("README.md").read() + "\n" + \ 6 | open("CHANGES.txt").read() + "\n" 7 | 8 | setup(name=name, 9 | version=version, 10 | description="Websockify.", 11 | long_description=long_description, 12 | classifiers=[ 13 | "Programming Language :: Python", 14 | "Programming Language :: Python :: 2", 15 | "Programming Language :: Python :: 2.6", 16 | "Programming Language :: Python :: 2.7", 17 | "Programming Language :: Python :: 3", 18 | "Programming Language :: Python :: 3.3", 19 | "Programming Language :: Python :: 3.4" 20 | ], 21 | data_files=[('share/websockify/include', 22 | ['include/util.js', 23 | 'include/base64.js', 24 | 'include/websock.js']), 25 | ('share/websockify/include/web-socket-js', 26 | ['include/web-socket-js/WebSocketMain.swf', 27 | 'include/web-socket-js/swfobject.js', 28 | 'include/web-socket-js/web_socket.js'])], 29 | keywords='noVNC websockify', 30 | license='LGPLv3', 31 | url="https://github.com/kanaka/websockify", 32 | author="Joel Martin", 33 | author_email="github@martintribe.org", 34 | 35 | packages=['websockify'], 36 | include_package_data=True, 37 | install_requires=['numpy'], 38 | zip_safe=False, 39 | entry_points={ 40 | 'console_scripts': [ 41 | 'websockify = websockify.websocketproxy:websockify_init', 42 | ] 43 | }, 44 | ) 45 | -------------------------------------------------------------------------------- /tests/echo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A WebSocket server that echos back whatever it receives from the client. 4 | # Copyright 2011 Joel Martin 5 | # Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) 6 | 7 | require 'socket' 8 | $: << "other" 9 | $: << "../other" 10 | require 'websocket' 11 | 12 | class WebSocketEcho < WebSocketServer 13 | 14 | # Echo back whatever is received 15 | def new_websocket_client(client) 16 | 17 | cqueue = [] 18 | c_pend = 0 19 | rlist = [client] 20 | 21 | loop do 22 | wlist = [] 23 | 24 | if cqueue.length > 0 or c_pend 25 | wlist << client 26 | end 27 | 28 | ins, outs, excepts = IO.select(rlist, wlist, nil, 1) 29 | if excepts.length > 0 30 | raise Exception, "Socket exception" 31 | end 32 | 33 | if outs.include?(client) 34 | # Send queued data to the client 35 | c_pend = send_frames(cqueue) 36 | cqueue = [] 37 | end 38 | 39 | if ins.include?(client) 40 | # Receive client data, decode it, and send it back 41 | frames, closed = recv_frames 42 | cqueue += frames 43 | 44 | if closed 45 | raise EClose, closed 46 | end 47 | end 48 | 49 | end # loop 50 | end 51 | end 52 | 53 | port = ARGV[0].to_i || 8080 54 | puts "Starting server on port #{port}" 55 | server_cert = nil 56 | server_key = nil 57 | if ARGV.length > 2 58 | server_cert = ARGV[1] 59 | server_key = ARGV[2] 60 | end 61 | 62 | server = WebSocketEcho.new('listen_port' => port, 'verbose' => true, 63 | 'server_cert' => server_cert, 'server_key' => server_key) 64 | server.start 65 | server.join 66 | 67 | puts "Server has been terminated" 68 | 69 | # vim: sw=2 70 | -------------------------------------------------------------------------------- /Windows/Windows Service Readme.txt: -------------------------------------------------------------------------------- 1 | ----------------------------------- 2 | Windows noVNC Websockify Service 3 | ----------------------------------- 4 | 5 | The "noVNC Websocket Service.exe" file is a windows service wrapper created with Visual Studio 2010 to create a windows service to start stop the noVNC Websocket Server. All files used to create the wrapper can be found in 'noVNC Websocket Service Project' folder. 6 | 7 | To download the precompiled executables please grab the zip in the downloads section of websockify project: 8 | https://github.com/kanaka/websockify 9 | 10 | --------------------------- 11 | Installation 12 | --------------------------- 13 | 14 | 1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here: 15 | https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable 16 | 17 | 2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line: 18 | 19 | sc create "noVNC Websocket Server" binPath= "PATH TO noVNC eg C:\noVNC\utils\Windows\Websocket Service.exe" DisplayName= "noVNC Websocket Server" 20 | 21 | 3 .Once this is run you will be able to access the service via Control Panel > Admin Tools > Services. In here you can specify whether you want the service to run automatically and start at stop the service. 22 | 23 | --------------------------- 24 | Configuration 25 | --------------------------- 26 | The file noVNCConfig.ini must be in the same directory as "noVNC Websocket Service.exe". 27 | 28 | This file contains a single line which is the websockify.exe statup arguements. An example is: 29 | 192.168.0.1:5901 192.168.0.1:5900 30 | 31 | All websockify supported arguements will work if added here. 32 | 33 | --------------------------- 34 | Deletion 35 | --------------------------- 36 | 37 | You can delete the service at any time by running the commandline as admin and using this command: 38 | sc delete "noVNC Websocket Server". 39 | 40 | -------------------------------------------------------------------------------- /tests/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Websock Simple Client 5 | 6 | 7 | 8 | 9 | 10 | 11 | WebSocket/websockify URI:   12 | 14 |

15 |   16 |   18 |

19 | Log:
20 | 21 | 22 | 23 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /other/websocket.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define BUFSIZE 65536 4 | #define DBUFSIZE (BUFSIZE * 3) / 4 - 20 5 | 6 | #define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\ 7 | Upgrade: WebSocket\r\n\ 8 | Connection: Upgrade\r\n\ 9 | %sWebSocket-Origin: %s\r\n\ 10 | %sWebSocket-Location: %s://%s%s\r\n\ 11 | %sWebSocket-Protocol: %s\r\n\ 12 | \r\n%s" 13 | 14 | #define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\ 15 | Upgrade: websocket\r\n\ 16 | Connection: Upgrade\r\n\ 17 | Sec-WebSocket-Accept: %s\r\n\ 18 | Sec-WebSocket-Protocol: %s\r\n\ 19 | \r\n" 20 | 21 | #define HYBI_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 22 | 23 | #define HYBI10_ACCEPTHDRLEN 29 24 | 25 | #define HIXIE_MD5_DIGEST_LENGTH 16 26 | 27 | #define POLICY_RESPONSE "\n" 28 | 29 | typedef struct { 30 | char path[1024+1]; 31 | char host[1024+1]; 32 | char origin[1024+1]; 33 | char version[1024+1]; 34 | char connection[1024+1]; 35 | char protocols[1024+1]; 36 | char key1[1024+1]; 37 | char key2[1024+1]; 38 | char key3[8+1]; 39 | } headers_t; 40 | 41 | typedef struct { 42 | int sockfd; 43 | SSL_CTX *ssl_ctx; 44 | SSL *ssl; 45 | int hixie; 46 | int hybi; 47 | headers_t *headers; 48 | char *cin_buf; 49 | char *cout_buf; 50 | char *tin_buf; 51 | char *tout_buf; 52 | } ws_ctx_t; 53 | 54 | typedef struct { 55 | int verbose; 56 | char listen_host[256]; 57 | int listen_port; 58 | void (*handler)(ws_ctx_t*); 59 | int handler_id; 60 | char *cert; 61 | char *key; 62 | int ssl_only; 63 | int daemon; 64 | int run_once; 65 | } settings_t; 66 | 67 | 68 | ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len); 69 | 70 | ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len); 71 | 72 | /* base64.c declarations */ 73 | //int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); 74 | //int b64_pton(char const *src, u_char *target, size_t targsize); 75 | 76 | #define gen_handler_msg(stream, ...) \ 77 | if (! settings.daemon) { \ 78 | fprintf(stream, " %d: ", settings.handler_id); \ 79 | fprintf(stream, __VA_ARGS__); \ 80 | } 81 | 82 | #define handler_msg(...) gen_handler_msg(stdout, __VA_ARGS__); 83 | #define handler_emsg(...) gen_handler_msg(stderr, __VA_ARGS__); 84 | 85 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/ProjectInstaller.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MELT_Command_Websocket 2 | { 3 | partial class ProjectInstaller 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); 32 | this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); 33 | // 34 | // serviceProcessInstaller1 35 | // 36 | this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.NetworkService; 37 | this.serviceProcessInstaller1.Installers.AddRange(new System.Configuration.Install.Installer[] { 38 | this.serviceInstaller1}); 39 | this.serviceProcessInstaller1.Password = null; 40 | this.serviceProcessInstaller1.Username = null; 41 | // 42 | // serviceInstaller1 43 | // 44 | this.serviceInstaller1.Description = "noVNC Websocket Service"; 45 | this.serviceInstaller1.DisplayName = "noVNC Websocket Service"; 46 | this.serviceInstaller1.ServiceName = "noVNC Websocket Service"; 47 | this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; 48 | // 49 | // ProjectInstaller 50 | // 51 | this.Installers.AddRange(new System.Configuration.Install.Installer[] { 52 | this.serviceProcessInstaller1}); 53 | 54 | } 55 | 56 | #endregion 57 | 58 | private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; 59 | private System.ServiceProcess.ServiceInstaller serviceInstaller1; 60 | } 61 | } -------------------------------------------------------------------------------- /websockify/token_plugins.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class BasePlugin(object): 4 | def __init__(self, src): 5 | self.source = src 6 | 7 | def lookup(self, token): 8 | return None 9 | 10 | 11 | class ReadOnlyTokenFile(BasePlugin): 12 | # source is a token file with lines like 13 | # token: host:port 14 | # or a directory of such files 15 | def __init__(self, *args, **kwargs): 16 | super(ReadOnlyTokenFile, self).__init__(*args, **kwargs) 17 | self._targets = None 18 | 19 | def _load_targets(self): 20 | if os.path.isdir(self.source): 21 | cfg_files = [os.path.join(self.source, f) for 22 | f in os.listdir(self.source)] 23 | else: 24 | cfg_files = [self.source] 25 | 26 | self._targets = {} 27 | for f in cfg_files: 28 | for line in [l.strip() for l in open(f).readlines()]: 29 | if line and not line.startswith('#'): 30 | tok, target = line.split(': ') 31 | self._targets[tok] = target.strip().rsplit(':', 1) 32 | 33 | def lookup(self, token): 34 | if self._targets is None: 35 | self._load_targets() 36 | 37 | if token in self._targets: 38 | return self._targets[token] 39 | else: 40 | return None 41 | 42 | 43 | # the above one is probably more efficient, but this one is 44 | # more backwards compatible (although in most cases 45 | # ReadOnlyTokenFile should suffice) 46 | class TokenFile(ReadOnlyTokenFile): 47 | # source is a token file with lines like 48 | # token: host:port 49 | # or a directory of such files 50 | def lookup(self, token): 51 | self._load_targets() 52 | 53 | return super(TokenFile, self).lookup(token) 54 | 55 | 56 | class BaseTokenAPI(BasePlugin): 57 | # source is a url with a '%s' in it where the token 58 | # should go 59 | 60 | # we import things on demand so that other plugins 61 | # in this file can be used w/o unecessary dependencies 62 | 63 | def process_result(self, resp): 64 | return resp.text.split(':') 65 | 66 | def lookup(self, token): 67 | import requests 68 | 69 | resp = requests.get(self.source % token) 70 | 71 | if resp.ok: 72 | return self.process_result(resp) 73 | else: 74 | return None 75 | 76 | 77 | class JSONTokenApi(BaseTokenAPI): 78 | # source is a url with a '%s' in it where the token 79 | # should go 80 | 81 | def process_result(self, resp): 82 | resp_json = resp.json() 83 | return (resp_json['host'], resp_json['port']) 84 | -------------------------------------------------------------------------------- /wstelnet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Telnet client using WebSockets 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Host:   24 | Port:   25 | Encrypt:   26 |   28 | 29 |

30 | 31 |

32 | 
33 |         
71 | 
72 |     
73 | 
74 | 
75 | 


--------------------------------------------------------------------------------
/tests/echo.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | 
 3 | '''
 4 | A WebSocket server that echos back whatever it receives from the client.
 5 | Copyright 2010 Joel Martin
 6 | Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
 7 | 
 8 | You can make a cert/key with openssl using:
 9 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
10 | as taken from http://docs.python.org/dev/library/ssl.html#certificates
11 | '''
12 | 
13 | import os, sys, select, optparse, logging
14 | sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
15 | from websockify.websocket import WebSocketServer, WebSocketRequestHandler
16 | 
17 | class WebSocketEcho(WebSocketRequestHandler):
18 |     """
19 |     WebSockets server that echos back whatever is received from the
20 |     client.  """
21 |     buffer_size = 8096
22 | 
23 |     def new_websocket_client(self):
24 |         """
25 |         Echo back whatever is received.
26 |         """
27 | 
28 |         cqueue = []
29 |         c_pend = 0
30 |         cpartial = ""
31 |         rlist = [self.request]
32 | 
33 |         while True:
34 |             wlist = []
35 | 
36 |             if cqueue or c_pend: wlist.append(self.request)
37 |             ins, outs, excepts = select.select(rlist, wlist, [], 1)
38 |             if excepts: raise Exception("Socket exception")
39 | 
40 |             if self.request in outs:
41 |                 # Send queued target data to the client
42 |                 c_pend = self.send_frames(cqueue)
43 |                 cqueue = []
44 | 
45 |             if self.request in ins:
46 |                 # Receive client data, decode it, and send it back
47 |                 frames, closed = self.recv_frames()
48 |                 cqueue.extend(frames)
49 | 
50 |                 if closed:
51 |                     self.send_close()
52 | 
53 | if __name__ == '__main__':
54 |     parser = optparse.OptionParser(usage="%prog [options] listen_port")
55 |     parser.add_option("--verbose", "-v", action="store_true",
56 |             help="verbose messages and per frame traffic")
57 |     parser.add_option("--cert", default="self.pem",
58 |             help="SSL certificate file")
59 |     parser.add_option("--key", default=None,
60 |             help="SSL key file (if separate from cert)")
61 |     parser.add_option("--ssl-only", action="store_true",
62 |             help="disallow non-encrypted connections")
63 |     (opts, args) = parser.parse_args()
64 | 
65 |     try:
66 |         if len(args) != 1: raise
67 |         opts.listen_port = int(args[0])
68 |     except:
69 |         parser.error("Invalid arguments")
70 | 
71 |     logging.basicConfig(level=logging.INFO)
72 | 
73 |     opts.web = "."
74 |     server = WebSocketServer(WebSocketEcho, **opts.__dict__)
75 |     server.start_server()
76 | 
77 | 


--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
 1 | Changes
 2 | =======
 3 | 
 4 | 0.8.0
 5 | -----
 6 | 
 7 | * Make websockify properly terminate children on SIGTERM (#226)
 8 | * Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
 9 | * Make it easier to log to a file (#205)
10 | * Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
11 | * Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
12 | * Fix bug in JSONTokenAPI token plugin (#192)
13 | * Fix a missing variable in the exception handler (#178)
14 | 
15 | 0.7.0
16 | -----
17 | 
18 | * Python 3 support fixes (#140, #155, #159)
19 | * Generic token-parsing plugins support (#162)
20 | * Generic authentication plugins support (#172)
21 | * Fixed frame corruption on big-endian systems (#161)
22 | * Support heartbeats (via PING) and automatic responses to PONG (#169)
23 | * Automatically reject unmasked client frames by default (strict mode) (#174)
24 | * Automatically restart interrupted select calls (#175)
25 | * Make 'run' respect environment settings (including virtualenv) (#176)
26 | 
27 | 0.6.1 - May 11, 2015
28 | --------------------
29 | 
30 | * **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
31 | 
32 | 0.6.0 - Feb 18, 2014
33 | --------------------
34 | 
35 | * **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
36 | * Refactor to use standard SocketServer RequestHandler design
37 | * Fix zombie process bug on certain systems when using multiprocessing
38 | * Add better unit tests
39 | * Log information via python `logging` module
40 | 
41 | 0.5.1 - Jun 27, 2013
42 | --------------------
43 | 
44 |  * use upstream einaros/ws (>=0.4.27) with websockify.js
45 |  * file_only and no_parent security options for WSRequestHandler
46 |  * Update build of web-socket-js (c0855c6cae)
47 |  * add include/web-socket-js-project submodule to gimite/web-socket-js
48 |    for DSFG compliance.
49 |  * drop Hixie protocol support
50 | 
51 | 0.4.1 - Mar 12, 2013
52 | --------------------
53 | 
54 |  * ***NOTE*** : 0.5.0 will drop Hixie protocol support
55 |  * add include/ directory and remove some dev files from source
56 |    distribution.
57 | 
58 | 0.4.0 - Mar 12, 2013
59 | --------------------
60 | 
61 |  * ***NOTE*** : 0.5.0 will drop Hixie protocol support
62 |  * use Buffer base64 support in Node.js implementation
63 | 
64 | 0.3.0 - Jan 15, 2013
65 | --------------------
66 | 
67 |  * refactor into modules: websocket, websocketproxy
68 |  * switch to web-socket-js that uses IETF 6455
69 |  * change to MPL 2.0 license for include/*.js
70 |  * fix session recording
71 | 
72 | 0.2.1 - Oct 15, 2012
73 | --------------------
74 | 
75 |  * re-released with updated version number
76 | 
77 | 0.2.0 - Sep 17, 2012
78 | --------------------
79 | 
80 |  * Binary data support in websock.js
81 |  * Target config file/dir and multiple targets with token selector
82 |  * IPv6 fixes
83 |  * SSL target support
84 |  * Proxy to/from unix socket
85 | 
86 | 
87 | 0.1.0 - May 11, 2012
88 | --------------------
89 | 
90 |  * Initial versioned release.
91 | 
92 | 


--------------------------------------------------------------------------------
/websockify/auth_plugins.py:
--------------------------------------------------------------------------------
 1 | class BasePlugin(object):
 2 |     def __init__(self, src=None):
 3 |         self.source = src
 4 | 
 5 |     def authenticate(self, headers, target_host, target_port):
 6 |         pass
 7 | 
 8 | 
 9 | class AuthenticationError(Exception):
10 |     def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None):
11 |         self.code = response_code
12 |         self.headers = response_headers
13 |         self.msg = response_msg
14 | 
15 |         if log_msg is None:
16 |             log_msg = response_msg
17 | 
18 |         super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
19 | 
20 | 
21 | class InvalidOriginError(AuthenticationError):
22 |     def __init__(self, expected, actual):
23 |         self.expected_origin = expected
24 |         self.actual_origin = actual
25 | 
26 |         super(InvalidOriginError, self).__init__(
27 |             response_msg='Invalid Origin',
28 |             log_msg="Invalid Origin Header: Expected one of "
29 |                     "%s, got '%s'" % (expected, actual))
30 | 
31 | 
32 | class BasicHTTPAuth(object):
33 |     """Verifies Basic Auth headers. Specify src as username:password"""
34 | 
35 |     def __init__(self, src=None):
36 |         self.src = src
37 | 
38 |     def authenticate(self, headers, target_host, target_port):
39 |         import base64
40 |         auth_header = headers.get('Authorization')
41 |         if auth_header:
42 |             if not auth_header.startswith('Basic '):
43 |                 raise AuthenticationError(response_code=403)
44 | 
45 |             try:
46 |                 user_pass_raw = base64.b64decode(auth_header[6:])
47 |             except TypeError:
48 |                 raise AuthenticationError(response_code=403)
49 | 
50 |             try:
51 |                 # http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication
52 |                 user_pass_as_text = user_pass_raw.decode('ISO-8859-1')
53 |             except UnicodeDecodeError:
54 |                 raise AuthenticationError(response_code=403)
55 | 
56 |             user_pass = user_pass_as_text.split(':', 1)
57 |             if len(user_pass) != 2:
58 |                 raise AuthenticationError(response_code=403)
59 | 
60 |             if not self.validate_creds(*user_pass):
61 |                 raise AuthenticationError(response_code=403)
62 | 
63 |         else:
64 |             raise AuthenticationError(response_code=401,
65 |                                       response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
66 | 
67 |     def validate_creds(self, username, password):
68 |         if '%s:%s' % (username, password) == self.src:
69 |             return True
70 |         else:
71 |             return False
72 | 
73 | class ExpectOrigin(object):
74 |     def __init__(self, src=None):
75 |         if src is None:
76 |             self.source = []
77 |         else:
78 |             self.source = src.split()
79 | 
80 |     def authenticate(self, headers, target_host, target_port):
81 |         origin = headers.get('Origin', None)
82 |         if origin is None or origin not in self.source:
83 |             raise InvalidOriginError(expected=self.source, actual=origin)
84 | 


--------------------------------------------------------------------------------
/rebind.c:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * rebind: Intercept bind calls and bind to a different port
 3 |  * Copyright 2010 Joel Martin
 4 |  * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
 5 |  *
 6 |  * Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
 7 |  * REBIND_PORT_NEW environment variables are set then bind on the new
 8 |  * port (of localhost) instead of the old port. 
 9 |  *
10 |  * This allows a bridge/proxy (such as websockify) to run on the old port and
11 |  * translate traffic to/from the new port.
12 |  *
13 |  * Usage:
14 |  *     LD_PRELOAD=./rebind.so \
15 |  *     REBIND_PORT_OLD=23 \
16 |  *     REBIND_PORT_NEW=2023 \
17 |  *     program
18 |  */
19 | 
20 | //#define DO_DEBUG 1
21 | 
22 | #include 
23 | #include 
24 | 
25 | #define __USE_GNU 1  // Pull in RTLD_NEXT
26 | #include 
27 | 
28 | #include 
29 | #include 
30 | 
31 | 
32 | #if defined(DO_DEBUG)
33 | #define DEBUG(...) \
34 |     fprintf(stderr, "rebind: "); \
35 |     fprintf(stderr, __VA_ARGS__);
36 | #else
37 | #define DEBUG(...)
38 | #endif
39 | 
40 | 
41 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
42 | {
43 |     static void * (*func)();
44 |     int do_move = 0;
45 |     struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
46 |     struct sockaddr_in addr_tmp;
47 |     socklen_t addrlen_tmp;
48 |     char * PORT_OLD, * PORT_NEW, * end1, * end2;
49 |     int ret, oldport, newport, askport = htons(addr_in->sin_port);
50 |     uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
51 |     if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
52 | 
53 |     DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
54 |           sockfd, addrlen, askaddr, askport);
55 | 
56 |     /* Determine if we should move this socket */
57 |     if (addr_in->sin_family == AF_INET) {
58 |         // TODO: support IPv6
59 |         PORT_OLD = getenv("REBIND_OLD_PORT");
60 |         PORT_NEW = getenv("REBIND_NEW_PORT");
61 |         if (PORT_OLD && (*PORT_OLD != '\0') &&
62 |             PORT_NEW && (*PORT_NEW != '\0')) {
63 |             oldport = strtol(PORT_OLD, &end1, 10);
64 |             newport = strtol(PORT_NEW, &end2, 10);
65 |             if (oldport && (*end1 == '\0') &&
66 |                 newport && (*end2 == '\0') &&
67 |                 (oldport == askport)) {
68 |                 do_move = 1;
69 |             }
70 |         }
71 |     }
72 | 
73 |     if (! do_move) {
74 |         /* Just pass everything right through to the real bind */
75 |         ret = (long) func(sockfd, addr, addrlen);
76 |         DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
77 |         return ret;
78 |     }
79 | 
80 |     DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
81 |         sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);
82 | 
83 |     /* Use a temporary location for the new address information */
84 |     addrlen_tmp = sizeof(addr_tmp);
85 |     memcpy(&addr_tmp, addr, addrlen_tmp);
86 | 
87 |     /* Bind to other port on the loopback instead */
88 |     addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
89 |     addr_tmp.sin_port = htons(newport);
90 |     ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
91 | 
92 |     DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
93 |     return ret;
94 | }
95 | 


--------------------------------------------------------------------------------
/Windows/noVNC Websocket Service Project/noVNC Websocket.csproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     Debug
 5 |     x86
 6 |     8.0.30703
 7 |     2.0
 8 |     {6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}
 9 |     WinExe
10 |     Properties
11 |     noVNC_Websocket_Service
12 |     noVNC Websocket Service
13 |     v3.5
14 |     512
15 |   
16 |   
17 |     x86
18 |     true
19 |     full
20 |     false
21 |     bin\Debug\
22 |     DEBUG;TRACE
23 |     prompt
24 |     4
25 |   
26 |   
27 |     x86
28 |     pdbonly
29 |     true
30 |     bin\Release\
31 |     TRACE
32 |     prompt
33 |     4
34 |   
35 |   
36 |     
37 |     
38 |     
39 |     
40 |     
41 |     
42 |     
43 |     
44 |     
45 |   
46 |   
47 |     
48 |       Component
49 |     
50 |     
51 |       ProjectInstaller.cs
52 |     
53 |     
54 |       Component
55 |     
56 |     
57 |       Service1.cs
58 |     
59 |     
60 |     
61 |   
62 |   
63 |     
64 |       ProjectInstaller.cs
65 |     
66 |   
67 |   
68 |   
75 | 


--------------------------------------------------------------------------------
/tests/base64.html:
--------------------------------------------------------------------------------
 1 |  
 2 |  
 3 |    
 4 |     Native Base64 Tests 
 5 |      
 6 |      
 7 |      
 8 |    
 9 |    
10 |         

Native Base64 Tests

11 | 12 |
13 | Messages:
14 | 15 | 16 |
17 | 18 | 19 | 92 | -------------------------------------------------------------------------------- /other/launch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | if [ "$*" ]; then 5 | echo "$*" 6 | echo 7 | fi 8 | echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]" 9 | echo 10 | echo "Starts the WebSockets proxy and a mini-webserver and " 11 | echo "provides a cut-and-paste URL to go to." 12 | echo 13 | echo " --listen PORT Port for proxy/webserver to listen on" 14 | echo " Default: 6080" 15 | echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" 16 | echo " Default: localhost:5900" 17 | echo " --cert CERT Path to combined cert/key file" 18 | echo " Default: self.pem" 19 | echo " --web WEB Path to web files (e.g. vnc.html)" 20 | echo " Default: ./" 21 | exit 2 22 | } 23 | 24 | NAME="$(basename $0)" 25 | HERE="$(cd "$(dirname "$0")" && pwd)" 26 | PORT="6080" 27 | VNC_DEST="localhost:5900" 28 | CERT="" 29 | WEB="" 30 | proxy_pid="" 31 | 32 | die() { 33 | echo "$*" 34 | exit 1 35 | } 36 | 37 | cleanup() { 38 | trap - TERM QUIT INT EXIT 39 | trap "true" CHLD # Ignore cleanup messages 40 | echo 41 | if [ -n "${proxy_pid}" ]; then 42 | echo "Terminating WebSockets proxy (${proxy_pid})" 43 | kill ${proxy_pid} 44 | fi 45 | } 46 | 47 | # Process Arguments 48 | 49 | # Arguments that only apply to chrooter itself 50 | while [ "$*" ]; do 51 | param=$1; shift; OPTARG=$1 52 | case $param in 53 | --listen) PORT="${OPTARG}"; shift ;; 54 | --vnc) VNC_DEST="${OPTARG}"; shift ;; 55 | --cert) CERT="${OPTARG}"; shift ;; 56 | --web) WEB="${OPTARG}"; shift ;; 57 | -h|--help) usage ;; 58 | -*) usage "Unknown chrooter option: ${param}" ;; 59 | *) break ;; 60 | esac 61 | done 62 | 63 | # Sanity checks 64 | which netstat >/dev/null 2>&1 \ 65 | || die "Must have netstat installed" 66 | 67 | netstat -ltn | grep -qs "${PORT} .*LISTEN" \ 68 | && die "Port ${PORT} in use. Try --listen PORT" 69 | 70 | trap "cleanup" TERM QUIT INT EXIT 71 | 72 | # Find vnc.html 73 | if [ -n "${WEB}" ]; then 74 | if [ ! -e "${WEB}/vnc.html" ]; then 75 | die "Could not find ${WEB}/vnc.html" 76 | fi 77 | elif [ -e "$(pwd)/vnc.html" ]; then 78 | WEB=$(pwd) 79 | elif [ -e "${HERE}/../vnc.html" ]; then 80 | WEB=${HERE}/../ 81 | elif [ -e "${HERE}/vnc.html" ]; then 82 | WEB=${HERE} 83 | elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then 84 | WEB=${HERE}/../share/novnc/ 85 | else 86 | die "Could not find vnc.html" 87 | fi 88 | 89 | # Find self.pem 90 | if [ -n "${CERT}" ]; then 91 | if [ ! -e "${CERT}" ]; then 92 | die "Could not find ${CERT}" 93 | fi 94 | elif [ -e "$(pwd)/self.pem" ]; then 95 | CERT="$(pwd)/self.pem" 96 | elif [ -e "${HERE}/../self.pem" ]; then 97 | CERT="${HERE}/../self.pem" 98 | elif [ -e "${HERE}/self.pem" ]; then 99 | CERT="${HERE}/self.pem" 100 | else 101 | echo "Warning: could not find self.pem" 102 | fi 103 | 104 | echo "Starting webserver and WebSockets proxy on port ${PORT}" 105 | ${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & 106 | proxy_pid="$!" 107 | sleep 1 108 | if ! ps -p ${proxy_pid} >/dev/null; then 109 | proxy_pid= 110 | echo "Failed to start WebSockets proxy" 111 | exit 1 112 | fi 113 | 114 | echo -e "\n\nNavigate to this URL:\n" 115 | echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" 116 | echo -e "Press Ctrl-C to exit\n\n" 117 | 118 | wait ${proxy_pid} 119 | -------------------------------------------------------------------------------- /wsirc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IRC Client using WebSockets 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Host:   24 | Port:   25 | Encrypt:   26 | 27 |
28 | Nick:   29 |
30 | Channel: #  31 | 32 |

33 | 34 |
35 | > 36 | 37 | 38 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /include/base64.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js 6 | 7 | /*jslint white: false, bitwise: false, plusplus: false */ 8 | /*global console */ 9 | 10 | var Base64 = { 11 | 12 | /* Convert data (an array of integers) to a Base64 string. */ 13 | toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''), 14 | base64Pad : '=', 15 | 16 | encode: function (data) { 17 | "use strict"; 18 | var result = ''; 19 | var toBase64Table = Base64.toBase64Table; 20 | var base64Pad = Base64.base64Pad; 21 | var length = data.length; 22 | var i; 23 | // Convert every three bytes to 4 ascii characters. 24 | /* BEGIN LOOP */ 25 | for (i = 0; i < (length - 2); i += 3) { 26 | result += toBase64Table[data[i] >> 2]; 27 | result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; 28 | result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; 29 | result += toBase64Table[data[i+2] & 0x3f]; 30 | } 31 | /* END LOOP */ 32 | 33 | // Convert the remaining 1 or 2 bytes, pad out to 4 characters. 34 | if (length%3) { 35 | i = length - (length%3); 36 | result += toBase64Table[data[i] >> 2]; 37 | if ((length%3) === 2) { 38 | result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; 39 | result += toBase64Table[(data[i+1] & 0x0f) << 2]; 40 | result += base64Pad; 41 | } else { 42 | result += toBase64Table[(data[i] & 0x03) << 4]; 43 | result += base64Pad + base64Pad; 44 | } 45 | } 46 | 47 | return result; 48 | }, 49 | 50 | /* Convert Base64 data to a string */ 51 | toBinaryTable : [ 52 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 53 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 54 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 55 | 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, 56 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 57 | 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, 58 | -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 59 | 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 60 | ], 61 | 62 | decode: function (data, offset) { 63 | "use strict"; 64 | offset = typeof(offset) !== 'undefined' ? offset : 0; 65 | var toBinaryTable = Base64.toBinaryTable; 66 | var base64Pad = Base64.base64Pad; 67 | var result, result_length, idx, i, c, padding; 68 | var leftbits = 0; // number of bits decoded, but yet to be appended 69 | var leftdata = 0; // bits decoded, but yet to be appended 70 | var data_length = data.indexOf('=') - offset; 71 | 72 | if (data_length < 0) { data_length = data.length - offset; } 73 | 74 | /* Every four characters is 3 resulting numbers */ 75 | result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5); 76 | result = new Array(result_length); 77 | 78 | // Convert one by one. 79 | /* BEGIN LOOP */ 80 | for (idx = 0, i = offset; i < data.length; i++) { 81 | c = toBinaryTable[data.charCodeAt(i) & 0x7f]; 82 | padding = (data.charAt(i) === base64Pad); 83 | // Skip illegal characters and whitespace 84 | if (c === -1) { 85 | console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i); 86 | continue; 87 | } 88 | 89 | // Collect data into leftdata, update bitcount 90 | leftdata = (leftdata << 6) | c; 91 | leftbits += 6; 92 | 93 | // If we have 8 or more bits, append 8 bits to the result 94 | if (leftbits >= 8) { 95 | leftbits -= 8; 96 | // Append if not padding. 97 | if (!padding) { 98 | result[idx++] = (leftdata >> leftbits) & 0xff; 99 | } 100 | leftdata &= (1 << leftbits) - 1; 101 | } 102 | } 103 | /* END LOOP */ 104 | 105 | // If there are any bits left, the base64 string was corrupted 106 | if (leftbits) { 107 | throw {name: 'Base64-Error', 108 | message: 'Corrupted base64 string'}; 109 | } 110 | 111 | return result; 112 | } 113 | 114 | }; /* End of Base64 namespace */ 115 | -------------------------------------------------------------------------------- /other/websockify.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A WebSocket to TCP socket proxy 4 | # Copyright 2011 Joel Martin 5 | # Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) 6 | 7 | require 'socket' 8 | $: << "other" 9 | $: << "../other" 10 | require 'websocket' 11 | require 'optparse' 12 | 13 | # Proxy traffic to and from a WebSockets client to a normal TCP 14 | # socket server target. All traffic to/from the client is base64 15 | # encoded/decoded to allow binary data to be sent/received to/from 16 | # the target. 17 | class WebSocketProxy < WebSocketServer 18 | 19 | @@Traffic_legend = " 20 | Traffic Legend: 21 | } - Client receive 22 | }. - Client receive partial 23 | { - Target receive 24 | 25 | > - Target send 26 | >. - Target send partial 27 | < - Client send 28 | <. - Client send partial 29 | " 30 | 31 | 32 | def initialize(opts) 33 | vmsg "in WebSocketProxy.initialize" 34 | 35 | super(opts) 36 | 37 | @target_host = opts["target_host"] 38 | @target_port = opts["target_port"] 39 | end 40 | 41 | # Echo back whatever is received 42 | def new_websocket_client(client) 43 | 44 | msg "connecting to: %s:%s" % [@target_host, @target_port] 45 | tsock = TCPSocket.open(@target_host, @target_port) 46 | 47 | if @verbose then puts @@Traffic_legend end 48 | 49 | begin 50 | do_proxy(client, tsock) 51 | rescue 52 | tsock.shutdown(Socket::SHUT_RDWR) 53 | tsock.close 54 | raise 55 | end 56 | end 57 | 58 | # Proxy client WebSocket to normal target socket. 59 | def do_proxy(client, target) 60 | cqueue = [] 61 | c_pend = 0 62 | tqueue = [] 63 | rlist = [client, target] 64 | 65 | loop do 66 | wlist = [] 67 | 68 | if tqueue.length > 0 69 | wlist << target 70 | end 71 | if cqueue.length > 0 || c_pend > 0 72 | wlist << client 73 | end 74 | 75 | ins, outs, excepts = IO.select(rlist, wlist, nil, 0.001) 76 | if excepts && excepts.length > 0 77 | raise Exception, "Socket exception" 78 | end 79 | 80 | # Send queued client data to the target 81 | if outs && outs.include?(target) 82 | dat = tqueue.shift 83 | sent = target.send(dat, 0) 84 | if sent == dat.length 85 | traffic ">" 86 | else 87 | tqueue.unshift(dat[sent...dat.length]) 88 | traffic ".>" 89 | end 90 | end 91 | 92 | # Receive target data and queue for the client 93 | if ins && ins.include?(target) 94 | buf = target.recv(@@Buffer_size) 95 | if buf.length == 0 96 | raise EClose, "Target closed" 97 | end 98 | 99 | cqueue << buf 100 | traffic "{" 101 | end 102 | 103 | # Encode and send queued data to the client 104 | if outs && outs.include?(client) 105 | c_pend = send_frames(cqueue) 106 | cqueue = [] 107 | end 108 | 109 | # Receive client data, decode it, and send it back 110 | if ins && ins.include?(client) 111 | frames, closed = recv_frames 112 | tqueue += frames 113 | 114 | if closed 115 | send_close 116 | raise EClose, closed 117 | end 118 | end 119 | 120 | end # loop 121 | end 122 | end 123 | 124 | # Parse parameters 125 | opts = {} 126 | parser = OptionParser.new do |o| 127 | o.on('--verbose', '-v') { |b| opts['verbose'] = b } 128 | o.parse! 129 | end 130 | 131 | if ARGV.length < 2 132 | puts "Too few arguments" 133 | exit 2 134 | end 135 | 136 | # Parse host:port and convert ports to numbers 137 | if ARGV[0].count(":") > 0 138 | opts['listen_host'], _, opts['listen_port'] = ARGV[0].rpartition(':') 139 | else 140 | opts['listen_host'], opts['listen_port'] = nil, ARGV[0] 141 | end 142 | 143 | begin 144 | opts['listen_port'] = opts['listen_port'].to_i 145 | rescue 146 | puts "Error parsing listen port" 147 | exit 2 148 | end 149 | 150 | if ARGV[1].count(":") > 0 151 | opts['target_host'], _, opts['target_port'] = ARGV[1].rpartition(':') 152 | else 153 | puts "Error parsing target" 154 | exit 2 155 | end 156 | 157 | begin 158 | opts['target_port'] = opts['target_port'].to_i 159 | rescue 160 | puts "Error parsing target port" 161 | exit 2 162 | end 163 | 164 | puts "Starting server on #{opts['listen_host']}:#{opts['listen_port']}" 165 | server = WebSocketProxy.new(opts) 166 | server.start(100) 167 | server.join 168 | 169 | puts "Server has been terminated" 170 | 171 | # vim: sw=2 172 | -------------------------------------------------------------------------------- /docs/latency_results.txt: -------------------------------------------------------------------------------- 1 | This data is raw copy from the latency tester set to send a frame with 2 | a little over 2000 KB of data every 10ms. 3 | 4 | The number of packets sent and received is just a visual counter and 5 | is just the total when I chose to stop the test (around 3000 or so 6 | packets). 7 | 8 | The latency measure are from the point the packet was sent to when it 9 | was received back again in milliseconds. One notable data point 10 | missing from this is how long it actually took for the client to send 11 | 3000 packets because sending large packets can put load on the browser 12 | and it may be a lot longer than 10ms before the timer event to 13 | send the next packet fires. So even with low latency numbers, the 14 | actual send rate may be fairly low because sending the WebSockets 15 | frames is impacting the performance of the browser in general. 16 | 17 | 18 | ------------------------------------------------------------ 19 | 20 | Native WebSockets implementations, 2000 byte payload, 10ms delay 21 | 22 | Chrome 8.0.552 - native WebSockets 23 | Packets sent: 2998 24 | Packets Received: 2998 25 | Average Latency: 1.84 26 | 40 Frame Running Average Latency: 1.90 27 | Minimum Latency: 1.00 28 | Maximum Latency: 10.00 29 | 30 | firefox 4.0b9 - WebSockets enabled 31 | Packets sent: 3011 32 | Packets Received: 3011 33 | Average Latency: 6.45 34 | 40 Frame Running Average Latency: 6.08 35 | Minimum Latency: 5.00 36 | Maximum Latency: 119.00 37 | 38 | Opera 11 - WebSockets enabled 39 | Packets sent: 3065 40 | Packets Received: 3064 41 | Average Latency: 9.56 42 | 40 Frame Running Average Latency: 8.15 43 | Minimum Latency: 4.00 44 | Maximum Latency: 53.00 45 | 46 | ------------------------------------------------------------ 47 | 48 | New web-socket-js (20f837425d4), 2000 byte payload, 10ms delay 49 | 50 | firefox 4.0b9 - no WebSockets 51 | Packets sent: 3088 52 | Packets Received: 3087 53 | Average Latency: 16.71 54 | 40 Frame Running Average Latency: 16.80 55 | Minimum Latency: 7.00 56 | Maximum Latency: 75.00 57 | 58 | - First 1000 sent in 13 seconds 59 | - Second 1000 sent in 12 seconds 60 | - Third 1000 sent in 12 seconds 61 | 62 | firefox 3.6.10 - no WebSockets 63 | Packets sent: 3100 64 | Packets Received: 3099 65 | Average Latency: 17.32 66 | 40 Frame Running Average Latency: 16.73 67 | Minimum Latency: 6.00 68 | Maximum Latency: 72.00 69 | 70 | Opera 11 - no WebSockets 71 | Packets sent: 3007 72 | Packets Received: 3007 73 | Average Latency: 465.91 74 | 40 Frame Running Average Latency: 147.95 75 | Minimum Latency: 12.00 76 | Maximum Latency: 9143.00 77 | 78 | - average starts at around 28ms 79 | - time for each 500 packets: 13s, 16s, 25s, 37s, 50s, 72s 80 | - also start seeing sent, receive lags around 1200 packets 81 | 82 | --------------------------------------------------------------- 83 | 84 | Old web-socket-js (9e7663771), 2000 byte payload, 10ms delay 85 | 86 | firefox 4.0b9 - no WebSockets 87 | Packets sent: 3024 88 | Packets Received: 3020 89 | Average Latency: 80.59 90 | 40 Frame Running Average Latency: 60.15 91 | Minimum Latency: 10.00 92 | Maximm Latency: 348.00 93 | 94 | 95 | firefox 3.6.10 - no WebSockets 96 | Packets sent: 2777 97 | Packets Received: 2775 98 | Average Latency: 34.89 99 | 40 Frame Running Average Latency: 24.50 100 | Minimum Latency: 10.00 101 | Maximum Latency: 208.00 102 | 103 | 104 | Opera 11 - no Websockets 105 | Packets sent: 3012 106 | Packets Received: 3011 107 | Average Latency: 380.87 108 | 40 Frame Running Average Latency: 341.90 109 | Minimum Latency: 28.00 110 | Maximum Latency: 2175.00 111 | 112 | - average starts at around 290ms 113 | - time for each 1000 packets: 23s, 38s, 65s 114 | 115 | -------------------------------------------------------------------------------- /include/keysym.js: -------------------------------------------------------------------------------- 1 | /* 2 | * from noVNC: HTML5 VNC client 3 | * Copyright (C) 2010 Joel Martin 4 | * Licensed under LGPL-3 (see LICENSE.txt) 5 | */ 6 | 7 | /* Translate DOM key down/up event to keysym value */ 8 | function getKeysym(e) { 9 | var evt, keysym; 10 | evt = (e ? e : window.event); 11 | 12 | /* Remap modifier and special keys */ 13 | switch ( evt.keyCode ) { 14 | case 8 : keysym = 0xFF08; break; // BACKSPACE 15 | case 9 : keysym = 0xFF09; break; // TAB 16 | case 13 : keysym = 0xFF0D; break; // ENTER 17 | case 27 : keysym = 0xFF1B; break; // ESCAPE 18 | case 45 : keysym = 0xFF63; break; // INSERT 19 | case 46 : keysym = 0xFFFF; break; // DELETE 20 | case 36 : keysym = 0xFF50; break; // HOME 21 | case 35 : keysym = 0xFF57; break; // END 22 | case 33 : keysym = 0xFF55; break; // PAGE_UP 23 | case 34 : keysym = 0xFF56; break; // PAGE_DOWN 24 | case 37 : keysym = 0xFF51; break; // LEFT 25 | case 38 : keysym = 0xFF52; break; // UP 26 | case 39 : keysym = 0xFF53; break; // RIGHT 27 | case 40 : keysym = 0xFF54; break; // DOWN 28 | case 112 : keysym = 0xFFBE; break; // F1 29 | case 113 : keysym = 0xFFBF; break; // F2 30 | case 114 : keysym = 0xFFC0; break; // F3 31 | case 115 : keysym = 0xFFC1; break; // F4 32 | case 116 : keysym = 0xFFC2; break; // F5 33 | case 117 : keysym = 0xFFC3; break; // F6 34 | case 118 : keysym = 0xFFC4; break; // F7 35 | case 119 : keysym = 0xFFC5; break; // F8 36 | case 120 : keysym = 0xFFC6; break; // F9 37 | case 121 : keysym = 0xFFC7; break; // F10 38 | case 122 : keysym = 0xFFC8; break; // F11 39 | case 123 : keysym = 0xFFC9; break; // F12 40 | case 16 : keysym = 0xFFE1; break; // SHIFT 41 | case 17 : keysym = 0xFFE3; break; // CONTROL 42 | //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) 43 | case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) 44 | default : keysym = evt.keyCode; break; 45 | } 46 | 47 | /* Remap symbols */ 48 | switch (keysym) { 49 | case 186 : keysym = 59; break; // ; (IE) 50 | case 187 : keysym = 61; break; // = (IE) 51 | case 188 : keysym = 44; break; // , (Mozilla, IE) 52 | case 109 : // - (Mozilla) 53 | if (Util.Engine.gecko) { 54 | keysym = 45; } 55 | break; 56 | case 189 : keysym = 45; break; // - (IE) 57 | case 190 : keysym = 46; break; // . (Mozilla, IE) 58 | case 191 : keysym = 47; break; // / (Mozilla, IE) 59 | case 192 : keysym = 96; break; // ` (Mozilla, IE) 60 | case 219 : keysym = 91; break; // [ (Mozilla, IE) 61 | case 220 : keysym = 92; break; // \ (Mozilla, IE) 62 | case 221 : keysym = 93; break; // ] (Mozilla, IE) 63 | case 222 : keysym = 39; break; // ' (Mozilla, IE) 64 | } 65 | 66 | /* Remap shifted and unshifted keys */ 67 | if (!!evt.shiftKey) { 68 | switch (keysym) { 69 | case 48 : keysym = 41 ; break; // ) (shifted 0) 70 | case 49 : keysym = 33 ; break; // ! (shifted 1) 71 | case 50 : keysym = 64 ; break; // @ (shifted 2) 72 | case 51 : keysym = 35 ; break; // # (shifted 3) 73 | case 52 : keysym = 36 ; break; // $ (shifted 4) 74 | case 53 : keysym = 37 ; break; // % (shifted 5) 75 | case 54 : keysym = 94 ; break; // ^ (shifted 6) 76 | case 55 : keysym = 38 ; break; // & (shifted 7) 77 | case 56 : keysym = 42 ; break; // * (shifted 8) 78 | case 57 : keysym = 40 ; break; // ( (shifted 9) 79 | 80 | case 59 : keysym = 58 ; break; // : (shifted `) 81 | case 61 : keysym = 43 ; break; // + (shifted ;) 82 | case 44 : keysym = 60 ; break; // < (shifted ,) 83 | case 45 : keysym = 95 ; break; // _ (shifted -) 84 | case 46 : keysym = 62 ; break; // > (shifted .) 85 | case 47 : keysym = 63 ; break; // ? (shifted /) 86 | case 96 : keysym = 126; break; // ~ (shifted `) 87 | case 91 : keysym = 123; break; // { (shifted [) 88 | case 92 : keysym = 124; break; // | (shifted \) 89 | case 93 : keysym = 125; break; // } (shifted ]) 90 | case 39 : keysym = 34 ; break; // " (shifted ') 91 | } 92 | } else if ((keysym >= 65) && (keysym <=90)) { 93 | /* Remap unshifted A-Z */ 94 | keysym += 32; 95 | } 96 | 97 | return keysym; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /tests/echo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSockets Echo Test 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Host:   21 | Port:   22 | Encrypt:   23 |   25 | 26 | 27 |
28 | Log:
29 | 30 | 31 | 32 | 33 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /tests/test_websocketproxy.py: -------------------------------------------------------------------------------- 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 2 | 3 | # Copyright(c) 2015 Red Hat, Inc All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | """ Unit tests for websocketproxy """ 18 | 19 | import unittest 20 | import unittest 21 | import socket 22 | 23 | from mox3 import stubout 24 | 25 | from websockify import websocket 26 | from websockify import websocketproxy 27 | from websockify import token_plugins 28 | from websockify import auth_plugins 29 | 30 | try: 31 | from StringIO import StringIO 32 | BytesIO = StringIO 33 | except ImportError: 34 | from io import StringIO 35 | from io import BytesIO 36 | 37 | 38 | class FakeSocket(object): 39 | def __init__(self, data=''): 40 | if isinstance(data, bytes): 41 | self._data = data 42 | else: 43 | self._data = data.encode('latin_1') 44 | 45 | def recv(self, amt, flags=None): 46 | res = self._data[0:amt] 47 | if not (flags & socket.MSG_PEEK): 48 | self._data = self._data[amt:] 49 | 50 | return res 51 | 52 | def makefile(self, mode='r', buffsize=None): 53 | if 'b' in mode: 54 | return BytesIO(self._data) 55 | else: 56 | return StringIO(self._data.decode('latin_1')) 57 | 58 | 59 | class FakeServer(object): 60 | class EClose(Exception): 61 | pass 62 | 63 | def __init__(self): 64 | self.token_plugin = None 65 | self.auth_plugin = None 66 | self.wrap_cmd = None 67 | self.ssl_target = None 68 | self.unix_target = None 69 | 70 | class ProxyRequestHandlerTestCase(unittest.TestCase): 71 | def setUp(self): 72 | super(ProxyRequestHandlerTestCase, self).setUp() 73 | self.stubs = stubout.StubOutForTesting() 74 | self.handler = websocketproxy.ProxyRequestHandler( 75 | FakeSocket(''), "127.0.0.1", FakeServer()) 76 | self.handler.path = "https://localhost:6080/websockify?token=blah" 77 | self.handler.headers = None 78 | self.stubs.Set(websocket.WebSocketServer, 'socket', 79 | staticmethod(lambda *args, **kwargs: None)) 80 | 81 | def tearDown(self): 82 | self.stubs.UnsetAll() 83 | super(ProxyRequestHandlerTestCase, self).tearDown() 84 | 85 | def test_get_target(self): 86 | class TestPlugin(token_plugins.BasePlugin): 87 | def lookup(self, token): 88 | return ("some host", "some port") 89 | 90 | host, port = self.handler.get_target( 91 | TestPlugin(None), self.handler.path) 92 | 93 | self.assertEqual(host, "some host") 94 | self.assertEqual(port, "some port") 95 | 96 | def test_get_target_unix_socket(self): 97 | class TestPlugin(token_plugins.BasePlugin): 98 | def lookup(self, token): 99 | return ("unix_socket", "/tmp/socket") 100 | 101 | _, socket = self.handler.get_target( 102 | TestPlugin(None), self.handler.path) 103 | 104 | self.assertEqual(socket, "/tmp/socket") 105 | 106 | def test_get_target_raises_error_on_unknown_token(self): 107 | class TestPlugin(token_plugins.BasePlugin): 108 | def lookup(self, token): 109 | return None 110 | 111 | self.assertRaises(FakeServer.EClose, self.handler.get_target, 112 | TestPlugin(None), "https://localhost:6080/websockify?token=blah") 113 | 114 | def test_token_plugin(self): 115 | class TestPlugin(token_plugins.BasePlugin): 116 | def lookup(self, token): 117 | return (self.source + token).split(',') 118 | 119 | self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error', 120 | staticmethod(lambda *args, **kwargs: None)) 121 | 122 | self.handler.server.token_plugin = TestPlugin("somehost,") 123 | self.handler.validate_connection() 124 | 125 | self.assertEqual(self.handler.server.target_host, "somehost") 126 | self.assertEqual(self.handler.server.target_port, "blah") 127 | 128 | def test_auth_plugin(self): 129 | class TestPlugin(auth_plugins.BasePlugin): 130 | def authenticate(self, headers, target_host, target_port): 131 | if target_host == self.source: 132 | raise auth_plugins.AuthenticationError(response_msg="some_error") 133 | 134 | self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error', 135 | staticmethod(lambda *args, **kwargs: None)) 136 | 137 | self.handler.server.auth_plugin = TestPlugin("somehost") 138 | self.handler.server.target_host = "somehost" 139 | self.handler.server.target_port = "someport" 140 | 141 | self.assertRaises(auth_plugins.AuthenticationError, 142 | self.handler.validate_connection) 143 | 144 | self.handler.server.target_host = "someotherhost" 145 | self.handler.validate_connection() 146 | 147 | -------------------------------------------------------------------------------- /tests/load.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | WebSocket server-side load test program. Sends and receives traffic 5 | that has a random payload (length and content) that is checksummed and 6 | given a sequence number. Any errors are reported and counted. 7 | ''' 8 | 9 | import sys, os, select, random, time, optparse, logging 10 | sys.path.insert(0,os.path.join(os.path.dirname(__file__), "..")) 11 | from websockify.websocket import WebSocketServer, WebSocketRequestHandler 12 | 13 | class WebSocketLoadServer(WebSocketServer): 14 | 15 | recv_cnt = 0 16 | send_cnt = 0 17 | 18 | def __init__(self, *args, **kwargs): 19 | self.delay = kwargs.pop('delay') 20 | 21 | WebSocketServer.__init__(self, *args, **kwargs) 22 | 23 | 24 | class WebSocketLoad(WebSocketRequestHandler): 25 | 26 | max_packet_size = 10000 27 | 28 | def new_websocket_client(self): 29 | print "Prepopulating random array" 30 | self.rand_array = [] 31 | for i in range(0, self.max_packet_size): 32 | self.rand_array.append(random.randint(0, 9)) 33 | 34 | self.errors = 0 35 | self.send_cnt = 0 36 | self.recv_cnt = 0 37 | 38 | try: 39 | self.responder(self.request) 40 | except: 41 | print "accumulated errors:", self.errors 42 | self.errors = 0 43 | raise 44 | 45 | def responder(self, client): 46 | c_pend = 0 47 | cqueue = [] 48 | cpartial = "" 49 | socks = [client] 50 | last_send = time.time() * 1000 51 | 52 | while True: 53 | ins, outs, excepts = select.select(socks, socks, socks, 1) 54 | if excepts: raise Exception("Socket exception") 55 | 56 | if client in ins: 57 | frames, closed = self.recv_frames() 58 | 59 | err = self.check(frames) 60 | if err: 61 | self.errors = self.errors + 1 62 | print err 63 | 64 | if closed: 65 | self.send_close() 66 | 67 | now = time.time() * 1000 68 | if client in outs: 69 | if c_pend: 70 | last_send = now 71 | c_pend = self.send_frames() 72 | elif now > (last_send + self.server.delay): 73 | last_send = now 74 | c_pend = self.send_frames([self.generate()]) 75 | 76 | def generate(self): 77 | length = random.randint(10, self.max_packet_size) 78 | numlist = self.rand_array[self.max_packet_size-length:] 79 | # Error in length 80 | #numlist.append(5) 81 | chksum = sum(numlist) 82 | # Error in checksum 83 | #numlist[0] = 5 84 | nums = "".join( [str(n) for n in numlist] ) 85 | data = "^%d:%d:%d:%s$" % (self.send_cnt, length, chksum, nums) 86 | self.send_cnt += 1 87 | 88 | return data 89 | 90 | 91 | def check(self, frames): 92 | 93 | err = "" 94 | for data in frames: 95 | if data.count('$') > 1: 96 | raise Exception("Multiple parts within single packet") 97 | if len(data) == 0: 98 | self.traffic("_") 99 | continue 100 | 101 | if data[0] != "^": 102 | err += "buf did not start with '^'\n" 103 | continue 104 | 105 | try: 106 | cnt, length, chksum, nums = data[1:-1].split(':') 107 | cnt = int(cnt) 108 | length = int(length) 109 | chksum = int(chksum) 110 | except: 111 | print "\n" + repr(data) + "" 112 | err += "Invalid data format\n" 113 | continue 114 | 115 | if self.recv_cnt != cnt: 116 | err += "Expected count %d but got %d\n" % (self.recv_cnt, cnt) 117 | self.recv_cnt = cnt + 1 118 | continue 119 | 120 | self.recv_cnt += 1 121 | 122 | if len(nums) != length: 123 | err += "Expected length %d but got %d\n" % (length, len(nums)) 124 | continue 125 | 126 | inv = nums.translate(None, "0123456789") 127 | if inv: 128 | err += "Invalid characters found: %s\n" % inv 129 | continue 130 | 131 | real_chksum = 0 132 | for num in nums: 133 | real_chksum += int(num) 134 | 135 | if real_chksum != chksum: 136 | err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum) 137 | return err 138 | 139 | 140 | if __name__ == '__main__': 141 | parser = optparse.OptionParser(usage="%prog [options] listen_port") 142 | parser.add_option("--verbose", "-v", action="store_true", 143 | help="verbose messages and per frame traffic") 144 | parser.add_option("--cert", default="self.pem", 145 | help="SSL certificate file") 146 | parser.add_option("--key", default=None, 147 | help="SSL key file (if separate from cert)") 148 | parser.add_option("--ssl-only", action="store_true", 149 | help="disallow non-encrypted connections") 150 | (opts, args) = parser.parse_args() 151 | 152 | try: 153 | if len(args) != 1: raise 154 | opts.listen_port = int(args[0]) 155 | 156 | if len(args) not in [1,2]: raise 157 | opts.listen_port = int(args[0]) 158 | if len(args) == 2: 159 | opts.delay = int(args[1]) 160 | else: 161 | opts.delay = 10 162 | except: 163 | parser.error("Invalid arguments") 164 | 165 | logging.basicConfig(level=logging.INFO) 166 | 167 | opts.web = "." 168 | server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__) 169 | server.start_server() 170 | 171 | -------------------------------------------------------------------------------- /tests/plain_echo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSockets Echo Test 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Host:   19 | Port:   20 | Encrypt:   21 |   23 | 24 | 25 |
26 | Log:
27 | 28 | 29 | 30 | 31 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/websockify.1: -------------------------------------------------------------------------------- 1 | .TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS" 2 | 3 | .SH NAME 4 | 5 | websockify - WebSockets to TCP socket bridge 6 | 7 | .SH SYNOPSIS 8 | 9 | websockify [options] [source_addr:]source_port target_addr:target_port 10 | websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE 11 | 12 | .SH OPTIONS 13 | 14 | -h, --help show this help message and exit 15 | -v, --verbose verbose messages and per frame traffic 16 | --record=FILE record sessions to FILE.[session_number] 17 | -D, --daemon become a daemon (background process) 18 | --run-once handle a single WebSocket connection and exit 19 | --timeout=TIMEOUT after TIMEOUT seconds exit when not connected 20 | --cert=CERT SSL certificate file 21 | --key=KEY SSL key file (if separate from cert) 22 | --ssl-only disallow non-encrypted connections 23 | --web=DIR run webserver on same port. Serve files from DIR. 24 | --wrap-mode=MODE action to take when the wrapped program exits or 25 | daemonizes: exit (default), ignore, respawn 26 | 27 | .SH DESCRIPTION 28 | 29 | At the most basic level, websockify just translates WebSockets traffic to normal TCP socket traffic. Websockify accepts the WebSockets handshake, parses it, and then begins forwarding traffic between the client and the target in both directions. 30 | 31 | websockify was formerly named wsproxy and was part of the noVNC project. 32 | 33 | .SH NOTES 34 | 35 | .SS WebSockets binary data 36 | 37 | Websockify supports all versions of the WebSockets protocol (Hixie and HyBI). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an encoding must used to encapsulate the data within UTF-8. Websockify uses base64 to encode all traffic to and from the client. This does not affect the data between websockify and the server. 38 | 39 | .SS Encrypted WebSocket connections (wss://) 40 | 41 | To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to generate a certificate for websockify to load. By default websockify loads a certificate file name self.pem but the --cert=CERT option can override the file name. You can generate a self-signed certificate using openssl. When asked for the common name, use the hostname of the server where the proxy will be running: 42 | 43 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem 44 | 45 | .SS Websock Javascript library 46 | 47 | The websock.js (see https://github.com/kanaka/websockify) Javascript library library provides a Websock object that is similar to the standard WebSocket object but Websock enables communication with raw TCP sockets (i.e. the binary stream) via websockify. This is accomplished by base64 encoding the data stream between Websock and websockify. 48 | 49 | Websock has built-in receive queue buffering; the message event does not contain actual data but is simply a notification that there is new data available. Several rQ* methods are available to read binary data off of the receive queue. 50 | 51 | The Websock API is documented on the websock.js API wiki page: 52 | 53 | https://github.com/kanaka/websockify/wiki/websock.js 54 | 55 | See the "Wrap a Program" section below for an example of using Websock and websockify as a browser telnet client (wstelnet.html). 56 | 57 | .SS Additional websockify features 58 | 59 | These are not necessary for the basic operation. 60 | 61 | .IP * 62 | Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process. 63 | 64 | .IP * 65 | SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL). 66 | 67 | .IP * 68 | Flash security policy: websockify detects flash security policy requests (again by sniffing the first packet) and answers with an appropriate flash security policy response (and then closes the port). This means no separate flash security policy server is needed for supporting the flash WebSockets fallback emulator. 69 | 70 | .IP * 71 | Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option. 72 | 73 | .IP * 74 | Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy and Flash security policy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve. 75 | 76 | .IP * 77 | Wrap a program: see the "Wrap a Program" section below. 78 | 79 | .SS Wrap a Program 80 | 81 | In addition to proxying from a source address to a target address (which may be on a different system), websockify has the ability to launch a program on the local system and proxy WebSockets traffic to a normal TCP port owned/bound by the program. 82 | 83 | The is accomplished with a small LD_PRELOAD library (rebind.so) which intercepts bind() system calls by the program. The specified port is moved to a new localhost/loopback free high port. websockify then proxies WebSockets traffic directed to the original port to the new (moved) port of the program. 84 | 85 | The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap. 86 | 87 | `./websockify 2023 -- PROGRAM ARGS` 88 | 89 | The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes. 90 | 91 | Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC: 92 | 93 | `./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1` 94 | 95 | Here is an example of wrapping telnetd (from krb5-telnetd).telnetd exits after the connection closes so the wrap mode is set to respawn the command: 96 | 97 | `sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023` 98 | 99 | The wstelnet.html page demonstrates a simple WebSockets based telnet client. 100 | 101 | 102 | .SH AUTHOR 103 | Joel Martin (github@martintribe.org) 104 | 105 | .SH SEE ALSO 106 | 107 | https://github.com/kanaka/websockify/ 108 | 109 | https://github.com/kanaka/websockify/wiki/ 110 | 111 | -------------------------------------------------------------------------------- /include/wsirc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSockets IRC client 3 | * Copyright (C) 2011 Joel Martin 4 | * Licensed under LGPL-3 (see LICENSE.txt) 5 | * 6 | * Includes VT100.js from: 7 | * http://code.google.com/p/sshconsole 8 | * Which was modified from: 9 | * http://fzort.org/bi/o.php#vt100_js 10 | 11 | * IRC Client protocol: 12 | * http://www.faqs.org/rfcs/rfc2812.html 13 | */ 14 | 15 | 16 | function IRC(target, connect_callback, disconnect_callback) { 17 | 18 | var that = {}, // Public API interface 19 | vt100, ws, sQ = [], 20 | state = "unconnected", 21 | irc_nick, irc_channel, 22 | termType = "VT100"; 23 | 24 | 25 | Array.prototype.pushStr = function (str) { 26 | var n = str.length; 27 | for (var i=0; i < n; i++) { 28 | this.push(str.charCodeAt(i)); 29 | } 30 | } 31 | 32 | function do_send() { 33 | if (sQ.length > 0) { 34 | Util.Debug("Sending " + sQ); 35 | ws.send(sQ); 36 | sQ = []; 37 | } 38 | } 39 | 40 | function do_recv() { 41 | console.log(">> do_recv"); 42 | var rQ, rQi, i; 43 | 44 | while (ws.rQlen() > 1) { 45 | rQ = ws.get_rQ(); 46 | rQi = ws.get_rQi(); 47 | for (i = rQi; i < rQ.length; i++) { 48 | if (rQ[i] === 10) { 49 | break; 50 | } 51 | } 52 | if (i >= rQ.length) { 53 | // No line break found 54 | break; 55 | } 56 | recvMsg(ws.rQshiftStr((i-rQi) + 1)); 57 | } 58 | //console.log("<< do_recv"); 59 | } 60 | 61 | // Handle an IRC message 62 | function recvMsg(msg) { 63 | Util.Debug(">> recvMsg('" + msg + "')"); 64 | 65 | var tokens = msg.split(' '), in_params = true, 66 | prefix, command, params = [], trailing = []; 67 | 68 | Util.Info(" tokens: " + tokens); 69 | 70 | if (tokens[0].charAt(0) === ":") { 71 | prefix = tokens.shift(); 72 | } 73 | 74 | command = tokens.shift(); 75 | 76 | while (tokens.length > 0) { 77 | if (tokens[0].charAt(0) === ":") { 78 | in_params = false; 79 | } 80 | if (in_params) { 81 | params.push(tokens.shift()); 82 | } else { 83 | trailing.push(tokens.shift()); 84 | } 85 | } 86 | 87 | Util.Info(" prefix: " + prefix); 88 | Util.Info(" command: " + command); 89 | Util.Info(" params: " + params); 90 | Util.Info(" trailing: " + trailing); 91 | 92 | // Show raw received 93 | vt100.write(msg); 94 | 95 | switch (command) { 96 | case "004": 97 | state = "registered"; 98 | vt100.write("Joining channel #" + irc_channel); 99 | sendCmd("JOIN #" + irc_channel); 100 | break; 101 | case "JOIN": 102 | state = "joined"; 103 | vt100.write("Joined channel #" + irc_channel); 104 | break; 105 | 106 | } 107 | 108 | Util.Debug("<< recvMsg('" + msg + "')"); 109 | } 110 | 111 | function sendCmd(msg) { 112 | Util.Info("Sending: " + msg); 113 | sQ.pushStr(msg + "\r\n"); 114 | do_send(); 115 | } 116 | 117 | that.sendMsg = function(msg) { 118 | // TODO parse into message 119 | sendCmd("PRIVMSG #" + irc_channel + " :" + msg); 120 | } 121 | 122 | 123 | that.connect = function(host, port, encrypt, nick, channel) { 124 | var host = host, 125 | port = port, 126 | scheme = "ws://", uri; 127 | 128 | irc_nick = nick; 129 | irc_channel = channel; 130 | 131 | Util.Debug(">> connect"); 132 | if ((!host) || (!port)) { 133 | alert("must set host and port"); 134 | return false; 135 | } 136 | 137 | if (ws) { 138 | ws.close(); 139 | } 140 | 141 | if (encrypt) { 142 | scheme = "wss://"; 143 | } 144 | uri = scheme + host + ":" + port; 145 | Util.Info("connecting to " + uri); 146 | 147 | ws.open(uri); 148 | 149 | Util.Debug("<< connect"); 150 | 151 | return true; 152 | } 153 | 154 | that.disconnect = function() { 155 | Util.Debug(">> disconnect"); 156 | if (ws) { 157 | ws.close(); 158 | } 159 | 160 | disconnect_callback(); 161 | Util.Debug("<< disconnect"); 162 | } 163 | 164 | 165 | function constructor() { 166 | /* Initialize Websock object */ 167 | ws = new Websock(); 168 | 169 | ws.on('message', do_recv); 170 | ws.on('open', function(e) { 171 | Util.Info(">> WebSockets.onopen"); 172 | // Send registration commands 173 | state = "connected"; 174 | sendCmd("NICK " + irc_nick); 175 | // TODO: how to determine this? 176 | sendCmd("USER joelm 0 * :Joel Martin"); 177 | connect_callback(); 178 | Util.Info("<< WebSockets.onopen"); 179 | }); 180 | ws.on('close', function(e) { 181 | Util.Info(">> WebSockets.onclose"); 182 | that.disconnect(); 183 | Util.Info("<< WebSockets.onclose"); 184 | }); 185 | ws.on('error', function(e) { 186 | Util.Info(">> WebSockets.onerror"); 187 | that.disconnect(); 188 | Util.Info("<< WebSockets.onerror"); 189 | }); 190 | 191 | /* Initialize the terminal emulator/renderer */ 192 | 193 | vt100 = new VT100(80, 24, target); 194 | 195 | // Show cursor 196 | vt100.curs_set(true, false); 197 | 198 | /* 199 | * Override VT100 I/O routines 200 | */ 201 | 202 | // Set handler for sending characters 203 | vt100.getch( 204 | function send_chr(chr, vt) { 205 | var i; 206 | Util.Debug(">> send_chr: " + chr); 207 | for (i = 0; i < chr.length; i++) { 208 | sQ.push(chr.charCodeAt(i)); 209 | } 210 | do_send(); 211 | vt100.getch(send_chr); 212 | } 213 | ); 214 | 215 | vt100.debug = function(message) { 216 | Util.Debug(message + "\n"); 217 | } 218 | 219 | vt100.warn = function(message) { 220 | Util.Warn(message + "\n"); 221 | } 222 | 223 | vt100.curs_set = function(vis, grab, eventist) 224 | { 225 | this.debug("curs_set:: vis: " + vis + ", grab: " + grab); 226 | if (vis !== undefined) 227 | this.cursor_vis_ = (vis > 0); 228 | } 229 | 230 | return that; 231 | } 232 | 233 | return constructor(); // Return the public API interface 234 | 235 | } // End of Telnet() 236 | -------------------------------------------------------------------------------- /include/web-socket-js/README.txt: -------------------------------------------------------------------------------- 1 | * How to try 2 | 3 | Assuming you have Web server (e.g. Apache) running at http://example.com/ . 4 | 5 | - Download web_socket.rb from: 6 | http://github.com/gimite/web-socket-ruby/tree/master 7 | - Run sample Web Socket server (echo server) in example.com with: (#1) 8 | $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 9 | - 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. 10 | - Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html). 11 | - Change ws://localhost:10081 to ws://example.com:10081 in sample.html. 12 | - Open sample.html in your browser. 13 | - After "onopen" is shown, input something, click [Send] and confirm echo back. 14 | 15 | #1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com. 16 | 17 | 18 | * Troubleshooting 19 | 20 | If it doesn't work, try these: 21 | 22 | 1. Try Chrome and Firefox 3.x. 23 | - It doesn't work on Chrome: 24 | -- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log. 25 | - It works on Chrome but it doesn't work on Firefox: 26 | -- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below). 27 | - It works on both Chrome and Firefox, but it doesn't work on your browser: 28 | -- Check "Supported environment" section below. Your browser may not be supported by web-socket-js. 29 | 30 | 2. Add this line before your code: 31 | WEB_SOCKET_DEBUG = true; 32 | and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors. 33 | 34 | 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. 35 | 36 | 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. 37 | 38 | 5. Check if sample.html bundled with web-socket-js works. 39 | 40 | 6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall. 41 | 42 | 7. Install debugger version of Flash Player available here to see Flash errors: 43 | http://www.adobe.com/support/flashplayer/downloads.html 44 | 45 | 46 | * Supported environments 47 | 48 | It should work on: 49 | - Google Chrome 4 or later (just uses native implementation) 50 | - Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later 51 | 52 | It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself. 53 | 54 | 55 | * Flash socket policy file 56 | 57 | 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. 58 | 59 | If you use web-socket-ruby available at 60 | http://github.com/gimite/web-socket-ruby/tree/master 61 | , 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. 62 | 63 | If you use other Web Socket server implementation, you need to provide socket policy file yourself. See 64 | http://www.lightsphere.com/dev/articles/flash_socket_policy.html 65 | for details and sample script to run socket policy file server. node.js implementation is available here: 66 | http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js 67 | 68 | Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster. 69 | 70 | 71 | * Cookie considerations 72 | 73 | Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard). 74 | 75 | Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin. 76 | 77 | 78 | * Proxy considerations 79 | 80 | The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method. 81 | 82 | The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy. 83 | 84 | The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails. 85 | 86 | 87 | * How to host HTML file and SWF file in different domains 88 | 89 | By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain. 90 | 91 | WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie. 92 | 93 | - Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf. 94 | - Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf. 95 | - In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf. 96 | 97 | 98 | * How to build WebSocketMain.swf 99 | 100 | Install Flex 4 SDK: 101 | http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4 102 | 103 | $ cd flash-src 104 | $ ./build.sh 105 | 106 | 107 | * License 108 | 109 | New BSD License. 110 | -------------------------------------------------------------------------------- /other/js/websockify.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // A WebSocket to TCP socket proxy 4 | // Copyright 2012 Joel Martin 5 | // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) 6 | 7 | // Known to work with node 0.8.9 8 | // Requires node modules: ws, optimist and policyfile 9 | // npm install ws optimist policyfile 10 | 11 | 12 | var argv = require('optimist').argv, 13 | net = require('net'), 14 | http = require('http'), 15 | https = require('https'), 16 | url = require('url'), 17 | path = require('path'), 18 | fs = require('fs'), 19 | policyfile = require('policyfile'), 20 | 21 | Buffer = require('buffer').Buffer, 22 | WebSocketServer = require('ws').Server, 23 | 24 | webServer, wsServer, 25 | source_host, source_port, target_host, target_port, 26 | web_path = null; 27 | 28 | 29 | // Handle new WebSocket client 30 | new_client = function(client) { 31 | var clientAddr = client._socket.remoteAddress, log; 32 | console.log(client.upgradeReq.url); 33 | log = function (msg) { 34 | console.log(' ' + clientAddr + ': '+ msg); 35 | }; 36 | log('WebSocket connection'); 37 | log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol); 38 | 39 | var target = net.createConnection(target_port,target_host, function() { 40 | log('connected to target'); 41 | }); 42 | target.on('data', function(data) { 43 | //log("sending message: " + data); 44 | try { 45 | if (client.protocol === 'base64') { 46 | client.send(new Buffer(data).toString('base64')); 47 | } else { 48 | client.send(data,{binary: true}); 49 | } 50 | } catch(e) { 51 | log("Client closed, cleaning up target"); 52 | target.end(); 53 | } 54 | }); 55 | target.on('end', function() { 56 | log('target disconnected'); 57 | client.close(); 58 | }); 59 | target.on('error', function() { 60 | log('target connection error'); 61 | target.end(); 62 | client.close(); 63 | }); 64 | 65 | client.on('message', function(msg) { 66 | //log('got message: ' + msg); 67 | if (client.protocol === 'base64') { 68 | target.write(new Buffer(msg, 'base64')); 69 | } else { 70 | target.write(msg,'binary'); 71 | } 72 | }); 73 | client.on('close', function(code, reason) { 74 | log('WebSocket client disconnected: ' + code + ' [' + reason + ']'); 75 | target.end(); 76 | }); 77 | client.on('error', function(a) { 78 | log('WebSocket client error: ' + a); 79 | target.end(); 80 | }); 81 | }; 82 | 83 | 84 | // Send an HTTP error response 85 | http_error = function (response, code, msg) { 86 | response.writeHead(code, {"Content-Type": "text/plain"}); 87 | response.write(msg + "\n"); 88 | response.end(); 89 | return; 90 | } 91 | 92 | // Process an HTTP static file request 93 | http_request = function (request, response) { 94 | // console.log("pathname: " + url.parse(req.url).pathname); 95 | // res.writeHead(200, {'Content-Type': 'text/plain'}); 96 | // res.end('okay'); 97 | 98 | if (! argv.web) { 99 | return http_error(response, 403, "403 Permission Denied"); 100 | } 101 | 102 | var uri = url.parse(request.url).pathname 103 | , filename = path.join(argv.web, uri); 104 | 105 | fs.exists(filename, function(exists) { 106 | if(!exists) { 107 | return http_error(response, 404, "404 Not Found"); 108 | } 109 | 110 | if (fs.statSync(filename).isDirectory()) { 111 | filename += '/index.html'; 112 | } 113 | 114 | fs.readFile(filename, "binary", function(err, file) { 115 | if(err) { 116 | return http_error(response, 500, err); 117 | } 118 | 119 | response.writeHead(200); 120 | response.write(file, "binary"); 121 | response.end(); 122 | }); 123 | }); 124 | }; 125 | 126 | // Select 'binary' or 'base64' subprotocol, preferring 'binary' 127 | selectProtocol = function(protocols, callback) { 128 | if (protocols.indexOf('binary') >= 0) { 129 | callback(true, 'binary'); 130 | } else if (protocols.indexOf('base64') >= 0) { 131 | callback(true, 'base64'); 132 | } else { 133 | console.log("Client must support 'binary' or 'base64' protocol"); 134 | callback(false); 135 | } 136 | } 137 | 138 | // parse source and target arguments into parts 139 | try { 140 | source_arg = argv._[0].toString(); 141 | target_arg = argv._[1].toString(); 142 | 143 | var idx; 144 | idx = source_arg.indexOf(":"); 145 | if (idx >= 0) { 146 | source_host = source_arg.slice(0, idx); 147 | source_port = parseInt(source_arg.slice(idx+1), 10); 148 | } else { 149 | source_host = ""; 150 | source_port = parseInt(source_arg, 10); 151 | } 152 | 153 | idx = target_arg.indexOf(":"); 154 | if (idx < 0) { 155 | throw("target must be host:port"); 156 | } 157 | target_host = target_arg.slice(0, idx); 158 | target_port = parseInt(target_arg.slice(idx+1), 10); 159 | 160 | if (isNaN(source_port) || isNaN(target_port)) { 161 | throw("illegal port"); 162 | } 163 | } catch(e) { 164 | console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port"); 165 | process.exit(2); 166 | } 167 | 168 | console.log("WebSocket settings: "); 169 | console.log(" - proxying from " + source_host + ":" + source_port + 170 | " to " + target_host + ":" + target_port); 171 | if (argv.web) { 172 | console.log(" - Web server active. Serving: " + argv.web); 173 | } 174 | 175 | if (argv.cert) { 176 | argv.key = argv.key || argv.cert; 177 | var cert = fs.readFileSync(argv.cert), 178 | key = fs.readFileSync(argv.key); 179 | console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key); 180 | webServer = https.createServer({cert: cert, key: key}, http_request); 181 | } else { 182 | console.log(" - Running in unencrypted HTTP (ws://) mode"); 183 | webServer = http.createServer(http_request); 184 | } 185 | webServer.listen(source_port, function() { 186 | wsServer = new WebSocketServer({server: webServer, 187 | handleProtocols: selectProtocol}); 188 | wsServer.on('connection', new_client); 189 | }); 190 | 191 | // Attach Flash policyfile answer service 192 | policyfile.createServer().listen(-1, webServer); 193 | -------------------------------------------------------------------------------- /Windows/noVNC Websocket Service Project/ProjectInstaller.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 56 122 | 123 | 124 | 196, 17 125 | 126 | 127 | False 128 | 129 | -------------------------------------------------------------------------------- /include/webutil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * from noVNC: HTML5 VNC client 3 | * Copyright (C) 2012 Joel Martin 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | "use strict"; 10 | /*jslint bitwise: false, white: false */ 11 | /*global Util, window, document */ 12 | 13 | // Globals defined here 14 | var WebUtil = {}, $D; 15 | 16 | /* 17 | * Simple DOM selector by ID 18 | */ 19 | if (!window.$D) { 20 | window.$D = function (id) { 21 | if (document.getElementById) { 22 | return document.getElementById(id); 23 | } else if (document.all) { 24 | return document.all[id]; 25 | } else if (document.layers) { 26 | return document.layers[id]; 27 | } 28 | return undefined; 29 | }; 30 | } 31 | 32 | 33 | /* 34 | * ------------------------------------------------------ 35 | * Namespaced in WebUtil 36 | * ------------------------------------------------------ 37 | */ 38 | 39 | // init log level reading the logging HTTP param 40 | WebUtil.init_logging = function(level) { 41 | if (typeof level !== "undefined") { 42 | Util._log_level = level; 43 | } else { 44 | Util._log_level = (document.location.href.match( 45 | /logging=([A-Za-z0-9\._\-]*)/) || 46 | ['', Util._log_level])[1]; 47 | } 48 | Util.init_logging(); 49 | }; 50 | 51 | 52 | WebUtil.dirObj = function (obj, depth, parent) { 53 | var i, msg = "", val = ""; 54 | if (! depth) { depth=2; } 55 | if (! parent) { parent= ""; } 56 | 57 | // Print the properties of the passed-in object 58 | for (i in obj) { 59 | if ((depth > 1) && (typeof obj[i] === "object")) { 60 | // Recurse attributes that are objects 61 | msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i); 62 | } else { 63 | //val = new String(obj[i]).replace("\n", " "); 64 | if (typeof(obj[i]) === "undefined") { 65 | val = "undefined"; 66 | } else { 67 | val = obj[i].toString().replace("\n", " "); 68 | } 69 | if (val.length > 30) { 70 | val = val.substr(0,30) + "..."; 71 | } 72 | msg += parent + "." + i + ": " + val + "\n"; 73 | } 74 | } 75 | return msg; 76 | }; 77 | 78 | // Read a query string variable 79 | WebUtil.getQueryVar = function(name, defVal) { 80 | var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'), 81 | match = document.location.href.match(re); 82 | if (typeof defVal === 'undefined') { defVal = null; } 83 | if (match) { 84 | return decodeURIComponent(match[1]); 85 | } else { 86 | return defVal; 87 | } 88 | }; 89 | 90 | 91 | /* 92 | * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html 93 | */ 94 | 95 | // No days means only for this browser session 96 | WebUtil.createCookie = function(name,value,days) { 97 | var date, expires; 98 | if (days) { 99 | date = new Date(); 100 | date.setTime(date.getTime()+(days*24*60*60*1000)); 101 | expires = "; expires="+date.toGMTString(); 102 | } 103 | else { 104 | expires = ""; 105 | } 106 | document.cookie = name+"="+value+expires+"; path=/"; 107 | }; 108 | 109 | WebUtil.readCookie = function(name, defaultValue) { 110 | var i, c, nameEQ = name + "=", ca = document.cookie.split(';'); 111 | for(i=0; i < ca.length; i += 1) { 112 | c = ca[i]; 113 | while (c.charAt(0) === ' ') { c = c.substring(1,c.length); } 114 | if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); } 115 | } 116 | return (typeof defaultValue !== 'undefined') ? defaultValue : null; 117 | }; 118 | 119 | WebUtil.eraseCookie = function(name) { 120 | WebUtil.createCookie(name,"",-1); 121 | }; 122 | 123 | /* 124 | * Setting handling. 125 | */ 126 | 127 | WebUtil.initSettings = function(callback) { 128 | var callbackArgs = Array.prototype.slice.call(arguments, 1); 129 | if (window.chrome && window.chrome.storage) { 130 | window.chrome.storage.sync.get(function (cfg) { 131 | WebUtil.settings = cfg; 132 | console.log(WebUtil.settings); 133 | if (callback) { 134 | callback.apply(this, callbackArgs); 135 | } 136 | }); 137 | } else { 138 | // No-op 139 | if (callback) { 140 | callback.apply(this, callbackArgs); 141 | } 142 | } 143 | }; 144 | 145 | // No days means only for this browser session 146 | WebUtil.writeSetting = function(name, value) { 147 | if (window.chrome && window.chrome.storage) { 148 | //console.log("writeSetting:", name, value); 149 | if (WebUtil.settings[name] !== value) { 150 | WebUtil.settings[name] = value; 151 | window.chrome.storage.sync.set(WebUtil.settings); 152 | } 153 | } else { 154 | localStorage.setItem(name, value); 155 | } 156 | }; 157 | 158 | WebUtil.readSetting = function(name, defaultValue) { 159 | var value; 160 | if (window.chrome && window.chrome.storage) { 161 | value = WebUtil.settings[name]; 162 | } else { 163 | value = localStorage.getItem(name); 164 | } 165 | if (typeof value === "undefined") { 166 | value = null; 167 | } 168 | if (value === null && typeof defaultValue !== undefined) { 169 | return defaultValue; 170 | } else { 171 | return value; 172 | } 173 | }; 174 | 175 | WebUtil.eraseSetting = function(name) { 176 | if (window.chrome && window.chrome.storage) { 177 | window.chrome.storage.sync.remove(name); 178 | delete WebUtil.settings[name]; 179 | } else { 180 | localStorage.removeItem(name); 181 | } 182 | }; 183 | 184 | /* 185 | * Alternate stylesheet selection 186 | */ 187 | WebUtil.getStylesheets = function() { var i, links, sheets = []; 188 | links = document.getElementsByTagName("link"); 189 | for (i = 0; i < links.length; i += 1) { 190 | if (links[i].title && 191 | links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) { 192 | sheets.push(links[i]); 193 | } 194 | } 195 | return sheets; 196 | }; 197 | 198 | // No sheet means try and use value from cookie, null sheet used to 199 | // clear all alternates. 200 | WebUtil.selectStylesheet = function(sheet) { 201 | var i, link, sheets = WebUtil.getStylesheets(); 202 | if (typeof sheet === 'undefined') { 203 | sheet = 'default'; 204 | } 205 | for (i=0; i < sheets.length; i += 1) { 206 | link = sheets[i]; 207 | if (link.title === sheet) { 208 | Util.Debug("Using stylesheet " + sheet); 209 | link.disabled = false; 210 | } else { 211 | //Util.Debug("Skipping stylesheet " + link.title); 212 | link.disabled = true; 213 | } 214 | } 215 | return sheet; 216 | }; 217 | -------------------------------------------------------------------------------- /docs/LICENSE.LGPL-3: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## websockify: WebSockets support for any application/server 2 | 3 | websockify was formerly named wsproxy and was part of the 4 | [noVNC](https://github.com/kanaka/noVNC) project. 5 | 6 | At the most basic level, websockify just translates WebSockets traffic 7 | to normal socket traffic. Websockify accepts the WebSockets handshake, 8 | parses it, and then begins forwarding traffic between the client and 9 | the target in both directions. 10 | 11 | ### News/help/contact 12 | 13 | Notable commits, announcements and news are posted to 14 | @noVNC 15 | 16 | If you are a websockify developer/integrator/user (or want to be) 17 | please join the noVNC/websockify 19 | discussion group 20 | 21 | Bugs and feature requests can be submitted via [github 22 | issues](https://github.com/kanaka/websockify/issues). 23 | 24 | If you want to show appreciation for websockify you could donate to a great 25 | non-profits such as: [Compassion 26 | International](http://www.compassion.com/), [SIL](http://www.sil.org), 27 | [Habitat for Humanity](http://www.habitat.org), [Electronic Frontier 28 | Foundation](https://www.eff.org/), [Against Malaria 29 | Foundation](http://www.againstmalaria.com/), [Nothing But 30 | Nets](http://www.nothingbutnets.net/), etc. Please tweet @noVNC if you do. 32 | 33 | ### WebSockets binary data 34 | 35 | Starting with websockify 0.5.0, only the HyBi / IETF 36 | 6455 WebSocket protocol is supported. 37 | 38 | Websockify negotiates whether to base64 encode traffic to and from the 39 | client via the subprotocol header (Sec-WebSocket-Protocol). The valid 40 | subprotocol values are 'binary' and 'base64' and if the client sends 41 | both then the server (the python implementation) will prefer 'binary'. 42 | The 'binary' subprotocol indicates that the data will be sent raw 43 | using binary WebSocket frames. Some HyBi clients (such as the Flash 44 | fallback and older Chrome and iOS versions) do not support binary data 45 | which is why the negotiation is necessary. 46 | 47 | 48 | ### Encrypted WebSocket connections (wss://) 49 | 50 | To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to 51 | generate a certificate and key for Websockify to load. By default, Websockify 52 | loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY` 53 | options can override the file name. You can generate a self-signed certificate 54 | using openssl. When asked for the common name, use the hostname of the server 55 | where the proxy will be running: 56 | 57 | ``` 58 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem 59 | ``` 60 | 61 | For a self-signed certificate to work, you need to make your client/browser 62 | understand it. You can do this by installing it as accepted certificate, or by 63 | using that same certificate for a HTTPS connection to which you navigate first 64 | and approve. Browsers generally don't give you the "trust certificate?" prompt 65 | by opening a WSS socket with invalid certificate, hence you need to have it 66 | acccept it by either of those two methods. 67 | 68 | If you have a commercial/valid SSL certificate with one ore more intermediate 69 | certificates, concat them into one file, server certificate first, then the 70 | intermediate(s) from the CA, etc. Point to this file with the `--cert` option 71 | and then also to the key with `--key`. Finally, use `--ssl-only` as needed. 72 | 73 | 74 | ### Websock Javascript library 75 | 76 | 77 | The `include/websock.js` Javascript library library provides a Websock 78 | object that is similar to the standard WebSocket object but Websock 79 | enables communication with raw TCP sockets (i.e. the binary stream) 80 | via websockify. This is accomplished by base64 encoding the data 81 | stream between Websock and websockify. 82 | 83 | Websock has built-in receive queue buffering; the message event 84 | does not contain actual data but is simply a notification that 85 | there is new data available. Several rQ* methods are available to 86 | read binary data off of the receive queue. 87 | 88 | The Websock API is documented on the [websock.js API wiki page](https://github.com/kanaka/websockify/wiki/websock.js) 89 | 90 | See the "Wrap a Program" section below for an example of using Websock 91 | and websockify as a browser telnet client (`wstelnet.html`). 92 | 93 | 94 | ### Additional websockify features 95 | 96 | These are not necessary for the basic operation. 97 | 98 | * Daemonizing: When the `-D` option is specified, websockify runs 99 | in the background as a daemon process. 100 | 101 | * SSL (the wss:// WebSockets URI): This is detected automatically by 102 | websockify by sniffing the first byte sent from the client and then 103 | wrapping the socket if the data starts with '\x16' or '\x80' 104 | (indicating SSL). 105 | 106 | * Flash security policy: websockify detects flash security policy 107 | requests (again by sniffing the first packet) and answers with an 108 | appropriate flash security policy response (and then closes the 109 | port). This means no separate flash security policy server is needed 110 | for supporting the flash WebSockets fallback emulator. 111 | 112 | * Session recording: This feature that allows recording of the traffic 113 | sent and received from the client to a file using the `--record` 114 | option. 115 | 116 | * Mini-webserver: websockify can detect and respond to normal web 117 | requests on the same port as the WebSockets proxy and Flash security 118 | policy. This functionality is activated with the `--web DIR` option 119 | where DIR is the root of the web directory to serve. 120 | 121 | * Wrap a program: see the "Wrap a Program" section below. 122 | 123 | * Log files: websockify can save all logging information in a file. 124 | This functionality is activated with the `--log-file FILE` option 125 | where FILE is the file where the logs should be saved. 126 | 127 | ### Implementations of websockify 128 | 129 | The primary implementation of websockify is in python. There are 130 | several alternate implementations in other languages (C, Node.js, 131 | Clojure, Ruby) in the `other/` subdirectory (with varying levels of 132 | functionality). 133 | 134 | In addition there are several other external projects that implement 135 | the websockify "protocol". See the alternate implementation [Feature 136 | Matrix](https://github.com/kanaka/websockify/wiki/Feature_Matrix) for 137 | more information. 138 | 139 | 140 | ### Wrap a Program 141 | 142 | In addition to proxying from a source address to a target address 143 | (which may be on a different system), websockify has the ability to 144 | launch a program on the local system and proxy WebSockets traffic to 145 | a normal TCP port owned/bound by the program. 146 | 147 | The is accomplished with a small LD_PRELOAD library (`rebind.so`) 148 | which intercepts bind() system calls by the program. The specified 149 | port is moved to a new localhost/loopback free high port. websockify 150 | then proxies WebSockets traffic directed to the original port to the 151 | new (moved) port of the program. 152 | 153 | The program wrap mode is invoked by replacing the target with `--` 154 | followed by the program command line to wrap. 155 | 156 | `./run 2023 -- PROGRAM ARGS` 157 | 158 | The `--wrap-mode` option can be used to indicate what action to take 159 | when the wrapped program exits or daemonizes. 160 | 161 | Here is an example of using websockify to wrap the vncserver command 162 | (which backgrounds itself) for use with 163 | [noVNC](https://github.com/kanaka/noVNC): 164 | 165 | `./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1` 166 | 167 | Here is an example of wrapping telnetd (from krb5-telnetd). telnetd 168 | exits after the connection closes so the wrap mode is set to respawn 169 | the command: 170 | 171 | `sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023` 172 | 173 | The `wstelnet.html` page demonstrates a simple WebSockets based telnet 174 | client (use 'localhost' and '2023' for the host and port 175 | respectively). 176 | 177 | 178 | ### Building the Python ssl module (for python 2.5 and older) 179 | 180 | * Install the build dependencies. On Ubuntu use this command: 181 | 182 | `sudo aptitude install python-dev bluetooth-dev` 183 | 184 | * At the top level of the websockify repostory, download, build and 185 | symlink the ssl module: 186 | 187 | `wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz` 188 | 189 | `tar xvzf ssl-1.15.tar.gz` 190 | 191 | `cd ssl-1.15` 192 | 193 | `make` 194 | 195 | `cd ../` 196 | 197 | `ln -sf ssl-1.15/build/lib.linux-*/ssl ssl` 198 | 199 | -------------------------------------------------------------------------------- /other/websockify.clj: -------------------------------------------------------------------------------- 1 | (ns websockify 2 | ;(:use ring.adapter.jetty) 3 | (:require [clojure.tools.cli :as cli] 4 | [clojure.string :as string]) 5 | 6 | (:import 7 | 8 | ;; Netty TCP Client 9 | [java.util.concurrent Executors] 10 | [java.net InetSocketAddress] 11 | [org.jboss.netty.channel 12 | Channels SimpleChannelHandler ChannelPipelineFactory] 13 | [org.jboss.netty.buffer ChannelBuffers] 14 | [org.jboss.netty.channel.socket.nio NioClientSocketChannelFactory] 15 | [org.jboss.netty.bootstrap ClientBootstrap] 16 | [org.jboss.netty.handler.codec.base64 Base64] 17 | [org.jboss.netty.util CharsetUtil] 18 | 19 | ;; Jetty WebSocket Server 20 | [org.eclipse.jetty.server Server] 21 | [org.eclipse.jetty.server.nio BlockingChannelConnector] 22 | [org.eclipse.jetty.servlet 23 | ServletContextHandler ServletHolder DefaultServlet] 24 | [org.eclipse.jetty.websocket 25 | WebSocket WebSocket$OnTextMessage 26 | WebSocketClientFactory WebSocketClient WebSocketServlet])) 27 | 28 | 29 | ;; TCP / NIO 30 | 31 | ;; (defn tcp-channel [host port] 32 | ;; (try 33 | ;; (let [address (InetSocketAddress. host port) 34 | ;; channel (doto (SocketChannel/open) 35 | ;; (.connect address))] 36 | ;; channel) 37 | ;; (catch Exception e 38 | ;; (println (str "Failed to connect to'" host ":" port "':" e)) 39 | ;; nil))) 40 | 41 | ;; http://docs.jboss.org/netty/3.2/guide/html/start.html#d0e51 42 | ;; http://stackoverflow.com/questions/5453602/highly-concurrent-http-with-netty-and-nio 43 | ;; https://github.com/datskos/ring-netty-adapter/blob/master/src/ring/adapter/netty.clj 44 | 45 | 46 | (defn netty-client [host port open close message] 47 | (let [handler (proxy [SimpleChannelHandler] [] 48 | (channelConnected [ctx e] (open ctx e)) 49 | (channelDisconnected [ctx e] (close ctx e)) 50 | (messageReceived [ctx e] (message ctx e)) 51 | (exceptionCaught [ctx e] 52 | (println "exceptionCaught:" e))) 53 | pipeline (proxy [ChannelPipelineFactory] [] 54 | (getPipeline [] 55 | (doto (Channels/pipeline) 56 | (.addLast "handler" handler)))) 57 | bootstrap (doto (ClientBootstrap. 58 | (NioClientSocketChannelFactory. 59 | (Executors/newCachedThreadPool) 60 | (Executors/newCachedThreadPool))) 61 | (.setPipelineFactory pipeline) 62 | (.setOption "tcpNoDelay" true) 63 | (.setOption "keepAlive" true)) 64 | channel-future (.connect bootstrap (InetSocketAddress. host port)) 65 | channel (.. channel-future (awaitUninterruptibly) (getChannel))] 66 | channel)) 67 | 68 | 69 | 70 | ;; WebSockets 71 | 72 | ;; http://wiki.eclipse.org/Jetty/Feature/WebSockets 73 | (defn make-websocket-servlet [open close message] 74 | (proxy [WebSocketServlet] [] 75 | (doGet [request response] 76 | ;;(println "doGet" request) 77 | (.. (proxy-super getServletContext) 78 | (getNamedDispatcher (proxy-super getServletName)) 79 | (forward request response))) 80 | (doWebSocketConnect [request response] 81 | (println "doWebSocketConnect") 82 | (reify WebSocket$OnTextMessage 83 | (onOpen [this connection] (open this connection)) 84 | (onClose [this code message] (close this code message)) 85 | (onMessage [this data] (message this data)))))) 86 | 87 | (defn websocket-server 88 | [port & {:keys [open close message ws-path web] 89 | :or {open (fn [_ conn] 90 | (println "New websocket client:" conn)) 91 | close (fn [_ code reason] 92 | (println "Websocket client closed:" code reason)) 93 | message (fn [_ data] 94 | (println "Websocket message:" data)) 95 | 96 | ws-path "/websocket"}}] 97 | (let [http-servlet (doto (ServletHolder. (DefaultServlet.)) 98 | (.setInitParameter "dirAllowed" "true") 99 | (.setInitParameter "resourceBase" web)) 100 | ws-servlet (ServletHolder. 101 | (make-websocket-servlet open close message)) 102 | context (doto (ServletContextHandler.) 103 | (.setContextPath "/") 104 | (.addServlet ws-servlet ws-path)) 105 | connector (doto (BlockingChannelConnector.) 106 | (.setPort port) 107 | (.setMaxIdleTime Integer/MAX_VALUE)) 108 | server (doto (Server.) 109 | (.setHandler context) 110 | (.addConnector connector))] 111 | 112 | (when web (.addServlet context http-servlet "/")) 113 | server)) 114 | 115 | 116 | 117 | ;; Websockify 118 | 119 | (defonce settings (atom {})) 120 | 121 | ;; WebSocket client to TCP target mappings 122 | 123 | (defonce clients (atom {})) 124 | (defonce targets (atom {})) 125 | 126 | 127 | (defn target-open [ctx e] 128 | (println "Connected to target") 129 | #_(println "channelConnected:" e)) 130 | 131 | (defn target-close [ctx e] 132 | #_(println "channelDisconnected:" e) 133 | (println "Target closed") 134 | (when-let [channel (get @targets (.getChannel ctx))] 135 | (.disconnect channel))) 136 | 137 | (defn target-message [ctx e] 138 | (let [channel (.getChannel ctx) 139 | client (get @targets channel) 140 | msg (.getMessage e) 141 | len (.readableBytes msg) 142 | b64 (Base64/encode msg false) 143 | blen (.readableBytes b64)] 144 | #_(println "received" len "bytes from target") 145 | #_(println "target receive:" (.toString msg 0 len CharsetUtil/UTF_8)) 146 | #_(println "sending to client:" (.toString b64 0 blen CharsetUtil/UTF_8)) 147 | (.sendMessage client (.toString b64 0 blen CharsetUtil/UTF_8)))) 148 | 149 | (defn client-open [this connection] 150 | #_(println "Got WebSocket connection:" connection) 151 | (println "New client") 152 | (let [target (netty-client 153 | (:target-host @settings) 154 | (:target-port @settings) 155 | target-open target-close target-message)] 156 | (swap! clients assoc this {:client connection 157 | :target target}) 158 | (swap! targets assoc target connection))) 159 | 160 | (defn client-close [this code message] 161 | (println "WebSocket connection closed") 162 | (when-let [target (:target (get @clients this))] 163 | (println "Closing target") 164 | (.close target) 165 | (println "Target closed") 166 | (swap! targets dissoc target)) 167 | (swap! clients dissoc this)) 168 | 169 | (defn client-message [this data] 170 | #_(println "WebSocket onMessage:" data) 171 | (let [target (:target (get @clients this)) 172 | cbuf (ChannelBuffers/copiedBuffer data CharsetUtil/UTF_8) 173 | decbuf (Base64/decode cbuf) 174 | rlen (.readableBytes decbuf)] 175 | #_(println "Sending" rlen "bytes to target") 176 | #_(println "Sending to target:" (.toString decbuf 0 rlen CharsetUtil/UTF_8)) 177 | (.write target decbuf))) 178 | 179 | (defn start-websockify 180 | [& {:keys [listen-port target-host target-port web] 181 | :or {listen-port 6080 182 | target-host "localhost" 183 | target-port 5900 184 | }}] 185 | 186 | (reset! clients {}) 187 | (reset! targets {}) 188 | 189 | (reset! settings {:target-host target-host 190 | :target-port target-port}) 191 | (let [server (websocket-server listen-port 192 | :web web 193 | :ws-path "/websockify" 194 | :open client-open 195 | :close client-close 196 | :message client-message)] 197 | 198 | (.start server) 199 | 200 | (if web 201 | (println "Serving web requests from:" web) 202 | (println "Not serving web requests")) 203 | 204 | (defn stop-websockify [] 205 | (doseq [client (vals @clients)] 206 | (.disconnect (:client client)) 207 | (.close (:target client))) 208 | (.stop server) 209 | (reset! clients {}) 210 | (reset! targets {}) 211 | nil))) 212 | 213 | (defn -main [& args] 214 | (let [[options args banner] 215 | (cli/cli 216 | args 217 | ["-v" "--[no-]verbose" "Verbose output"] 218 | ["--web" "Run webserver with root at given location"] 219 | ["-h" "--help" "Show help" :default false :flag true] 220 | )] 221 | (when (or (:help options) 222 | (not= 2 (count args))) 223 | (println banner) 224 | (System/exit 0)) 225 | (println options) 226 | (println args) 227 | (let [target (second args) 228 | [target-host target-port] (string/split target #":")] 229 | (start-websockify :listen-port (Integer/parseInt (first args)) 230 | :target-host target-host 231 | :target-port (Integer/parseInt target-port) 232 | :web (:web options)))) 233 | nil) -------------------------------------------------------------------------------- /tests/load.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSockets Load Test 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Host:   21 | Port:   22 | Encrypt:   23 | Send Delay (ms):   24 |   26 | 27 |

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
Packets sent:
0
Good Packets Received:
0
Errors (Bad Packets Received:)
0
40 | 41 |
42 | Errors:
43 | 44 | 45 | 46 | 47 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /include/web-socket-js/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab 2 | 3 | 4 | WebSockets Latency Test 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Host:   21 | Port:   22 | Encrypt: 23 |
24 | Payload Size:   25 | Send Delay (ms):   26 |   28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
Packets sent:
Packets Received:
Average Latency:
40 Frame Running Average Latency:
Minimum Latency:
Maximum Latency:
51 | 52 |
53 | Messages:
54 | 55 | 56 | 57 | 58 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /include/wstelnet.js: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSockets telnet client 3 | * Copyright (C) 2011 Joel Martin 4 | * Licensed under LGPL-3 (see LICENSE.txt) 5 | * 6 | * Includes VT100.js from: 7 | * http://code.google.com/p/sshconsole 8 | * Which was modified from: 9 | * http://fzort.org/bi/o.php#vt100_js 10 | * 11 | * Telnet protocol: 12 | * http://www.networksorcery.com/enp/protocol/telnet.htm 13 | * http://www.networksorcery.com/enp/rfc/rfc1091.txt 14 | * 15 | * ANSI escape sequeneces: 16 | * http://en.wikipedia.org/wiki/ANSI_escape_code 17 | * http://ascii-table.com/ansi-escape-sequences-vt-100.php 18 | * http://www.termsys.demon.co.uk/vtansi.htm 19 | * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 20 | * 21 | * ASCII codes: 22 | * http://en.wikipedia.org/wiki/ASCII 23 | * http://www.hobbyprojects.com/ascii-table/ascii-table.html 24 | * 25 | * Other web consoles: 26 | * http://stackoverflow.com/questions/244750/ajax-console-window-with-ansi-vt100-support 27 | */ 28 | 29 | 30 | 31 | 32 | function Telnet(target, connect_callback, disconnect_callback) { 33 | 34 | var that = {}, // Public API interface 35 | vt100, ws, sQ = []; 36 | termType = "VT100"; 37 | 38 | 39 | Array.prototype.pushStr = function (str) { 40 | var n = str.length; 41 | for (var i=0; i < n; i++) { 42 | this.push(str.charCodeAt(i)); 43 | } 44 | } 45 | 46 | function do_send() { 47 | if (sQ.length > 0) { 48 | Util.Debug("Sending " + sQ); 49 | ws.send(sQ); 50 | sQ = []; 51 | } 52 | } 53 | 54 | function do_recv() { 55 | //console.log(">> do_recv"); 56 | var arr = ws.rQshiftBytes(ws.rQlen()), str = "", 57 | chr, cmd, code, value; 58 | 59 | Util.Debug("Received array '" + arr + "'"); 60 | while (arr.length > 0) { 61 | chr = arr.shift(); 62 | switch (chr) { 63 | case 255: // IAC 64 | cmd = chr; 65 | code = arr.shift(); 66 | value = arr.shift(); 67 | switch (code) { 68 | case 254: // DONT 69 | Util.Debug("Got Cmd DONT '" + value + "', ignoring"); 70 | break; 71 | case 253: // DO 72 | Util.Debug("Got Cmd DO '" + value + "'"); 73 | if (value === 24) { 74 | // Terminal type 75 | Util.Info("Send WILL '" + value + "' (TERM-TYPE)"); 76 | sQ.push(255, 251, value); 77 | } else { 78 | // Refuse other DO requests with a WONT 79 | Util.Debug("Send WONT '" + value + "'"); 80 | sQ.push(255, 252, value); 81 | } 82 | break; 83 | case 252: // WONT 84 | Util.Debug("Got Cmd WONT '" + value + "', ignoring"); 85 | break; 86 | case 251: // WILL 87 | Util.Debug("Got Cmd WILL '" + value + "'"); 88 | if (value === 1) { 89 | // Server will echo, turn off local echo 90 | vt100.noecho(); 91 | // Affirm echo with DO 92 | Util.Info("Send Cmd DO '" + value + "' (echo)"); 93 | sQ.push(255, 253, value); 94 | } else { 95 | // Reject other WILL offers with a DONT 96 | Util.Debug("Send Cmd DONT '" + value + "'"); 97 | sQ.push(255, 254, value); 98 | } 99 | break; 100 | case 250: // SB (subnegotiation) 101 | if (value === 24) { 102 | Util.Info("Got IAC SB TERM-TYPE SEND(1) IAC SE"); 103 | // TERM-TYPE subnegotiation 104 | if (arr[0] === 1 && 105 | arr[1] === 255 && 106 | arr[2] === 240) { 107 | arr.shift(); arr.shift(); arr.shift(); 108 | Util.Info("Send IAC SB TERM-TYPE IS(0) '" + 109 | termType + "' IAC SE"); 110 | sQ.push(255, 250, 24, 0); 111 | sQ.pushStr(termType); 112 | sQ.push(255, 240); 113 | } else { 114 | Util.Info("Invalid subnegotiation received" + arr); 115 | } 116 | } else { 117 | Util.Info("Ignoring SB " + value); 118 | } 119 | break; 120 | default: 121 | Util.Info("Got Cmd " + cmd + " " + value + ", ignoring"); } 122 | continue; 123 | case 242: // Data Mark (Synch) 124 | cmd = chr; 125 | code = arr.shift(); 126 | value = arr.shift(); 127 | Util.Info("Ignoring Data Mark (Synch)"); 128 | break; 129 | default: // everything else 130 | str += String.fromCharCode(chr); 131 | } 132 | } 133 | 134 | if (sQ) { 135 | do_send(); 136 | } 137 | 138 | if (str) { 139 | vt100.write(str); 140 | } 141 | 142 | //console.log("<< do_recv"); 143 | } 144 | 145 | 146 | 147 | that.connect = function(host, port, encrypt) { 148 | var host = host, 149 | port = port, 150 | scheme = "ws://", uri; 151 | 152 | Util.Debug(">> connect"); 153 | if ((!host) || (!port)) { 154 | console.log("must set host and port"); 155 | return; 156 | } 157 | 158 | if (ws) { 159 | ws.close(); 160 | } 161 | 162 | if (encrypt) { 163 | scheme = "wss://"; 164 | } 165 | uri = scheme + host + ":" + port; 166 | Util.Info("connecting to " + uri); 167 | 168 | ws.open(uri); 169 | 170 | Util.Debug("<< connect"); 171 | } 172 | 173 | that.disconnect = function() { 174 | Util.Debug(">> disconnect"); 175 | if (ws) { 176 | ws.close(); 177 | } 178 | vt100.curs_set(true, false); 179 | 180 | disconnect_callback(); 181 | Util.Debug("<< disconnect"); 182 | } 183 | 184 | 185 | function constructor() { 186 | /* Initialize Websock object */ 187 | ws = new Websock(); 188 | 189 | ws.on('message', do_recv); 190 | ws.on('open', function(e) { 191 | Util.Info(">> WebSockets.onopen"); 192 | vt100.curs_set(true, true); 193 | connect_callback(); 194 | Util.Info("<< WebSockets.onopen"); 195 | }); 196 | ws.on('close', function(e) { 197 | Util.Info(">> WebSockets.onclose"); 198 | that.disconnect(); 199 | Util.Info("<< WebSockets.onclose"); 200 | }); 201 | ws.on('error', function(e) { 202 | Util.Info(">> WebSockets.onerror"); 203 | that.disconnect(); 204 | Util.Info("<< WebSockets.onerror"); 205 | }); 206 | 207 | /* Initialize the terminal emulator/renderer */ 208 | 209 | vt100 = new VT100(80, 24, target); 210 | 211 | 212 | /* 213 | * Override VT100 I/O routines 214 | */ 215 | 216 | // Set handler for sending characters 217 | vt100.getch( 218 | function send_chr(chr, vt) { 219 | var i; 220 | Util.Debug(">> send_chr: " + chr); 221 | for (i = 0; i < chr.length; i++) { 222 | sQ.push(chr.charCodeAt(i)); 223 | } 224 | do_send(); 225 | vt100.getch(send_chr); 226 | } 227 | ); 228 | 229 | vt100.debug = function(message) { 230 | Util.Debug(message + "\n"); 231 | } 232 | 233 | vt100.warn = function(message) { 234 | Util.Warn(message + "\n"); 235 | } 236 | 237 | vt100.curs_set = function(vis, grab, eventist) 238 | { 239 | this.debug("curs_set:: vis: " + vis + ", grab: " + grab); 240 | if (vis !== undefined) 241 | this.cursor_vis_ = (vis > 0); 242 | if (eventist === undefined) 243 | eventist = window; 244 | if (grab === true || grab === false) { 245 | if (grab === this.grab_events_) 246 | return; 247 | if (grab) { 248 | this.grab_events_ = true; 249 | VT100.the_vt_ = this; 250 | Util.addEvent(eventist, 'keydown', vt100.key_down); 251 | Util.addEvent(eventist, 'keyup', vt100.key_up); 252 | } else { 253 | Util.removeEvent(eventist, 'keydown', vt100.key_down); 254 | Util.removeEvent(eventist, 'keyup', vt100.key_up); 255 | this.grab_events_ = false; 256 | VT100.the_vt_ = undefined; 257 | } 258 | } 259 | } 260 | 261 | vt100.key_down = function(e) { 262 | var vt = VT100.the_vt_, keysym, ch, str = ""; 263 | 264 | if (vt === undefined) 265 | return true; 266 | 267 | keysym = getKeysym(e); 268 | 269 | if (keysym < 128) { 270 | if (e.ctrlKey) { 271 | if (keysym == 64) { 272 | // control 0 273 | ch = 0; 274 | } else if ((keysym >= 97) && (keysym <= 122)) { 275 | // control codes 1-26 276 | ch = keysym - 96; 277 | } else if ((keysym >= 91) && (keysym <= 95)) { 278 | // control codes 27-31 279 | ch = keysym - 64; 280 | } else { 281 | Util.Info("Debug unknown control keysym: " + keysym); 282 | } 283 | } else { 284 | ch = keysym; 285 | } 286 | str = String.fromCharCode(ch); 287 | } else { 288 | switch (keysym) { 289 | case 65505: // Shift, do not send directly 290 | break; 291 | case 65507: // Ctrl, do not send directly 292 | break; 293 | case 65293: // Carriage return, line feed 294 | str = '\n'; break; 295 | case 65288: // Backspace 296 | str = '\b'; break; 297 | case 65307: // Escape 298 | str = '\x1b'; break; 299 | case 65361: // Left arrow 300 | str = '\x1b[D'; break; 301 | case 65362: // Up arrow 302 | str = '\x1b[A'; break; 303 | case 65363: // Right arrow 304 | str = '\x1b[C'; break; 305 | case 65364: // Down arrow 306 | str = '\x1b[B'; break; 307 | default: 308 | Util.Info("Unrecoginized keysym " + keysym); 309 | } 310 | } 311 | 312 | if (str) { 313 | vt.key_buf_.push(str); 314 | setTimeout(VT100.go_getch_, 0); 315 | } 316 | 317 | Util.stopEvent(e); 318 | return false; 319 | } 320 | 321 | vt100.key_up = function(e) { 322 | var vt = VT100.the_vt_; 323 | if (vt === undefined) 324 | return true; 325 | Util.stopEvent(e); 326 | return false; 327 | } 328 | 329 | 330 | return that; 331 | } 332 | 333 | return constructor(); // Return the public API interface 334 | 335 | } // End of Telnet() 336 | -------------------------------------------------------------------------------- /include/websock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Websock: high-performance binary WebSockets 3 | * Copyright (C) 2012 Joel Martin 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * Websock is similar to the standard WebSocket object but Websock 7 | * enables communication with raw TCP sockets (i.e. the binary stream) 8 | * via websockify. This is accomplished by base64 encoding the data 9 | * stream between Websock and websockify. 10 | * 11 | * Websock has built-in receive queue buffering; the message event 12 | * does not contain actual data but is simply a notification that 13 | * there is new data available. Several rQ* methods are available to 14 | * read binary data off of the receive queue. 15 | */ 16 | 17 | /*jslint browser: true, bitwise: false, plusplus: false */ 18 | /*global Util, Base64 */ 19 | 20 | 21 | // Load Flash WebSocket emulator if needed 22 | 23 | // To force WebSocket emulator even when native WebSocket available 24 | //window.WEB_SOCKET_FORCE_FLASH = true; 25 | // To enable WebSocket emulator debug: 26 | //window.WEB_SOCKET_DEBUG=1; 27 | 28 | if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 29 | Websock_native = true; 30 | } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 31 | Websock_native = true; 32 | window.WebSocket = window.MozWebSocket; 33 | } else { 34 | /* no builtin WebSocket so load web_socket.js */ 35 | 36 | Websock_native = false; 37 | (function () { 38 | window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() + 39 | "web-socket-js/WebSocketMain.swf"; 40 | if (Util.Engine.trident) { 41 | Util.Debug("Forcing uncached load of WebSocketMain.swf"); 42 | window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); 43 | } 44 | Util.load_scripts(["web-socket-js/swfobject.js", 45 | "web-socket-js/web_socket.js"]); 46 | }()); 47 | } 48 | 49 | 50 | function Websock() { 51 | "use strict"; 52 | 53 | var api = {}, // Public API 54 | websocket = null, // WebSocket object 55 | mode = 'base64', // Current WebSocket mode: 'binary', 'base64' 56 | rQ = [], // Receive queue 57 | rQi = 0, // Receive queue index 58 | rQmax = 10000, // Max receive queue size before compacting 59 | sQ = [], // Send queue 60 | 61 | eventHandlers = { 62 | 'message' : function() {}, 63 | 'open' : function() {}, 64 | 'close' : function() {}, 65 | 'error' : function() {} 66 | }, 67 | 68 | test_mode = false; 69 | 70 | 71 | // 72 | // Queue public functions 73 | // 74 | 75 | function get_sQ() { 76 | return sQ; 77 | } 78 | 79 | function get_rQ() { 80 | return rQ; 81 | } 82 | function get_rQi() { 83 | return rQi; 84 | } 85 | function set_rQi(val) { 86 | rQi = val; 87 | } 88 | 89 | function rQlen() { 90 | return rQ.length - rQi; 91 | } 92 | 93 | function rQpeek8() { 94 | return (rQ[rQi] ); 95 | } 96 | function rQshift8() { 97 | return (rQ[rQi++] ); 98 | } 99 | function rQunshift8(num) { 100 | if (rQi === 0) { 101 | rQ.unshift(num); 102 | } else { 103 | rQi -= 1; 104 | rQ[rQi] = num; 105 | } 106 | 107 | } 108 | function rQshift16() { 109 | return (rQ[rQi++] << 8) + 110 | (rQ[rQi++] ); 111 | } 112 | function rQshift32() { 113 | return (rQ[rQi++] << 24) + 114 | (rQ[rQi++] << 16) + 115 | (rQ[rQi++] << 8) + 116 | (rQ[rQi++] ); 117 | } 118 | function rQshiftStr(len) { 119 | if (typeof(len) === 'undefined') { len = rQlen(); } 120 | var arr = rQ.slice(rQi, rQi + len); 121 | rQi += len; 122 | return String.fromCharCode.apply(null, arr); 123 | } 124 | function rQshiftBytes(len) { 125 | if (typeof(len) === 'undefined') { len = rQlen(); } 126 | rQi += len; 127 | return rQ.slice(rQi-len, rQi); 128 | } 129 | 130 | function rQslice(start, end) { 131 | if (end) { 132 | return rQ.slice(rQi + start, rQi + end); 133 | } else { 134 | return rQ.slice(rQi + start); 135 | } 136 | } 137 | 138 | // Check to see if we must wait for 'num' bytes (default to FBU.bytes) 139 | // to be available in the receive queue. Return true if we need to 140 | // wait (and possibly print a debug message), otherwise false. 141 | function rQwait(msg, num, goback) { 142 | var rQlen = rQ.length - rQi; // Skip rQlen() function call 143 | if (rQlen < num) { 144 | if (goback) { 145 | if (rQi < goback) { 146 | throw("rQwait cannot backup " + goback + " bytes"); 147 | } 148 | rQi -= goback; 149 | } 150 | //Util.Debug(" waiting for " + (num-rQlen) + 151 | // " " + msg + " byte(s)"); 152 | return true; // true means need more data 153 | } 154 | return false; 155 | } 156 | 157 | // 158 | // Private utility routines 159 | // 160 | 161 | function encode_message() { 162 | if (mode === 'binary') { 163 | // Put in a binary arraybuffer 164 | return (new Uint8Array(sQ)).buffer; 165 | } else { 166 | // base64 encode 167 | return Base64.encode(sQ); 168 | } 169 | } 170 | 171 | function decode_message(data) { 172 | //Util.Debug(">> decode_message: " + data); 173 | if (mode === 'binary') { 174 | // push arraybuffer values onto the end 175 | var u8 = new Uint8Array(data); 176 | for (var i = 0; i < u8.length; i++) { 177 | rQ.push(u8[i]); 178 | } 179 | } else { 180 | // base64 decode and concat to the end 181 | rQ = rQ.concat(Base64.decode(data, 0)); 182 | } 183 | //Util.Debug(">> decode_message, rQ: " + rQ); 184 | } 185 | 186 | 187 | // 188 | // Public Send functions 189 | // 190 | 191 | function flush() { 192 | if (websocket.bufferedAmount !== 0) { 193 | Util.Debug("bufferedAmount: " + websocket.bufferedAmount); 194 | } 195 | if (websocket.bufferedAmount < api.maxBufferedAmount) { 196 | //Util.Debug("arr: " + arr); 197 | //Util.Debug("sQ: " + sQ); 198 | if (sQ.length > 0) { 199 | websocket.send(encode_message(sQ)); 200 | sQ = []; 201 | } 202 | return true; 203 | } else { 204 | Util.Info("Delaying send, bufferedAmount: " + 205 | websocket.bufferedAmount); 206 | return false; 207 | } 208 | } 209 | 210 | // overridable for testing 211 | function send(arr) { 212 | //Util.Debug(">> send_array: " + arr); 213 | sQ = sQ.concat(arr); 214 | return flush(); 215 | } 216 | 217 | function send_string(str) { 218 | //Util.Debug(">> send_string: " + str); 219 | api.send(str.split('').map( 220 | function (chr) { return chr.charCodeAt(0); } ) ); 221 | } 222 | 223 | // 224 | // Other public functions 225 | 226 | function recv_message(e) { 227 | //Util.Debug(">> recv_message: " + e.data.length); 228 | 229 | try { 230 | decode_message(e.data); 231 | if (rQlen() > 0) { 232 | eventHandlers.message(); 233 | // Compact the receive queue 234 | if (rQ.length > rQmax) { 235 | //Util.Debug("Compacting receive queue"); 236 | rQ = rQ.slice(rQi); 237 | rQi = 0; 238 | } 239 | } else { 240 | Util.Debug("Ignoring empty message"); 241 | } 242 | } catch (exc) { 243 | if (typeof exc.stack !== 'undefined') { 244 | Util.Warn("recv_message, caught exception: " + exc.stack); 245 | } else if (typeof exc.description !== 'undefined') { 246 | Util.Warn("recv_message, caught exception: " + exc.description); 247 | } else { 248 | Util.Warn("recv_message, caught exception:" + exc); 249 | } 250 | if (typeof exc.name !== 'undefined') { 251 | eventHandlers.error(exc.name + ": " + exc.message); 252 | } else { 253 | eventHandlers.error(exc); 254 | } 255 | } 256 | //Util.Debug("<< recv_message"); 257 | } 258 | 259 | 260 | // Set event handlers 261 | function on(evt, handler) { 262 | eventHandlers[evt] = handler; 263 | } 264 | 265 | function init(protocols, ws_schema) { 266 | rQ = []; 267 | rQi = 0; 268 | sQ = []; 269 | websocket = null; 270 | 271 | var bt = false, 272 | wsbt = false, 273 | try_binary = false; 274 | 275 | // Check for full typed array support 276 | if (('Uint8Array' in window) && 277 | ('set' in Uint8Array.prototype)) { 278 | bt = true; 279 | } 280 | // Check for full binary type support in WebSocket 281 | // Inspired by: 282 | // https://github.com/Modernizr/Modernizr/issues/370 283 | // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js 284 | try { 285 | if (bt && ('binaryType' in WebSocket.prototype || 286 | !!(new WebSocket(ws_schema + '://.').binaryType))) { 287 | Util.Info("Detected binaryType support in WebSockets"); 288 | wsbt = true; 289 | } 290 | } catch (exc) { 291 | // Just ignore failed test localhost connections 292 | } 293 | 294 | // Default protocols if not specified 295 | if (typeof(protocols) === "undefined") { 296 | if (wsbt) { 297 | protocols = ['binary', 'base64']; 298 | } else { 299 | protocols = 'base64'; 300 | } 301 | } 302 | 303 | // If no binary support, make sure it was not requested 304 | if (!wsbt) { 305 | if (protocols === 'binary') { 306 | throw("WebSocket binary sub-protocol requested but not supported"); 307 | } 308 | if (typeof(protocols) === "object") { 309 | var new_protocols = []; 310 | for (var i = 0; i < protocols.length; i++) { 311 | if (protocols[i] === 'binary') { 312 | Util.Error("Skipping unsupported WebSocket binary sub-protocol"); 313 | } else { 314 | new_protocols.push(protocols[i]); 315 | } 316 | } 317 | if (new_protocols.length > 0) { 318 | protocols = new_protocols; 319 | } else { 320 | throw("Only WebSocket binary sub-protocol was requested and not supported."); 321 | } 322 | } 323 | } 324 | 325 | return protocols; 326 | } 327 | 328 | function open(uri, protocols) { 329 | var ws_schema = uri.match(/^([a-z]+):\/\//)[1]; 330 | protocols = init(protocols, ws_schema); 331 | 332 | if (test_mode) { 333 | websocket = {}; 334 | } else { 335 | websocket = new WebSocket(uri, protocols); 336 | if (protocols.indexOf('binary') >= 0) { 337 | websocket.binaryType = 'arraybuffer'; 338 | } 339 | } 340 | 341 | websocket.onmessage = recv_message; 342 | websocket.onopen = function() { 343 | Util.Debug(">> WebSock.onopen"); 344 | if (websocket.protocol) { 345 | mode = websocket.protocol; 346 | Util.Info("Server chose sub-protocol: " + websocket.protocol); 347 | } else { 348 | mode = 'base64'; 349 | Util.Error("Server select no sub-protocol!: " + websocket.protocol); 350 | } 351 | eventHandlers.open(); 352 | Util.Debug("<< WebSock.onopen"); 353 | }; 354 | websocket.onclose = function(e) { 355 | Util.Debug(">> WebSock.onclose"); 356 | eventHandlers.close(e); 357 | Util.Debug("<< WebSock.onclose"); 358 | }; 359 | websocket.onerror = function(e) { 360 | Util.Debug(">> WebSock.onerror: " + e); 361 | eventHandlers.error(e); 362 | Util.Debug("<< WebSock.onerror"); 363 | }; 364 | } 365 | 366 | function close() { 367 | if (websocket) { 368 | if ((websocket.readyState === WebSocket.OPEN) || 369 | (websocket.readyState === WebSocket.CONNECTING)) { 370 | Util.Info("Closing WebSocket connection"); 371 | websocket.close(); 372 | } 373 | websocket.onmessage = function (e) { return; }; 374 | } 375 | } 376 | 377 | // Override internal functions for testing 378 | // Takes a send function, returns reference to recv function 379 | function testMode(override_send, data_mode) { 380 | test_mode = true; 381 | mode = data_mode; 382 | api.send = override_send; 383 | api.close = function () {}; 384 | return recv_message; 385 | } 386 | 387 | function constructor() { 388 | // Configuration settings 389 | api.maxBufferedAmount = 200; 390 | 391 | // Direct access to send and receive queues 392 | api.get_sQ = get_sQ; 393 | api.get_rQ = get_rQ; 394 | api.get_rQi = get_rQi; 395 | api.set_rQi = set_rQi; 396 | 397 | // Routines to read from the receive queue 398 | api.rQlen = rQlen; 399 | api.rQpeek8 = rQpeek8; 400 | api.rQshift8 = rQshift8; 401 | api.rQunshift8 = rQunshift8; 402 | api.rQshift16 = rQshift16; 403 | api.rQshift32 = rQshift32; 404 | api.rQshiftStr = rQshiftStr; 405 | api.rQshiftBytes = rQshiftBytes; 406 | api.rQslice = rQslice; 407 | api.rQwait = rQwait; 408 | 409 | api.flush = flush; 410 | api.send = send; 411 | api.send_string = send_string; 412 | 413 | api.on = on; 414 | api.init = init; 415 | api.open = open; 416 | api.close = close; 417 | api.testMode = testMode; 418 | 419 | return api; 420 | } 421 | 422 | return constructor(); 423 | 424 | } 425 | --------------------------------------------------------------------------------