55 Commits

Author SHA1 Message Date
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
Joel Martin 903198a724 tests: use new module path and remove Hixie code. 2013-03-14 12:24:58 -05: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
32 changed files with 1063 additions and 377 deletions
+3
View File
@@ -4,5 +4,8 @@
other/.lein-deps-sum other/.lein-deps-sum
other/classes other/classes
other/lib other/lib
other/node_modules
.project .project
.pydevproject .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
+48 -2
View File
@@ -1,8 +1,54 @@
Changes Changes
======= =======
0.1.0 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. * 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 websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and
docs/LICENSE.LGPL-3) with the following exceptions: docs/LICENSE.LGPL-3) with the following exceptions:
include/base64.js : Choice of MIT 1.1, GPL-2 or LGPL-2.1 include/websock.js : MPL 2.0
include/web-socket-js/ : New BSD license. Source code at 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 https://github.com/gimite/web-socket-js
other/kumina.c : Simplified BSD license (2 clause). other/kumina.c : Simplified BSD license (2 clause).
Original source at Original source at
https://github.com/kumina/wsproxy 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
+12 -6
View File
@@ -10,12 +10,18 @@ the target in both directions.
### WebSockets binary data ### WebSockets binary data
Websockify supports all versions of the WebSockets protocol (Hixie and Starting with websockify 0.5.0, only the HyBi / IETF
HyBI). The older Hixie versions of the protocol only support UTF-8 6455 WebSocket protocol is supported.
text payloads. In order to transport binary data over UTF-8 an
encoding must used to encapsulate the data within UTF-8. Websockify Websockify negotiates whether to base64 encode traffic to and from the
uses base64 to encode all traffic to and from the client. This does client via the subprotocol header (Sec-WebSocket-Protocol). The valid
not affect the data between websockify and the server. 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://) ### Encrypted WebSocket connections (wss://)
+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 - Go implementation
- Rust implementation
- Support multiple targets that are selected by the path line. Some - Add sub-protocol support to upstream einaros/ws module and use that
sort of wildcarding of ports too. instead of the patched module.
- Support SSL targets too.
- wstelnet: support CSI L and CSI M - wstelnet: support CSI L and CSI M
+9 -2
View File
@@ -13,5 +13,12 @@ browser.
Building web-socket-js emulator: Building web-socket-js emulator:
cd include/web-socket-js/flash-src cd include/web-socket-js/flash-src
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as 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 - Create version tag and tarball from tag
WVER=0.1.0 WVER=0.1.0
git tag ${WVER} git tag v${WVER}
git archive --format=tar --prefix=websockify-${WVER}/ v${WVER} > websockify-${WVER}.tar git push origin master
gzip websockify-${WVER}.tar git push origin v${WVER}
- Upload tarball to repo - 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
+32 -65
View File
@@ -1,45 +1,8 @@
/* /* This Source Code Form is subject to the terms of the Mozilla Public
* Modified from: * License, v. 2.0. If a copy of the MPL was not distributed with this
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
*/
/* ***** BEGIN LICENSE BLOCK ***** // From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
* 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 ***** */
/*jslint white: false, bitwise: false, plusplus: false */ /*jslint white: false, bitwise: false, plusplus: false */
/*global console */ /*global console */
@@ -47,35 +10,37 @@
var Base64 = { var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */ /* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
base64Pad : '=', base64Pad : '=',
encode: function (data) { encode: function (data) {
"use strict"; "use strict";
var result = '', var result = '';
chrTable = Base64.toBase64Table.split(''), var toBase64Table = Base64.toBase64Table;
pad = Base64.base64Pad, var base64Pad = Base64.base64Pad;
length = data.length, var length = data.length;
i; var i;
// Convert every three bytes to 4 ascii characters. // Convert every three bytes to 4 ascii characters.
/* BEGIN LOOP */
for (i = 0; i < (length - 2); i += 3) { for (i = 0; i < (length - 2); i += 3) {
result += chrTable[data[i] >> 2]; result += toBase64Table[data[i] >> 2];
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += chrTable[data[i+2] & 0x3f]; result += toBase64Table[data[i+2] & 0x3f];
} }
/* END LOOP */
// Convert the remaining 1 or 2 bytes, pad out to 4 characters. // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length%3) { if (length%3) {
i = length - (length%3); i = length - (length%3);
result += chrTable[data[i] >> 2]; result += toBase64Table[data[i] >> 2];
if ((length%3) === 2) { if ((length%3) === 2) {
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[(data[i+1] & 0x0f) << 2]; result += toBase64Table[(data[i+1] & 0x0f) << 2];
result += pad; result += base64Pad;
} else { } else {
result += chrTable[(data[i] & 0x03) << 4]; result += toBase64Table[(data[i] & 0x03) << 4];
result += pad + pad; result += base64Pad + base64Pad;
} }
} }
@@ -97,12 +62,12 @@ toBinaryTable : [
decode: function (data, offset) { decode: function (data, offset) {
"use strict"; "use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0; offset = typeof(offset) !== 'undefined' ? offset : 0;
var binTable = Base64.toBinaryTable, var toBinaryTable = Base64.toBinaryTable;
pad = Base64.base64Pad, var base64Pad = Base64.base64Pad;
result, result_length, idx, i, c, padding, var result, result_length, idx, i, c, padding;
leftbits = 0, // number of bits decoded, but yet to be appended var leftbits = 0; // number of bits decoded, but yet to be appended
leftdata = 0, // bits decoded, but yet to be appended var leftdata = 0; // bits decoded, but yet to be appended
data_length = data.indexOf('=') - offset; var data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; } if (data_length < 0) { data_length = data.length - offset; }
@@ -111,9 +76,10 @@ decode: function (data, offset) {
result = new Array(result_length); result = new Array(result_length);
// Convert one by one. // Convert one by one.
/* BEGIN LOOP */
for (idx = 0, i = offset; i < data.length; i++) { for (idx = 0, i = offset; i < data.length; i++) {
c = binTable[data.charCodeAt(i) & 0x7f]; c = toBinaryTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === pad); padding = (data.charAt(i) === base64Pad);
// Skip illegal characters and whitespace // Skip illegal characters and whitespace
if (c === -1) { if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i); console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
@@ -134,6 +100,7 @@ decode: function (data, offset) {
leftdata &= (1 << leftbits) - 1; leftdata &= (1 << leftbits) - 1;
} }
} }
/* END LOOP */
// If there are any bits left, the base64 string was corrupted // If there are any bits left, the base64 string was corrupted
if (leftbits) { if (leftbits) {
+79 -3
View File
@@ -1,7 +1,7 @@
/* /*
* noVNC: HTML5 VNC client * from noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * 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 * 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}) { } else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10); val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') { } else if (type === 'func') {
if (!val) { if (!val) {
val = function () {}; val = function () {};
@@ -190,6 +207,65 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
* Cross-browser routines * 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 // Get DOM element position on page
Util.getPosition = function (obj) { Util.getPosition = function (obj) {
var x = 0, y = 0; var x = 0, y = 0;
Binary file not shown.
+82 -32
View File
@@ -1,49 +1,69 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/> // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License // License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/ // Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol // Reference: http://tools.ietf.org/html/rfc6455
(function() { (function() {
if (window.WebSocket) return; if (window.WEB_SOCKET_FORCE_FLASH) {
// Keeps going.
var console = window.console; } else if (window.WebSocket) {
if (!console || !console.log || !console.error) { return;
console = {log: function(){ }, error: function(){ }}; } else if (window.MozWebSocket) {
// Firefox.
window.WebSocket = MozWebSocket;
return;
} }
if (!swfobject.hasFlashPlayerVersion("10.0.0")) { var logger;
console.error("Flash Player >= 10.0.0 is required."); 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; return;
} }
if (location.protocol == "file:") { if (location.protocol == "file:") {
console.error( logger.error(
"WARNING: web-socket-js doesn't work in file:///... URL " + "WARNING: web-socket-js doesn't work in file:///... URL " +
"unless you set Flash Security Settings properly. " + "unless you set Flash Security Settings properly. " +
"Open the page via Web server i.e. http://..."); "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} url
* @param {string} protocol * @param {array or string} protocols
* @param {string} proxyHost * @param {string} proxyHost
* @param {int} proxyPort * @param {int} proxyPort
* @param {string} headers * @param {string} headers
*/ */
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) { window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
var self = this; var self = this;
self.__id = WebSocket.__nextId++; self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self; WebSocket.__instances[self.__id] = self;
self.readyState = WebSocket.CONNECTING; self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0; self.bufferedAmount = 0;
self.__events = {}; 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. // 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. // Otherwise, when onopen fires immediately, onopen is called before it is set.
setTimeout(function() { self.__createTask = setTimeout(function() {
WebSocket.__addTask(function() { WebSocket.__addTask(function() {
self.__createTask = null;
WebSocket.__flash.create( WebSocket.__flash.create(
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null); self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
}); });
}, 0); }, 0);
}; };
@@ -78,6 +98,12 @@
* Close this web socket gracefully. * Close this web socket gracefully.
*/ */
WebSocket.prototype.close = function() { 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) { if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return; return;
} }
@@ -131,7 +157,7 @@
events[i](event); events[i](event);
} }
var handler = this["on" + event.type]; var handler = this["on" + event.type];
if (handler) handler(event); if (handler) handler.apply(this, [event]);
}; };
/** /**
@@ -139,16 +165,22 @@
* @param {Object} flashEvent * @param {Object} flashEvent
*/ */
WebSocket.prototype.__handleEvent = function(flashEvent) { WebSocket.prototype.__handleEvent = function(flashEvent) {
if ("readyState" in flashEvent) { if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState; this.readyState = flashEvent.readyState;
} }
if ("protocol" in flashEvent) {
this.protocol = flashEvent.protocol;
}
var jsEvent; var jsEvent;
if (flashEvent.type == "open" || flashEvent.type == "error") { if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type); jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") { } else if (flashEvent.type == "close") {
// TODO implement jsEvent.wasClean
jsEvent = this.__createSimpleEvent("close"); jsEvent = this.__createSimpleEvent("close");
jsEvent.wasClean = flashEvent.wasClean ? true : false;
jsEvent.code = flashEvent.code;
jsEvent.reason = flashEvent.reason;
} else if (flashEvent.type == "message") { } else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message); var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data); jsEvent = this.__createMessageEvent("message", data);
@@ -157,6 +189,7 @@
} }
this.dispatchEvent(jsEvent); this.dispatchEvent(jsEvent);
}; };
WebSocket.prototype.__createSimpleEvent = function(type) { WebSocket.prototype.__createSimpleEvent = function(type) {
@@ -188,6 +221,9 @@
WebSocket.CLOSING = 2; WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3; WebSocket.CLOSED = 3;
// Field to check implementation of WebSocket.
WebSocket.__isFlashImplementation = true;
WebSocket.__initialized = false;
WebSocket.__flash = null; WebSocket.__flash = null;
WebSocket.__instances = {}; WebSocket.__instances = {};
WebSocket.__tasks = []; WebSocket.__tasks = [];
@@ -207,16 +243,31 @@
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash. * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/ */
WebSocket.__initialize = function() { WebSocket.__initialize = function() {
if (WebSocket.__flash) return;
if (WebSocket.__initialized) return;
WebSocket.__initialized = true;
if (WebSocket.__swfLocation) { if (WebSocket.__swfLocation) {
// For backword compatibility. // For backword compatibility.
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
} }
if (!window.WEB_SOCKET_SWF_LOCATION) { 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; 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"); var container = document.createElement("div");
container.id = "webSocketContainer"; container.id = "webSocketContainer";
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
@@ -250,9 +301,11 @@
null, null,
function(e) { function(e) {
if (!e.success) { 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]); WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
} }
} catch (e) { } catch (e) {
console.error(e); logger.error(e);
} }
}, 0); }, 0);
return true; return true;
@@ -295,12 +348,12 @@
// Called by Flash. // Called by Flash.
WebSocket.__log = function(message) { WebSocket.__log = function(message) {
console.log(decodeURIComponent(message)); logger.log(decodeURIComponent(message));
}; };
// Called by Flash. // Called by Flash.
WebSocket.__error = function(message) { WebSocket.__error = function(message) {
console.error(decodeURIComponent(message)); logger.error(decodeURIComponent(message));
}; };
WebSocket.__addTask = function(task) { WebSocket.__addTask = function(task) {
@@ -327,15 +380,12 @@
}; };
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
if (window.addEventListener) { // NOTE:
window.addEventListener("load", function(){ // This fires immediately if web_socket.js is dynamically loaded after
WebSocket.__initialize(); // the document is loaded.
}, false); swfobject.addDomLoadEvent(function() {
} else { WebSocket.__initialize();
window.attachEvent("onload", function(){ });
WebSocket.__initialize();
});
}
} }
})(); })();
+25 -21
View File
@@ -1,7 +1,7 @@
/* /*
* Websock: high-performance binary WebSockets * Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* Websock is similar to the standard WebSocket object but Websock * Websock is similar to the standard WebSocket object but Websock
* enables communication with raw TCP sockets (i.e. the binary stream) * enables communication with raw TCP sockets (i.e. the binary stream)
@@ -35,23 +35,14 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = false; Websock_native = false;
(function () { (function () {
function get_INCLUDE_URI() { window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
return (typeof INCLUDE_URI !== "undefined") ?
INCLUDE_URI : "include/";
}
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf"; "web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) { if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf"); Util.Debug("Forcing uncached load of WebSocketMain.swf");
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
} }
extra += start + "web-socket-js/swfobject.js" + end; Util.load_scripts(["web-socket-js/swfobject.js",
extra += start + "web-socket-js/web_socket.js" + end; "web-socket-js/web_socket.js"]);
document.write(extra);
}()); }());
} }
@@ -61,7 +52,7 @@ function Websock() {
var api = {}, // Public API var api = {}, // Public API
websocket = null, // WebSocket object websocket = null, // WebSocket object
mode = 'base64', mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
rQ = [], // Receive queue rQ = [], // Receive queue
rQi = 0, // Receive queue index rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting rQmax = 10000, // Max receive queue size before compacting
@@ -168,10 +159,11 @@ function rQwait(msg, num, goback) {
// //
function encode_message() { function encode_message() {
/* base64 encode */
if (mode === 'binary') { if (mode === 'binary') {
// Put in a binary arraybuffer
return (new Uint8Array(sQ)).buffer; return (new Uint8Array(sQ)).buffer;
} else { } else {
// base64 encode
return Base64.encode(sQ); return Base64.encode(sQ);
} }
} }
@@ -180,7 +172,10 @@ function decode_message(data) {
//Util.Debug(">> decode_message: " + data); //Util.Debug(">> decode_message: " + data);
if (mode === 'binary') { if (mode === 'binary') {
// push arraybuffer values onto the end // push arraybuffer values onto the end
rQ.push.apply(rQ, (new Uint8Array(data))); var u8 = new Uint8Array(data);
for (var i = 0; i < u8.length; i++) {
rQ.push(u8[i]);
}
} else { } else {
// base64 decode and concat to the end // base64 decode and concat to the end
rQ = rQ.concat(Base64.decode(data, 0)); rQ = rQ.concat(Base64.decode(data, 0));
@@ -310,11 +305,19 @@ function init(protocols) {
throw("WebSocket binary sub-protocol requested but not supported"); throw("WebSocket binary sub-protocol requested but not supported");
} }
if (typeof(protocols) === "object") { if (typeof(protocols) === "object") {
var new_protocols = [];
for (var i = 0; i < protocols.length; i++) { for (var i = 0; i < protocols.length; i++) {
if (protocols[i] === 'binary') { if (protocols[i] === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported"); 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.");
}
} }
} }
@@ -328,6 +331,9 @@ function open(uri, protocols) {
websocket = {}; websocket = {};
} else { } else {
websocket = new WebSocket(uri, protocols); websocket = new WebSocket(uri, protocols);
if (protocols.indexOf('binary') >= 0) {
websocket.binaryType = 'arraybuffer';
}
} }
websocket.onmessage = recv_message; websocket.onmessage = recv_message;
@@ -340,9 +346,6 @@ function open(uri, protocols) {
mode = 'base64'; mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol); Util.Error("Server select no sub-protocol!: " + websocket.protocol);
} }
if (mode === 'binary') {
websocket.binaryType = 'arraybuffer';
}
eventHandlers.open(); eventHandlers.open();
Util.Debug("<< WebSock.onopen"); Util.Debug("<< WebSock.onopen");
}; };
@@ -371,8 +374,9 @@ function close() {
// Override internal functions for testing // Override internal functions for testing
// Takes a send function, returns reference to recv function // Takes a send function, returns reference to recv function
function testMode(override_send) { function testMode(override_send, data_mode) {
test_mode = true; test_mode = true;
mode = data_mode;
api.send = override_send; api.send = override_send;
api.close = function () {}; api.close = function () {};
return recv_message; return recv_message;
+78 -10
View File
@@ -1,7 +1,7 @@
/* /*
* from noVNC: HTML5 VNC client * from noVNC: HTML5 VNC client
* Copyright (C) 2010 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
@@ -37,14 +37,16 @@ if (!window.$D) {
*/ */
// init log level reading the logging HTTP param // init log level reading the logging HTTP param
WebUtil.init_logging = function() { WebUtil.init_logging = function(level) {
Util._log_level = (document.location.href.match( if (typeof level !== "undefined") {
/logging=([A-Za-z0-9\._\-]*)/) || Util._log_level = level;
['', Util._log_level])[1]; } else {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
}
Util.init_logging(); Util.init_logging();
}; };
WebUtil.init_logging();
WebUtil.dirObj = function (obj, depth, parent) { WebUtil.dirObj = function (obj, depth, parent) {
@@ -75,9 +77,14 @@ WebUtil.dirObj = function (obj, depth, parent) {
// Read a query string variable // Read a query string variable
WebUtil.getQueryVar = function(name, defVal) { 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; } 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); 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 * 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.5.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 // A WebSocket to TCP socket proxy
// Copyright 2012 Joel Martin // Copyright 2012 Joel Martin
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
// Known to work with node 0.6 // Known to work with node 0.8.9
// Requires node modules: ws, base64, optimist and policyfile // Requires node modules: ws, optimist and policyfile
// npm install ws base64 optimist policyfile // npm install ws optimist policyfile
var argv = require('optimist').argv, var argv = require('optimist').argv,
net = require('net'), net = require('net'),
http = require('http'), http = require('http'),
https = require('https'),
url = require('url'), url = require('url'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
policyfile = require('policyfile'), policyfile = require('policyfile'),
base64 = require('base64/build/Release/base64'),
Buffer = require('buffer').Buffer, Buffer = require('buffer').Buffer,
WebSocketServer = require('ws').Server, WebSocketServer = require('ws').Server,
httpServer, wsServer, webServer, wsServer,
source_host, source_port, target_host, target_port, source_host, source_port, target_host, target_port,
web_path = null; web_path = null;
// Handle new WebSocket client // Handle new WebSocket client
new_client = function(client) { new_client = function(client) {
console.log('WebSocket client connected'); var clientAddr = client._socket.remoteAddress, log;
//console.log('protocol: ' + client.protocol); 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); var target = net.createConnection(target_port,target_host, function() {
target.on('begin', function() { log('connected to target');
console.log('connected to target');
}); });
target.on('data', function(data) { 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() { 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) { client.on('message', function(msg) {
//console.log('got some message'); //log('got message: ' + msg);
target.write(base64.decode(msg),'binary'); if (client.protocol === 'base64') {
target.write(new Buffer(msg, 'base64'));
} else {
target.write(msg,'binary');
}
}); });
client.on('close', function(code, reason) { 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) { 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 var uri = url.parse(request.url).pathname
, filename = path.join(argv.web, uri); , filename = path.join(argv.web, uri);
path.exists(filename, function(exists) { fs.exists(filename, function(exists) {
if(!exists) { if(!exists) {
return http_error(response, 404, "404 Not Found"); 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 // parse source and target arguments into parts
source_arg = argv._[0].toString();
target_arg = argv._[1].toString();
try { try {
source_arg = argv._[0].toString();
target_arg = argv._[1].toString();
var idx; var idx;
idx = source_arg.indexOf(":"); idx = source_arg.indexOf(":");
if (idx >= 0) { if (idx >= 0) {
@@ -119,7 +161,7 @@ try {
throw("illegal port"); throw("illegal port");
} }
} catch(e) { } 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); process.exit(2);
} }
@@ -130,11 +172,21 @@ if (argv.web) {
console.log(" - Web server active. Serving: " + argv.web); console.log(" - Web server active. Serving: " + argv.web);
} }
httpServer = http.createServer(http_request); if (argv.cert) {
httpServer.listen(source_port, function() { argv.key = argv.key || argv.cert;
wsServer = new WebSocketServer({server: httpServer}); 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); wsServer.on('connection', new_client);
}); });
// Attach Flash policyfile answer service // 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 " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file" echo " --cert CERT Path to combined cert/key file"
echo " Default: self.pem" echo " Default: self.pem"
echo " --web WEB Path to web files (e.g. vnc.html)"
echo " Default: ./"
exit 2 exit 2
} }
@@ -24,6 +26,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
PORT="6080" PORT="6080"
VNC_DEST="localhost:5900" VNC_DEST="localhost:5900"
CERT="" CERT=""
WEB=""
proxy_pid="" proxy_pid=""
die() { die() {
@@ -50,6 +53,7 @@ while [ "$*" ]; do
--listen) PORT="${OPTARG}"; shift ;; --listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;; --vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;; --cert) CERT="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;;
-h|--help) usage ;; -h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;; -*) usage "Unknown chrooter option: ${param}" ;;
*) break ;; *) break ;;
@@ -60,18 +64,24 @@ done
which netstat >/dev/null 2>&1 \ which netstat >/dev/null 2>&1 \
|| die "Must have netstat installed" || 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" && die "Port ${PORT} in use. Try --listen PORT"
trap "cleanup" TERM QUIT INT EXIT trap "cleanup" TERM QUIT INT EXIT
# Find vnc.html # 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) WEB=$(pwd)
elif [ -e "${HERE}/../vnc.html" ]; then elif [ -e "${HERE}/../vnc.html" ]; then
WEB=${HERE}/../ WEB=${HERE}/../
elif [ -e "${HERE}/vnc.html" ]; then elif [ -e "${HERE}/vnc.html" ]; then
WEB=${HERE} WEB=${HERE}
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
WEB=${HERE}/../share/novnc/
else else
die "Could not find vnc.html" die "Could not find vnc.html"
fi fi
@@ -92,7 +102,7 @@ else
fi fi
echo "Starting webserver and WebSockets proxy on port ${PORT}" 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="$!" proxy_pid="$!"
sleep 1 sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then if ! ps -p ${proxy_pid} >/dev/null; then
@@ -101,7 +111,7 @@ if ! ps -p ${proxy_pid} >/dev/null; then
exit 1 exit 1
fi 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 " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
echo -e "Press Ctrl-C to exit\n\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"); handler_msg("handler exit\n");
} else { } else {
handler_msg("wsproxy exit\n"); handler_msg("websockify exit\n");
} }
} }
+2 -2
View File
@@ -7,8 +7,8 @@
* REBIND_PORT_NEW environment variables are set then bind on the new * REBIND_PORT_NEW environment variables are set then bind on the new
* port (of localhost) instead of the old port. * port (of localhost) instead of the old port.
* *
* This allows a proxy (such as wsproxy) to run on the old port and translate * This allows a bridge/proxy (such as websockify) to run on the old port and
* traffic to/from the new port. * translate traffic to/from the new port.
* *
* Usage: * Usage:
* LD_PRELOAD=./rebind.so \ * LD_PRELOAD=./rebind.so \
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 from setuptools import setup, find_packages
version = '0.1.0' version = '0.5.1'
name = 'websockify' name = 'websockify'
long_description = open("README.md").read() + "\n" + \ long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n" open("CHANGES.txt").read() + "\n"
@@ -12,19 +12,27 @@ setup(name=name,
classifiers=[ classifiers=[
"Programming Language :: Python", "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', keywords='noVNC websockify',
license='LGPLv3', license='LGPLv3',
url="https://github.com/kanaka/websockify", url="https://github.com/kanaka/websockify",
author="Joel Martin", author="Joel Martin",
author_email="github@martintribe.org", author_email="github@martintribe.org",
packages=find_packages(), packages=['websockify'],
include_package_data=True, include_package_data=True,
install_requires=['numpy'], install_requires=['numpy'],
zip_safe=False, zip_safe=False,
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'websockify = websockify:websockify_init', 'websockify = websockify.websocketproxy:websockify_init',
] ]
}, },
) )
+1 -1
View File
@@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
''' '''
import os, sys, select, optparse import os, sys, select, optparse
sys.path.insert(0,os.path.dirname(__file__) + "/../") sys.path.insert(0,os.path.dirname(__file__) + "/../websockify")
from websocket import WebSocketServer from websocket import WebSocketServer
class WebSocketEcho(WebSocketServer): class WebSocketEcho(WebSocketServer):
+1 -1
View File
@@ -7,7 +7,7 @@ given a sequence number. Any errors are reported and counted.
''' '''
import sys, os, select, random, time, optparse import sys, os, select, random, time, optparse
sys.path.insert(0,os.path.dirname(__file__) + "/../") sys.path.insert(0,os.path.dirname(__file__) + "/../websockify")
from websocket import WebSocketServer from websocket import WebSocketServer
class WebSocketLoad(WebSocketServer): class WebSocketLoad(WebSocketServer):
+3 -5
View File
@@ -6,17 +6,15 @@ Display UTF-8 encoding for 0-255.'''
import sys, os, socket, ssl, time, traceback import sys, os, socket, ssl, time, traceback
from select import select from select import select
sys.path.insert(0,os.path.dirname(__file__) + "/../") sys.path.insert(0,os.path.dirname(__file__) + "/../websockify")
from websocket import WebSocketServer from websocket import WebSocketServer
if __name__ == '__main__': if __name__ == '__main__':
print "val: hixie | hybi_base64 | hybi_binary" print "val: hybi_base64 | hybi_binary"
for c in range(0, 256): for c in range(0, 256):
hixie = WebSocketServer.encode_hixie(chr(c))
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1, hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
base64=True) base64=True)
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2, hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
base64=False) base64=False)
print "%d: %s | %s | %s" % (c, repr(hixie), repr(hybi_base64), print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
repr(hybi_binary))
+1 -1
View File
@@ -1 +1 @@
websockify run
+2
View File
@@ -0,0 +1,2 @@
from websocket import *
from websocketproxy import *
+101 -147
View File
@@ -6,9 +6,9 @@ Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
Supports following protocol versions: Supports following protocol versions:
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
- http://tools.ietf.org/html/rfc6455
You can make a cert/key with openssl using: You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
@@ -37,8 +37,8 @@ try: from http.server import SimpleHTTPRequestHandler
except: from SimpleHTTPServer import SimpleHTTPRequestHandler except: from SimpleHTTPServer import SimpleHTTPRequestHandler
# python 2.6 differences # python 2.6 differences
try: from hashlib import md5, sha1 try: from hashlib import sha1
except: from md5 import md5; from sha import sha as sha1 except: from sha import sha as sha1
# python 2.5 differences # python 2.5 differences
try: try:
@@ -50,15 +50,15 @@ except:
return struct.unpack(fmt, slice) return struct.unpack(fmt, slice)
# Degraded functionality if these imports are missing # Degraded functionality if these imports are missing
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'), for mod, msg in [('numpy', 'HyBi protocol will be slower'),
('multiprocessing', 'Multi-Processing'), ('ssl', 'TLS/SSL/wss is disabled'),
('resource', 'daemonizing')]: ('multiprocessing', 'Multi-Processing is disabled'),
('resource', 'daemonizing is disabled')]:
try: try:
globals()[mod] = __import__(mod) globals()[mod] = __import__(mod)
except ImportError: except ImportError:
globals()[mod] = None globals()[mod] = None
print("WARNING: no '%s' module, %s is slower or disabled" % ( print("WARNING: no '%s' module, %s" % (mod, msg))
mod, sup))
if multiprocessing and sys.platform == 'win32': if multiprocessing and sys.platform == 'win32':
# make sockets pickle-able/inheritable # make sockets pickle-able/inheritable
import multiprocessing.reduction import multiprocessing.reduction
@@ -72,14 +72,6 @@ class WebSocketServer(object):
buffer_size = 65536 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 server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r Upgrade: websocket\r
Connection: Upgrade\r Connection: Upgrade\r
@@ -100,8 +92,8 @@ Sec-WebSocket-Accept: %s\r
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None, verbose=False, cert='', key='', ssl_only=None,
daemon=False, record='', web='', daemon=False, record='', web='', file_only=False, no_parent=False,
run_once=False, timeout=0): run_once=False, timeout=0, idle_timeout=0):
# settings # settings
self.verbose = verbose self.verbose = verbose
@@ -112,11 +104,15 @@ Sec-WebSocket-Accept: %s\r
self.daemon = daemon self.daemon = daemon
self.run_once = run_once self.run_once = run_once
self.timeout = timeout self.timeout = timeout
self.idle_timeout = idle_timeout
self.launch_time = time.time() self.launch_time = time.time()
self.ws_connection = False self.ws_connection = False
self.handler_id = 1 self.handler_id = 1
self.file_only = file_only
self.no_parent = no_parent
# Make paths settings absolute # Make paths settings absolute
self.cert = os.path.abspath(cert) self.cert = os.path.abspath(cert)
self.key = self.web = self.record = '' self.key = self.web = self.record = ''
@@ -239,32 +235,33 @@ Sec-WebSocket-Accept: %s\r
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
@staticmethod @staticmethod
def unmask(buf, f): def unmask(buf, hlen, plen):
pstart = f['hlen'] + 4 pstart = hlen + 4
pend = pstart + f['length'] pend = pstart + plen
if numpy: if numpy:
b = c = s2b('') b = c = s2b('')
if f['length'] >= 4: if plen >= 4:
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=f['hlen'], count=1) offset=hlen, count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=pstart, count=int(f['length'] / 4)) offset=pstart, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data #b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring() b = numpy.bitwise_xor(data, mask).tostring()
if f['length'] % 4: if plen % 4:
#print("Partial unmask") #print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'), mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=f['hlen'], count=(f['length'] % 4)) offset=hlen, count=(plen % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'), data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=pend - (f['length'] % 4), offset=pend - (plen % 4),
count=(f['length'] % 4)) count=(plen % 4))
c = numpy.bitwise_xor(data, mask).tostring() c = numpy.bitwise_xor(data, mask).tostring()
return b + c return b + c
else: else:
# Slower fallback # Slower fallback
mask = buf[hlen:hlen+4]
data = array.array('B') data = array.array('B')
mask = s2a(f['mask']) mask = s2a(mask)
data.fromstring(buf[pstart:pend]) data.fromstring(buf[pstart:pend])
for i in range(len(data)): for i in range(len(data)):
data[i] ^= mask[i % 4] data[i] ^= mask[i % 4]
@@ -303,7 +300,7 @@ Sec-WebSocket-Accept: %s\r
Returns: Returns:
{'fin' : 0_or_1, {'fin' : 0_or_1,
'opcode' : number, 'opcode' : number,
'mask' : 32_bit_number, 'masked' : boolean,
'hlen' : header_bytes_number, 'hlen' : header_bytes_number,
'length' : payload_bytes_number, 'length' : payload_bytes_number,
'payload' : decoded_buffer, 'payload' : decoded_buffer,
@@ -314,7 +311,7 @@ Sec-WebSocket-Accept: %s\r
f = {'fin' : 0, f = {'fin' : 0,
'opcode' : 0, 'opcode' : 0,
'mask' : 0, 'masked' : False,
'hlen' : 2, 'hlen' : 2,
'length' : 0, 'length' : 0,
'payload' : None, 'payload' : None,
@@ -331,7 +328,7 @@ Sec-WebSocket-Accept: %s\r
b1, b2 = unpack_from(">BB", buf) b1, b2 = unpack_from(">BB", buf)
f['opcode'] = b1 & 0x0f f['opcode'] = b1 & 0x0f
f['fin'] = (b1 & 0x80) >> 7 f['fin'] = (b1 & 0x80) >> 7
has_mask = (b2 & 0x80) >> 7 f['masked'] = (b2 & 0x80) >> 7
f['length'] = b2 & 0x7f f['length'] = b2 & 0x7f
@@ -346,7 +343,7 @@ Sec-WebSocket-Accept: %s\r
return f # Incomplete frame header return f # Incomplete frame header
(f['length'],) = unpack_from('>xxQ', buf) (f['length'],) = unpack_from('>xxQ', buf)
full_len = f['hlen'] + has_mask * 4 + f['length'] full_len = f['hlen'] + f['masked'] * 4 + f['length']
if blen < full_len: # Incomplete frame if blen < full_len: # Incomplete frame
return f # Incomplete frame header return f # Incomplete frame header
@@ -355,13 +352,13 @@ Sec-WebSocket-Accept: %s\r
f['left'] = blen - full_len f['left'] = blen - full_len
# Process 1 frame # Process 1 frame
if has_mask: if f['masked']:
# unmask payload # unmask payload
f['mask'] = buf[f['hlen']:f['hlen']+4] f['payload'] = WebSocketServer.unmask(buf, f['hlen'],
f['payload'] = WebSocketServer.unmask(buf, f) f['length'])
else: else:
print("Unmasked frame: %s" % repr(buf)) print("Unmasked frame: %s" % repr(buf))
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len] f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
if base64 and f['opcode'] in [1, 2]: if base64 and f['opcode'] in [1, 2]:
try: try:
@@ -379,32 +376,6 @@ Sec-WebSocket-Accept: %s\r
return f 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 # WebSocketServer logging/output functions
@@ -441,21 +412,15 @@ Sec-WebSocket-Accept: %s\r
if bufs: if bufs:
for buf in bufs: for buf in bufs:
if self.version.startswith("hybi"): if self.base64:
if self.base64: encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=1, base64=True)
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=1, base64=True)
else:
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=2, base64=False)
else: else:
encbuf, lenhead, lentail = self.encode_hixie(buf) encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=2, base64=False)
if self.rec: if self.rec:
self.rec.write("%s,\n" % self.rec.write("%s,\n" %
repr("{%s{" % tdelta repr("{%s{" % tdelta
+ encbuf[lenhead:-lentail])) + encbuf[lenhead:len(encbuf)-lentail]))
self.send_parts.append(encbuf) self.send_parts.append(encbuf)
@@ -495,48 +460,34 @@ Sec-WebSocket-Accept: %s\r
self.recv_part = None self.recv_part = None
while buf: while buf:
if self.version.startswith("hybi"): frame = self.decode_hybi(buf, base64=self.base64)
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
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
if frame['payload'] == None:
# Incomplete/partial frame
self.traffic("}.")
if frame['left'] > 0:
self.recv_part = buf[-frame['left']:]
break
else: else:
if buf[0:2] == s2b('\xff\x00'): if frame['opcode'] == 0x8: # connection close
closed = {'code': 1000, closed = {'code': frame['close_code'],
'reason': "Client sent orderly close frame"} 'reason': frame['close_reason']}
break 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("}") self.traffic("}")
if self.rec: if self.rec:
start = frame['hlen'] start = frame['hlen']
end = frame['hlen'] + frame['length'] end = frame['hlen'] + frame['length']
if frame['masked']:
recbuf = WebSocketServer.unmask(buf, frame['hlen'],
frame['length'])
else:
recbuf = buf[frame['hlen']:frame['hlen'] +
frame['length']]
self.rec.write("%s,\n" % self.rec.write("%s,\n" %
repr("}%s}" % tdelta + buf[start:end])) repr("}%s}" % tdelta + recbuf))
bufs.append(frame['payload']) bufs.append(frame['payload'])
@@ -551,17 +502,9 @@ Sec-WebSocket-Accept: %s\r
def send_close(self, code=1000, reason=''): def send_close(self, code=1000, reason=''):
""" Send a WebSocket orderly close frame. """ """ Send a WebSocket orderly close frame. """
if self.version.startswith("hybi"): msg = pack(">H%ds" % len(reason), code, reason)
msg = pack(">H%ds" % len(reason), code, reason) buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
self.client.send(buf)
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_websocket_handshake(self, headers, path): def do_websocket_handshake(self, headers, path):
h = self.headers = headers h = self.headers = headers
@@ -603,28 +546,7 @@ Sec-WebSocket-Accept: %s\r
response += "\r\n" response += "\r\n"
else: else:
# Hixie version of the protocol (75 or 76) raise self.EClose("Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
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, self.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
return response return response
@@ -701,7 +623,8 @@ Sec-WebSocket-Accept: %s\r
self.scheme = "ws" self.scheme = "ws"
stype = "Plain non-SSL (ws://)" stype = "Plain non-SSL (ws://)"
wsh = WSRequestHandler(retsock, address, not self.web) wsh = WSRequestHandler(retsock, address, not self.web,
self.file_only, self.no_parent)
if wsh.last_code == 101: if wsh.last_code == 101:
# Continue on to handle WebSocket upgrade # Continue on to handle WebSocket upgrade
pass pass
@@ -778,6 +701,10 @@ Sec-WebSocket-Accept: %s\r
self.handler_id) self.handler_id)
self.msg("opening record file: %s" % fname) self.msg("opening record file: %s" % fname)
self.rec = open(fname, 'w+') self.rec = open(fname, 'w+')
encoding = "binary"
if self.base64: encoding = "base64"
self.rec.write("var VNC_frame_encoding = '%s';\n"
% encoding)
self.rec.write("var VNC_frame_data = [\n") self.rec.write("var VNC_frame_data = [\n")
self.ws_connection = True self.ws_connection = True
@@ -799,7 +726,7 @@ Sec-WebSocket-Accept: %s\r
self.msg(traceback.format_exc()) self.msg(traceback.format_exc())
finally: finally:
if self.rec: if self.rec:
self.rec.write("'EOF']\n") self.rec.write("'EOF'];\n")
self.rec.close() self.rec.close()
if self.client and self.client != startsock: if self.client and self.client != startsock:
@@ -831,12 +758,17 @@ Sec-WebSocket-Accept: %s\r
# os.fork() (python 2.4) child reaper # os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD) signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
last_active_time = self.launch_time
while True: while True:
try: try:
try: try:
self.client = None self.client = None
startsock = None startsock = None
pid = err = 0 pid = err = 0
child_count = 0
if multiprocessing and self.idle_timeout:
child_count = len(multiprocessing.active_children())
time_elapsed = time.time() - self.launch_time time_elapsed = time.time() - self.launch_time
if self.timeout and time_elapsed > self.timeout: if self.timeout and time_elapsed > self.timeout:
@@ -844,6 +776,19 @@ Sec-WebSocket-Accept: %s\r
% self.timeout) % self.timeout)
break break
if self.idle_timeout:
idle_time = 0
if child_count == 0:
idle_time = time.time() - last_active_time
else:
idle_time = 0
last_active_time = time.time()
if idle_time > self.idle_timeout and child_count == 0:
self.msg('listener exit due to --idle-timeout %s'
% self.idle_timeout)
break
try: try:
self.poll() self.poll()
@@ -910,22 +855,27 @@ Sec-WebSocket-Accept: %s\r
if startsock: if startsock:
startsock.close() startsock.close()
# Close listen port
self.vmsg("Closing socket listening at %s:%s"
% (self.listen_host, self.listen_port))
lsock.close()
# HTTP handler with WebSocket upgrade support # HTTP handler with WebSocket upgrade support
class WSRequestHandler(SimpleHTTPRequestHandler): class WSRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, addr, only_upgrade=False): def __init__(self, req, addr, only_upgrade=False, file_only=False,
no_parent=False):
self.only_upgrade = only_upgrade # only allow upgrades self.only_upgrade = only_upgrade # only allow upgrades
self.webroot = os.path.realpath(".")
self.file_only = file_only
self.no_parent = no_parent
SimpleHTTPRequestHandler.__init__(self, req, addr, object()) SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def do_GET(self): def do_GET(self):
abspath = os.path.realpath("." + (self.path.split('?')[0]))
if (self.headers.get('upgrade') and if (self.headers.get('upgrade') and
self.headers.get('upgrade').lower() == 'websocket'): 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 # Just indicate that an WebSocket upgrade is needed
self.last_code = 101 self.last_code = 101
self.last_message = "101 Switching Protocols" self.last_message = "101 Switching Protocols"
@@ -933,6 +883,10 @@ class WSRequestHandler(SimpleHTTPRequestHandler):
# Normal web request responses are disabled # Normal web request responses are disabled
self.last_code = 405 self.last_code = 405
self.last_message = "405 Method Not Allowed" self.last_message = "405 Method Not Allowed"
elif self.file_only and not os.path.isfile(abspath):
self.send_response(404, "No such file")
elif self.no_parent and not abspath.startswith(self.webroot):
self.send_response(403, "Hidden resources")
else: else:
SimpleHTTPRequestHandler.do_GET(self) SimpleHTTPRequestHandler.do_GET(self)
+45 -32
View File
@@ -11,11 +11,14 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
''' '''
import socket, optparse, time, os, sys, subprocess import signal, socket, optparse, time, os, sys, subprocess
from select import select from select import select
import websocket import websocket
try: from urllib.parse import parse_qs, urlparse try:
except: from urlparse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
except:
from cgi import parse_qs
from urlparse import urlparse
class WebSocketProxy(websocket.WebSocketServer): class WebSocketProxy(websocket.WebSocketServer):
""" """
@@ -41,13 +44,13 @@ Traffic Legend:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Save off proxy specific options # Save off proxy specific options
self.target_host = kwargs.pop('target_host') self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port') self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd') self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode') self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target') self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target') self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg') self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run # Last 3 timestamps command was run
self.wrap_times = [0, 0, 0] self.wrap_times = [0, 0, 0]
@@ -87,7 +90,7 @@ Traffic Legend:
self.wrap_times.append(time.time()) self.wrap_times.append(time.time())
self.wrap_times.pop(0) self.wrap_times.pop(0)
self.cmd = subprocess.Popen( self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ) self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True self.spawn_message = True
def started(self): def started(self):
@@ -144,7 +147,7 @@ Traffic Legend:
else: else:
self.run_wrap_cmd() self.run_wrap_cmd()
# #
# Routines above this point are run in the master listener # Routines above this point are run in the master listener
# process. # process.
# #
@@ -165,13 +168,13 @@ Traffic Legend:
# Connect to the target # Connect to the target
if self.wrap_cmd: if self.wrap_cmd:
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port) msg = "connecting to command: '%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target: elif self.unix_target:
msg = "connecting to unix socket: %s" % self.unix_target msg = "connecting to unix socket: %s" % self.unix_target
else: else:
msg = "connecting to: %s:%s" % ( msg = "connecting to: %s:%s" % (
self.target_host, self.target_port) self.target_host, self.target_port)
if self.ssl_target: if self.ssl_target:
msg += " (using SSL)" msg += " (using SSL)"
self.msg(msg) self.msg(msg)
@@ -205,7 +208,7 @@ Traffic Legend:
# Extract the token parameter from url # Extract the token parameter from url
args = parse_qs(urlparse(path)[4]) # 4 is the query from url args = parse_qs(urlparse(path)[4]) # 4 is the query from url
if not len(args['token']): if not args.has_key('token') or not len(args['token']):
raise self.EClose("Token not present") raise self.EClose("Token not present")
token = args['token'][0].rstrip('\n') token = args['token'][0].rstrip('\n')
@@ -249,6 +252,24 @@ Traffic Legend:
ins, outs, excepts = select(rlist, wlist, [], 1) ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception") if excepts: raise Exception("Socket exception")
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?
self.vmsg("%s:%s: Client closed connection" %(
self.target_host, self.target_port))
raise self.CClose(closed['code'], closed['reason'])
if target in outs: if target in outs:
# Send queued client data to the target # Send queued client data to the target
dat = tqueue.pop(0) dat = tqueue.pop(0)
@@ -273,24 +294,13 @@ Traffic Legend:
self.traffic("{") self.traffic("{")
if self.client in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = [] 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)
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?
self.vmsg("%s:%s: Client closed connection" %(
self.target_host, self.target_port))
raise self.CClose(closed['code'], closed['reason'])
def websockify_init(): def websockify_init():
usage = "\n %prog [options]" usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]" usage += " [source_addr:]source_port [target_addr:target_port]"
@@ -308,6 +318,9 @@ def websockify_init():
help="handle a single WebSocket connection and exit") help="handle a single WebSocket connection and exit")
parser.add_option("--timeout", type=int, default=0, parser.add_option("--timeout", type=int, default=0,
help="after TIMEOUT seconds exit when not connected") 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", parser.add_option("--cert", default="self.pem",
help="SSL certificate file") help="SSL certificate file")
parser.add_option("--key", default=None, parser.add_option("--key", default=None,
@@ -335,7 +348,7 @@ def websockify_init():
(opts, args) = parser.parse_args() (opts, args) = parser.parse_args()
# Sanity checks # Sanity checks
if len(args) < 2 and not opts.target_cfg: if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments") parser.error("Too few arguments")
if sys.argv.count('--'): if sys.argv.count('--'):
opts.wrap_cmd = args[1:] opts.wrap_cmd = args[1:]
@@ -346,7 +359,7 @@ def websockify_init():
if not websocket.ssl and opts.ssl_target: if not websocket.ssl and opts.ssl_target:
parser.error("SSL target requested and Python SSL module not loaded."); parser.error("SSL target requested and Python SSL module not loaded.");
if opts.ssl_only and not os.path.exists(opts.cert): if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert) parser.error("SSL only and %s not found" % opts.cert)