173 Commits

Author SHA1 Message Date
Solly Ross cd4bc7590e Update to 0.6.1
This is a minor patch release which fixes the functionality of
the file_only parameter and prevents uninteded directory listings.
2015-05-11 10:35:55 -04:00
Solly Ross d626fed76a Restore functionaltiy to file_only
There was a bug in WebSocketServer that prevented the `file_only`
parameter from actually being set as an instance property, causing
directory listings to appear even with `file_only=True`.  This
commit fixes that.

See-Also: https://bugs.launchpad.net/nova/+bug/1447675

Conflicts:
	websockify/websocket.py
2015-05-11 10:30:49 -04:00
Solly Ross 739af6ecb4 Update to v0.6.0
*** NOTE ***

This version of websockify will break existing code which sub-classes
`WebsocketProxy` -- see pull requests #110 and #111
2014-02-18 17:03:09 -05:00
Joel Martin 56b740ef5c README: update with general contact info. 2014-02-03 09:26:03 -06:00
Solly Ross 18e70c52e1 Fix default log level in echo and load tests
Since we switched to using the `logging` module to log
in pull request #100, none of the messages on the 'INFO'
level were being shown from `tests/echo.py` and
`tests/load.py`, since the default log level is 'WARNING'.

Now, the log level is set to INFO in `tests/echo.py` and
`tests/load.py`, to match the log level in the main websockify
executable.

Fixes #109
2014-01-30 17:11:20 -05:00
Joel Martin cc3ccf0ddd README: fix wrap mode examples. 2014-01-13 13:25:13 -06:00
astrand 38db12c2b0 Merge pull request #111 from astrand/master
Refactor to use standard SocketServer RequestHandler design.
2013-12-19 01:31:14 -08:00
Peter Åstrand (astrand) a749611370 Merge remote branch 'upstream/master' 2013-12-19 10:28:37 +01:00
Peter Åstrand (astrand) b662d185ca Prepare for fixing https://github.com/kanaka/websockify/pull/111
Move around functions and methods, so that connection-related and
server-related stuff are close together.

This patch just moves things around - it does not change anything at
all. This can be verified with:

git diff | grep ^- | cut -c 2- | sort > removed
git diff | grep ^+ | cut -c 2- | sort > added
diff -u removed added
2013-12-19 10:24:36 +01:00
Peter Åstrand (astrand) 6de6933819 Minor whitespace and layout tweaks, to reduce diff against
upstream/master.
2013-12-17 14:20:14 +01:00
Peter Åstrand (astrand) f46db48399 Merge remote branch 'upstream/master'
Conflicts:
	websockify/websocket.py
2013-12-17 14:04:30 +01:00
Peter Åstrand (astrand) 8049ddfe26 Cherry-picked 4e3388964a from
astrand/websockify:

    Prepare for fixing https://github.com/kanaka/websockify/issues/71:
    Move around functions and methods, so that connection-related and
    server-related stuff are close together.

    This patch just moves things around - it does not change anything at
    all. This can be verified with:

    git diff websocket.py | grep ^- | cut -c 2- | sort > removed
    git diff websocket.py | grep ^+ | cut -c 2- | sort > added
    diff -u removed added
2013-12-17 13:56:20 +01:00
Peter Åstrand (astrand) f64e3dea51 Merge remote branch 'upstream/master', after #110 was merged. 2013-12-16 16:18:41 +01:00
astrand 17455afe4d Merge pull request #110 from astrand/rename-self-client
Rename self.client and new_client - prepare for #72 / #111
2013-12-16 05:45:03 -08:00
Peter Åstrand (astrand) 047ce47742 Rename new_client to new_websocket_client, in order to have a better
name in the SocketServer/HTTPServer request handler hierarchy. Prepare
for merge pull request #72. This work has been picked out of
7b3dd8a6f5 .
2013-11-28 13:33:28 +01:00
Peter Åstrand (astrand) 558402848e Rename self.client to self.request, in preparation for merging pull
request #72. The standard Python SocketServer/BaseRequestHandler
requires this name.
2013-11-28 13:23:50 +01:00
Peter Åstrand (astrand) db93395061 Follow up on 131f9ea645: Proper logging
in request handler class.
2013-11-28 12:37:57 +01:00
Peter Åstrand (astrand) e246e98b20 Merge commit '34a1b68d79a13c03aa63b5c4194796341c9383fe'
* commit '34a1b68d79a13c03aa63b5c4194796341c9383fe':
  Clarify ssl module build for old python versions.
2013-11-28 09:34:43 +01:00
Peter Åstrand (astrand) 7ecfa4f384 Merge commit 'a04edfe80f54b44df5a3579f71710560c6b7b4fc'
* commit 'a04edfe80f54b44df5a3579f71710560c6b7b4fc':
  Added temp dir for unit test data and cleanup
2013-11-28 09:34:33 +01:00
Peter Åstrand (astrand) bc216fb7d1 Merge commit 'a47be21f9fa69ddf8d888ff9e3c75cdfc9e31c00'
* commit 'a47be21f9fa69ddf8d888ff9e3c75cdfc9e31c00':
  Added unit tests for websocket and websocketproxy
2013-11-28 09:32:49 +01:00
Peter Åstrand (astrand) b92528aeba Merge commit 'c3acdc2e38f871e28ffda1847b4338c4b02296b8'
* commit 'c3acdc2e38f871e28ffda1847b4338c4b02296b8':
  Adds optional TCP_KEEPALIVE to WebSocketServer
2013-11-28 09:32:30 +01:00
Peter Åstrand (astrand) 81e2a53692 Merge commit '13c99bcf053f7f3af8ba84c0d963a9591e020f49'
* commit '13c99bcf053f7f3af8ba84c0d963a9591e020f49':
  Fix search path construction in tests.
2013-11-28 09:14:25 +01:00
Peter Åstrand (astrand) 602248425a Merge commit 'b4e0b534d5d04d57265045b4baf49dd81555064b'
* commit 'b4e0b534d5d04d57265045b4baf49dd81555064b':
  Fix crash when an import is missing
2013-11-28 09:09:03 +01:00
Peter Åstrand (astrand) 3b802c08b7 Merge commit '32b0567343aee7753b2b6be1bc1ee9a69657ba26'
* commit '32b0567343aee7753b2b6be1bc1ee9a69657ba26':
  Fix syntax errors in other/websockify.rb
2013-11-28 09:07:52 +01:00
Peter Åstrand (astrand) 972b30ddc2 Merge commit '0e5c3ecfda3b1506b41412052db75d84df2b4ae7'
* commit '0e5c3ecfda3b1506b41412052db75d84df2b4ae7':
  Handle SIGCHLD properly for multiprocessing
2013-11-28 09:07:41 +01:00
Peter Åstrand (astrand) f58b49fa08 Merge commit 'a61ae52610642ae58e914dda705df8bb9c8213ec'
* commit 'a61ae52610642ae58e914dda705df8bb9c8213ec':
  fixed 1.8 compatibility bug for OpenSSL::SSL::SSLSocket#read_nonblock vs #readpartial tested in 1.8 and 2.0
  adding SSL support and Ruby1.9 support
2013-11-28 09:07:30 +01:00
Peter Åstrand (astrand) 131f9ea645 Merge commit '477dce6cf86d61b20a394f3cbf3170e60d199658'
* commit '477dce6cf86d61b20a394f3cbf3170e60d199658':
  websocket: use python logging module
  websocket: fix exception statement introduced by comment 903e3f06ee557

Adapted to new standard SocketServer RequestHandler design. For
example, this means that self.i_am_client is not needed.
2013-11-28 09:05:24 +01:00
Peter Åstrand (astrand) cbf05f84fe Merge commit '4459824cc8196ad78fe9258b6c560ad46fe4cd52'
* commit '4459824cc8196ad78fe9258b6c560ad46fe4cd52':
  websocket: do not exit at the middle of process
  websocket: restore signals after processing
  websocket: support SIGTERM as exit signal
2013-11-27 14:49:54 +01:00
Peter Åstrand (astrand) 611da86353 Merge commit 'a7fa97f0e14926cc4433483fcb7581e0b3782140'
* commit 'a7fa97f0e14926cc4433483fcb7581e0b3782140':
  WebSocketProxy: support non path target_cfg
2013-11-27 13:41:00 +01:00
Peter Åstrand (astrand) fb4ac5ae51 Merge commit 'edde5cd0ff6059bddae10c9b9faf0b3e8f388a9e'
* commit 'edde5cd0ff6059bddae10c9b9faf0b3e8f388a9e':
  Fixed wiki reference
2013-11-27 13:38:03 +01:00
Peter Åstrand (astrand) 063c7de783 Merge commit 'bff3c373b32ebf707085e3c677bfad19b44fa054' 2013-11-27 13:34:17 +01:00
Peter Åstrand (astrand) 08d37e0deb Merge commit 'f30ad05c70ab2a43c9078e2f79da40f1dc0c60ec'
* commit 'f30ad05c70ab2a43c9078e2f79da40f1dc0c60ec':
  Fix #97: rebind.so not found when installed
2013-11-27 13:33:30 +01:00
Peter Åstrand (astrand) ff30e5461f Merge commit 'ab389d4e7114d7ddbfd085591d336ea5cc06c00d'
* commit 'ab389d4e7114d7ddbfd085591d336ea5cc06c00d':
  Collect zombie child processes within server loop
2013-11-27 13:30:30 +01:00
Peter Åstrand (astrand) 4071291fa9 Merge commit '264f8fdd7f12bd5b9f6813fb8de81c55b6328d9b'
* commit '264f8fdd7f12bd5b9f6813fb8de81c55b6328d9b':
  Update to version 0.5.1
2013-11-27 13:30:22 +01:00
Peter Åstrand (astrand) 611effd83b Merge commit '36cb8f4676c7b5ff34bd22ad729e00e77efb6f00'
* commit '36cb8f4676c7b5ff34bd22ad729e00e77efb6f00':
  Move javascript websockify files to other/js
2013-11-27 13:30:13 +01:00
Peter Åstrand (astrand) b63d197292 Merge commit '36fcd5784fa0825eedbf31d91bc42c970605ddb4'
* commit '36fcd5784fa0825eedbf31d91bc42c970605ddb4':
  Add package file for websockify.js
2013-11-27 13:30:05 +01:00
Peter Åstrand (astrand) cf901ddac5 Merge commit '46450577c2fb119fc5bf0ac09664f22651a080d0' 2013-11-27 13:29:54 +01:00
Peter Åstrand (astrand) 122985bd67 Merge commit 'd3ba23fa64d79eeb602ff1015ec31014fd8e9b35'
* commit 'd3ba23fa64d79eeb602ff1015ec31014fd8e9b35':
  Update to c0855c6cae of web-socket-js
2013-11-27 13:29:45 +01:00
Peter Åstrand (astrand) 19c8482236 Merge commit '6d27b5d321978586ea1601f757ead73dfba03da7'
* commit '6d27b5d321978586ea1601f757ead73dfba03da7':
  Add 2 arguments to websockify.WSRequestHandler

As of now, only implemented the first command; see #83 for details.
2013-11-27 13:27:38 +01:00
Peter Åstrand (astrand) 33a1bd2337 Merge commit 'b7f255ce0b21dc42189205b1f0e46b4f1d9854f9'
* commit 'b7f255ce0b21dc42189205b1f0e46b4f1d9854f9':
  Clarify messages when optional modules are not found.
2013-11-27 12:27:43 +01:00
Peter Åstrand (astrand) 5f8cb96bab Merge commit '477947ba96a00032ae35ac55fd02a4a5f485497e'
* commit '477947ba96a00032ae35ac55fd02a4a5f485497e':
  Remove wsproxy references. Sync launch.sh from noVNC.
2013-11-27 12:27:31 +01:00
Peter Åstrand (astrand) 6ddfaa86ae Merge commit '33e9a21ce4e38d52f43fe775fb154fc71c09c827'
* commit '33e9a21ce4e38d52f43fe775fb154fc71c09c827':
  Add gimite/web-socket-js submodule for DFSG compliance.
2013-11-27 12:26:53 +01:00
Peter Åstrand (astrand) 4dc7c0da7a Merge commit '2d078e8cd83735dad7d98fadcece652d93e2a037'
* commit '2d078e8cd83735dad7d98fadcece652d93e2a037':
  correctly include include directory in egg.
2013-11-27 11:36:47 +01:00
Joel Martin 34a1b68d79 Clarify ssl module build for old python versions. 2013-11-20 07:30:23 -06:00
Joel Martin a04edfe80f Merge pull request #105 from dosaboy/topic/unit-test-cleanup
Added temp dir for unit test data and cleanup
2013-11-20 05:21:18 -08:00
Edward Hope-Morley 32c1abd5d9 Added temp dir for unit test data and cleanup
Unit test data will now go to a temporary dir that will be deleted
once the test completes. The unit tests also setup a logger which
will persist so that it can be inspected once tests complete.

Also fixes a bug where instance var is missing from decode_hybi()

Co-authored-by: natsume.takashi@lab.ntt.co.jp
2013-11-14 12:38:03 +00:00
Joel Martin a47be21f9f Merge pull request #94 from dosaboy/topic/add-unit-tests
Added unit tests for websocket and websocketproxy
2013-10-29 09:44:48 -07:00
Edward Hope-Morley 5e0ff7d855 Added unit tests for websocket and websocketproxy
To run the unit tests just run tox from the top
level directory which will try to run unit tests
for most versions of python. Requires tox to be
installed. To run tox for a specifice env, run
tox -e<env> e.g. for python 2.7 run 'tox -epy27'.

Co-authored-by: natsume.takashi@lab.ntt.co.jp
2013-10-29 16:19:39 +00:00
Joel Martin c3acdc2e38 Merge pull request #93 from dosaboy/topic/set-keepalive-options
Adds optional TCP_KEEPALIVE to WebSocketServer
2013-10-29 08:18:53 -07:00
Joel Martin 13c99bcf05 Fix search path construction in tests.
This way the tests can be run directly from within the tests directory
and they might also have a chance of running on Windows (where the
path separator is different).
2013-10-29 09:41:26 -05:00
Samuel b4e0b534d5 Merge pull request #104 from stevschmid/patch-1
Fix crash when module is missing
2013-10-24 04:22:44 -07:00
Steven Schmid be4119f84f Fix crash when an import is missing
self.msg is not available when checking the imports.

I had the problem on a host where numpy is missing (python 2.7.3).
2013-10-24 11:42:49 +02:00
Solly 32b0567343 Merge pull request #103 from dellsystem/master
Fix syntax errors in other/websockify.rb
2013-10-21 18:29:45 -07:00
dellsystem 82fe329129 Fix syntax errors in other/websockify.rb 2013-10-21 20:59:16 -04:00
Solly 0e5c3ecfda Merge pull request #102 from DirectXMan12/master
Handle SIGCHLD properly for multiprocessing (fixes #101)
2013-10-21 17:16:09 -07:00
Solly Ross 53f1f1989e Handle SIGCHLD properly for multiprocessing
This commit should fix #101 by enabling a special SIGCHLD
handler for when multiprocessing is in use.  The handler
simply calls `multiprocessing.active_children()` (which in
turn calls `_cleanup()`) upon receiving a SIGCHLD.  Now,
the `fallback_SIGCHLD` is only called when `multiprocessing`
is not in use.  See also #95.
2013-10-21 16:52:42 -04:00
Edward Hope-Morley 081046b6cd Adds optional TCP_KEEPALIVE to WebSocketServer
TCP_KEEPALIVE is now enabled by default. Settings for
KEEPCNT, KEEPINTVL and KEEPIDLE can be supplied when
creating WebSocketServer and KEEPALIVE can also be
disabled if required.

Also adds new unit test for testing.

Co-authored-by: natsume.takashi@lab.ntt.co.jp
2013-10-16 11:24:21 +01:00
Joel Martin a61ae52610 Merge pull request #86 from chrislee35/master
OpenSSL Support for Ruby Module of Websockify
2013-10-15 10:32:57 -07:00
Joel Martin 477dce6cf8 Merge pull request #100 from alonbl/log
websocket: use python logging module
2013-10-15 09:41:35 -07:00
Alon Bar-Lev 8a0a47223d websocket: use python logging module
WebSocketServer is a library module, as such, it cannot assume it can
write output to process stdout.

Python logging module is designed in order to allow subscribers to
handle the output out of modules. It is simple and generic mechanism to
separate between data producer and data handling.

Python logging API also has the nature of log level, so the verbose
parameter can probably be obsoleted in favor of logging level. And of
course the logging API has built in support for exception tracebacks, no
need for manual format.

Per upstream request a wrapper is created around python logging to
enable shorter statements and optional replacement.

Add --traffic parameter for traffic specific debug, this is required as
it uses direct unformatted stdout output.

Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-15 19:18:17 +03:00
Alon Bar-Lev 1f798214de websocket: fix exception statement introduced by comment 903e3f06ee557
Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-15 19:18:17 +03:00
Joel Martin 4459824cc8 Merge pull request #98 from alonbl/apicleanup
Minor API cleanups
2013-10-14 13:12:40 -07:00
Alon Bar-Lev c2a40d6900 websocket: do not exit at the middle of process
WebSocketServer is a library module, as such it should not exit process
but return from a method, allowing the caller to execute process show
down.

Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-14 22:17:19 +03:00
Joel Martin a7fa97f0e1 Merge pull request #99 from alonbl/targetconfig
WebSocketProxy: support non path target_cfg
2013-10-14 11:38:05 -07:00
Alon Bar-Lev 1190fe1204 websocket: restore signals after processing
WebSocketServer is a library module, as such it should try to restore state
after processing, to allow caller to resume normal operation.

Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-14 21:13:11 +03:00
Alon Bar-Lev 7026e26d68 websocket: support SIGTERM as exit signal
Similar to SIGINT that is already supported, support SIGTERM in daemon
and non daemon modes.

Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-14 21:01:44 +03:00
Alon Bar-Lev 37e40a78f2 WebSocketProxy: support non path target_cfg
The WebSocketProxy class is usable for creating derived applications
with different logic, especially for the target validation.

Current code assumes that target is a path while in other implementation
it can be object that is loaded at initialization.

This change moves the conversion to absolute path into main function, so
that the WebSocketProxy class will not make that assumption.

Signed-off-by: Alon Bar-Lev <alon.barlev@gmail.com>
2013-10-14 20:31:24 +03:00
Samuel edde5cd0ff Merge pull request #91 from MartinF/master
Fixed wiki reference
2013-10-09 06:50:08 -07:00
Joel Martin bff3c373b3 Merge pull request #96 from DirectXMan12/master
Enable Process Reaping in All Conditions
2013-09-27 06:05:55 -07:00
Joel Martin f30ad05c70 Fix #97: rebind.so not found when installed
This should fix the upstream Debian bug:
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=719889
2013-09-27 07:42:57 -05:00
directxman12 354dd6b0a2 Enable Process Reaping in All Conditions
Process reaping via the SIGCHLD handler is now enabled in all
circumstances, instead of just when os.fork is being used.

Fixes #95
2013-09-20 14:12:35 -04:00
Martin From e06de83295 Fixed wiki reference 2013-09-09 11:32:57 +02:00
Joel Martin ab389d4e71 Merge pull request #85 from dillaman/master
Collect zombie child processes within server loop
2013-07-02 10:55:53 -07:00
chrislee35 eee946d2cf fixed 1.8 compatibility bug for OpenSSL::SSL::SSLSocket#read_nonblock vs #readpartial
tested in 1.8 and 2.0
2013-07-02 22:16:01 +09:00
chrislee35 73af324a3a adding SSL support and Ruby1.9 support 2013-07-02 21:38:22 +09:00
Jason Dillaman 832118e61a Collect zombie child processes within server loop 2013-06-28 15:22:35 -04:00
Joel Martin 264f8fdd7f Update to version 0.5.1
*** NOTE ***

This version of websockify no longer supports the old Hixie protocol.
2013-06-27 16:35:54 -05:00
Joel Martin 36cb8f4676 Move javascript websockify files to other/js 2013-06-27 16:29:17 -05:00
Joel Martin 36fcd5784f Add package file for websockify.js 2013-06-27 16:29:17 -05:00
Joel Martin 46450577c2 Use upstream einaros/ws 0.4.27 with sub-protocol support.
Also, some connection error handling.
2013-06-27 16:29:17 -05:00
Joel Martin d3ba23fa64 Update to c0855c6cae of web-socket-js
Update both the submodule and the swf build. The submodule now
contains the unobfuscated source for swfobject.js which should make
websockify more DFSG compliant.
2013-06-27 16:29:17 -05:00
Joel Martin 6d27b5d321 Merge pull request #84 from nttdatainc-openstackers/master
Fix for issue #83
2013-06-27 14:01:07 -07:00
NTT Data OpenStackers 888e75a8fb Add 2 arguments to websockify.WSRequestHandler
This patch adds 2 arguments to websockify.WSRequestHandler for security:

* file_only: returns 404 response if non-file contents are requested.
             Required to disable directory listing.

* no_parent: returns 403 response if contents out of the web root are
             requested. Required to disable directory traversal.
2013-06-24 06:09:01 -07:00
Joel Martin b7f255ce0b Clarify messages when optional modules are not found. 2013-04-19 17:14:20 -05:00
Joel Martin 477947ba96 Remove wsproxy references. Sync launch.sh from noVNC. 2013-04-15 12:22:08 -05:00
Joel Martin 33e9a21ce4 Add gimite/web-socket-js submodule for DFSG compliance. 2013-04-12 08:24:07 -05:00
Joel Martin 2d078e8cd8 Merge pull request #76 from SlapOS/include
correctly include include directory in egg.
2013-04-03 09:44:37 -07:00
Cédric de Saint Martin d6d960dbe5 correctly include include directory in egg. 2013-04-03 12:12:37 +02:00
Peter Åstrand (astrand) ed109d7ec8 Fix Python3 compatibility when using --libserver. 2013-03-20 15:09:58 +01:00
Peter Åstrand (astrand) bc917863e0 Improved class documentation. 2013-03-20 13:30:16 +01:00
Peter Åstrand (astrand) e964c1edff Let our ProxyRequestHandler be default. This allows you to inherit
from WebSocketProxy without having to specify handler class.
2013-03-20 11:34:46 +01:00
Peter Åstrand (astrand) f5e42ff6f4 Move WebSocketProxy class so that it is defined after the
requesthandler. Removed comments about above/below which does not make
sense any longer. No functional changes.
2013-03-20 11:30:38 +01:00
Peter Åstrand (astrand) f594d70daf Removed unused import of SimpleHTTPRequestHandler. 2013-03-20 11:00:34 +01:00
Peter Åstrand (astrand) d0608a63b6 Make echo.py and load.py work again, after the refactoring of
websocket.py:

* With echo.py, which doesn't need any server configuration, we can
  just switch over our application class to inherit from
  WebSocketRequestHandler instead of WebSocketServer. Also, need to
  use the new method name new_websocket_client.

* With load.py, since we have the "delay" configuration, we need both
  a server class and a request handler.

Note that for both tests, I've removed the raising of
self.EClose(closed). This is incorrect. First of all, it's described
as "An exception before the WebSocket connection was established", so
not suitable for our case. Second, it will cause send_close to be
called twice. Finally, self.EClose is now in the WebSocketServer
class, and not a member of the request handler.
2013-03-20 10:03:04 +01:00
Peter Åstrand (astrand) 09f3ec7125 Rename self.client to self.request, ie adapt to:
>commit b9e1295f7a
>    Prepare for solving https://github.com/kanaka/websockify/issues/71:
>
>    Rename self.client to self.request, since this is what standard
>    SocketServer request handlers are using.
2013-03-20 09:03:18 +01:00
Peter Åstrand (astrand) 95593ac4bf Merge branch 'master' of github.com:astrand/websockify 2013-03-18 14:59:03 +01:00
Peter Åstrand (astrand) 1eecc7b17a Merge remote branch 'upstream/master' 2013-03-18 14:50:46 +01:00
Peter Åstrand (astrand) b05b773bd7 Corrected last commit. 2013-03-18 13:25:53 +01:00
Peter Åstrand (astrand) debc926612 Renamed CustomProxyServer to WebSocketProxy; this was the earlier name.
Also, call the server instance "server", not "httpd", even when using
LibProxyServer.
2013-03-18 13:22:48 +01:00
Peter Åstrand (astrand) 70eb75a3e6 Fix error with modern Python 2.X versions:
TypeError: exceptions must be old-style classes or derived from
BaseException, not str

Thus, we are not allowed to raise a string. Raise Exception instead.
2013-03-18 12:04:50 +01:00
Joel Martin 903198a724 tests: use new module path and remove Hixie code. 2013-03-14 12:24:58 -05:00
Peter Åstrand (astrand) 7b3dd8a6f5 Try to solve https://github.com/kanaka/websockify/issues/71 by
refactoring. Basically, we are dividing WebSocketServer into two
classes: One request handler following the SocketServer Requesthandler
API, and one optional server engine. The standard Python SocketServer
engine can also be used.

websocketproxy.py has been updated to match the API change. I've also
added a new option --libserver in order to use the Python built in
server instead.

I've done a lot of testing with the new code. This includes: verbose,
daemon, run-once, timeout, idle-timeout, ssl, web, libserver. I've
tested both Python 2 and 3. I've also tested websocket.py in another
external service.

Code details follows:

* The new request handler class is called WebSocketRequestHandler,
  inheriting SimpleHTTPRequestHandler.

* The service engine is called WebSocketServer, just like before.

* do_websocket_handshake: Using send_header() etc, instead of manually
  sending HTTP response.

* A new method called handle_websocket() upgrades the connection to
  WebSocket, if requested. Otherwise, it returns False. A typical
  application use is:

    def do_GET(self):
        if not self.handle_websocket():
	   # handle normal requests

* new_client has been renamed to new_websocket_client, in order to
  have a better name in the SocketServer/HTTPServer request handler
  hierarchy.

* Note that in the request handler, configuration variables must be
  provided by the "server" object, ie self.server.target_host.
2013-03-14 16:07:40 +01:00
Peter Åstrand (astrand) 208f83b9a2 Prepare for fixing https://github.com/kanaka/websockify/issues/71:
* Move traffic_legend.

* Since websocket.WebSocketServer.socket is static, don't call it with
  self.socket.
2013-03-14 16:00:11 +01:00
Peter Åstrand (astrand) 4e3388964a Prepare for fixing https://github.com/kanaka/websockify/issues/71:
Move around functions and methods, so that connection-related and
server-related stuff are close together.

This patch just moves things around - it does not change anything at
all. This can be verified with:

git diff websocket.py | grep ^- | cut -c 2- | sort > removed
git diff websocket.py | grep ^+ | cut -c 2- | sort > added
diff -u removed added
2013-03-14 15:50:49 +01:00
Peter Åstrand (astrand) b9e1295f7a Prepare for solving https://github.com/kanaka/websockify/issues/71:
Rename self.client to self.request, since this is what standard
SocketServer request handlers are using.
2013-03-14 15:23:44 +01:00
Joel Martin d3865688c8 README: no longer supporting Hixie.
NOTE: Hixie support is in version 0.4.X. If necessary, I will branch
and apply critical fixes release a new 0.4.X tagged version.
2013-03-12 13:54:11 -05:00
Joel Martin 5a726c2e4d Merge pull request #70 from astrand/master
Remove support for old Hixie protocols
2013-03-12 11:52:30 -07:00
Joel Martin db6a7e3e28 Update to version 0.4.1
*** IMPORTANT NOTE ***

0.4.X is the last minor version to support the Hixie protocol. 0.5.0
will drop the Hixie protocol support. The latest released version of
all major browsers (and web-socket-js) support the IETF 6455 protocol.

- Other changes: add 'include/' sub-dir to source distribution and to
  installed packaged.
2013-03-12 13:46:45 -05:00
Joel Martin d9aedfe7d3 Update to version 0.4.0
*** IMPORTANT NOTE ***

This is the last minor version to support the Hixie protocol. I may do
bug fixes (e.g. 0.4.1) but 0.5.0 will drop the Hixie protocol support.
The latest released version of all major browsers (and web-socket-js)
support the IETF 6455 protocol.

- Other changes: updating TODO and release process notes.
2013-03-12 13:10:53 -05:00
Peter Åstrand (astrand) b2fe57c950 Remove support for old Hixie protocols.
Hybi protocols 7 and 8 are still supported, in addition to
protocol 13 -  RFC 6455.
2013-03-04 09:38:29 +01:00
Joel Martin 805026360e Use Buffer base64 support instead of pkrumins/node-base64 2013-02-19 13:12:10 -06:00
Joel Martin 3d37d21b5f Update to version 0.3.0 and upload to pypi.
Update to 0.3.0 and document uploading to pypi.python.org.

Install thus:
    sudo pip install websockify
2013-01-15 11:49:08 -06:00
Joel Martin c33f0b52e7 In Opera 12.10, set binaryType earlier.
Otherwise, the first message received may be blob depending on timing.
2012-11-14 12:24:08 -05:00
Joel Martin 1d795c0643 Pull include/util.js from noVNC.
Pull version 51562999c7 load_scripts fixes.
2012-11-01 11:07:26 -05:00
Joel Martin c00c0eed1a websocket.py: close lsock on stop.
If WebSocketServer is used as a library with run_once or timeout, then
cleanup the socket listener socket so that when start_server returns
(due to run_once or timeout) then port is freed up.
2012-10-31 12:04:16 -05:00
Joel Martin 47fb367486 websocketproxy.py: fix for python2.4
Thanks to https://github.com/WhiteRavenTechnology for catching this.
2012-10-30 08:34:47 -05:00
Joel Martin 5e16b38524 websocketproxy.py: put client socket handling first.
Should at least mostly address this issue:
https://github.com/kanaka/websockify/issues/63

The problem is that the target in the test case often immediately
closed the target after sending the data. In this case the last data
received from the target is never sent to the client because the
client is not in the list of sockets being selected against (because
it is only added if their is pending data). By moving the client
conditionals first, we give the client socket a chance to be sent data
before the target is detected as close and we terminate the loop.
2012-10-29 18:12:54 -05:00
Joel Martin c728f43c63 README.md: clarify binary vs base64 negotiation. 2012-10-29 17:17:56 -05:00
Joel Martin d1458d0063 websocketproxy.py: better missing token exception. 2012-10-29 17:05:23 -05:00
Joel Martin 66d772a6bc Ignore other/node_modules directory (node.js modules). 2012-10-29 17:04:39 -05:00
Joel Martin a670af376e websockify.js: add encrypted HTTPS/WSS support.
This works a bit differently than python websockify implementation
since the server either runs in HTTP or in HTTPS and both web and
websocket servers only support the same mode. Specifying the --cert
parameter activates encrypted HTTPS/WSS mode.
2012-10-29 16:59:46 -05:00
Joel Martin 16691395e0 websock.js: use iteration to receive binary data
Instead of using apply with the Uint8Array to push the data onto the
receive queue, iterate through the binary data and push it an element
at a time. Apparently, doing an apply with a very large binary array
can blow the stack. Performance-wise this seems equivalent in Chrome
22 and Firefox 16.
2012-10-26 18:01:35 -05:00
Joel Martin 3018cf8c1a websockify.js: executable, fix usage statement. 2012-10-26 18:01:05 -05:00
Joel Martin 6a126405bd websockify.js: binary data, better logs and err handling.
This version requires a patched version of einaros/ws that can be
found here: https://github.com/kanaka/ws You can use the patched
version like this:

    cd websockify/other
    git clone https://github.com/kanaka/ws
    npm link ./ws

Once the upstream 'ws' module supports subprotocol negotiation then
this will no longer be necessary.

Changes:
- Adds support for binary data and subprotocol negotiation of 'base64'
  vs 'binary' with 'binary' preferred if the client offers it.
- Add client address to log messages.
- Close the target when the client closes.
- Catch errors when we try and send to a client that is no longer
  connected.
2012-10-26 16:08:47 -05:00
Joel Martin ee2f269c06 websocket.py: fix recording and refactor unmask.
Fix recording so that it records the actual payload bytes sent to the
client.  This means that if the client and server and using base64
encoding then the captured data will still be base64 encoded. However,
data received from the client is unmasked when recorded. Note that
this is not done efficiently; when recording, client data is unmasked
twice (once for sending on to the target and once for recording). This
could be made more efficient but that would require a refactor of how
frame reception and unmasking works and recording is not considered
a performance sensitive mode.
2012-10-17 11:59:03 -05:00
Joel Martin 780cb8a9e6 util.js, websock.js: sync with noVNC - dynamic script loading.
Sync with noVNC ad29479ca90f9. Use new dynamic script loading
mechanism in util.js.

window.onscriptsload is called when dynamic scripts have loaded
(window.onload fires too early in IE 9).
2012-10-17 11:59:03 -05:00
Joel Martin 388d9573fd Merge pull request #65 from lovelysystems/master
updated version in setup.py
2012-10-15 14:04:27 -07:00
Joel Martin 578dba1987 include/*util.js: update to versions from noVNC. 2012-10-15 16:02:38 -05:00
Joel Martin 124c9a7d88 Change include/websock.js (and deps) to MPL 2.0 license.
The MPL 2.0 license is a "file-level" copyleft license vs the
"project-level" nature of the L/GPL. The intention of the websock.js
file has always been that it should be easy to incorporate into
existing projects and sites whether free/open or
proprietary/commercial. The MPL 2.0 is designed for this sort of
combination project but still requires that any distributed
modifications to noVNC source files must also be published under the
same license.

In addition, the MPL 2.0 allows the code to be used in L/GPL projects
(the secondary license clause). This means that any projects that are
already incorporating noVNC should not be impacted by this change and
in fact it should clarify the licensing situation (the exact
application of the L/GPL to web applications and interpreted code is
somewhat ambiguous).

The dependencies on include/websock.js are also updated to MPL 2.0
including util.js and webutil.js. The base64.js has been updated to
the MPL 2.0 licensed version from Mozilla.

The websockify python code (and other implementations) remain under
a LGPLv3 license.
2012-10-15 16:02:38 -05:00
Jodok Batlogg e53fa10397 updated version 2012-10-15 22:06:50 +02:00
Joel Martin 1295668abb Merge pull request #64 from vishvananda/fix-import
In websockify/__init__.py import everything from websocketproxy and websocket
2012-09-26 07:35:05 -07:00
Vishvananda Ishaya 880257a431 Import everything in websocketproxy and websocket
Old users of the websockify library used websockify.WebSocketProxy
The refactor into a module unneccessarily broke this import. The
current imports in __init__.py don't actually do anything so this
patch changes the import to import everything so that the old import
still works. This appears to be the original intention of the existing
import statements.
2012-09-25 22:24:10 -07:00
Joel Martin 8b3125bcf3 other/websockify.js: arg cleanup, remove deprecated call.
Change deprecated path.exists call to fs.exists.

Confirmed that this runs with node v0.8.9
2012-09-21 08:19:40 -05:00
Joel Martin 471b504799 Merge branch 'master' of github.com:kanaka/websockify 2012-09-21 07:32:54 -05:00
Joel Martin b713acbeff Merge pull request #59 from dosht/master
--unix-target option breaks argument sanity check
2012-09-21 05:31:47 -07:00
Joel Martin ff736e9ad3 Fix issue #60: not all arguments converted
https://github.com/kanaka/websockify/issues/60

String formatting issue with wrapped cmds.
2012-09-21 07:28:08 -05:00
Joel Martin 384c2772fb Merge pull request #61 from vishvananda/fix-popen
Reset SIGPIPE handler when calling Popen

References:

http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html

http://bugs.python.org/issue1615376

http://bugs.python.org/issue1652
2012-09-21 05:24:50 -07:00
Joel Martin e6d8d8f242 Gracefully handle errors when popping kwargs.
https://github.com/kanaka/websockify/pull/53
2012-09-21 07:10:34 -05:00
Vishvananda Ishaya ca8efbf657 Reset SIGPIPE handler when calling Popen
Python ignores SIGPIPE on startup, because it prefers to check every
write and raise an IOError exception rather than taking the signal. Most
Unix subprocesses don't expect to work this way. This patch (adapted
from Colin Watson's post at http://tinyurl.com/2a7mzh5) sets SIGPIPE
back to the default action.
2012-09-20 07:46:04 -07:00
mostafa sameh eb6d4e183b Fix --unix-target option 2012-09-18 09:20:39 +02:00
Joel Martin 6d9deda9c5 Pull upstream web-socket-js with IETF 6455 support.
Pull in web-socket-js 7677e7a954.

The biggest change of note is that this updates web-socket-js to IETF
6455 rather than the previous Hixie protocol.
2012-09-17 16:50:46 -05:00
Joel Martin 96890fab97 include/websock.js: cleanup and fix protocol list check.
If a protocol list is specified and we don't support binary WebSockets
then strip binary from the list and check the list to make sure there
is still an option left.
2012-09-17 16:49:36 -05:00
Joel Martin c0d23e27e4 Refactor into python modules: websocket, websocketproxy
Make websockify subdirectory and move websocket.py ->
websockify/websocket.py and websockify ->
websockify/websocketproxy.py. Create a ./run script that launches
websockify as before (unfortunately can't have a websockify script at
the same level since this is now a directory). Make websockify.py
a symlink to ./run. Once the package is installed, the main launch
script will be /usr/bin/websockify.

This makes it easier to package up websockify as a python module.
setup.py should now properly install websockify as a module.

Note that to include the base websocket module/class you will now do:

    import websockify.websocket
    #OR
    from websockify.websocket import WebSocketServer

To import the full websocket proxy functionality:

    import websockify.websocketproxy
    #OR
    from websockify.websocket import WebSocketProxy

This will also help with startup speed slightly because the code in
websocketproxy will now be byte compiled since it is no longer in the
main invocation script.
2012-09-17 14:06:51 -05:00
Joel Martin 2d2798954e websockify version 0.2.0 2012-09-17 12:56:34 -05:00
Joel Martin 44e5fa0b82 Merge pull request #56 from AricStewart/master
Implement --idle-timeout for issue #55
2012-09-06 20:02:04 -07:00
Aric Stewart 82cb31f3b6 change --idle-timeout to reset the idle timeout when active 2012-08-31 10:55:24 -05:00
Aric Stewart 9348dd5208 Implement option --idle-timeout
server exits after TIMEOUT seconds if there are no active connections
2012-08-31 09:24:09 -05:00
Joel Martin 17175afd73 websock.js: make protocols parameter of open().
If no protocols are selected then defaults to ['binary', 'base64'] (or
just 'base64' if there is not full binary type support.

Checks to make sure binary types are fully supported and throws an
exception if they are requested but not supported.
2012-08-16 13:28:28 -05:00
Joel Martin 53b074e8e7 websock.js: simplify rQshiftStr with apply(). 2012-08-16 13:27:50 -05:00
Joel Martin 068065e465 Simpler (but working) binary support.
Instead of trying to handle the receive queue as a typed array, just
replace the base64 encode/decode with conversion from/to typed arrays
and handle the receive and send queue as before (plain Javascript
arrays).

There is a lot of opportunity here for optimization of course, but for
now it's more important that it work properly.
2012-08-16 12:21:21 -05:00
Joel Martin 376872d993 Use WebSocket binary data (no base64 enc/dec) if available.
If typed arrays (arraybuffers) are available and the WebSocket
implementation supports them, then send and receive direct binary
frames and skip base64 encode/decode. Otherwise we just fallback to
the current method of sending base64 encoded strings (with a couple of
extra checks for mode in the send/receive path).

The check for binaryType support in WebSocket is a collosal hack right
now due to the fact that the 'binaryType' property doesn't exist on
the WebSocket prototype. So we have to create a connection to
a localhost port in order to test.

A potentionally big performance boost could probably be achieved by
re-using a larger typed array for storing the data instead of creating
a typed array every time we receive a message.
2012-08-14 15:19:38 -05:00
Joel Martin f55362ff07 websock.js: jslint comments, debug comments, copyright date 2012-08-14 15:18:14 -05:00
Joel Martin 36bdb09630 More verbosity about who closed the connection. 2012-08-14 15:14:00 -05:00
Joel Martin d575e571fd Fix split out handshake parsing for Hixie.
Need the scheme to be accessible when constructing the response for
Hixie clients.
2012-08-14 15:12:48 -05:00
Joel Martin 00e9d3bf29 websockify.rb: correct comment, no SSL support yet. 2012-07-24 09:31:26 -05:00
Joel Martin 26e8095244 websockify: rename config opt, fix config dir reading 2012-07-13 13:17:56 -05:00
Joel Martin e4b9d510f1 websocket.py: split out handshake parsing.
This makes it easier to use the websocket code in a WSGI script in the
future.
2012-07-13 13:09:32 -05:00
Joel Martin 65e96176cb websockify: change cfg file syntax and clean up parsing.
Config file syntax is now like this:

----------------------
    # Comments and blank lines are allow
    token1: host1:port1

    token2: host2:port2
----------------------
2012-07-12 19:34:27 -05:00
Joel Martin 52beba8695 Move target-list processing to websockify.
websocket.py has no concept of target/proxy so any target processing
should happen in websockify itself.

Also:

- remove URL parsing imports from websocket.py since they are not
  needed with SimpleHTTPRequestHandler doing the parsing.

- read the absolute path of the target_list file on startup since the
  --web option will change directories if set.
2012-07-12 19:10:12 -05:00
Hector Sanjuan e17e1158d8 Enable multiple targets in websockify by passing a target-list configuration file.
The --target-list option is used to pass a configuration file on websockify start.

This file contains entries in the form host:port:token, one per line.

When a new connection is open (from noVNC for example), the url should look like:

ws://websockify_host:websockify_port/?token=ABCD

The token is then extracted and checked against the entries in the configuration file.

When the token matches, the connection is proxied to the host:port indicated in the file.

If the configuration file is a folder, then all the entries from files in it are read.
2012-07-11 16:00:13 +02:00
Joel Martin 2a8df6327e Merge pull request #49 from libricoleur/master
Better support of IPv6
2012-06-26 07:40:38 -07:00
Alexandre Sicard 67d4c17516 Allow preference of IPv6 for source_addr
Add the option "-6, --prefer-ipv6". When set, the 'prefer_ipv6' flag in
websocket.py is used so that 'source_addr' is resolved to IPv6 if
available. If 'source_addr' is not set, binds to [::].
2012-06-26 13:57:50 +02:00
Alexandre Sicard 1f5b492e10 Support IPv6 brackets notation
Parse square brackets-enclosed IPv6 for 'source_addr' and 'target_addr'
parameters.
2012-06-26 13:55:19 +02:00
Joel Martin cb925eb486 doc/websockify.1: add man page for websockify command.
Mostly it is based on the project README.md but with some tweaks and
extra content removed.

Thanks for Adam Young <ayoung@redhat.com> for the original version of
this.

Also a decent man format reference is here:
http://www.fnal.gov/docs/products/ups/ReferenceManual/html/manpages.html
2012-06-22 18:40:11 -05:00
Joel Martin e295098330 Fix wrap mode when used with --web option.
- The --web option changes directory so the wrap mode needs to get an
  absolute path to the rebinder.
- Also, use long instead of int in rebind.c so avoid compile warnings.
2012-05-31 09:20:01 -05:00
Joel Martin cddc1613ff Fixup bugs from merge (pull #46).
- Rename unix socket option to '--unix-target' to be consistent with
  '--ssl-target' which is an analogous switch.
- Fix normal socket target mode which was broken after merge.
- Normalize/fix output for SSL, unix socket and wrap command modes.
2012-05-31 09:17:51 -05:00
Joel Martin 233b622e47 Merge pull request #46 from cloud9ers/master
Adding support for unix sockets as target socket.
2012-05-30 07:22:29 -07:00
Karim Allah Ahmed c7ba8c7826 Merge with kanaka's mainstream master branch 2012-05-28 13:09:07 +02:00
Joel Martin 89d2c92474 Move SSL target support into websocket.py.
This is cleanup related to:
https://github.com/kanaka/websockify/pull/45
2012-05-23 09:20:08 -05:00
Joel Martin d24f474362 Merge pull request #45 from d4nshields/master
feature from issue #8: Support connecting to SSL/TLS target socket
2012-05-23 06:47:14 -07:00
Karim Allah Ahmed f3054df53a Adding TCP_NODELAY to the source sockets 2012-05-22 16:49:00 +02:00
Karim Allah Ahmed c8018f29c9 Adding support for proxying from a unix socket 2012-05-22 16:09:07 +02:00
Daniel Shields 763d2d7c1c Feature: target_host is wrapped in SSL using --ssl-target option 2012-05-20 13:58:45 -04:00
42 changed files with 3153 additions and 1494 deletions
+5
View File
@@ -4,3 +4,8 @@
other/.lein-deps-sum
other/classes
other/lib
other/node_modules
.project
.pydevproject
target.cfg
target.cfg.d
+3
View File
@@ -0,0 +1,3 @@
[submodule "include/web-socket-js-project"]
path = include/web-socket-js-project
url = https://github.com/gimite/web-socket-js.git
+62 -2
View File
@@ -1,8 +1,68 @@
Changes
=======
0.1.0
-----------------
0.6.1 - May 11, 2015
--------------------
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
0.6.0 - Feb 18, 2014
--------------------
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
* Refactor to use standard SocketServer RequestHandler design
* Fix zombie process bug on certain systems when using multiprocessing
* Add better unit tests
* Log information via python `logging` module
0.5.1 - Jun 27, 2013
--------------------
* use upstream einaros/ws (>=0.4.27) with websockify.js
* file_only and no_parent security options for WSRequestHandler
* Update build of web-socket-js (c0855c6cae)
* add include/web-socket-js-project submodule to gimite/web-socket-js
for DSFG compliance.
* drop Hixie protocol support
0.4.1 - Mar 12, 2013
--------------------
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
* add include/ directory and remove some dev files from source
distribution.
0.4.0 - Mar 12, 2013
--------------------
* ***NOTE*** : 0.5.0 will drop Hixie protocol support
* use Buffer base64 support in Node.js implementation
0.3.0 - Jan 15, 2013
--------------------
* refactor into modules: websocket, websocketproxy
* switch to web-socket-js that uses IETF 6455
* change to MPL 2.0 license for include/*.js
* fix session recording
0.2.1 - Oct 15, 2012
--------------------
* re-released with updated version number
0.2.0 - Sep 17, 2012
--------------------
* Binary data support in websock.js
* Target config file/dir and multiple targets with token selector
* IPv6 fixes
* SSL target support
* Proxy to/from unix socket
0.1.0 - May 11, 2012
--------------------
* Initial versioned release.
+8 -3
View File
@@ -1,11 +1,16 @@
websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and
docs/LICENSE.LGPL-3) with the following exceptions:
include/base64.js : Choice of MIT 1.1, GPL-2 or LGPL-2.1
include/web-socket-js/ : New BSD license. Source code at
include/websock.js : MPL 2.0
include/base64.js : MPL 2.0
include/des.js : Various BSD style licenses
include/web-socket-js/ : New BSD license (3-clause). Source code at
https://github.com/gimite/web-socket-js
other/kumina.c : Simplified BSD license (2 clause).
Original source at
https://github.com/kumina/wsproxy
+2 -1
View File
@@ -1 +1,2 @@
include CHANGES.txt *.py README.md LICENSE.txt
include CHANGES.txt README.md LICENSE.txt
graft include
+43 -15
View File
@@ -8,14 +8,42 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
parses it, and then begins forwarding traffic between the client and
the target in both directions.
### News/help/contact
Notable commits, announcements and news are posted to
<a href="http://www.twitter.com/noVNC">@noVNC</a>
If you are a websockify developer/integrator/user (or want to be)
please join the <a
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
discussion group</a>
Bugs and feature requests can be submitted via [github
issues](https://github.com/kanaka/websockify/issues).
If you want to show appreciation for websockify you could donate to a great
non-profits such as: [Compassion
International](http://www.compassion.com/), [SIL](http://www.sil.org),
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
Foundation](https://www.eff.org/), [Against Malaria
Foundation](http://www.againstmalaria.com/), [Nothing But
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
### WebSockets binary data
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.
Starting with websockify 0.5.0, only the HyBi / IETF
6455 WebSocket protocol is supported.
Websockify negotiates whether to base64 encode traffic to and from the
client via the subprotocol header (Sec-WebSocket-Protocol). The valid
subprotocol values are 'binary' and 'base64' and if the client sends
both then the server (the python implementation) will prefer 'binary'.
The 'binary' subprotocol indicates that the data will be sent raw
using binary WebSocket frames. Some HyBi clients (such as the Flash
fallback and older Chrome and iOS versions) do not support binary data
which is why the negotiation is necessary.
### Encrypted WebSocket connections (wss://)
@@ -110,7 +138,7 @@ new (moved) port of the program.
The program wrap mode is invoked by replacing the target with `--`
followed by the program command line to wrap.
`./websockify 2023 -- PROGRAM ARGS`
`./run 2023 -- PROGRAM ARGS`
The `--wrap-mode` option can be used to indicate what action to take
when the wrapped program exits or daemonizes.
@@ -119,16 +147,17 @@ Here is an example of using websockify to wrap the vncserver command
(which backgrounds itself) for use with
[noVNC](https://github.com/kanaka/noVNC):
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
`./run 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
Here is an example of wrapping telnetd (from krb5-telnetd).telnetd
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:
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
`sudo ./run 2023 --wrap-mode=respawn -- telnetd -debug 2023`
The `wstelnet.html` page demonstrates a simple WebSockets based telnet
client.
client (use 'localhost' and '2023' for the host and port
respectively).
### Building the Python ssl module (for python 2.5 and older)
@@ -137,11 +166,10 @@ client.
`sudo aptitude install python-dev bluetooth-dev`
* Download, build the ssl module and symlink to it:
* At the top level of the websockify repostory, download, build and
symlink the ssl module:
`cd websockify/`
`wget http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
`wget --no-check-certificate http://pypi.python.org/packages/source/s/ssl/ssl-1.15.tar.gz`
`tar xvzf ssl-1.15.tar.gz`
+1 -1
View File
@@ -12,7 +12,7 @@ Installation
---------------------------
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:
https://github.com/kanaka/noVNC/wiki/Compiling-Websockify-to-Windows-Executable
https://github.com/kanaka/websockify/wiki/Compiling-Websockify-as-Windows-Executable
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
+373
View File
@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+3 -6
View File
@@ -1,9 +1,6 @@
- Go implementation
- Support multiple targets that are selected by the path line. Some
sort of wildcarding of ports too.
- Support SSL targets too.
- Rust implementation
- Add sub-protocol support to upstream einaros/ws module and use that
instead of the patched module.
- wstelnet: support CSI L and CSI M
+9 -2
View File
@@ -13,5 +13,12 @@ browser.
Building web-socket-js emulator:
cd include/web-socket-js/flash-src
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
cd include/web-socket-js/flash-src
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
Building release tarball:
- not really necessary since tagged revision can be downloaded
from github as tarballs
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
gzip websockify-${WVER}.tar
+11 -5
View File
@@ -1,7 +1,13 @@
- Update setup.py and CHANGES.txt and commit
- Update setup.py, CHANGES.txt and other/package.json and commit
- Create version tag and tarball from tag
WVER=0.1.0
git tag ${WVER}
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar
gzip websockify-${WVER}.tar
- Upload tarball to repo
git tag v${WVER}
git push origin master
git push origin v${WVER}
- Register with pypi.python.org (once):
python setup.py register
- Upload the source distribution to pypi
python setup.py sdist upload
- Register with npmjs.org (once)
- Upload websockify.js npmjs.org package
npm publish other/js
+110
View File
@@ -0,0 +1,110 @@
.TH websockify 1 "June 7, 2012" "version 0.3" "USER COMMANDS"
.SH NAME
websockify - WebSockets to TCP socket bridge
.SH SYNOPSIS
websockify [options] [source_addr:]source_port target_addr:target_port
websockify [options] [source_addr:]source_port \-\- WRAP_COMMAND_LINE
.SH OPTIONS
-h, --help show this help message and exit
-v, --verbose verbose messages and per frame traffic
--record=FILE record sessions to FILE.[session_number]
-D, --daemon become a daemon (background process)
--run-once handle a single WebSocket connection and exit
--timeout=TIMEOUT after TIMEOUT seconds exit when not connected
--cert=CERT SSL certificate file
--key=KEY SSL key file (if separate from cert)
--ssl-only disallow non-encrypted connections
--web=DIR run webserver on same port. Serve files from DIR.
--wrap-mode=MODE action to take when the wrapped program exits or
daemonizes: exit (default), ignore, respawn
.SH DESCRIPTION
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.
websockify was formerly named wsproxy and was part of the noVNC project.
.SH NOTES
.SS WebSockets binary data
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.
.SS Encrypted WebSocket connections (wss://)
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:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
.SS Websock Javascript library
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.
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.
The Websock API is documented on the websock.js API wiki page:
https://github.com/kanaka/websockify/wiki/websock.js
See the "Wrap a Program" section below for an example of using Websock and websockify as a browser telnet client (wstelnet.html).
.SS Additional websockify features
These are not necessary for the basic operation.
.IP *
Daemonizing: When the -D option is specified, websockify runs in the background as a daemon process.
.IP *
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).
.IP *
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.
.IP *
Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
.IP *
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.
.IP *
Wrap a program: see the "Wrap a Program" section below.
.SS Wrap a Program
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.
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.
The program wrap mode is invoked by replacing the target with -- followed by the program command line to wrap.
`./websockify 2023 -- PROGRAM ARGS`
The --wrap-mode option can be used to indicate what action to take when the wrapped program exits or daemonizes.
Here is an example of using websockify to wrap the vncserver command (which backgrounds itself) for use with noVNC:
`./websockify 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1`
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:
`sudo ./websockify 2023 --wrap-mode=respawn -- telnetd -debug 2023`
The wstelnet.html page demonstrates a simple WebSockets based telnet client.
.SH AUTHOR
Joel Martin (github@martintribe.org)
.SH SEE ALSO
https://github.com/kanaka/websockify/
https://github.com/kanaka/websockify/wiki/
+32 -65
View File
@@ -1,45 +1,8 @@
/*
* Modified from:
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla XML-RPC Client component.
*
* The Initial Developer of the Original Code is
* Digital Creations 2, Inc.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Martijn Pieters <mj@digicool.com> (original author)
* Samuel Sieb <samuel@sieb.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
/*jslint white: false, bitwise: false, plusplus: false */
/*global console */
@@ -47,35 +10,37 @@
var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
base64Pad : '=',
encode: function (data) {
"use strict";
var result = '',
chrTable = Base64.toBase64Table.split(''),
pad = Base64.base64Pad,
length = data.length,
i;
var result = '';
var toBase64Table = Base64.toBase64Table;
var base64Pad = Base64.base64Pad;
var length = data.length;
var i;
// Convert every three bytes to 4 ascii characters.
/* BEGIN LOOP */
for (i = 0; i < (length - 2); i += 3) {
result += chrTable[data[i] >> 2];
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += chrTable[data[i+2] & 0x3f];
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += toBase64Table[data[i+2] & 0x3f];
}
/* END LOOP */
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length%3) {
i = length - (length%3);
result += chrTable[data[i] >> 2];
result += toBase64Table[data[i] >> 2];
if ((length%3) === 2) {
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[(data[i+1] & 0x0f) << 2];
result += pad;
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[(data[i+1] & 0x0f) << 2];
result += base64Pad;
} else {
result += chrTable[(data[i] & 0x03) << 4];
result += pad + pad;
result += toBase64Table[(data[i] & 0x03) << 4];
result += base64Pad + base64Pad;
}
}
@@ -97,12 +62,12 @@ toBinaryTable : [
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var binTable = Base64.toBinaryTable,
pad = Base64.base64Pad,
result, result_length, idx, i, c, padding,
leftbits = 0, // number of bits decoded, but yet to be appended
leftdata = 0, // bits decoded, but yet to be appended
data_length = data.indexOf('=') - offset;
var toBinaryTable = Base64.toBinaryTable;
var base64Pad = Base64.base64Pad;
var result, result_length, idx, i, c, padding;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
var data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; }
@@ -111,9 +76,10 @@ decode: function (data, offset) {
result = new Array(result_length);
// Convert one by one.
/* BEGIN LOOP */
for (idx = 0, i = offset; i < data.length; i++) {
c = binTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === pad);
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === base64Pad);
// Skip illegal characters and whitespace
if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
@@ -134,6 +100,7 @@ decode: function (data, offset) {
leftdata &= (1 << leftbits) - 1;
}
}
/* END LOOP */
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
+79 -3
View File
@@ -1,7 +1,7 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* from noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
@@ -57,6 +57,21 @@ if (!Array.prototype.map)
};
}
//
// requestAnimationFrame shim with setTimeout fallback
//
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
/*
* ------------------------------------------------------
* Namespaced in Util
@@ -131,6 +146,8 @@ Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
}
} else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') {
if (!val) {
val = function () {};
@@ -190,6 +207,65 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
* Cross-browser routines
*/
// Dynamically load scripts without using document.write()
// Reference: http://unixpapa.com/js/dyna.html
//
// Handles the case where load_scripts is invoked from a script that
// itself is loaded via load_scripts. Once all scripts are loaded the
// window.onscriptsloaded handler is called (if set).
Util.get_include_uri = function() {
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
}
Util._loading_scripts = [];
Util._pending_scripts = [];
Util.load_scripts = function(files) {
var head = document.getElementsByTagName('head')[0], script,
ls = Util._loading_scripts, ps = Util._pending_scripts;
for (var f=0; f<files.length; f++) {
script = document.createElement('script');
script.type = 'text/javascript';
script.src = Util.get_include_uri() + files[f];
//console.log("loading script: " + script.src);
script.onload = script.onreadystatechange = function (e) {
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
ls[0].readyState === 'complete')) {
// For IE, append the script to trigger execution
var s = ls.shift();
//console.log("loaded script: " + s.src);
head.appendChild(s);
}
if (!this.readyState ||
(Util.Engine.presto && this.readyState === 'loaded') ||
this.readyState === 'complete') {
if (ps.indexOf(this) >= 0) {
this.onload = this.onreadystatechange = null;
//console.log("completed script: " + this.src);
ps.splice(ps.indexOf(this), 1);
// Call window.onscriptsload after last script loads
if (ps.length === 0 && window.onscriptsload) {
window.onscriptsload();
}
}
}
};
// In-order script execution tricks
if (Util.Engine.trident) {
// For IE wait until readyState is 'loaded' before
// appending it which will trigger execution
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
ls.push(script);
} else {
// For webkit and firefox set async=false and append now
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
script.async = false;
head.appendChild(script);
}
ps.push(script);
}
}
// Get DOM element position on page
Util.getPosition = function (obj) {
var x = 0, y = 0;
Binary file not shown.
+82 -32
View File
@@ -1,49 +1,69 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
// Reference: http://tools.ietf.org/html/rfc6455
(function() {
if (window.WebSocket) return;
var console = window.console;
if (!console || !console.log || !console.error) {
console = {log: function(){ }, error: function(){ }};
if (window.WEB_SOCKET_FORCE_FLASH) {
// Keeps going.
} else if (window.WebSocket) {
return;
} else if (window.MozWebSocket) {
// Firefox.
window.WebSocket = MozWebSocket;
return;
}
if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
console.error("Flash Player >= 10.0.0 is required.");
var logger;
if (window.WEB_SOCKET_LOGGER) {
logger = WEB_SOCKET_LOGGER;
} else if (window.console && window.console.log && window.console.error) {
// In some environment, console is defined but console.log or console.error is missing.
logger = window.console;
} else {
logger = {log: function(){ }, error: function(){ }};
}
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
if (swfobject.getFlashPlayerVersion().major < 10) {
logger.error("Flash Player >= 10.0.0 is required.");
return;
}
if (location.protocol == "file:") {
console.error(
logger.error(
"WARNING: web-socket-js doesn't work in file:///... URL " +
"unless you set Flash Security Settings properly. " +
"Open the page via Web server i.e. http://...");
}
/**
* This class represents a faux web socket.
* Our own implementation of WebSocket class using Flash.
* @param {string} url
* @param {string} protocol
* @param {array or string} protocols
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} headers
*/
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
var self = this;
self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self;
self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0;
self.__events = {};
if (!protocols) {
protocols = [];
} else if (typeof protocols == "string") {
protocols = [protocols];
}
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
// Otherwise, when onopen fires immediately, onopen is called before it is set.
setTimeout(function() {
self.__createTask = setTimeout(function() {
WebSocket.__addTask(function() {
self.__createTask = null;
WebSocket.__flash.create(
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
});
}, 0);
};
@@ -78,6 +98,12 @@
* Close this web socket gracefully.
*/
WebSocket.prototype.close = function() {
if (this.__createTask) {
clearTimeout(this.__createTask);
this.__createTask = null;
this.readyState = WebSocket.CLOSED;
return;
}
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return;
}
@@ -131,7 +157,7 @@
events[i](event);
}
var handler = this["on" + event.type];
if (handler) handler(event);
if (handler) handler.apply(this, [event]);
};
/**
@@ -139,16 +165,22 @@
* @param {Object} flashEvent
*/
WebSocket.prototype.__handleEvent = function(flashEvent) {
if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState;
}
if ("protocol" in flashEvent) {
this.protocol = flashEvent.protocol;
}
var jsEvent;
if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") {
// TODO implement jsEvent.wasClean
jsEvent = this.__createSimpleEvent("close");
jsEvent.wasClean = flashEvent.wasClean ? true : false;
jsEvent.code = flashEvent.code;
jsEvent.reason = flashEvent.reason;
} else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data);
@@ -157,6 +189,7 @@
}
this.dispatchEvent(jsEvent);
};
WebSocket.prototype.__createSimpleEvent = function(type) {
@@ -188,6 +221,9 @@
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
// Field to check implementation of WebSocket.
WebSocket.__isFlashImplementation = true;
WebSocket.__initialized = false;
WebSocket.__flash = null;
WebSocket.__instances = {};
WebSocket.__tasks = [];
@@ -207,16 +243,31 @@
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
if (WebSocket.__flash) return;
if (WebSocket.__initialized) return;
WebSocket.__initialized = true;
if (WebSocket.__swfLocation) {
// For backword compatibility.
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
}
if (!window.WEB_SOCKET_SWF_LOCATION) {
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
return;
}
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
var swfHost = RegExp.$1;
if (location.host != swfHost) {
logger.error(
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
"('" + location.host + "' != '" + swfHost + "'). " +
"See also 'How to host HTML file and SWF file in different domains' section " +
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
}
}
var container = document.createElement("div");
container.id = "webSocketContainer";
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
@@ -250,9 +301,11 @@
null,
function(e) {
if (!e.success) {
console.error("[WebSocket] swfobject.embedSWF failed");
logger.error("[WebSocket] swfobject.embedSWF failed");
}
});
}
);
};
/**
@@ -287,7 +340,7 @@
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
}
} catch (e) {
console.error(e);
logger.error(e);
}
}, 0);
return true;
@@ -295,12 +348,12 @@
// Called by Flash.
WebSocket.__log = function(message) {
console.log(decodeURIComponent(message));
logger.log(decodeURIComponent(message));
};
// Called by Flash.
WebSocket.__error = function(message) {
console.error(decodeURIComponent(message));
logger.error(decodeURIComponent(message));
};
WebSocket.__addTask = function(task) {
@@ -327,15 +380,12 @@
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
if (window.addEventListener) {
window.addEventListener("load", function(){
WebSocket.__initialize();
}, false);
} else {
window.attachEvent("onload", function(){
WebSocket.__initialize();
});
}
// NOTE:
// This fires immediately if web_socket.js is dynamically loaded after
// the document is loaded.
swfobject.addDomLoadEvent(function() {
WebSocket.__initialize();
});
}
})();
+102 -33
View File
@@ -1,7 +1,7 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2011 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but Websock
* enables communication with raw TCP sockets (i.e. the binary stream)
@@ -14,9 +14,17 @@
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed
// To force WebSocket emulator even when native WebSocket available
//window.WEB_SOCKET_FORCE_FLASH = true;
// To enable WebSocket emulator debug:
//window.WEB_SOCKET_DEBUG=1;
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
@@ -25,28 +33,16 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
} else {
/* no builtin WebSocket so load web_socket.js */
// To enable debug:
// window.WEB_SOCKET_DEBUG=1;
Websock_native = false;
(function () {
function get_INCLUDE_URI() {
return (typeof INCLUDE_URI !== "undefined") ?
INCLUDE_URI : "include/";
}
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end;
document.write(extra);
Util.load_scripts(["web-socket-js/swfobject.js",
"web-socket-js/web_socket.js"]);
}());
}
@@ -56,6 +52,7 @@ function Websock() {
var api = {}, // Public API
websocket = null, // WebSocket object
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
@@ -87,7 +84,7 @@ function get_rQi() {
}
function set_rQi(val) {
rQi = val;
};
}
function rQlen() {
return rQ.length - rQi;
@@ -122,9 +119,7 @@ function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return arr.map(function (num) {
return String.fromCharCode(num); } ).join('');
return String.fromCharCode.apply(null, arr);
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
@@ -164,14 +159,27 @@ function rQwait(msg, num, goback) {
//
function encode_message() {
/* base64 encode */
return Base64.encode(sQ);
if (mode === 'binary') {
// Put in a binary arraybuffer
return (new Uint8Array(sQ)).buffer;
} else {
// base64 encode
return Base64.encode(sQ);
}
}
function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
/* base64 decode */
rQ = rQ.concat(Base64.decode(data, 0));
if (mode === 'binary') {
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
for (var i = 0; i < u8.length; i++) {
rQ.push(u8[i]);
}
} else {
// base64 decode and concat to the end
rQ = rQ.concat(Base64.decode(data, 0));
}
//Util.Debug(">> decode_message, rQ: " + rQ);
}
@@ -254,29 +262,89 @@ function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init() {
function init(protocols) {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
var bt = false,
wsbt = false,
try_binary = false;
// Check for full typed array support
if (('Uint8Array' in window) &&
('set' in Uint8Array.prototype)) {
bt = true;
}
// Check for full binary type support in WebSockets
// TODO: this sucks, the property should exist on the prototype
// but it does not.
try {
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
Util.Info("Detected binaryType support in WebSockets");
wsbt = true;
}
} catch (exc) {
// Just ignore failed test localhost connections
}
// Default protocols if not specified
if (typeof(protocols) === "undefined") {
if (wsbt) {
protocols = ['binary', 'base64'];
} else {
protocols = 'base64';
}
}
// If no binary support, make sure it was not requested
if (!wsbt) {
if (protocols === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported");
}
if (typeof(protocols) === "object") {
var new_protocols = [];
for (var i = 0; i < protocols.length; i++) {
if (protocols[i] === 'binary') {
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
} else {
new_protocols.push(protocols[i]);
}
}
if (new_protocols.length > 0) {
protocols = new_protocols;
} else {
throw("Only WebSocket binary sub-protocol was requested and not supported.");
}
}
}
return protocols;
}
function open(uri) {
init();
function open(uri, protocols) {
protocols = init(protocols);
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, 'base64');
// TODO: future native binary support
//websocket = new WebSocket(uri, ['binary', 'base64']);
websocket = new WebSocket(uri, protocols);
if (protocols.indexOf('binary') >= 0) {
websocket.binaryType = 'arraybuffer';
}
}
websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
mode = websocket.protocol;
Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
}
eventHandlers.open();
Util.Debug("<< WebSock.onopen");
@@ -306,8 +374,9 @@ function close() {
// Override internal functions for testing
// Takes a send function, returns reference to recv function
function testMode(override_send) {
function testMode(override_send, data_mode) {
test_mode = true;
mode = data_mode;
api.send = override_send;
api.close = function () {};
return recv_message;
+78 -10
View File
@@ -1,7 +1,7 @@
/*
* from noVNC: HTML5 VNC client
* Copyright (C) 2010 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
@@ -37,14 +37,16 @@ if (!window.$D) {
*/
// init log level reading the logging HTTP param
WebUtil.init_logging = function() {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
WebUtil.init_logging = function(level) {
if (typeof level !== "undefined") {
Util._log_level = level;
} else {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
}
Util.init_logging();
};
WebUtil.init_logging();
WebUtil.dirObj = function (obj, depth, parent) {
@@ -75,9 +77,14 @@ WebUtil.dirObj = function (obj, depth, parent) {
// Read a query string variable
WebUtil.getQueryVar = function(name, defVal) {
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
return (document.location.href.match(re) || ['',defVal])[1];
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
@@ -113,6 +120,67 @@ WebUtil.eraseCookie = function(name) {
WebUtil.createCookie(name,"",-1);
};
/*
* Setting handling.
*/
WebUtil.initSettings = function(callback) {
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
WebUtil.settings = cfg;
console.log(WebUtil.settings);
if (callback) {
callback.apply(this, callbackArgs);
}
});
} else {
// No-op
if (callback) {
callback.apply(this, callbackArgs);
}
}
};
// No days means only for this browser session
WebUtil.writeSetting = function(name, value) {
if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) {
WebUtil.settings[name] = value;
window.chrome.storage.sync.set(WebUtil.settings);
}
} else {
localStorage.setItem(name, value);
}
};
WebUtil.readSetting = function(name, defaultValue) {
var value;
if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name];
} else {
value = localStorage.getItem(name);
}
if (typeof value === "undefined") {
value = null;
}
if (value === null && typeof defaultValue !== undefined) {
return defaultValue;
} else {
return value;
}
};
WebUtil.eraseSetting = function(name) {
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name];
} else {
localStorage.removeItem(name);
}
};
/*
* Alternate stylesheet selection
*/
+7
View File
@@ -0,0 +1,7 @@
A JavaScript implementation of the websockify WebSocket-to-TCP bridge/proxy.
Copyright (C) 2013 - Joel Martin (github.com/kanaka)
Licensed under LGPL-3.
See http://github.com/kanaka/websockify for more info.
+24
View File
@@ -0,0 +1,24 @@
{
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
"name": "websockify",
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
"license": "LGPL-3",
"version": "0.6.1",
"repository": {
"type": "git",
"url": "git://github.com/kanaka/websockify.git"
},
"files": ["../../docs/LICENSE.LGPL-3"],
"bin": {
"websockify": "./websockify.js"
},
"engines": {
"node": ">=0.8.9"
},
"dependencies": {
"ws": ">=0.4.27",
"base64": "latest",
"optimist": "latest",
"policyfile": "latest"
}
}
+76 -24
View File
@@ -1,53 +1,82 @@
#!/usr/bin/env node
// A WebSocket to TCP socket proxy
// Copyright 2012 Joel Martin
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
// Known to work with node 0.6
// Requires node modules: ws, base64, optimist and policyfile
// npm install ws base64 optimist policyfile
// Known to work with node 0.8.9
// Requires node modules: ws, optimist and policyfile
// npm install ws optimist policyfile
var argv = require('optimist').argv,
net = require('net'),
http = require('http'),
https = require('https'),
url = require('url'),
path = require('path'),
fs = require('fs'),
policyfile = require('policyfile'),
base64 = require('base64/build/Release/base64'),
Buffer = require('buffer').Buffer,
WebSocketServer = require('ws').Server,
httpServer, wsServer,
webServer, wsServer,
source_host, source_port, target_host, target_port,
web_path = null;
// Handle new WebSocket client
new_client = function(client) {
console.log('WebSocket client connected');
//console.log('protocol: ' + client.protocol);
var clientAddr = client._socket.remoteAddress, log;
console.log(client.upgradeReq.url);
log = function (msg) {
console.log(' ' + clientAddr + ': '+ msg);
};
log('WebSocket connection');
log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol);
var target = net.createConnection(target_port,target_host);
target.on('begin', function() {
console.log('connected to target');
var target = net.createConnection(target_port,target_host, function() {
log('connected to target');
});
target.on('data', function(data) {
client.send(base64.encode(new Buffer(data)));
//log("sending message: " + data);
try {
if (client.protocol === 'base64') {
client.send(new Buffer(data).toString('base64'));
} else {
client.send(data,{binary: true});
}
} catch(e) {
log("Client closed, cleaning up target");
target.end();
}
});
target.on('end', function() {
console.log('target disconnected');
log('target disconnected');
client.close();
});
target.on('error', function() {
log('target connection error');
target.end();
client.close();
});
client.on('message', function(msg) {
//console.log('got some message');
target.write(base64.decode(msg),'binary');
//log('got message: ' + msg);
if (client.protocol === 'base64') {
target.write(new Buffer(msg, 'base64'));
} else {
target.write(msg,'binary');
}
});
client.on('close', function(code, reason) {
console.log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
target.end();
});
client.on('error', function(a) {
console.log('WebSocket client error: ' + a);
log('WebSocket client error: ' + a);
target.end();
});
};
@@ -73,7 +102,7 @@ http_request = function (request, response) {
var uri = url.parse(request.url).pathname
, filename = path.join(argv.web, uri);
path.exists(filename, function(exists) {
fs.exists(filename, function(exists) {
if(!exists) {
return http_error(response, 404, "404 Not Found");
}
@@ -94,10 +123,23 @@ http_request = function (request, response) {
});
};
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
selectProtocol = function(protocols, callback) {
if (protocols.indexOf('binary') >= 0) {
callback(true, 'binary');
} else if (protocols.indexOf('base64') >= 0) {
callback(true, 'base64');
} else {
console.log("Client must support 'binary' or 'base64' protocol");
callback(false);
}
}
// parse source and target arguments into parts
source_arg = argv._[0].toString();
target_arg = argv._[1].toString();
try {
source_arg = argv._[0].toString();
target_arg = argv._[1].toString();
var idx;
idx = source_arg.indexOf(":");
if (idx >= 0) {
@@ -119,7 +161,7 @@ try {
throw("illegal port");
}
} catch(e) {
console.error("wsproxy.py [source_addr:]source_port target_addr:target_port");
console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port");
process.exit(2);
}
@@ -130,11 +172,21 @@ if (argv.web) {
console.log(" - Web server active. Serving: " + argv.web);
}
httpServer = http.createServer(http_request);
httpServer.listen(source_port, function() {
wsServer = new WebSocketServer({server: httpServer});
if (argv.cert) {
argv.key = argv.key || argv.cert;
var cert = fs.readFileSync(argv.cert),
key = fs.readFileSync(argv.key);
console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key);
webServer = https.createServer({cert: cert, key: key}, http_request);
} else {
console.log(" - Running in unencrypted HTTP (ws://) mode");
webServer = http.createServer(http_request);
}
webServer.listen(source_port, function() {
wsServer = new WebSocketServer({server: webServer,
handleProtocols: selectProtocol});
wsServer.on('connection', new_client);
});
// Attach Flash policyfile answer service
policyfile.createServer().listen(-1, httpServer);
policyfile.createServer().listen(-1, webServer);
+14 -4
View File
@@ -16,6 +16,8 @@ usage() {
echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file"
echo " Default: self.pem"
echo " --web WEB Path to web files (e.g. vnc.html)"
echo " Default: ./"
exit 2
}
@@ -24,6 +26,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
PORT="6080"
VNC_DEST="localhost:5900"
CERT=""
WEB=""
proxy_pid=""
die() {
@@ -50,6 +53,7 @@ while [ "$*" ]; do
--listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
@@ -60,18 +64,24 @@ done
which netstat >/dev/null 2>&1 \
|| die "Must have netstat installed"
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
&& die "Port ${PORT} in use. Try --listen PORT"
trap "cleanup" TERM QUIT INT EXIT
# Find vnc.html
if [ -e "$(pwd)/vnc.html" ]; then
if [ -n "${WEB}" ]; then
if [ ! -e "${WEB}/vnc.html" ]; then
die "Could not find ${WEB}/vnc.html"
fi
elif [ -e "$(pwd)/vnc.html" ]; then
WEB=$(pwd)
elif [ -e "${HERE}/../vnc.html" ]; then
WEB=${HERE}/../
elif [ -e "${HERE}/vnc.html" ]; then
WEB=${HERE}
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
WEB=${HERE}/../share/novnc/
else
die "Could not find vnc.html"
fi
@@ -92,7 +102,7 @@ else
fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
proxy_pid="$!"
sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then
@@ -101,7 +111,7 @@ if ! ps -p ${proxy_pid} >/dev/null; then
exit 1
fi
echo -e "\n\nNavigate to to this URL:\n"
echo -e "\n\nNavigate to this URL:\n"
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
echo -e "Press Ctrl-C to exit\n\n"
+1 -1
View File
@@ -795,7 +795,7 @@ void start_server() {
}
handler_msg("handler exit\n");
} else {
handler_msg("wsproxy exit\n");
handler_msg("websockify exit\n");
}
}
+61 -24
View File
@@ -9,11 +9,22 @@
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
require 'gserver'
require 'openssl'
require 'stringio'
require 'digest/md5'
require 'digest/sha1'
require 'base64'
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
module OpenSSL
module SSL
class SSLSocket
alias :read_nonblock :readpartial
end
end
end
end
class EClose < Exception
end
@@ -44,7 +55,17 @@ Sec-WebSocket-Accept: %s\r
host = opts['listen_host'] || GServer::DEFAULT_HOST
super(port, host)
msg opts.inspect
if opts['server_cert']
msg "creating ssl context"
@sslContext = OpenSSL::SSL::SSLContext.new
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
@sslContext.ca_file = opts['server_cert']
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
@sslContext.verify_depth = 0
end
@@client_id = 0 # Track client number total on class
@verbose = opts['verbose']
@@ -53,6 +74,18 @@ Sec-WebSocket-Accept: %s\r
def serve(io)
@@client_id += 1
msg self.inspect
if @sslContext
msg "Enabling SSL context"
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
#ssl.sync_close = true
#ssl.sync = true
msg "SSL accepting"
ssl.accept
io = ssl # replace the unencrypted handle with the encrypted one
end
msg "initializing thread"
# Initialize per thread state
t = Thread.current
@@ -61,11 +94,11 @@ Sec-WebSocket-Accept: %s\r
t[:recv_part] = nil
t[:base64] = nil
puts "in serve, client: #{t[:client].inspect}"
puts "in serve, client: #{t[:my_client_id].inspect}"
begin
t[:client] = do_handshake(io)
new_client(t[:client])
new_websocket_client(t[:client])
rescue EClose => e
msg "Client closed: #{e.message}"
return
@@ -85,12 +118,12 @@ Sec-WebSocket-Accept: %s\r
if @verbose then print token; STDOUT.flush; end
end
def msg(msg)
puts "% 3d: %s" % [Thread.current[:my_client_id], msg]
def msg(m)
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
end
def vmsg(msg)
if @verbose then msg(msg) end
def vmsg(m)
if @verbose then msg(m) end
end
#
@@ -110,12 +143,11 @@ Sec-WebSocket-Accept: %s\r
def unmask(buf, hlen, length)
pstart = hlen + 4
mask = buf[hlen...hlen+4]
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
data = buf[pstart...pstart+length]
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
for i in (0...data.length) do
data[i] ^= mask[i%4]
end
i=-1
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
return data
end
@@ -237,7 +269,7 @@ Sec-WebSocket-Accept: %s\r
while t[:send_parts].length > 0
buf = t[:send_parts].shift
sent = t[:client].send(buf, 0)
sent = t[:client].write(buf)
if sent == buf.length
traffic "<"
@@ -257,7 +289,7 @@ Sec-WebSocket-Accept: %s\r
closed = false
bufs = []
buf = t[:client].recv(@@Buffer_size)
buf = t[:client].read_nonblock(@@Buffer_size)
if buf.length == 0
return bufs, "Client closed abrubtly"
@@ -286,10 +318,10 @@ Sec-WebSocket-Accept: %s\r
end
end
else
if buf[0...2] == "\xff\x00":
if buf[0...2] == "\xff\x00"
closed = "Client sent orderly close frame"
break
elsif buf[0...2] == "\x00\xff":
elsif buf[0...2] == "\x00\xff"
buf = buf[2...buf.length]
continue # No-op frame
elsif buf.count("\xff") == 0
@@ -308,7 +340,7 @@ Sec-WebSocket-Accept: %s\r
bufs << frame['payload']
if frame['left'] > 0:
if frame['left'] > 0
buf = buf[-frame['left']...buf.length]
else
buf = ''
@@ -328,10 +360,10 @@ Sec-WebSocket-Accept: %s\r
end
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
t[:client].send(buf, 0)
t[:client].write(buf)
elsif t[:version] == "hixie-76"
buf = "\xff\x00"
t[:client].send(buf, 0)
t[:client].write(buf)
end
end
@@ -344,16 +376,21 @@ Sec-WebSocket-Accept: %s\r
raise EClose, "ignoring socket not ready"
end
handshake = sock.recv(1024, Socket::MSG_PEEK)
#msg "Handshake [#{handshake.inspect}]"
handshake = ""
msg "About to read from sock [#{sock.inspect}]"
handshake = sock.read_nonblock(1024)
msg "Handshake [#{handshake.inspect}]"
if handshake == ""
if handshake == nil or handshake == ""
raise(EClose, "ignoring empty handshake")
else
stype = "Plain non-SSL (ws://)"
scheme = "ws"
if sock.class == OpenSSL::SSL::SSLSocket
stype = "SSL (wss://)"
scheme = "wss"
end
retsock = sock
sock.recv(1024)
end
h = t[:headers] = {}
@@ -365,7 +402,7 @@ Sec-WebSocket-Accept: %s\r
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
h[hsplit[1].strip.downcase] = hsplit[2]
end
#puts "Headers: #{h.inspect}"
puts "Headers: #{h.inspect}"
unless h.has_key?('upgrade') &&
h['upgrade'].downcase == 'websocket'
@@ -445,7 +482,7 @@ Sec-WebSocket-Accept: %s\r
if t[:path] then msg "Path: '%s'" % [t[:path]] end
#puts "sending reponse #{response.inspect}"
retsock.send(response, 0)
retsock.write(response)
# Return the WebSocket socket which may be SSL wrapped
return retsock
+4 -4
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
# A WebSocket to TCP socket proxy with support for "wss://" encryption.
# A WebSocket to TCP socket proxy
# Copyright 2011 Joel Martin
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
@@ -39,7 +39,7 @@ Traffic Legend:
end
# Echo back whatever is received
def new_client(client)
def new_websocket_client(client)
msg "connecting to: %s:%s" % [@target_host, @target_port]
tsock = TCPSocket.open(@target_host, @target_port)
@@ -92,7 +92,7 @@ Traffic Legend:
# Receive target data and queue for the client
if ins && ins.include?(target)
buf = target.recv(@@Buffer_size)
if buf.length == 0:
if buf.length == 0
raise EClose, "Target closed"
end
@@ -128,7 +128,7 @@ parser = OptionParser.new do |o|
o.parse!
end
if ARGV.length < 2:
if ARGV.length < 2
puts "Too few arguments"
exit 2
end
+4 -4
View File
@@ -7,8 +7,8 @@
* REBIND_PORT_NEW environment variables are set then bind on the new
* port (of localhost) instead of the old port.
*
* This allows a proxy (such as wsproxy) to run on the old port and translate
* traffic to/from the new port.
* This allows a bridge/proxy (such as websockify) to run on the old port and
* translate traffic to/from the new port.
*
* Usage:
* LD_PRELOAD=./rebind.so \
@@ -72,7 +72,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
if (! do_move) {
/* Just pass everything right through to the real bind */
ret = (int) func(sockfd, addr, addrlen);
ret = (long) func(sockfd, addr, addrlen);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
}
@@ -87,7 +87,7 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
/* Bind to other port on the loopback instead */
addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr_tmp.sin_port = htons(newport);
ret = (int) func(sockfd, &addr_tmp, addrlen_tmp);
ret = (long) func(sockfd, &addr_tmp, addrlen_tmp);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/python
import websockify
websockify.websocketproxy.websockify_init()
+11 -3
View File
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = '0.1.0'
version = '0.6.1'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
@@ -12,19 +12,27 @@ setup(name=name,
classifiers=[
"Programming Language :: Python",
],
data_files=[('share/websockify/include',
['include/util.js',
'include/base64.js',
'include/websock.js']),
('share/websockify/include/web-socket-js',
['include/web-socket-js/WebSocketMain.swf',
'include/web-socket-js/swfobject.js',
'include/web-socket-js/web_socket.js'])],
keywords='noVNC websockify',
license='LGPLv3',
url="https://github.com/kanaka/websockify",
author="Joel Martin",
author_email="github@martintribe.org",
packages=find_packages(),
packages=['websockify'],
include_package_data=True,
install_requires=['numpy'],
zip_safe=False,
entry_points={
'console_scripts': [
'websockify = websockify:websockify_init',
'websockify = websockify.websocketproxy:websockify_init',
]
},
)
+12 -11
View File
@@ -10,17 +10,17 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import os, sys, select, optparse
sys.path.insert(0,os.path.dirname(__file__) + "/../")
from websocket import WebSocketServer
import os, sys, select, optparse, logging
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
class WebSocketEcho(WebSocketServer):
class WebSocketEcho(WebSocketRequestHandler):
"""
WebSockets server that echos back whatever is received from the
client. """
buffer_size = 8096
def new_client(self):
def new_websocket_client(self):
"""
Echo back whatever is received.
"""
@@ -28,28 +28,27 @@ class WebSocketEcho(WebSocketServer):
cqueue = []
c_pend = 0
cpartial = ""
rlist = [self.client]
rlist = [self.request]
while True:
wlist = []
if cqueue or c_pend: wlist.append(self.client)
if cqueue or c_pend: wlist.append(self.request)
ins, outs, excepts = select.select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if self.client in outs:
if self.request in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.client in ins:
if self.request in ins:
# Receive client data, decode it, and send it back
frames, closed = self.recv_frames()
cqueue.extend(frames)
if closed:
self.send_close()
raise self.EClose(closed)
if __name__ == '__main__':
parser = optparse.OptionParser(usage="%prog [options] listen_port")
@@ -69,7 +68,9 @@ if __name__ == '__main__':
except:
parser.error("Invalid arguments")
logging.basicConfig(level=logging.INFO)
opts.web = "."
server = WebSocketEcho(**opts.__dict__)
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
server.start_server()
+10 -3
View File
@@ -12,7 +12,7 @@ require 'websocket'
class WebSocketEcho < WebSocketServer
# Echo back whatever is received
def new_client(client)
def new_websocket_client(client)
cqueue = []
c_pend = 0
@@ -50,10 +50,17 @@ class WebSocketEcho < WebSocketServer
end
end
port = ARGV[0].to_i
port = ARGV[0].to_i || 8080
puts "Starting server on port #{port}"
server_cert = nil
server_key = nil
if ARGV.length > 2
server_cert = ARGV[1]
server_key = ARGV[2]
end
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true)
server = WebSocketEcho.new('listen_port' => port, 'verbose' => true,
'server_cert' => server_cert, 'server_key' => server_key)
server.start
server.join
+18 -15
View File
@@ -6,35 +6,37 @@ that has a random payload (length and content) that is checksummed and
given a sequence number. Any errors are reported and counted.
'''
import sys, os, select, random, time, optparse
sys.path.insert(0,os.path.dirname(__file__) + "/../")
from websocket import WebSocketServer
import sys, os, select, random, time, optparse, logging
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
class WebSocketLoad(WebSocketServer):
class WebSocketLoadServer(WebSocketServer):
buffer_size = 65536
max_packet_size = 10000
recv_cnt = 0
send_cnt = 0
def __init__(self, *args, **kwargs):
self.errors = 0
self.delay = kwargs.pop('delay')
WebSocketServer.__init__(self, *args, **kwargs)
class WebSocketLoad(WebSocketRequestHandler):
max_packet_size = 10000
def new_websocket_client(self):
print "Prepopulating random array"
self.rand_array = []
for i in range(0, self.max_packet_size):
self.rand_array.append(random.randint(0, 9))
WebSocketServer.__init__(self, *args, **kwargs)
def new_client(self):
self.errors = 0
self.send_cnt = 0
self.recv_cnt = 0
try:
self.responder(self.client)
self.responder(self.request)
except:
print "accumulated errors:", self.errors
self.errors = 0
@@ -61,14 +63,13 @@ class WebSocketLoad(WebSocketServer):
if closed:
self.send_close()
raise self.EClose(closed)
now = time.time() * 1000
if client in outs:
if c_pend:
last_send = now
c_pend = self.send_frames()
elif now > (last_send + self.delay):
elif now > (last_send + self.server.delay):
last_send = now
c_pend = self.send_frames([self.generate()])
@@ -161,7 +162,9 @@ if __name__ == '__main__':
except:
parser.error("Invalid arguments")
logging.basicConfig(level=logging.INFO)
opts.web = "."
server = WebSocketLoad(**opts.__dict__)
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
server.start_server()
+243
View File
@@ -0,0 +1,243 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright(c)2013 NTT corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
""" Unit tests for websocket """
import errno
import os
import logging
import select
import shutil
import socket
import ssl
import stubout
import sys
import tempfile
import unittest
from ssl import SSLError
from websockify import websocket as websocket
from SimpleHTTPServer import SimpleHTTPRequestHandler
class MockConnection(object):
def __init__(self, path):
self.path = path
def makefile(self, mode='r', bufsize=-1):
return open(self.path, mode, bufsize)
class WebSocketTestCase(unittest.TestCase):
def _init_logger(self, tmpdir):
name = 'websocket-unittest'
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.propagate = True
filename = "%s.log" % (name)
handler = logging.FileHandler(filename)
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
def setUp(self):
"""Called automatically before each test."""
super(WebSocketTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
# Temporary dir for test data
self.tmpdir = tempfile.mkdtemp()
# Put log somewhere persistent
self._init_logger('./')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
self.server = self._get_websockserver(daemon=True,
ssl_only=False)
self.soc = self.server.socket('localhost')
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
shutil.rmtree(self.tmpdir)
super(WebSocketTestCase, self).tearDown()
def _get_websockserver(self, **kwargs):
return websocket.WebSocketServer(listen_host='localhost',
listen_port=80,
key=self.tmpdir,
web=self.tmpdir,
record=self.tmpdir,
**kwargs)
def _mock_os_open_oserror(self, file, flags):
raise OSError('')
def _mock_os_close_oserror(self, fd):
raise OSError('')
def _mock_os_close_oserror_EBADF(self, fd):
raise OSError(errno.EBADF, '')
def _mock_socket(self, *args, **kwargs):
return self.soc
def _mock_select(self, rlist, wlist, xlist, timeout=None):
return '_mock_select'
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
raise Exception
def _mock_select_keyboardinterrupt(self, rlist, wlist,
xlist, timeout=None):
raise KeyboardInterrupt
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
sys.exit()
def test_daemonize_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: None)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(os, 'close', self._mock_os_close_oserror)
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
def test_daemonize_EBADF_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: None)
self.stubs.Set(os, 'setsid', lambda *args: None)
self.stubs.Set(os, 'close', self._mock_os_close_oserror_EBADF)
self.stubs.Set(os, 'open', self._mock_os_open_oserror)
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
def test_decode_hybi(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.assertRaises(Exception, soc.decode_hybi, 'a' * 128,
base64=True)
def test_do_websocket_handshake(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
soc.scheme = 'scheme'
headers = {'Sec-WebSocket-Protocol': 'binary',
'Sec-WebSocket-Version': '7',
'Sec-WebSocket-Key': 'foo'}
soc.do_websocket_handshake(headers, '127.0.0.1')
def test_do_handshake(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
self.stubs.Set(select, 'select', self._mock_select)
self.stubs.Set(socket._socketobject, 'recv', lambda *args: 'mock_recv')
self.assertRaises(Exception, soc.do_handshake, self.soc, '127.0.0.1')
def test_do_handshake_ssl_error(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
def _mock_wrap_socket(*args, **kwargs):
from ssl import SSLError
raise SSLError('unit test exception')
self.stubs.Set(select, 'select', self._mock_select)
self.stubs.Set(socket._socketobject, 'recv', lambda *args: '\x16')
self.stubs.Set(ssl, 'wrap_socket', _mock_wrap_socket)
self.assertRaises(SSLError, soc.do_handshake, self.soc, '127.0.0.1')
def test_fallback_SIGCHILD(self):
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
soc.fallback_SIGCHLD(None, None)
def test_start_server_Exception(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', self._mock_select_exception)
self.assertEqual(None, soc.start_server())
def test_start_server_KeyboardInterrupt(self):
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', self._mock_select_keyboardinterrupt)
self.assertEqual(None, soc.start_server())
def test_start_server_systemexit(self):
websocket.ssl = None
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', self._mock_select_systemexit)
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1,
verbose=True)
self.assertEqual(None, soc.start_server())
def test_WSRequestHandle_do_GET_nofile(self):
request = 'GET /tmp.txt HTTP/0.9'
with tempfile.NamedTemporaryFile() as test_file:
test_file.write(request)
test_file.flush()
test_file.seek(0)
con = MockConnection(test_file.name)
soc = websocket.WSRequestHandler(con, "127.0.0.1", file_only=True)
soc.path = ''
soc.headers = {'upgrade': ''}
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
lambda *args: None)
soc.do_GET()
self.assertEqual(404, soc.last_code)
def test_WSRequestHandle_do_GET_hidden_resource(self):
request = 'GET /tmp.txt HTTP/0.9'
with tempfile.NamedTemporaryFile() as test_file:
test_file.write(request)
test_file.flush()
test_file.seek(0)
con = MockConnection(test_file.name)
soc = websocket.WSRequestHandler(con, '127.0.0.1', no_parent=True)
soc.path = test_file.name + '?'
soc.headers = {'upgrade': ''}
soc.webroot = 'no match startswith'
self.stubs.Set(SimpleHTTPRequestHandler,
'send_response',
lambda *args: None)
soc.do_GET()
self.assertEqual(403, soc.last_code)
def testsocket_set_keepalive_options(self):
keepcnt = 12
keepidle = 34
keepintvl = 56
sock = self.server.socket('localhost',
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPCNT), keepcnt)
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPIDLE), keepidle)
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPINTVL), keepintvl)
sock = self.server.socket('localhost',
tcp_keepalive=False,
tcp_keepcnt=keepcnt,
tcp_keepidle=keepidle,
tcp_keepintvl=keepintvl)
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPCNT), keepcnt)
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPIDLE), keepidle)
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
socket.TCP_KEEPINTVL), keepintvl)
+127
View File
@@ -0,0 +1,127 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright(c)2013 NTT corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
""" Unit tests for websocketproxy """
import os
import logging
import select
import shutil
import stubout
import subprocess
import tempfile
import time
import unittest
from websockify import websocketproxy
class MockSocket(object):
def __init__(*args, **kwargs):
pass
def shutdown(*args):
pass
def close(*args):
pass
class WebSocketProxyTest(unittest.TestCase):
def _init_logger(self, tmpdir):
name = 'websocket-unittest'
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.propagate = True
filename = "%s.log" % (name)
handler = logging.FileHandler(filename)
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
def setUp(self):
"""Called automatically before each test."""
super(WebSocketProxyTest, self).setUp()
self.soc = ''
self.stubs = stubout.StubOutForTesting()
# Temporary dir for test data
self.tmpdir = tempfile.mkdtemp()
# Put log somewhere persistent
self._init_logger('./')
# Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
def tearDown(self):
"""Called automatically after each test."""
self.stubs.UnsetAll()
shutil.rmtree(self.tmpdir)
super(WebSocketProxyTest, self).tearDown()
def _get_websockproxy(self, **kwargs):
return websocketproxy.WebSocketProxy(key=self.tmpdir,
web=self.tmpdir,
record=self.tmpdir,
**kwargs)
def test_run_wrap_cmd(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
def mock_Popen(*args, **kwargs):
return '_mock_cmd'
self.stubs.Set(subprocess, 'Popen', mock_Popen)
web_socket_proxy.run_wrap_cmd()
self.assertEquals(web_socket_proxy.spawn_message, True)
def test_started(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["spawn_message"] = False
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
def mock_run_wrap_cmd(*args, **kwargs):
web_socket_proxy.__dict__["spawn_message"] = True
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
web_socket_proxy.started()
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
def test_poll(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
web_socket_proxy.__dict__["wrap_mode"] = "respawn"
web_socket_proxy.__dict__["wrap_times"] = [99999999]
web_socket_proxy.__dict__["spawn_message"] = True
web_socket_proxy.__dict__["cmd"] = None
self.stubs.Set(time, 'time', lambda: 100000000.000)
web_socket_proxy.poll()
self.assertEquals(web_socket_proxy.spawn_message, False)
def test_new_client(self):
web_socket_proxy = self._get_websockproxy()
web_socket_proxy.__dict__["verbose"] = "verbose"
web_socket_proxy.__dict__["daemon"] = None
web_socket_proxy.__dict__["client"] = "client"
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
def mock_select(*args, **kwargs):
ins = None
outs = None
excepts = "excepts"
return ins, outs, excepts
self.stubs.Set(select, 'select', mock_select)
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
+20
View File
@@ -0,0 +1,20 @@
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
envlist = py24,py25,py26,py27,py30
setupdir = ../
[testenv]
commands = nosetests {posargs}
deps =
mox
nose
# At some point we should enable this since tox epdctes it to exist but
# the code will need pep8ising first.
#[testenv:pep8]
#commands = flake8
#dep = flake8
+4 -7
View File
@@ -5,18 +5,15 @@ Display UTF-8 encoding for 0-255.'''
import sys, os, socket, ssl, time, traceback
from select import select
sys.path.insert(0,os.path.dirname(__file__) + "/../")
from websocket import WebSocketServer
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
from websockify.websocket import WebSocketServer
if __name__ == '__main__':
print "val: hixie | hybi_base64 | hybi_binary"
print "val: hybi_base64 | hybi_binary"
for c in range(0, 256):
hixie = WebSocketServer.encode_hixie(chr(c))
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
base64=True)
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
base64=False)
print "%d: %s | %s | %s" % (c, repr(hixie), repr(hybi_base64),
repr(hybi_binary))
print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
-930
View File
@@ -1,930 +0,0 @@
#!/usr/bin/env python
'''
Python WebSocket library with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
Supports following protocol versions:
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import os, sys, time, errno, signal, socket, traceback, select
import array, struct
from cgi import parse_qsl
from base64 import b64encode, b64decode
# Imports that vary by python version
# python 3.0 differences
if sys.hexversion > 0x3000000:
b2s = lambda buf: buf.decode('latin_1')
s2b = lambda s: s.encode('latin_1')
s2a = lambda s: s
else:
b2s = lambda buf: buf # No-op
s2b = lambda s: s # No-op
s2a = lambda s: [ord(c) for c in s]
try: from io import StringIO
except: from cStringIO import StringIO
try: from http.server import SimpleHTTPRequestHandler
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
try: from urllib.parse import urlsplit
except: from urlparse import urlsplit
# python 2.6 differences
try: from hashlib import md5, sha1
except: from md5 import md5; from sha import sha as sha1
# python 2.5 differences
try:
from struct import pack, unpack_from
except:
from struct import pack
def unpack_from(fmt, buf, offset=0):
slice = buffer(buf, offset, struct.calcsize(fmt))
return struct.unpack(fmt, slice)
# Degraded functionality if these imports are missing
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
('multiprocessing', 'Multi-Processing'),
('resource', 'daemonizing')]:
try:
globals()[mod] = __import__(mod)
except ImportError:
globals()[mod] = None
print("WARNING: no '%s' module, %s is slower or disabled" % (
mod, sup))
if multiprocessing and sys.platform == 'win32':
# make sockets pickle-able/inheritable
import multiprocessing.reduction
class WebSocketServer(object):
"""
WebSockets server class.
Must be sub-classed with new_client method definition.
"""
buffer_size = 65536
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r
Connection: Upgrade\r
%sWebSocket-Origin: %s\r
%sWebSocket-Location: %s://%s%s\r
"""
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: %s\r
"""
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
# An exception before the WebSocket connection was established
class EClose(Exception):
pass
# An exception while the WebSocket client was connected
class CClose(Exception):
pass
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None,
daemon=False, record='', web='',
run_once=False, timeout=0):
# settings
self.verbose = verbose
self.listen_host = listen_host
self.listen_port = listen_port
self.ssl_only = ssl_only
self.daemon = daemon
self.run_once = run_once
self.timeout = timeout
self.launch_time = time.time()
self.ws_connection = False
self.handler_id = 1
# Make paths settings absolute
self.cert = os.path.abspath(cert)
self.key = self.web = self.record = ''
if key:
self.key = os.path.abspath(key)
if web:
self.web = os.path.abspath(web)
if record:
self.record = os.path.abspath(record)
if self.web:
os.chdir(self.web)
# Sanity checks
if not ssl and self.ssl_only:
raise Exception("No 'ssl' module and SSL-only specified")
if self.daemon and not resource:
raise Exception("Module 'resource' required to daemonize")
# Show configuration
print("WebSocket server settings:")
print(" - Listen on %s:%s" % (
self.listen_host, self.listen_port))
print(" - Flash security policy server")
if self.web:
print(" - Web server. Web root: %s" % self.web)
if ssl:
if os.path.exists(self.cert):
print(" - SSL/TLS support")
if self.ssl_only:
print(" - Deny non-SSL/TLS connections")
else:
print(" - No SSL/TLS support (no cert file)")
else:
print(" - No SSL/TLS support (no 'ssl' module)")
if self.daemon:
print(" - Backgrounding (daemon)")
if self.record:
print(" - Recording to '%s.*'" % self.record)
#
# WebSocketServer static methods
#
@staticmethod
def socket(host, port=None, connect=False, prefer_ipv6=False):
""" Resolve a host (and optional port) to an IPv4 or IPv6
address. Create a socket. Bind to it if listen is set,
otherwise connect to it. Return the socket.
"""
flags = 0
if host == '':
host = None
if connect and not port:
raise Exception("Connect mode requires a port")
if not connect:
flags = flags | socket.AI_PASSIVE
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
socket.IPPROTO_TCP, flags)
if not addrs:
raise Exception("Could resolve host '%s'" % host)
addrs.sort(key=lambda x: x[0])
if prefer_ipv6:
addrs.reverse()
sock = socket.socket(addrs[0][0], addrs[0][1])
if connect:
sock.connect(addrs[0][4])
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
sock.listen(100)
return sock
@staticmethod
def daemonize(keepfd=None, chdir='/'):
os.umask(0)
if chdir:
os.chdir(chdir)
else:
os.chdir('/')
os.setgid(os.getgid()) # relinquish elevations
os.setuid(os.getuid()) # relinquish elevations
# Double fork to daemonize
if os.fork() > 0: os._exit(0) # Parent exits
os.setsid() # Obtain new process group
if os.fork() > 0: os._exit(0) # Parent exits
# Signal handling
def terminate(a,b): os._exit(0)
signal.signal(signal.SIGTERM, terminate)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Close open files
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if maxfd == resource.RLIM_INFINITY: maxfd = 256
for fd in reversed(range(maxfd)):
try:
if fd != keepfd:
os.close(fd)
except OSError:
_, exc, _ = sys.exc_info()
if exc.errno != errno.EBADF: raise
# Redirect I/O to /dev/null
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
@staticmethod
def unmask(buf, f):
pstart = f['hlen'] + 4
pend = pstart + f['length']
if numpy:
b = c = s2b('')
if f['length'] >= 4:
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=f['hlen'], count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=pstart, count=int(f['length'] / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
if f['length'] % 4:
#print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=f['hlen'], count=(f['length'] % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=pend - (f['length'] % 4),
count=(f['length'] % 4))
c = numpy.bitwise_xor(data, mask).tostring()
return b + c
else:
# Slower fallback
data = array.array('B')
mask = s2a(f['mask'])
data.fromstring(buf[pstart:pend])
for i in range(len(data)):
data[i] ^= mask[i % 4]
return data.tostring()
@staticmethod
def encode_hybi(buf, opcode, base64=False):
""" Encode a HyBi style WebSocket frame.
Optional opcode:
0x0 - continuation
0x1 - text frame (base64 encode buf)
0x2 - binary frame (use raw buf)
0x8 - connection close
0x9 - ping
0xA - pong
"""
if base64:
buf = b64encode(buf)
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
payload_len = len(buf)
if payload_len <= 125:
header = pack('>BB', b1, payload_len)
elif payload_len > 125 and payload_len < 65536:
header = pack('>BBH', b1, 126, payload_len)
elif payload_len >= 65536:
header = pack('>BBQ', b1, 127, payload_len)
#print("Encoded: %s" % repr(header + buf))
return header + buf, len(header), 0
@staticmethod
def decode_hybi(buf, base64=False):
""" Decode HyBi style WebSocket packets.
Returns:
{'fin' : 0_or_1,
'opcode' : number,
'mask' : 32_bit_number,
'hlen' : header_bytes_number,
'length' : payload_bytes_number,
'payload' : decoded_buffer,
'left' : bytes_left_number,
'close_code' : number,
'close_reason' : string}
"""
f = {'fin' : 0,
'opcode' : 0,
'mask' : 0,
'hlen' : 2,
'length' : 0,
'payload' : None,
'left' : 0,
'close_code' : 1000,
'close_reason' : ''}
blen = len(buf)
f['left'] = blen
if blen < f['hlen']:
return f # Incomplete frame header
b1, b2 = unpack_from(">BB", buf)
f['opcode'] = b1 & 0x0f
f['fin'] = (b1 & 0x80) >> 7
has_mask = (b2 & 0x80) >> 7
f['length'] = b2 & 0x7f
if f['length'] == 126:
f['hlen'] = 4
if blen < f['hlen']:
return f # Incomplete frame header
(f['length'],) = unpack_from('>xxH', buf)
elif f['length'] == 127:
f['hlen'] = 10
if blen < f['hlen']:
return f # Incomplete frame header
(f['length'],) = unpack_from('>xxQ', buf)
full_len = f['hlen'] + has_mask * 4 + f['length']
if blen < full_len: # Incomplete frame
return f # Incomplete frame header
# Number of bytes that are part of the next frame(s)
f['left'] = blen - full_len
# Process 1 frame
if has_mask:
# unmask payload
f['mask'] = buf[f['hlen']:f['hlen']+4]
f['payload'] = WebSocketServer.unmask(buf, f)
else:
print("Unmasked frame: %s" % repr(buf))
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
if base64 and f['opcode'] in [1, 2]:
try:
f['payload'] = b64decode(f['payload'])
except:
print("Exception while b64decoding buffer: %s" %
repr(buf))
raise
if f['opcode'] == 0x08:
if f['length'] >= 2:
f['close_code'] = unpack_from(">H", f['payload'])[0]
if f['length'] > 3:
f['close_reason'] = f['payload'][2:]
return f
@staticmethod
def encode_hixie(buf):
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
@staticmethod
def decode_hixie(buf):
end = buf.find(s2b('\xff'))
return {'payload': b64decode(buf[1:end]),
'hlen': 1,
'length': end - 1,
'left': len(buf) - (end + 1)}
@staticmethod
def gen_md5(keys):
""" Generate hash value for WebSockets hixie-76. """
key1 = keys['Sec-WebSocket-Key1']
key2 = keys['Sec-WebSocket-Key2']
key3 = keys['key3']
spaces1 = key1.count(" ")
spaces2 = key2.count(" ")
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
return b2s(md5(pack('>II8s',
int(num1), int(num2), key3)).digest())
#
# WebSocketServer logging/output functions
#
def traffic(self, token="."):
""" Show traffic flow in verbose mode. """
if self.verbose and not self.daemon:
sys.stdout.write(token)
sys.stdout.flush()
def msg(self, msg):
""" Output message with handler_id prefix. """
if not self.daemon:
print("% 3d: %s" % (self.handler_id, msg))
def vmsg(self, msg):
""" Same as msg() but only if verbose. """
if self.verbose:
self.msg(msg)
#
# Main WebSocketServer methods
#
def send_frames(self, bufs=None):
""" Encode and send WebSocket frames. Any frames already
queued will be sent first. If buf is not set then only queued
frames will be sent. Returns the number of pending frames that
could not be fully sent. If returned pending frames is greater
than 0, then the caller should call again when the socket is
ready. """
tdelta = int(time.time()*1000) - self.start_time
if bufs:
for buf in bufs:
if self.version.startswith("hybi"):
if self.base64:
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=1, base64=True)
else:
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=2, base64=False)
else:
encbuf, lenhead, lentail = self.encode_hixie(buf)
if self.rec:
self.rec.write("%s,\n" %
repr("{%s{" % tdelta
+ encbuf[lenhead:-lentail]))
self.send_parts.append(encbuf)
while self.send_parts:
# Send pending frames
buf = self.send_parts.pop(0)
sent = self.client.send(buf)
if sent == len(buf):
self.traffic("<")
else:
self.traffic("<.")
self.send_parts.insert(0, buf[sent:])
break
return len(self.send_parts)
def recv_frames(self):
""" Receive and decode WebSocket frames.
Returns:
(bufs_list, closed_string)
"""
closed = False
bufs = []
tdelta = int(time.time()*1000) - self.start_time
buf = self.client.recv(self.buffer_size)
if len(buf) == 0:
closed = {'code': 1000, 'reason': "Client closed abruptly"}
return bufs, closed
if self.recv_part:
# Add partially received frames to current read buffer
buf = self.recv_part + buf
self.recv_part = None
while buf:
if self.version.startswith("hybi"):
frame = self.decode_hybi(buf, base64=self.base64)
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
if frame['payload'] == None:
# Incomplete/partial frame
self.traffic("}.")
if frame['left'] > 0:
self.recv_part = buf[-frame['left']:]
break
else:
if frame['opcode'] == 0x8: # connection close
closed = {'code': frame['close_code'],
'reason': frame['close_reason']}
break
else:
if buf[0:2] == s2b('\xff\x00'):
closed = {'code': 1000,
'reason': "Client sent orderly close frame"}
break
elif buf[0:2] == s2b('\x00\xff'):
buf = buf[2:]
continue # No-op
elif buf.count(s2b('\xff')) == 0:
# Partial frame
self.traffic("}.")
self.recv_part = buf
break
frame = self.decode_hixie(buf)
self.traffic("}")
if self.rec:
start = frame['hlen']
end = frame['hlen'] + frame['length']
self.rec.write("%s,\n" %
repr("}%s}" % tdelta + buf[start:end]))
bufs.append(frame['payload'])
if frame['left']:
buf = buf[-frame['left']:]
else:
buf = ''
return bufs, closed
def send_close(self, code=1000, reason=''):
""" Send a WebSocket orderly close frame. """
if self.version.startswith("hybi"):
msg = pack(">H%ds" % len(reason), code, reason)
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
self.client.send(buf)
elif self.version == "hixie-76":
buf = s2b('\xff\x00')
self.client.send(buf)
# No orderly close for 75
def do_handshake(self, sock, address):
"""
do_handshake does the following:
- Peek at the first few bytes from the socket.
- If the connection is Flash policy request then answer it,
close the socket and return.
- If the connection is an HTTPS/SSL/TLS connection then SSL
wrap the socket.
- Read from the (possibly wrapped) socket.
- If we have received a HTTP GET request and the webserver
functionality is enabled, answer it, close the socket and
return.
- Assume we have a WebSockets connection, parse the client
handshake data.
- Send a WebSockets handshake server response.
- Return the socket for this WebSocket client.
"""
stype = ""
ready = select.select([sock], [], [], 3)[0]
if not ready:
raise self.EClose("ignoring socket not ready")
# Peek, but do not read the data so that we have a opportunity
# to SSL wrap the socket first
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if handshake == "":
raise self.EClose("ignoring empty handshake")
elif handshake.startswith(s2b("<policy-file-request/>")):
# Answer Flash policy request
handshake = sock.recv(1024)
sock.send(s2b(self.policy_response))
raise self.EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80", 22, 128):
# SSL wrap the connection
if not ssl:
raise self.EClose("SSL connection but no 'ssl' module")
if not os.path.exists(self.cert):
raise self.EClose("SSL connection but '%s' not found"
% self.cert)
retsock = None
try:
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
if len(x.args) > 1:
raise self.EClose(x.args[1])
else:
raise self.EClose("Got SSL_ERROR_EOF")
else:
raise
scheme = "wss"
stype = "SSL/TLS (wss://)"
elif self.ssl_only:
raise self.EClose("non-SSL connection received but disallowed")
else:
retsock = sock
scheme = "ws"
stype = "Plain non-SSL (ws://)"
wsh = WSRequestHandler(retsock, address, not self.web)
if wsh.last_code == 101:
# Continue on to handle WebSocket upgrade
pass
elif wsh.last_code == 405:
raise self.EClose("Normal web request received but disallowed")
elif wsh.last_code < 200 or wsh.last_code >= 300:
raise self.EClose(wsh.last_message)
elif self.verbose:
raise self.EClose(wsh.last_message)
else:
raise self.EClose("")
h = self.headers = wsh.headers
path = self.path = wsh.path
prot = 'WebSocket-Protocol'
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
ver = h.get('Sec-WebSocket-Version')
if ver:
# HyBi/IETF version of the protocol
# HyBi-07 report version 7
# HyBi-08 - HyBi-12 report version 8
# HyBi-13 reports version 13
if ver in ['7', '8', '13']:
self.version = "hybi-%02d" % int(ver)
else:
raise self.EClose('Unsupported protocol version %s' % ver)
key = h['Sec-WebSocket-Key']
# Choose binary if client supports it
if 'binary' in protocols:
self.base64 = False
elif 'base64' in protocols:
self.base64 = True
else:
raise self.EClose("Client must support 'binary' or 'base64' protocol")
# Generate the hash value for the accept header
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
response = self.server_handshake_hybi % b2s(accept)
if self.base64:
response += "Sec-WebSocket-Protocol: base64\r\n"
else:
response += "Sec-WebSocket-Protocol: binary\r\n"
response += "\r\n"
else:
# Hixie version of the protocol (75 or 76)
if h.get('key3'):
trailer = self.gen_md5(h)
pre = "Sec-"
self.version = "hixie-76"
else:
trailer = ""
pre = ""
self.version = "hixie-75"
# We only support base64 in Hixie era
self.base64 = True
response = self.server_handshake_hixie % (pre,
h['Origin'], pre, scheme, h['Host'], path)
if 'base64' in protocols:
response += "%sWebSocket-Protocol: base64\r\n" % pre
else:
self.msg("Warning: client does not report 'base64' protocol support")
response += "\r\n" + trailer
self.msg("%s: %s WebSocket connection" % (address[0], stype))
self.msg("%s: Version %s, base64: '%s'" % (address[0],
self.version, self.base64))
if self.path != '/':
self.msg("%s: Path: '%s'" % (address[0], self.path))
# Send server WebSockets handshake response
#self.msg("sending response [%s]" % response)
retsock.send(s2b(response))
# Return the WebSockets socket which may be SSL wrapped
return retsock
#
# Events that can/should be overridden in sub-classes
#
def started(self):
""" Called after WebSockets startup """
self.vmsg("WebSockets server started")
def poll(self):
""" Run periodically while waiting for connections. """
#self.vmsg("Running poll()")
pass
def fallback_SIGCHLD(self, sig, stack):
# Reap zombies when using os.fork() (python 2.4)
self.vmsg("Got SIGCHLD, reaping zombies")
try:
result = os.waitpid(-1, os.WNOHANG)
while result[0]:
self.vmsg("Reaped child process %s" % result[0])
result = os.waitpid(-1, os.WNOHANG)
except (OSError):
pass
def do_SIGINT(self, sig, stack):
self.msg("Got SIGINT, exiting")
sys.exit(0)
def top_new_client(self, startsock, address):
""" Do something with a WebSockets client connection. """
# Initialize per client settings
self.send_parts = []
self.recv_part = None
self.base64 = False
self.rec = None
self.start_time = int(time.time()*1000)
# handler process
try:
try:
self.client = self.do_handshake(startsock, address)
if self.record:
# Record raw frame data as JavaScript array
fname = "%s.%s" % (self.record,
self.handler_id)
self.msg("opening record file: %s" % fname)
self.rec = open(fname, 'w+')
self.rec.write("var VNC_frame_data = [\n")
self.ws_connection = True
self.new_client()
except self.CClose:
# Close the client
_, exc, _ = sys.exc_info()
if self.client:
self.send_close(exc.args[0], exc.args[1])
except self.EClose:
_, exc, _ = sys.exc_info()
# Connection was not a WebSockets connection
if exc.args[0]:
self.msg("%s: %s" % (address[0], exc.args[0]))
except Exception:
_, exc, _ = sys.exc_info()
self.msg("handler exception: %s" % str(exc))
if self.verbose:
self.msg(traceback.format_exc())
finally:
if self.rec:
self.rec.write("'EOF']\n")
self.rec.close()
if self.client and self.client != startsock:
# Close the SSL wrapped socket
# Original socket closed by caller
self.client.close()
def new_client(self):
""" Do something with a WebSockets client connection. """
raise("WebSocketServer.new_client() must be overloaded")
def start_server(self):
"""
Daemonize if requested. Listen for for connections. Run
do_handshake() method for each connection. If the connection
is a WebSockets client then call new_client() method (which must
be overridden) for each new client connection.
"""
lsock = self.socket(self.listen_host, self.listen_port)
if self.daemon:
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
self.started() # Some things need to happen after daemonizing
# Allow override of SIGINT
signal.signal(signal.SIGINT, self.do_SIGINT)
if not multiprocessing:
# os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
while True:
try:
try:
self.client = None
startsock = None
pid = err = 0
time_elapsed = time.time() - self.launch_time
if self.timeout and time_elapsed > self.timeout:
self.msg('listener exit due to --timeout %s'
% self.timeout)
break
try:
self.poll()
ready = select.select([lsock], [], [], 1)[0]
if lsock in ready:
startsock, address = lsock.accept()
else:
continue
except Exception:
_, exc, _ = sys.exc_info()
if hasattr(exc, 'errno'):
err = exc.errno
elif hasattr(exc, 'args'):
err = exc.args[0]
else:
err = exc[0]
if err == errno.EINTR:
self.vmsg("Ignoring interrupted syscall")
continue
else:
raise
if self.run_once:
# Run in same process if run_once
self.top_new_client(startsock, address)
if self.ws_connection :
self.msg('%s: exiting due to --run-once'
% address[0])
break
elif multiprocessing:
self.vmsg('%s: new handler Process' % address[0])
p = multiprocessing.Process(
target=self.top_new_client,
args=(startsock, address))
p.start()
# child will not return
else:
# python 2.4
self.vmsg('%s: forking handler' % address[0])
pid = os.fork()
if pid == 0:
# child handler process
self.top_new_client(startsock, address)
break # child process exits
# parent process
self.handler_id += 1
except KeyboardInterrupt:
_, exc, _ = sys.exc_info()
print("In KeyboardInterrupt")
pass
except SystemExit:
_, exc, _ = sys.exc_info()
print("In SystemExit")
break
except Exception:
_, exc, _ = sys.exc_info()
self.msg("handler exception: %s" % str(exc))
if self.verbose:
self.msg(traceback.format_exc())
finally:
if startsock:
startsock.close()
# HTTP handler with WebSocket upgrade support
class WSRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, addr, only_upgrade=False):
self.only_upgrade = only_upgrade # only allow upgrades
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def do_GET(self):
if (self.headers.get('upgrade') and
self.headers.get('upgrade').lower() == 'websocket'):
if (self.headers.get('sec-websocket-key1') or
self.headers.get('websocket-key1')):
# For Hixie-76 read out the key hash
self.headers.__setitem__('key3', self.rfile.read(8))
# Just indicate that an WebSocket upgrade is needed
self.last_code = 101
self.last_message = "101 Switching Protocols"
elif self.only_upgrade:
# Normal web request responses are disabled
self.last_code = 405
self.last_message = "405 Method Not Allowed"
else:
SimpleHTTPRequestHandler.do_GET(self)
def send_response(self, code, message=None):
# Save the status code
self.last_code = code
SimpleHTTPRequestHandler.send_response(self, code, message)
def log_message(self, f, *args):
# Save instead of printing
self.last_message = f % args
-285
View File
@@ -1,285 +0,0 @@
#!/usr/bin/env python
'''
A WebSocket to TCP socket proxy with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import socket, optparse, time, os, sys, subprocess
from select import select
from websocket import WebSocketServer
class WebSocketProxy(WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
encoded/decoded to allow binary data to be sent/received to/from
the target.
"""
buffer_size = 65536
traffic_legend = """
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"""
def __init__(self, *args, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host')
self.target_port = kwargs.pop('target_port')
self.wrap_cmd = kwargs.pop('wrap_cmd')
self.wrap_mode = kwargs.pop('wrap_mode')
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
WebSocketServer.__init__(self, *args, **kwargs)
def run_wrap_cmd(self):
print("Starting '%s'" % " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
print(" - proxying from %s:%s to '%s' (port %s)\n" % (
self.listen_host, self.listen_port,
" ".join(self.wrap_cmd), self.target_port))
self.run_wrap_cmd()
else:
print(" - proxying from %s:%s to %s:%s\n" % (
self.listen_host, self.listen_port,
self.target_host, self.target_port))
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
print("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
#
# Routines above this point are run in the master listener
# process.
#
#
# Routines below this point are connection handler routines and
# will be run in a separate forked process for each connection.
#
def new_client(self):
"""
Called after a new WebSocket connection has been established.
"""
# Connect to the target
self.msg("connecting to: %s:%s" % (
self.target_host, self.target_port))
tsock = self.socket(self.target_host, self.target_port,
connect=True)
if self.verbose and not self.daemon:
print(self.traffic_legend)
# Start proxying
try:
self.do_proxy(tsock)
except:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
self.vmsg("%s:%s: Target closed" %(
self.target_host, self.target_port))
raise
def do_proxy(self, target):
"""
Proxy client WebSocket to normal target socket.
"""
cqueue = []
c_pend = 0
tqueue = []
rlist = [self.client, target]
while True:
wlist = []
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.client)
ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if target in outs:
# Send queued client data to the target
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
self.traffic(">")
else:
# requeue the remaining data
tqueue.insert(0, dat[sent:])
self.traffic(".>")
if target in ins:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
self.traffic("{")
if self.client in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.client in ins:
# Receive client data, decode it, and queue for target
bufs, closed = self.recv_frames()
tqueue.extend(bufs)
if closed:
# TODO: What about blocking on client socket?
raise self.CClose(closed['code'], closed['reason'])
def websockify_init():
usage = "\n %prog [options]"
usage += " [source_addr:]source_port target_addr:target_port"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
parser.add_option("--verbose", "-v", action="store_true",
help="verbose messages and per frame traffic")
parser.add_option("--record",
help="record sessions to FILE.[session_number]", metavar="FILE")
parser.add_option("--daemon", "-D",
dest="daemon", action="store_true",
help="become a daemon (background process)")
parser.add_option("--run-once", action="store_true",
help="handle a single WebSocket connection and exit")
parser.add_option("--timeout", type=int, default=0,
help="after TIMEOUT seconds exit when not connected")
parser.add_option("--cert", default="self.pem",
help="SSL certificate file")
parser.add_option("--key", default=None,
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted connections")
parser.add_option("--web", default=None, metavar="DIR",
help="run webserver on same port. Serve files from DIR.")
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
choices=["exit", "ignore", "respawn"],
help="action to take when the wrapped program exits "
"or daemonizes: exit (default), ignore, respawn")
(opts, args) = parser.parse_args()
# Sanity checks
if len(args) < 2:
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
else:
opts.wrap_cmd = None
if len(args) > 2:
parser.error("Too many arguments")
if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert)
# Parse host:port and convert ports to numbers
if args[0].count(':') > 0:
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
else:
opts.listen_host, opts.listen_port = '', args[0]
try: opts.listen_port = int(opts.listen_port)
except: parser.error("Error parsing listen port")
if opts.wrap_cmd:
opts.target_host = None
opts.target_port = None
else:
if args[1].count(':') > 0:
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
else:
parser.error("Error parsing target")
try: opts.target_port = int(opts.target_port)
except: parser.error("Error parsing target port")
# Create and start the WebSockets proxy
server = WebSocketProxy(**opts.__dict__)
server.start_server()
if __name__ == '__main__':
websockify_init()
+1 -1
View File
@@ -1 +1 @@
websockify
run
+2
View File
@@ -0,0 +1,2 @@
from websocket import *
from websocketproxy import *
File diff suppressed because it is too large Load Diff
+471
View File
@@ -0,0 +1,471 @@
#!/usr/bin/env python
'''
A WebSocket to TCP socket proxy with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess, logging
try: from socketserver import ForkingMixIn
except: from SocketServer import ForkingMixIn
try: from http.server import HTTPServer
except: from BaseHTTPServer import HTTPServer
from select import select
import websocket
try:
from urllib.parse import parse_qs, urlparse
except:
from cgi import parse_qs
from urlparse import urlparse
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
traffic_legend = """
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"""
def new_websocket_client(self):
"""
Called after a new WebSocket connection has been established.
"""
# Checks if we receive a token, and look
# for a valid target for it then
if self.server.target_cfg:
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
# Connect to the target
if self.server.wrap_cmd:
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
elif self.server.unix_target:
msg = "connecting to unix socket: %s" % self.server.unix_target
else:
msg = "connecting to: %s:%s" % (
self.server.target_host, self.server.target_port)
if self.server.ssl_target:
msg += " (using SSL)"
self.log_message(msg)
tsock = websocket.WebSocketServer.socket(self.server.target_host,
self.server.target_port,
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
self.print_traffic(self.traffic_legend)
# Start proxying
try:
self.do_proxy(tsock)
except:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
if self.verbose:
self.log_message("%s:%s: Closed target" %(
self.server.target_host, self.server.target_port))
raise
def get_target(self, target_cfg, path):
"""
Parses the path, extracts a token, and looks for a valid
target for that token in the configuration file(s). Sets
target_host and target_port if successful
"""
# The files in targets contain the lines
# in the form of token: host:port
# Extract the token parameter from url
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
if not args.has_key('token') or not len(args['token']):
raise self.EClose("Token not present")
token = args['token'][0].rstrip('\n')
# target_cfg can be a single config file or directory of
# config files
if os.path.isdir(target_cfg):
cfg_files = [os.path.join(target_cfg, f)
for f in os.listdir(target_cfg)]
else:
cfg_files = [target_cfg]
targets = {}
for f in cfg_files:
for line in [l.strip() for l in file(f).readlines()]:
if line and not line.startswith('#'):
ttoken, target = line.split(': ')
targets[ttoken] = target.strip()
self.vmsg("Target config: %s" % repr(targets))
if targets.has_key(token):
return targets[token].split(':')
else:
raise self.EClose("Token '%s' not found" % token)
def do_proxy(self, target):
"""
Proxy client WebSocket to normal target socket.
"""
cqueue = []
c_pend = 0
tqueue = []
rlist = [self.request, target]
while True:
wlist = []
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.request)
ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if self.request in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.request in ins:
# Receive client data, decode it, and queue for target
bufs, closed = self.recv_frames()
tqueue.extend(bufs)
if closed:
# TODO: What about blocking on client socket?
if self.verbose:
self.log_message("%s:%s: Client closed connection" %(
self.server.target_host, self.server.target_port))
raise self.CClose(closed['code'], closed['reason'])
if target in outs:
# Send queued client data to the target
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
self.print_traffic(">")
else:
# requeue the remaining data
tqueue.insert(0, dat[sent:])
self.print_traffic(".>")
if target in ins:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
if self.verbose:
self.log_message("%s:%s: Target closed connection" %(
self.server.target_host, self.server.target_port))
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
self.print_traffic("{")
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
encoded/decoded to allow binary data to be sent/received to/from
the target.
"""
buffer_size = 65536
def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
wsdir = os.path.dirname(sys.argv[0])
rebinder_path = [os.path.join(wsdir, "..", "lib"),
os.path.join(wsdir, "..", "lib", "websockify"),
wsdir]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
def run_wrap_cmd(self):
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
self.msg("%s", msg)
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
self.warn("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
def _subprocess_setup():
# Python installs a SIGPIPE handler by default. This is usually not what
# non-Python successfulbprocesses expect.
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
def logger_init():
logger = logging.getLogger(WebSocketProxy.log_prefix)
logger.propagate = False
logger.setLevel(logging.INFO)
h = logging.StreamHandler()
h.setLevel(logging.DEBUG)
h.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(h)
def websockify_init():
logger_init()
usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
parser.add_option("--verbose", "-v", action="store_true",
help="verbose messages")
parser.add_option("--traffic", action="store_true",
help="per frame traffic")
parser.add_option("--record",
help="record sessions to FILE.[session_number]", metavar="FILE")
parser.add_option("--daemon", "-D",
dest="daemon", action="store_true",
help="become a daemon (background process)")
parser.add_option("--run-once", action="store_true",
help="handle a single WebSocket connection and exit")
parser.add_option("--timeout", type=int, default=0,
help="after TIMEOUT seconds exit when not connected")
parser.add_option("--idle-timeout", type=int, default=0,
help="server exits after TIMEOUT seconds if there are no "
"active connections")
parser.add_option("--cert", default="self.pem",
help="SSL certificate file")
parser.add_option("--key", default=None,
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted client connections")
parser.add_option("--ssl-target", action="store_true",
help="connect to SSL target as SSL client")
parser.add_option("--unix-target",
help="connect to unix socket target", metavar="FILE")
parser.add_option("--web", default=None, metavar="DIR",
help="run webserver on same port. Serve files from DIR.")
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
choices=["exit", "ignore", "respawn"],
help="action to take when the wrapped program exits "
"or daemonizes: exit (default), ignore, respawn")
parser.add_option("--prefer-ipv6", "-6",
action="store_true", dest="source_is_ipv6",
help="prefer IPv6 when resolving source_addr")
parser.add_option("--target-config", metavar="FILE",
dest="target_cfg",
help="Configuration file containing valid targets "
"in the form 'token: host:port' or, alternatively, a "
"directory containing configuration files of this form")
parser.add_option("--libserver", action="store_true",
help="use Python library SocketServer engine")
(opts, args) = parser.parse_args()
if opts.verbose:
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
# Sanity checks
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
else:
opts.wrap_cmd = None
if len(args) > 2:
parser.error("Too many arguments")
if not websocket.ssl and opts.ssl_target:
parser.error("SSL target requested and Python SSL module not loaded.");
if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert)
# Parse host:port and convert ports to numbers
if args[0].count(':') > 0:
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
opts.listen_host = opts.listen_host.strip('[]')
else:
opts.listen_host, opts.listen_port = '', args[0]
try: opts.listen_port = int(opts.listen_port)
except: parser.error("Error parsing listen port")
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
opts.target_host = None
opts.target_port = None
else:
if args[1].count(':') > 0:
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
opts.target_host = opts.target_host.strip('[]')
else:
parser.error("Error parsing target")
try: opts.target_port = int(opts.target_port)
except: parser.error("Error parsing target port")
# Transform to absolute path as daemon may chdir
if opts.target_cfg:
opts.target_cfg = os.path.abspath(opts.target_cfg)
# Create and start the WebSockets proxy
libserver = opts.libserver
del opts.libserver
if libserver:
# Use standard Python SocketServer framework
server = LibProxyServer(**opts.__dict__)
server.serve_forever()
else:
# Use internal service framework
server = WebSocketProxy(**opts.__dict__)
server.start_server()
class LibProxyServer(ForkingMixIn, HTTPServer):
"""
Just like WebSocketProxy, but uses standard Python SocketServer
framework.
"""
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
self.daemon = False
self.target_cfg = None
# Server configuration
listen_host = kwargs.pop('listen_host', '')
listen_port = kwargs.pop('listen_port', None)
web = kwargs.pop('web', '')
# Configuration affecting base request handler
self.only_upgrade = not web
self.verbose = kwargs.pop('verbose', False)
record = kwargs.pop('record', '')
if record:
self.record = os.path.abspath(record)
self.run_once = kwargs.pop('run_once', False)
self.handler_id = 0
for arg in kwargs.keys():
print("warning: option %s ignored when using --libserver" % arg)
if web:
os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port),
RequestHandlerClass)
def process_request(self, request, client_address):
"""Override process_request to implement a counter"""
self.handler_id += 1
ForkingMixIn.process_request(self, request, client_address)
if __name__ == '__main__':
websockify_init()